JavaScript slice() 方法实战指南:从 Array 与 String 切片到 refine 源码中的分页、导出与路由参数解析
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
本篇技术指南系统讲解 JavaScript 中Array.prototype.slice与String.prototype.slice的方法签名、参数语义与边界行为,并结合 refine(一个用于构建内部工具、管理面板、仪表盘和 B2B 应用的 React 框架)仓库中的真实源码,展示slice()在客户端分页、数据导出、路由参数提取和字符串转换中的落地方式。读完本文,你可以准确掌握slice()的取/不取边界、负偏移、稀疏数组等全部细节,并能在类似 refine 的管理后台项目中将其应用于分页视图、不可变数据操作和字符串处理。
一、背景:slice 属于哪一类迭代方法
slice()是 JavaScript 迭代方法系列的一员。迭代(Iteration)指遍历一组数据并对每个元素执行操作(变换数据或产生副作用)的过程。常见的迭代方法有Array.prototype.forEach、Array.prototype.map和Array.prototype.reduce,它们会遍历集合中的全部元素;而slice()、some()、every()等方法只需部分遍历即可完成工作。
从迭代角度看,slice()与includes()类似,可能只遍历部分元素。refine 是面向 React 的内部工具框架,其文档站与示例代码中大量出现对数组和字符串的切片操作——例如在 blog 系列文档 中讲解迭代方法、在packages/下各包的源码中用它实现分页与截断。本文以 refine 仓库为佐证环境,深入这两个slice()版本。
二、JS 数组切片:Array.prototype.slice
Array.prototype.slice用于从已有数组中提取一段连续的子数组。它接受两个可选参数:start和end指示符,也可以完全不传参,因此有三种调用签名:
// 方法签名 slice(); slice(start); slice(start, end);2.1 基础用法:从源数组提取连续区段
一个典型场景是从源数组中产出前三个、后三个或某个区间的元素:
const elements = [ "Please", "Send", "Cats", "Monkeys", "And", "Zebras", "In", "Large", "Cages", "Make", "Sure", "Padlocked", ]; const firstThree = elements.slice(0, 3); const lastThree = elements.slice(-3, elements.length); const fromThirdToFifth = elements.slice(2, 5);slice()的两个参数都是从零开始的索引号,负值表示从数组末尾往前数的偏移。第一个参数是切片的起始索引(如firstThree中的0);第二个参数是提取应在其之前停止的索引(如3)。
console.log(firstThree); // ["Please", "Send", "Cats"] console.log(lastThree); // ["Make", "Sure", "Padlocked"] console.log(fromThirdToFifth); // ["Cats", "Monkeys", "And"]注意第二个参数所指向的元素不包含在切片结果中,这是slice()最容易被误解的语义。
2.2slice与splice的区别
Array.prototype.slice的第二个参数不是要提取的项数。而在Array.prototype.splice中,第二个参数是要删除的项数。两者本质差异在于:splice()会原地修改原数组,而slice()返回一份副本,不触碰原数组。
2.3 参数边界行为详解
以下各小节用示例覆盖Array.prototype.slice在不同参数取值下的行为。
无参调用:整体浅拷贝
不传任何参数时,得到源数组的浅拷贝:
const allCopied = elements.slice(); console.log(allCopied); // (12) ["Please", "Send", "Cats", "Monkeys", "And", "Zebras", "In", "Large", "Cages", "Make", "Sure", "Padlocked"]它等价于[...elements]。
省略第二个参数:一直取到末尾
不传第二个参数时,切片一直延伸到最后一个元素:
const fromThird = elements.slice(2); const lastThree = elements.slice(-3, elements.length); const lastThreeWithNoSecArg = elements.slice(-3); console.log(fromThird); // (10) ["Cats", "Monkeys", "And", "Zebras", "In", "Large", "Cages", "Make", "Sure", "Padlocked"] console.log(lastThree); // (3) ["Make", "Sure", "Padlocked"] console.log(lastThreeWithNoSecArg); // (3) ["Make", "Sure", "Padlocked"]可以看到lastThreeWithNoSecArg与lastThree结果一致,只是少传了第二个参数。
负偏移
两个参数都支持负数,表示从数组末尾倒数:
const latestTwoBeforeLast = elements.slice(-3, -1); console.log(latestTwoBeforeLast); // (2) ["Make", "Sure"]这里取到的是末尾倒数第二、第三个元素(即最后一个元素之前的两个)。
start 大于 end:返回空数组
const somewhereWeDontKnow = elements.slice(5, 2); console.log(somewhereWeDontKnow); // []start 超过数组长度:返回空数组
const somewhereInOuterSpace = elements.slice(15, 2); console.log(somewhereInOuterSpace); // []稀疏数组:空位也会被拷贝
如果切片区段内存在稀疏(空)项,它们会被一并复制:
const elements = [ "Please", "Send", , "Cats", , "Monkeys", "And", "Zebras", "In", "Large", "Cages", "Make", "Sure", "Padlocked", ]; const sparseItems = elements.slice(0, 6); console.log(sparseItems); // (6) [ 'Please', 'Send', <1 empty item>, 'Cats', <1 empty item>, 'Monkeys' ]2.4 进阶:从函数参数列表构造数组
下面演示两种用Array.prototype.slice将函数参数列表构造为数组的方式。
第一种,借助 rest 参数与Function.prototype.call:
const createArray = (...args) => Array.prototype.slice.call(args); const array = createArray(1, 2, 3, 4); console.log(array); // (4) [1, 2, 3, 4]这里先通过...args收集参数,再用Function.prototype.call以args为上下文调用Array.prototype.slice,得到一份新的数组副本。
第二种,用Function.prototype.bind做上下文绑定的“函数组合”:
const boundSlice = Function.prototype.call.bind(Array.prototype.slice); const createArray = (...args) => boundSlice(args); const array = createArray(1, 2, 3, 4); console.log(array); // (4) [1, 2, 3, 4]这里声明了一个辅助函数boundSlice:它是Function.prototype.call通过bind绑定到数组函数对象Array.prototype.slice上的产物。因此调用boundSlice()效果等同于调用Array.prototype.slice.call——传入args数组后,slice会基于它创建一份副本。createArray()收集 rest 参数后交给boundSlice(),最终返回与入参一致的新数组。这段示例展示了上下文绑定(call/bind)如何与slice()组合出通用工具函数。
三、什么时候该用 JavaScriptslice方法
以下七个场景是slice()的典型适用面:
- 创建数组子集:需要取数组的一部分又不改变原数组时,
slice是最合适的选择。 - 拷贝数组:
slice()可以创建整个数组的浅拷贝,适合在副本上工作、保持原数组不变。 - 字符串操作:
slice()同样适用于字符串,基于起止索引提取子串。 - 创建分页数据:实现分页时,用
slice()取出当前页应展示的数据子集。refine 正是这么做的(见第五节源码示例)。 - 条件移除元素:
slice()本身不移除元素,但可以与其他方法组合实现条件移除。 - 非破坏性数组操作:需要保证原数据结构不被修改时,
slice()优于会原地修改数组的splice()。 - 处理
arguments对象:在函数内部将类数组的arguments对象转换为真正的数组,从而可以使用数组方法。
四、JS 字符串切片:String.prototype.slice
String.prototype.slice做的事情与Array.prototype.slice完全相同,只是操作对象是字符串中的字符。它同样接受两个可选参数start和end,也可以无参调用:
// 方法签名 slice(); slice(start); slice(start, end);4.1 基础用法:提取子串
String.prototype.slice可以直接在字符串上工作,省去了先用String.prototype.split把字符串拆成数组的麻烦:
const mnemonic = "Please Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked"; const firstThreeChars = mnemonic.slice(0, 3); const lastThreeChars = mnemonic.slice(-3, mnemonic.length); const fromThirdToFifthChars = mnemonic.slice(2, 5); console.log(firstThreeChars); // "Ple" console.log(lastThreeChars); // "ked" console.log(fromThirdToFifthChars); // "eas"两个参数同样是零基索引或偏移值:第一个参数是起始位置,第二个参数表示提取应在其之前停止。refine 源码中这一“首尾分离”模式随处可见,例如 humanizeString 工具函数 用text.charAt(0).toUpperCase() + text.slice(1)把驼峰/下划线命名转为首字母大写的可读文本。
4.2 字符串 slice 的边界行为
无参调用:复制整个字符串
const mnemonic = "Please Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked"; const memorizedMnemonic = mnemonic.slice(); console.log(memorizedMnemonic); // "Please Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked"省略第二个参数:一直取到末尾
const charsFromThird = mnemonic.slice(2); console.log(charsFromThird); // "ease Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked"负偏移
与数组版本一致,负值表示从字符串末尾倒数:
const mnemonic = "Please Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked"; const lastThreeChars = mnemonic.slice(-3); const latestTwoCharsBeforeLast = mnemonic.slice(-3, -1); console.log(lastThreeChars); // "ked" console.log(latestTwoCharsBeforeLast); // "ke"start 大于 end 或超过长度:返回空字符串
const inAnotherDimension = mnemonic.slice(5, 2); const doingStringTheory = mnemonic.slice(15, 2); console.log(inAnotherDimension); // "" console.log(doingStringTheory); // ""五、slice()的高级用例
除常规用法外,slice()在更复杂的数据场景中有几类有意思的用法。
5.1 嵌套数组的选择性切片
处理嵌套数组、只想抽取某段子数组时,slice()不触碰原结构:
const nestedArray = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]; const slicedSubarray = nestedArray .slice(0, 2) .map((innerArr) => innerArr.slice(1)); console.log(slicedSubarray); // [[2, 3], [5, 6]]外层先取前两行,再对每个内层数组做切片,选择性强、原数组不受影响。
5.2 处理类数组对象
类数组对象(如函数arguments、DOM 中的NodeList)默认没有数组方法。用slice()可以直接把它们变成真正的数组,省去额外的循环或转换:
function getArgumentsArray() { return Array.prototype.slice.call(arguments); } const argsArray = getArgumentsArray(1, 2, 3); console.log(argsArray); // [1, 2, 3]5.3 配合不可变性(Redux 等场景)
在依赖不可变性的库(如 Redux)中,用slice()浅拷贝数组、在副本上做任意修改而保持原数据不变,是让状态更新可预测的常见手段:
const originalArray = [1, 2, 3, 4, 5]; const modifiedArray = originalArray.slice(0, 3).concat([10, 11]); console.log(modifiedArray); // [1, 2, 3, 10, 11] console.log(originalArray); // [1, 2, 3, 4, 5]这类用法让代码更整洁、数据流更可信赖。
六、使用slice()的最佳实践
用slice()保障不可变性
需要拷贝数组而不触碰原数组时,slice()是首选。例如 Redux store 中的 state 切片:
const originalArr = [1, 2, 3]; const copyArr = originalArr.slice(); copyArr.push(4); console.log(originalArr); // [1, 2, 3] console.log(copyArr); // [1, 2, 3, 4]与其他数组方法组合
slice()与map()、filter()等方法配合,可以只修改或提取数组的一部分而不影响整体:
const arr = [1, 2, 3, 4, 5]; const modifiedSubset = arr.slice(1, 4).map((num) => num * 2); console.log(modifiedSubset); // [4, 6, 8]这种“先切片再变换”的组合尤其适合从大数组中构造视图或分页数据集。
小心处理稀疏数组
slice()会保留空(稀疏)槽位,面对不完整数据集时可能产生意外行为。若想去掉空项,可以组合filter(Boolean):
const sparseArr = [1, , 3, , 5]; const cleanArr = sparseArr.slice().filter(Boolean); console.log(cleanArr); // [1, 3, 5]这样数组没有空洞,处理和展示都更一致。
七、refine 源码中的slice()实战印证
以下示例均取自 refine 仓库的源码,展示slice()在真实工程中的四种典型应用形态。
7.1 客户端分页:useList 的核心切片
refine 的核心数据 HookuseList在客户端分页模式下,正是用slice()从服务端返回的全量数据中切出当前页。useList.ts 中的关键逻辑:
if (prefferedPagination.mode === "client") { data = { ...data, data: data.data.slice( (prefferedPagination.currentPage - 1) * prefferedPagination.pageSize, prefferedPagination.currentPage * prefferedPagination.pageSize, ), total: data.total, }; }这里start = (currentPage - 1) * pageSize、end = currentPage * pageSize,是本文“创建分页数据”场景的标准公式。注意它保留了total,说明切片只影响展示子集而不影响总数统计。
7.2 数据导出时的行数截断
refine 的导出逻辑(useExport)在分批拉取数据时,用slice(0, maxItemCount)把已累积的原始数据截断到导出上限。export/index.ts:
if (maxItemCount && rawData.length >= maxItemCount) { rawData = rawData.slice(0, maxItemCount); preparingData = false; }这是slice(0, n)“取前 n 项”语义的直接应用,且截断的是重新赋值的副本,不影响分页循环本身。
7.3 路由参数解析:字符串slice(1)
refine 的路由辅助函数 pick-route-params.ts 从路由字符串(如/users/:id/posts/:postId)中提取参数名,参数段以:开头,用s.slice(1)剥掉前缀:
return segments.flatMap((s) => { if (isParameter(s)) { return [s.slice(1)]; } return []; });这与第四节“提取子串”的模式完全一致:字符串版本的slice()避免了把路由拆成字符数组再处理的开销。
7.4 生成代码中的文本截断与 CLI 段落切片
- refine 的代码生成器
inferencer在生成 Ant Design 列表页时,对 Markdown 字段渲染直接输出value.slice(0, 80) + "..."的截断逻辑,见 inferencers/antd/list.tsx——这是slice(0, n)用于控制 UI 文本长度的实例。 - CLI 包的公告解析 announcement/index.tsx 用
sections.slice(1)跳过split产生的第一个空段:
return sections .slice(1) .map((section) => `${ANNOUNCEMENT_DELIMITER}${section}`);这些实现印证了本文的结论:slice()是管理面板类框架中分页、截断、解析和不可变处理的基础工具。
八、小结
本文系统梳理了 JavaScript 的slice()方法:
- 两个同构实现:
Array.prototype.slice与String.prototype.slice语义一致,均接受可选的start、end参数,均返回源对象的副本,用于提取一段连续区段; - 参数语义:零基索引、
end指向元素被排除、负值从末尾倒数、start > end或越界返回空数组/空字符串、稀疏槽位会被保留; - 与
splice的本质区别:slice()非破坏性,splice()原地修改且第二个参数为删除项数; - 进阶能力:结合
Function.prototype.call/bind可把slice变成从参数列表构造数组的工具; - 工程落地:在 refine 中,
slice()直接支撑了 useList 客户端分页、导出行数截断、路由参数解析 与生成代码中的文本截断,是构建内部工具类应用时高频使用的数组/字符串基本功。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考