一、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()
方法使用的场景。
好了,以上就是本文的全部内容了。
如果你觉得写得还不错,欢迎转发!
?
(本篇完)