共计 3794 个字符,预计需要花费 10 分钟才能阅读完成。
为了保证的可读性,本文采用意译而非直译。
想阅读更多优质文章请猛戳 GitHub 博客, 一年百来篇优质文章等着你!
JS 数组 slice
方法是 JS 语言中最强大、最常用的内建函数之一。
随着 React 和其他面向功能的 JavaScript 实践的兴起,它变得越来越重要,原因有两个:
- 函数式编程,尤其是高阶函数,与数据列表密切配合
- 函数式编程需要纯函数,即不会产生副作用或修改输入数据的函数
JavaScript 数组 slice
方法符合这两个标准。
slice
方法可以在不修改原始列表的情况下创建列表子集的浅拷贝。因此,它为编写函数式 JS 提供了一个关键的构建块。
在这篇文章中,我们将通过实例来掌握 slice
方法,探索它的 8 种不同用法。
注意 :
slice
方法不要与splice
方法混淆,splice
方法会修改原始数组。
slice 工作原理
在深入研究一些更高级的用法之前,让我们看一下 slice
方法的基础知识。
如 MDN 文档,slice
是数组上的一个方法,它最多有两个参数:
arr.slice([begin[, end]])
begin
从该索引处开始提取原数组中的元素, 如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2)
表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。
如果省略 begin
,则 slice
从索引 0 开始。
end
在该索引处结束提取原数组元素(从 0 开始)。slice
会提取原数组中索引从 begin
到 end
的所有元素(包含 begin,但不包含 end)。
slice(1,4)
提取原数组中的第二个元素开始直到第四个元素的所有元素(索引为 1, 2, 3 的元素)。
如果该参数为负数,则它表示在原数组中的倒数第几个元素结束抽取。slice(-2,-1)
表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。
如果 end
被省略,则slice
会一直提取到原数组末尾。如果 end
大于数组长度,slice
也会一直提取到原数组末尾。
基本用法
我们的前 4 个例子突出 slice
的核心功能。
用法 1:简单的复制
const arr2 = arr.slice
没有任何参数的 slice
执行一个简单的浅拷贝。当前,主流的用法还是使用展开运算符合来实现,但是如果在旧的代码库中,或者没有使用 babel
的构建步骤,可能仍然希望使用slice
。
用法 2:获取从 N 开始的子数组
使用 slice
方法最简单的方法就是原始数组从 N
开始抽取的所有元素。
一种情况是希望弹出数组的第一个元素并使用它,返回剩余的数组,但希望在不修改原始数组的情况下执行此操作。
function useone (arr) {const usedItem = arr[0]
return arr.slice(1)
}
用法 3:获取从末尾 N 开始的子数组
slice
的另一种使用方法是获取数组的末尾,利用的是 负索引
从末尾开始计数。
这种负索引使删除任意数量的元素变得超级简单。例如,如果你只想抓取 3 个
const last3 = arr.slice(-3)
用法 4:获取数组的前 n 个
获取数组的前面的数,我们需要使用第二个参数:end
。
当有两个参数时,slice
方法返回一个从 begin
开始但不包括 end
的集合。
由于 JavaScript 数组是从 0
开始的(索引从 0 开始),这使得获取前 N 个元素变得非常简单:
const first4 = arr.slice(0, 4)
用法 5:获取数组中某段子数组
如果我们想要使用 slice
从任何索引开始获取数组的一段,该怎么办?
为此,我们需要从 (begin, length)
转换为(begin, end)
。计算逻辑很简单,我们可以定义一个简单的函数来做到这一点:
function pullSegment(arr, begin, length) {return arr.slice(begin, begin + length);
}
处理类似数组的对象
JavaScript 中,数组是一个特殊的对象,其 property
名为正整数,且其 length
属性会随着数组成员的增减而发生变化,同时又从 Array
构造函数中继承了一些用于进行数组操作的方法。
而对于一个普通的对象来说,如果它的所有 property
名均为正整数,同时也有相应的 length
属性,那么虽然该对象并不是由 Array
构造函数所创建的,它依然呈现出数组的行为,在这种情况下,这些对象被称为 “类数组对象”。
slice
方法也可用于类似数组的对象。
一些类似数组包如 arguments
(用于访问传递给函数的所有参数的关键字),NodeLists
(从返回节点列表的任何 DOM API 方法返回),甚至是使用数字索引并添加length
属性的原始对象。
要在类似数组的对象上使用 slice
方法,需要直接从 Array.prototype
引用它,如下所示:
Array.prototype.slice.call(arguments)
在这特定的场合中会很有用处。
用法 6:将类似数组的对象转换为数组
slice
在类似数组的对象上的一个常见用途是将它们转换为实际数组。例如:
const args = Array.prototype.slice.call(arguments);
你为什么要这么做? 为了使用数组方法。例如,想象一个像这样的函数
function addOne() {return arguments.map(i => i+1);
}
这看起来可行,但如果你试着去做,你就会得到错误:
> addOne(1, 2, 3)
TypeError: arguments.map is not a function
at test (repl:2:18)
at repl:1:1
at ContextifyScript.Script.runInThisContext (vm.js:44:33)
at REPLServer.defaultEval (repl.js:239:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:440:10)
at emitOne (events.js:120:20)
at REPLServer.emit (events.js:210:7)
at REPLServer.Interface._onLine (readline.js:279:10)
这是因为arguments
实际上不是数组,而是类似数组的对象。可以使用 slice 实现此功能,如下所示:
function addOne() {return Array.prototype.slice.call(arguments).map(i => i+1)
}
现在就可以得到了你所希望的数据:
> addOne(1, 2, 3)
[2, 3, 4]
用法 7:将任意长度多余的参数强制转换为数组
有时希望接受函数的多余参数,组成一个数组。
较新版本的 JavaScript 引入了所谓的 Rest 语法
来处理这个问题,但是如果为为了兼容旧浏览器,你可以使用 slice
做到这一点:
function myFunc(a, b) {const extraArgs = Array.prototype.slice.call(arguments, 2);
}
这允许使用任意数量的参数调用myFunc
,例如:
myFunc(1, 2, 3, 4, 5, 6, 7, 8)
在函数里面会得到a == 1
,b === 2
,extraArgs=== [3,4,5,6,7,8]
用法 8:修改数组中的特定索引
slice
在函数上下文中一个强大而常见的用法是替换数组中特定项的值。
从本质上讲,这很简单,只需要分配新值,但是在函数世界中,不能修改原始数组。
相反,可以将 slice
与扩展运算符一起使用,以返回一个相同但对于要更新的索引的新数组:
function replaceIdx(arr, index, newVal) {
return [...arr.slice(0, index),
newVal,
...arr.slice(index + 1)
]
}
偏函数应用
偏函数应用,英文是partial application
,也可以译作“局部应用”、“部分应用”、“偏应用”
函数式编程中的另一种常见模式是所谓的偏函数应用:将函数预先应用于函数,然后返回一个新函数。
这种模式允许你组合函数,通过使用具有不同预应用参数的相同核心函数来创建更大的可重用性。
虽然像 Haskell 这样的纯函数语言本身支持偏函数应用程序,但是在 JavaScript 中,我们可以使用 slice
实现一个函数来实现它
var partial = function() {const fn = arguments[0];
const args = Array.prototype.slice.call(arguments, 1);
// Return a function that calls fn
return function() {var remainingArgs = Array.prototype.slice.call(arguments);
return fn.apply(this, args.concat(remainingArgs));
}
}
交流
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复 福利,即可看到福利,你懂的。