先记住这个答案
splice 直接修改原数组,通过指定起始位置和删除个数来增删元素,返回被删除的元素数组;slice 不修改原数组,而是根据起始和结束索引(结束索引不包含)提取一段元素组成新数组返回。因此,需要原地修改时用 splice,需要获取副本或子数组时用 slice。
splice修改原数组,slice不改splice参数含删除个数,slice含结束索引slice结束索引为开区间,不包含该位置元素
原地操作与复制截取的本质区别
splice 是原地方法,它直接修改调用它的数组。第一个参数是起始索引,第二个参数是要删除的元素数量(若不传则删除到末尾)。之后可追加任意数量元素作为插入内容,这些元素会被插入在删除的起始位置。返回的是一个新数组,包含所有被删除的元素,若无删除则返回空数组。
slice 是复制方法,它不触碰原数组,而是根据 start 和 end 两个索引提取元素。end 索引指向的位置不被包含,因此 [1,2,3].slice(1,2) 得到 [2]。slice 支持负索引,会从数组末尾倒数。若 start 或 end 被省略或越界,会按规则调整为合法范围。由于 slice 生成新数组,常用于需要保留原数据不变的场合。
任务列表的删除与抽取
假设有一个待办任务数组 tasks = [{id:1,text:'A'}, {id:2,text:'B'}, {id:3,text:'C'}],需要删除 id=2 的任务并保留删除记录。此时必须用 splice:先找到索引 idx=1,调用 tasks.splice(idx,1),原数组变为 [{id:1},{id:3}],返回 [{id:2}] 供记录。
如果需要获取当前列表的副本用于展示,且绝不修改原数据,则用 slice:const copy = tasks.slice() 得到浅拷贝。若只需要前两条任务,用 tasks.slice(0,2)。这个场景中,splice 和 slice 各司其职,选错会导致原数据被意外修改或无法删除。
索引与边界条件的陷阱
splice 的起始索引为负数时,表示从数组末尾倒数,如 arr.splice(-2, 1) 会删除倒数第二个元素。deleteCount 会被转换为整数,若为负数或 NaN 则按 0 处理,若省略或大于从起始位置到末尾的元素数量,则删除到末尾。另外,若起始索引大于数组长度,splice 会把新元素追加到数组末尾,而不是报错。
slice 的边界更宽容:start 或 end 越界会被夹紧到合法范围,比如 [1,2,3].slice(-10, 10) 返回完整拷贝。但当 end 小于等于 start 时始终返回空数组。理解这些规则有助于避免边界错误,尤其在动态计算索引时。
容易答错的地方
- 误以为 slice 会修改原数组
- 常见错误:认为
arr.slice(1,3)像splice一样删除元素。实际上slice不修改原数组,它只是提取并返回新数组。必须用返回值接收结果。 - 混淆 splice 的第二参数含义
- 错误认识:把
splice的第二参数当作结束索引。实际上它表示要删除的元素个数,不是位置。例如arr.splice(1,2)删除索引 1 和 2 两个元素,而不是删除到索引 2。
面试官还会怎么问?
splice 和 slice 都能接收负数索引,规则一样吗?
规则相似但语义不同:splice 的负数索引也倒数,但第二参数是删除个数不能为负(负数会被当作0)。slice 的 start 和 end 都支持负数,且 end 为负数时表示倒数位置,并转为实际索引。
如果我想在不修改原数组的情况下删除中间元素,该怎么做?
可以先 slice 得到前后两段,再拼接:const result = arr.slice(0, i).concat(arr.slice(i+1))。但要注意这会产生中间数组,若操作频繁,可考虑直接用 splice 修改原数组,前提是允许改动。
splice 和 slice 对稀疏数组的处理有何不同?
slice 会保留稀疏数组中的空位,例如 [1,,3].slice(0,2) 返回 [1, empty]。splice 则是按索引操作,并非填充空位为 undefined:删除空位时该空位会被移除,后续元素前移;若删除位置后方存在空位,空位会随元素移动而保留,因此稀疏状态可能改变。两者本身不遍历元素,因此既不跳过也不额外填充空位,这与 map 等遍历方法保留空位的处理方式不同。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。