先记住这个答案
两者有三个区别。第一,slice 不修改原数组,返回截取出来的新数组;splice 直接修改原数组。第二,参数含义不同,slice(start, end) 的第二个参数是结束索引,而且不包含这个位置,splice(start, count, ...items) 的第二个参数是要删除的个数,后面还能跟要插入的元素。第三,返回值不同,slice 返回截取的部分,splice 返回被删掉的元素组成的数组。
slice不改原数组,splice改原数组slice第二参数是结束索引,不包含该位置splice第二参数是删除个数,返回被删元素- 想要不改原数组的
splice,用toSpliced()
同一个数组上分别调用
把两个方法用同样的数字参数调一遍,参数含义的差别就很清楚了。
const arr = [1, 2, 3, 4, 5]
const part = arr.slice(1, 3) // 从索引 1 截到索引 3 之前
console.log(JSON.stringify(part), JSON.stringify(arr))
const removed = arr.splice(1, 3) // 从索引 1 开始删 3 个
console.log(JSON.stringify(removed), JSON.stringify(arr))
arr.splice(1, 0, 'a', 'b') // 删 0 个,在索引 1 插入两个
console.log(JSON.stringify(arr))查看输出与解释
[2,3] [1,2,3,4,5]
[2,3,4] [1,5]
[1,"a","b",5]同样传 (1, 3) 这两个参数,slice 取的是索引 1 和 2 两个元素,原数组完整保留;splice 删掉了索引 1 开始的三个元素,原数组只剩 [1, 5]。最后一次 splice 删除个数为 0,只做插入,这是往数组中间加元素的标准写法。
记忆方法:slice 像复印一段,原件不动;splice 像在原件上动刀,切下来的部分交给你。
负数索引和省略参数
两个方法的 start 都支持负数,表示从末尾倒数,-1 是最后一个元素。所以 arr.slice(-2) 取最后两个,arr.splice(-1, 1) 删掉最后一个。slice 的 end 也可以是负数,arr.slice(0, -1) 就是去掉最后一个元素后的副本。
- 常见的省略写法
slice()不传参数时复制整个数组,是老代码里常见的浅拷贝写法。splice(start)只传一个参数时,会从start一直删到末尾,这点很容易被忽略,本来只想删一个,结果后面全没了。替换元素可以一步完成,arr.splice(2, 1, 'x')就是把索引 2 换成'x'。 另外,字符串也有slice,用法和数组一样;但字符串没有splice,因为字符串不可修改。
容易答错的地方
- 以为
splice返回修改后的数组 splice返回的是被删掉的元素。写成const next = arr.splice(1, 1)再去渲染next,拿到的是被删掉的那一项。修改后的结果就在arr本身。- 把
slice的第二个参数当成个数 arr.slice(2, 3)只取一个元素,不是从索引 2 开始取三个。slice的两个参数都是位置,取的是左闭右开的区间。- 在 React state 上直接用
splice splice改的是原数组,引用没变,React 可能不会重新渲染。删除某一项要写成list.filter((_, i) => i !== index),或者用不修改原数组的toSpliced。
面试官还会怎么问?
有没有不改原数组的 splice?
ES2023 新增了 toSpliced(),参数和 splice 完全一样,但返回的是修改后的新数组,原数组不动。注意它的返回值和 splice 不同:它返回修改后的整个数组,不是被删掉的元素。
删除数组某一项,用 splice 还是 delete?
用 splice。delete arr[i] 只是删掉这个索引上的属性,数组长度不变,中间留下一个空位;splice(i, 1) 会把后面的元素往前挪,长度减 1。
splice 删除中间元素的性能怎样?
删除或插入后,后面的元素都要挪位置,所以是 O(n)。在很长的数组里频繁删除中间元素开销会比较大,这种场景可以考虑先标记再统一 filter,或者换成更合适的数据结构。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。