先记住这个答案
copyWithin(target, start, end) 把当前数组 [start, end) 范围的元素复制到从 target 开始的位置,覆盖那里原来的值。它直接修改原数组并返回同一个数组,长度不会变,复制到末尾放不下的部分会被截掉。参数都支持负数,含义是从末尾往前数。源和目标重叠时不会出现「刚写进去的值又被当成源」的错误,结果等同于先把源区间完整拷一份再贴到目标位置。它复制的是值或引用,源位置的元素不会被删除。
- 修改原数组并返回它本身,长度永远不变
- 源区间左闭右开,支持负数索引
- 重叠时不会被覆盖错,相当于先拷贝再粘贴
- 源位置的值还在,只是目标位置被覆盖
先看几种基本用法
把 copyWithin 想成在同一张表格里「复制一段单元格,粘贴到另一处」,表格的行数不会变。
const a = [1, 2, 3, 4, 5]
console.log(JSON.stringify(a.copyWithin(0, 3))) // 把 [4, 5] 贴到开头
const c = [1, 2, 3, 4, 5]
console.log(JSON.stringify(c.copyWithin(-2, 0))) // 目标是下标 3,只放得下 2 个
const d = [1, 2, 3]
console.log(d.copyWithin(0, 1) === d, d.length) // 返回原数组,长度不变查看输出与解释
[4,5,3,4,5]
[1,2,3,1,2]
true 3第二个例子里,源区间是整个数组,一共 5 个元素,但目标从下标 3 开始,后面只剩 2 个位置,所以只复制了 1 和 2,其余被截掉。实际复制的数量是「源区间长度」和「目标到末尾的空间」两者中较小的那个。
第三个例子说明它是原地修改。d.copyWithin(0, 1) 返回的就是 d 本身,d 已经变成了 [2, 3, 3]。如果不想动原数组,要先复制一份再调用。
重叠时为什么不会覆盖错
当目标位置落在源区间内部、而且在源起点后面时,从前往后逐个赋值就会出错:前面刚写进去的值,会在后面被当成源再复制一次。
copyWithin 对比JavaScriptfunction naiveCopy(arr, target, start, end) {
for (let i = start; i < end; i++) arr[target + (i - start)] = arr[i]
return arr
}
console.log(JSON.stringify(naiveCopy([1, 2, 3, 4, 5], 1, 0, 3))) // 1 被一路复制下去
console.log(JSON.stringify([1, 2, 3, 4, 5].copyWithin(1, 0, 3)))查看输出与解释
[1,1,1,1,5]
[1,1,2,3,5]手写循环第一步把下标 1 改成了 1,第二步读下标 1 时读到的已经是新值,于是 1 被一路复制下去。copyWithin 在这种情况下会从后往前复制,先写下标 3,再写 2,最后写 1,每次读到的都还是原来的值。这和 C 语言里 memmove 处理重叠内存的思路一样。
自己实现时的规则是:目标在源起点之后且有重叠,就从后往前复制;否则从前往后。
- 它真正适合的场景
copyWithin最早是为 TypedArray 设计的,普通数组也顺带有了。它适合在固定长度的缓冲区里挪数据,比如音频采样数组整体左移一段、环形缓冲区腾位置,不需要创建新数组。普通业务数组很少用到它,至于是不是比手写循环快,要看数据类型和引擎,用真实数据测过再下结论。
容易答错的地方
- 以为它会返回新数组
copyWithin和fill、sort、reverse一样是原地修改的。在 React 状态上直接调用它,会改掉原状态,要先[...arr]复制一份。- 以为复制是「剪切」
- 源位置的元素不会被删除或清空。
[1, 2, 3, 4, 5].copyWithin(0, 3)之后,下标 3、4 上还是 4 和 5。想要移动效果,得自己再处理源位置。 - 以为超出长度会扩展数组
- 长度永远不变。
target大于等于长度时什么都不做,复制不下的部分直接丢掉,不会像给arr[10]赋值那样把数组撑长。
面试官还会怎么问?
copyWithin 复制对象时是深拷贝吗?
不是。它复制的是元素本身,对象元素复制的是引用,目标位置和源位置指向同一个对象,改一个另一个也变。
和 splice 有什么区别?
splice 可以删除和插入,会改变数组长度,后面的元素整体移动。copyWithin 只覆盖不插入,长度固定。要把一段元素挪到别处并保持其它元素顺序,用 splice;在定长缓冲区里覆盖一段,用 copyWithin。
源区间里有空位会怎样?
对应的目标位置会被删除成空位。比如 [1, , 3].copyWithin(0, 1) 的结果是下标 0 变成空位、后两项都是 3。这是因为规范在复制时先检查源属性是否存在,不存在就删除目标属性,而不是写入 undefined。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。