
一、copyWithin()对比slice()
JavaScript中数组的copyWithin()和slice()方法作用都是复制数组,且都是浅复制。
区别之一在于
copyWithin()是将数组中的一部分复制并替换另外一部分,总长度是不变的,而slice()方法只复制,不替换,返回的数组长度是由复制的数组项目个数决定的。
看一个区别,已知数组 arr 为 [1, 'A', '甲', 'I'],则分别执行 arr.copyWithin(2, 3) 和 arr.slice(2, 3) 会有如下所示的不同结果。
arr = [1, 'A', '甲', 'I'];
console.log(arr.copyWithin(2, 3));
// 结果是: [1, 'A', 'I', 'I']
arr = [1, 'A', '甲', 'I'];
console.log(arr.slice(2, 3));
// 结果是: ['甲']
在控制台运行结果如下图所示:

区别之二在于
copyWithin()方法会改变原始的数组,而slice()方法并不会。
例如:
arr = [1, 'A', '甲', 'I'];
arr.copyWithin(2, 3);
console.log(arr);
// 结果是: [1, 'A', 'I', 'I']
arr = [1, 'A', '甲', 'I'];
arr.slice(2, 3);
console.log(arr);
// 结果是:[1, 'A', '甲', 'I']
控制台运行结果如下图所示:

可以看到,copyWithin()方法执行后,原始的arr数组发生了变化,变成了复制后的数组内容。
区别之三在于
slice()复制对于字符串也是有效的,但是copyWithin()方法却不支持。
例如:
// 返回值是'angx'
('zhangxinxu').slice(2, 6)
但是下面的语句就会报错:
// 会无情报错
('zhangxinxu').copyWithin(2, 6)
二、copyWithin()的语法
copyWithin()的语法形式上不算复杂,但是理解起来有些费脑细胞,我也是看了好几遍才弄明白。
语法
Array.prototype.copyWithin(index, start, end)
表示复制start-end的内容,并从index开始替换。
使用示意:
[].copyWithin()
[].copyWithin(index)
[].copyWithin(index, start)
[].copyWithin(index, start, end)
参数
index : 复制的数组项开始替换的起始索引值。
start : 复制的数组项的起始位置(包含)。
end : 复制的数组项的结束位置(不包含)。
举例说明
例如:
arr = [1, 'A', '甲', 'I', '一'];
arr.copyWithin(2, 3, 4);
此时,start=3,end=4,表示复制数组的arr[3](包含)到arr[4]项(不包含),因此复制的值是 arr[3],也就是 'I'。
index=2,则表示复制的值从arr[2]这里开始替换。

于是复制的结果就是:[1, 'A', 'I', 'I', '一']
参数细节
copyWithin()方法的参数细节比较多,毕竟数值有大有小,有正有负,情况不同,规则也会不同。
具体如下:
1. index参数
- 
index参数不设置也是合法的,不过你不设置该参数数组就会原路复制返回,类似于设置为 0,这样处理只会浪费性能,没有任何实际意义。因此,
index参数可以认为是必须参数。 - 
如果index是小数,则会按照整数处理,并且是向下取整。例如:
// 返回值是 true [1, 2, 3].copyWithin(1.5).join() == [1, 2, 3].copyWithin(1).join() // copyWithin(1.5)执行结果是:[1, 1, 2] - 
如果index是负数。
- 
如果index负数的绝对值很大,甚至比数组的长度还要大,则此时 index 当作 0 处理。例如:
// 返回值是 ['zhang', 'xin', 'xu'] // 等同于 copyWithin(0) ['zhang', 'xin', 'xu'].copyWithin(-999) - 
否则,最终的 index 值等于 index + arr.length,比方说
[1, 2, 3].copyWithin(-1)就等同于[1, 2, 3].copyWithin(2);// 结果是[1, 2, 1] [1, 2, 3].copyWithin(-1); // 结果同样是[1, 2, 1] [1, 2, 3].copyWithin(2);
 
 - 
 - 
如果index的值超出了数组的长度范围,则不会有任何内容被复制。这里需要注意下,没有任何内容复制和所有内容都复制是有区别的,虽然看起来返回的都像是原数组,但其实并不是,性能这块是有着明显的差异的。 我们可以用非常简单的方法测试下,例如我们构造一个99万个项目的数组进行复制处理:
console.time('start'); arr = new Array(999999); console.timeLog('start'); arr.copyWithin(0); console.timeLog('start'); arr.copyWithin(1000000); console.timeEnd('start');我们在控制台跑一下上面的代码,通过观察执行时间就会发现执行copyWithin(0)用了近70ms,而执行copyWithin(1000000)的用时几乎就是0.

 - 
