为了在 JavaScript 中复制一个数组:以下哪个使用起来更快?
切片法
var dup_array = original_array.slice();
循环
for(var i = 0, len = original_array.length; i < len; ++i)
dup_array[i] = original_array[i];
我知道这两种方法都只做 浅拷贝:如果 original_array
包含对对象的引用,则不会克隆对象,但只会复制引用,因此两个数组都将引用相同的对象。但这不是这个问题的重点。
我只问速度。
克隆数组至少有 6 种(!)方法:
环形
片
数组.from()
连接
扩展运算符(最快)
地图 A.map(function(e){return e;});
有一个 huuuge BENCHMARKS thread,提供以下信息:
对于 Blink 浏览器 slice() 是最快的方法, concat() 有点慢,而 while 循环慢 2.4 倍。
对于其他浏览器,while 循环是最快的方法,因为这些浏览器没有对 slice 和 concat 进行内部优化。
2016 年 7 月仍然如此。
下面是一些简单的脚本,您可以将它们复制粘贴到浏览器的控制台中并运行几次以查看图片。它们输出毫秒,越低越好。
while 循环
n = 1000*1000;
start = + new Date();
a = Array(n);
b = Array(n);
i = a.length;
while(i--) b[i] = a[i];
console.log(new Date() - start);
片
n = 1000*1000;
start = + new Date();
a = Array(n);
b = a.slice();
console.log(new Date() - start);
请注意,这些方法将克隆 Array 对象本身,但数组内容是通过引用复制的,而不是深度克隆。
origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true
技术上slice
是最快的方法。 不过,如果添加 0
开始索引,速度会更快。
myArray.slice(0);
比
myArray.slice();
myArray.slice(0,myArray.length-1);
比 myArray.slice(0);
快吗?
slice()
更快,有时 slice(0)
,两者都只是稍微快一点(在 Firefox 56 和基于 Chrome 的最新 Vivaldi 中)。但是 slice(0, length)
总是明显变慢(除了它是 Firefox 87 中最快的)。
es6方式呢?
arr2 = [...arr1];
[].concat(_slice.call(arguments))
arguments
来自哪里...我认为您的 babel 输出混合了一些不同的功能。更有可能是 arr2 = [].concat(arr1)
。
arr2 = [].conact(arr1)
与 arr2 = [...arr1]
不同。 [...arr1]
语法会将洞转换为 undefined
。例如,arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3
。
n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
[{a: 'a', b: {c: 'c'}}]
。如果 c
的值在“复制”数组中更改,它将在原始数组中更改,因为它只是一个引用副本,而不是克隆。
深度克隆数组或对象的最简单方法:
var dup_array = JSON.parse(JSON.stringify(original_array))
undefined
或任何 function
。在 JSON.stringify
过程中,这两者都会为您转换为 null
。其他策略,例如 (['cool','array']).slice()
不会更改它们,但也不会深度克隆数组中的对象。所以有一个权衡。
n = 1000*1000; start = + new Date(); a = Array(n); var b = JSON.parse(JSON.stringify(a)) console.log(new Date() - start); // 221
🏁 克隆数组的最快方法
我制作了这个非常简单的实用函数来测试克隆数组所需的时间。它不是 100% 可靠的,但它可以让您大致了解克隆现有阵列需要多长时间:
function clone(fn) {
const arr = [...Array(1000000)];
console.time('timer');
fn(arr);
console.timeEnd('timer');
}
并测试了不同的方法:
1) 5.79ms -> clone(arr => Object.values(arr));
2) 7.23ms -> clone(arr => [].concat(arr));
3) 9.13ms -> clone(arr => arr.slice());
4) 24.04ms -> clone(arr => { const a = []; for (let val of arr) { a.push(val); } return a; });
5) 30.02ms -> clone(arr => [...arr]);
6) 39.72ms -> clone(arr => JSON.parse(JSON.stringify(arr)));
7) 99.80ms -> clone(arr => arr.map(i => i));
8) 259.29ms -> clone(arr => Object.assign([], arr));
9) Maximum call stack size exceeded -> clone(arr => Array.of(...arr));
更新:
测试是在 2018 年进行的,所以今天你很可能会在当前浏览器上得到不同的结果。在所有这些中,深度克隆数组的唯一方法是使用 JSON.parse(JSON.stringify(arr))。也就是说,如果您的数组可能包含函数,请不要使用上述内容,因为它将返回 null。感谢@GilEpshtain 提供此更新。
我在 Chrome 和 Firefox 中运行 @mesqueeb 的基准测试。 slice()
似乎仍然是一个不错的选择。
https://i.stack.imgur.com/S01im.png
https://i.stack.imgur.com/NqXf1.png
arr => arr.slice()
是最快的。
JSON.parse(JSON.stringify([function(){}]))
将输出 [null]
[...arr]
在 Chrome 中使用 4.653076171875ms
,在 Safari 中使用 8.565ms
。 Chrome 中第二快的是带有 6.162109375ms
的切片函数 arr.slice()
,而在 Safari 中第二快的是 [].concat(arr)
和 13.018ms
。
var cloned_array = [].concat(target_array);
我整理了一个快速演示:http://jsbin.com/agugo3/edit
我在 Internet Explorer 8 上的结果是 156、782 和 750,这表明 slice
在这种情况下要快得多。
a.map(e => e)
是此作业的另一种选择。到目前为止,.map()
在 Firefox 中的速度非常快(几乎与 .slice(0)
一样快),但在 Chrome 中则不然。
另一方面,如果一个数组是多维的,因为数组是对象,而对象是引用类型,所以 slice 或 concat 方法都不能治愈......所以克隆数组的一个正确方法是 { 1}如下。
Array.prototype.clone = function(){ return this.map(e => Array.isArray(e) ? e.clone() : e); }; var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ], brr = arr.clone(); brr[4][2][1] = "两个"; console.log(JSON.stringify(arr)); console.log(JSON.stringify(brr));
看看:link。这不是关于速度,而是关于舒适。此外,如您所见,您只能在 原始类型 上使用 slice(0)。
要制作数组的独立副本而不是对其引用的副本,您可以使用数组切片方法。
例子:
要制作数组的独立副本而不是对其引用的副本,您可以使用数组切片方法。 var oldArray = ["mip", "map", "mop"]; var newArray = oldArray.slice();复制或克隆对象: function cloneObject(source) { for (i in source) { if (typeof source[i] == 'source') { this[i] = new cloneObject(source[i]); } else{ 这[i] = 源[i]; } } } var obj1= {bla:'blabla',foo:'foofoo',etc:'etc'}; var obj2=新的克隆对象(obj1);
来源:link
for
循环。
克隆对象数组的最快方法是使用扩展运算符
var clonedArray=[...originalArray]
但是该克隆数组中的对象仍将指向旧的内存位置。因此更改 clonedArray 对象也将更改 orignalArray。所以
var clonedArray = originalArray.map(({...ele}) => {return ele})
这不仅会创建新数组,还会克隆对象。
使用 Spread
运算符的 ECMAScript 2015 方式:
基本示例:
var copyOfOldArray = [...oldArray]
var twoArraysBecomeOne = [...firstArray, ..seccondArray]
在浏览器控制台中尝试:
var oldArray = [1, 2, 3]
var copyOfOldArray = [...oldArray]
console.log(oldArray)
console.log(copyOfOldArray)
var firstArray = [5, 6, 7]
var seccondArray = ["a", "b", "c"]
var twoArraysBecomOne = [...firstArray, ...seccondArray]
console.log(twoArraysBecomOne);
参考
扩展运算符的 6 大用途
传播语法
正如@Dan 所说“这个答案很快就会过时。使用 benchmarks 检查实际情况”,jsperf 有一个具体的答案,它自己没有答案:while:
var i = a.length;
while(i--) { b[i] = a[i]; }
有 960,589 次操作/秒,亚军 a.concat()
为 578,129 次/秒,即 60%。
这是最新的 Firefox (40) 64 位。
@aleclarson 创建了一个新的、更可靠的基准。
基准时间!
函数日志(数据){ document.getElementById("log").textContent += data + "\n"; } benchmark = (() => { time_function = function(ms, f, num) { var z = 0; var t = new Date().getTime(); for (z = 0; ((new Date(). getTime() - t) < ms); z++) f(num); return (z) } function clone1(arr) { return arr.slice(0); } function clone2(arr) { return [...arr] } function clone3(arr) { return [].concat(arr); } Array.prototype.clone = function() { return this.map(e => Array.isArray(e) ? e.clone() : e) ; }; function clone4(arr) { return arr.clone(); } function benchmark() { function compare(a, b) { if (a[1] > b[1]) { return -1; } if ( a[1] < b[1]) { return 1; } return 0; } funcs = [clone1, clone2, clone3, clone4]; 结果 = []; funcs.forEach((ff) => { console.log( "基准测试:" + ff.name); var s = time_function(2500, ff, Array(1024)); results.push([ff, s]); console.log("分数:" + s); }) return results.sort(compare); } return benchmark; })() log("Starting benchmark...\n");水库=基准(); console.log("获胜者:" + res[0][0].name + " !!!");计数 = 1; res.forEach((r) => { log((count++) + "." + r[0].name +" score:" + Math.floor(10000 * r[1] / res[0][1] ) / 100 + ((count == 2) ? "% *winner*" : "% 获胜者速度。") + " (" + Math.round(r[1] * 100) / 100 + ")") ; }); log("\n中奖代码:\n");日志(res[0][0].toString());
自您单击按钮后,基准测试将运行 10 秒。
我的结果:
铬(V8 引擎):
1. clone1 score: 100% *winner* (4110764)
2. clone3 score: 74.32% speed of winner. (3055225)
3. clone2 score: 30.75% speed of winner. (1264182)
4. clone4 score: 21.96% speed of winner. (902929)
火狐(蜘蛛猴引擎):
1. clone1 score: 100% *winner* (8448353)
2. clone3 score: 16.44% speed of winner. (1389241)
3. clone4 score: 5.69% speed of winner. (481162)
4. clone2 score: 2.27% speed of winner. (192433)
获胜者代码:
function clone1(arr) {
return arr.slice(0);
}
获胜引擎:SpiderMonkey (Mozilla/Firefox)
这取决于浏览器。如果您查看博文 Array.prototype.slice vs manual array creation,则有一个粗略的性能指南:
https://i.stack.imgur.com/Yc73n.png
结果:
https://i.stack.imgur.com/JMwiQ.png
arguments
不是一个正确的数组,他使用 call
来强制 slice
在集合上运行。结果可能具有误导性。
有一个更清洁的解决方案:
var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);
长度检查是必需的,因为当使用一个参数调用 Array
构造函数时,它的行为会有所不同。
splice()
更语义化。但实际上,apply 和 this 几乎是直观的。
Array.of
并忽略长度:Array.of.apply(Array, array)
请记住 .slice() 不适用于二维数组。你需要一个这样的函数:
function copy(array) {
return array.map(function(arr) {
return arr.slice();
});
}
这取决于数组的长度。如果数组长度 <= 1,000,000,则 slice
和 concat
方法所用的时间大致相同。但是,当您给出更大的范围时,concat
方法会胜出。
例如,试试这个代码:
var original_array = [];
for(var i = 0; i < 10000000; i ++) {
original_array.push( Math.floor(Math.random() * 1000000 + 1));
}
function a1() {
var dup = [];
var start = Date.now();
dup = original_array.slice();
var end = Date.now();
console.log('slice method takes ' + (end - start) + ' ms');
}
function a2() {
var dup = [];
var start = Date.now();
dup = original_array.concat([]);
var end = Date.now();
console.log('concat method takes ' + (end - start) + ' ms');
}
function a3() {
var dup = [];
var start = Date.now();
for(var i = 0; i < original_array.length; i ++) {
dup.push(original_array[i]);
}
var end = Date.now();
console.log('for loop with push method takes ' + (end - start) + ' ms');
}
function a4() {
var dup = [];
var start = Date.now();
for(var i = 0; i < original_array.length; i ++) {
dup[i] = original_array[i];
}
var end = Date.now();
console.log('for loop with = method takes ' + (end - start) + ' ms');
}
function a5() {
var dup = new Array(original_array.length)
var start = Date.now();
for(var i = 0; i < original_array.length; i ++) {
dup.push(original_array[i]);
}
var end = Date.now();
console.log('for loop with = method and array constructor takes ' + (end - start) + ' ms');
}
a1();
a2();
a3();
a4();
a5();
如果将 original_array 的长度设置为 1,000,000,则 slice
方法和 concat
方法所用的时间大致相同(3-4 ms,具体取决于随机数)。
如果将 original_array 的长度设置为 10,000,000,则 slice
方法会占用 60 ms,而 concat
方法会占用 20 ms。
a5
中的 dup.push
错误,应改用 dup[i] =
在 ES6 中,您可以简单地使用 the Spread syntax。
例子:
let arr = ['a', 'b', 'c'];
let arr2 = [...arr];
请注意,扩展运算符会生成一个全新的数组,因此修改一个不会影响另一个。
例子:
arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
有几种方法可以克隆阵列。基本上,克隆分为两种方式:
浅拷贝 深拷贝
浅拷贝只覆盖数组的第一层,其余的被引用。如果您想要数组中嵌套元素的真实副本,则需要深度克隆。
例子 :
const arr1 = [1,2,3,4,5,6,7]
// Normal Array (shallow copy is enough)
const arr2 = [1,2,3,[4],[[5]],6,7]
// Nested Array (Deep copy required)
Approach 1 : Using (...)Spread Operator (Shallow copy enough)
const newArray = [...arr1] // [1,2,3,4,5,6,7]
Approach 2 : Using Array builtIn Slice method (Deep copy)
const newArray = arr1.slice() // [1,2,3,4,5,6,7]
Approach 3 : Using Array builtIn Concat method (Deep a copy)
const newArray = [].concat(arr1) // [1,2,3,4,5,6,7]
Approach 4 : Using JSON.stringify/parse. (Deep a copy & fastest)
const newArray = JSON.parse(JSON.stringify(arr2));) // [1,2,3,[4],[[5]],6,7]
Approach 5: Using own recursive function or using loadash's __.cloneDeep method. (Deep copy)
一个简单的解决方案:
original = [1,2,3]
cloned = original.map(x=>x)
const arr = ['1', '2', '3'];
// Old way
const cloneArr = arr.slice();
// ES6 way
const cloneArrES6 = [...arr];
// But problem with 3rd approach is that if you are using muti-dimensional
// array, then only first level is copied
const nums = [
[1, 2],
[10],
];
const cloneNums = [...nums];
// Let's change the first item in the first nested item in our cloned array.
cloneNums[0][0] = '8';
console.log(cloneNums);
// [ [ '8', 2 ], [ 10 ], [ 300 ] ]
// NOOooo, the original is also affected
console.log(nums);
// [ [ '8', 2 ], [ 10 ], [ 300 ] ]
因此,为了避免发生这些情况,请使用
const arr = ['1', '2', '3'];
const cloneArr = Array.from(arr);
cloneNums[0][0]
如何将更改传播到 nums[0][0]
是一件有效的事情 - 但这是因为 nums[0][0]
实际上是一个对象,其引用被扩展运算符复制到 cloneNums
中。也就是说,这种行为不会影响我们按值复制的代码(int、字符串等文字)。
在 JavaScript 中按顺序复制数组的快速方法:
#1: array1copy = [...array1];
#2: array1copy = array1.slice(0);
#3: array1copy = array1.slice();
如果您的数组对象包含一些 JSON 不可序列化的内容(函数、Number.POSITIVE_INFINITY 等)更好地使用
array1copy = JSON.parse(JSON.stringify(array1))
您可以遵循此代码。不可变方式数组克隆。这是阵列克隆的完美方式
const array = [1, 2, 3, 4]
const newArray = [...array]
newArray.push(6)
console.log(array)
console.log(newArray)
如果你想要一个真正的 JS 中的克隆对象/数组,其中包含所有属性和子对象的克隆引用:
export function clone(arr) {
return JSON.parse(JSON.stringify(arr))
}
所有其他操作都不会创建克隆,因为它们只是更改根元素的基地址,而不是包含对象的基地址。
除非您通过对象树递归遍历。
对于简单的副本,这些都可以。对于存储地址相关的操作,我建议(在大多数其他情况下,因为这很快!)将类型转换为字符串并返回一个完整的新对象。
如果您正在使用 slice ,它用于从数组中复制元素并创建具有相同编号的克隆。元素或更少的元素。
var arr = [1, 2, 3, 4, 5];函数 slc() { var sliced = arr.slice(0, 5); // arr.slice(开始复制主数组的位置,新数组中的项目数) console.log(sliced); } slc(arr);
splice
的 V8 源代码,您会感到惊讶(虽然...)A.map(function(e){return e;});