如果 index 的位置在 start 之后,则复制的内容粘贴到数组长度的尾部就停止了,也就是不会增加数组的长度的: 例如:
// 结果是 [1, 'A', '甲', 'A', '甲'] [1, 'A', '甲', 'I', '一'].copyWithin(3, 1); 
2. start参数
- start参数是可选的,如果不设置,则当作0处理,表示数组从头复制到尾部(index小于数值长度的前提下);
 - start如果是小数,会向下取整处理。例如
[].copyWithin(0, 1.9999)等同于[].copyWithin(0, 1); - start如果是负数,则从数组的后面开始取值,等同于 start + arr.length;
 - start如果是负数,同时绝对值小于 -1 * arr.length,则当作 0 处理。
 - start如果超出数组的长度范围,则不会发生复制。
 
2. end参数
- 
end参数是可选的,如果不设置,则当作
array.length处理,表示数组复制到尾部; - 
end如果是小数,会向下取整处理。例如
[].copyWithin(0, 1, 2.999)等同于[].copyWithin(0, 1, 2),例如:// 结果是:[2, 2, 3, 4] [1, 2, 3, 4].copyWithin(0, 1, 2.9999) - 
end如果是负数,则从数组的后面开始取值,等同于 end + arr.length;
 - 
end如果是负数,同时绝对值小于 -1 * arr.length,则当作 0 处理。
 - 
end如果超出数组的长度范围,则当作
array.length处理。 - 
如果end对应的位置比start小,则不会发生复制。
 
三、copyWithin实际应用
我想的一些应用,也找了一些应用, 欢迎大家补充更适合使用copyWithin()的场景。
1. 删除数组中间某一项
数组删除第一个项目可以使用 shift() 方法,删除最后一个项目可以使用 pop() 方法,但似乎没有删除中间某一项的方法。
在过去都是使用 splice() 方法实现的,语法示意:
[].splice(index, 1);
现在多了个选择,使用 copyWithin(),例如:
[].copyWithin(index, index + 1).pop();
例如,删除数组 arr 中的 第 2 项:
arr = ['甲', '乙', '丙', '丁'];
arr.copyWithin(1, 2).pop();
// 结果是['甲', '丙', '丁']
console.log(arr);
2. 置顶数组某一项
这种数组总长度不变的场景比较适合 copyWithin 方法:
// 自定义top置顶方法
Array.prototype.top = function (index) {
    const value = this[index];
    this.copyWithin(1, 0, index);
    this[0] = value;
};
例如:
arr = ['甲', '乙', '丙', '丁'];
arr.top(1);
// 结果是["乙", "甲", "丙", "丁"]
console.log(arr);
3. 模拟插入排序算法
下面这个案例源自这个stackoverflow:
const insertionSort = (data, compare) => {
  const arr = [...data];
  let unsort_index = 1;
while (unsort_index < arr.length) {
`while (arr[unsort_index] >= arr[unsort_index - 1]) unsort_index += 1;
const pick = arr[unsort_index];
let iter = 0;
while (iter < unsort_index && arr[iter] < pick) iter += 1;
arr.copyWithin(iter + 1, iter, unsort_index);
arr[iter] = pick;
unsort_index += 1;
`
}
return arr;
}
const input = [2, 3, 5, 1, 9, 8, 6, 6];
const asc = (a, b) => a - b;
const dsc = (a, b) => b - a;
console.log({ input, asc_sorted: insertionSort(input, asc) });
console.log({ input, dsc_sorted: insertionSort(input, dsc) });
等。
四、结束语
copyWithin()是一个非常高性能的数组移动方法。
不过,我查找了一圈,暂时没有发现特别适合这个方法的应用场景,理论上,这个仅适合当前数据内复制并粘贴的场景。
比方说一张画布内复制粘贴,对于在数据层面,这样的场景并不多,因为直接覆盖数据,还是用旁边的数据覆盖,并不常见。
也可能是自己目前所接触的开发场景还不够多。
希望以后可以碰到特别适合使用 copyWithin() 方法使用的场景。
好了,以上就是本文的全部内容了。
如果你觉得写得还不错,欢迎转发!
?
(本篇完)
51工具盒子