Самый быстрый способ дублировать массив в JavaScript - фрагмент против цикла for
Чтобы дублировать массив в JavaScript: что из следующего быстрее использовать?
Метод среза
var dup_array = original_array.slice();
For
петля
for(var i = 0, len = original_array.length; i < len; ++i)
dup_array[i] = original_array[i];
Я знаю, что оба способа делают только поверхностную копию: если original_array содержит ссылки на объекты, объекты не будут клонированы, а будут скопированы только ссылки, и, следовательно, оба массива будут иметь ссылки на одни и те же объекты. Но это не главное в этом вопросе.
Я спрашиваю только о скорости.
26 ответов
Существует как минимум 5 (!) Способов клонировать массив:
- петля
- ломтик
- Array.from()
- CONCAT
- оператор распространения и т. д. (так медленно, забудьте об этом сейчас)
Был поток huuuge BENCHMARKS, предоставляющий следующую информацию:
для мигающих браузеров
slice()
самый быстрый метод,concat()
немного медленнее, иwhile loop
в 2,4 раза медленнее.для других браузеров
while loop
это самый быстрый метод, так как эти браузеры не имеют внутренних оптимизаций дляslice
а такжеconcat
,
Это остается верным в июле 2016 года.
Ниже приведены простые сценарии, которые вы можете скопировать и вставить в консоль браузера и запустить несколько раз, чтобы увидеть картинку. Они выводят миллисекунды, чем меньше, тем лучше.
в то время как цикл
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();
Самый простой способ глубокого клонирования массива или объекта:
var dup_array = JSON.parse(JSON.stringify(original_array))
Массивы клонов
Я сделал эту очень простую служебную функцию, чтобы проверить время, необходимое для клонирования массива. Он не на 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));
Я собрал короткую демонстрацию: http://jsbin.com/agugo3/edit
Мои результаты в Internet Explorer 8 - 156, 782 и 750, что указывает на slice
намного быстрее в этом случае.
a.map(e => e)
еще одна альтернатива для этой работы. По состоянию на сегодня .map()
очень быстро (почти так же быстро, как .slice(0)
) в Firefox, но не в Chrome.
С другой стороны, если массив является многомерным, так как массивы являются объектами, а объекты являются ссылочными типами, ни один из методов slice или concat не будет лекарством... Так что одним из подходящих способов клонирования массива является изобретение Array.prototype.clone()
следующее.
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] = "two";
console.log(JSON.stringify(arr));
console.log(JSON.stringify(brr));
Самый быстрый способ клонировать массив объектов - использовать оператор распространения
var clonedArray=[...originalArray]
но объекты внутри этого клонированного массива по-прежнему будут указывать на старую ячейку памяти. следовательно, изменение объектов clonedArray также изменит orignalArray.
var clonedArray = originalArray.map(({...ele}) => {return ele})
это не только создаст новый массив, но и объекты будут клонированы.
Взгляните на: ссылку. Дело не в скорости, а в комфорте. Кроме того, как вы можете видеть, вы можете использовать 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{ this[i] = source[i]; } } } var obj1= {bla:'blabla',foo:'foofoo',etc:'etc'}; var obj2= new cloneObject(obj1);
Источник: ссылка
ECMAScript 2015 способ с Spread
оператор:
Основные примеры:
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);
Рекомендации
Это зависит от браузера. Если вы посмотрите в блоге Array.prototype.slice и ручное создание массива, есть приблизительное руководство по производительности каждого из них:
Результаты:
Как сказал @Dan: "Этот ответ быстро устареет. Используйте тесты для проверки реальной ситуации", есть один конкретный ответ от jsperf, у которого не было ответа для себя: while:
var i = a.length;
while(i--) { b[i] = a[i]; }
было 960 589 операций / сек с занявшим второе место a.concat()
со скоростью 578 129 операций в секунду, что составляет 60%.
Это последний Firefox (40) 64 бит.
@aleclarson создал новый, более надежный тест.
Контрольное время!
function log(data) {
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];
results = [];
funcs.forEach((ff) => {
console.log("Benchmarking: " + ff.name);
var s = time_function(2500, ff, Array(1024));
results.push([ff, s]);
console.log("Score: " + s);
})
return results.sort(compare);
}
return benchmark;
})()
log("Starting benchmark...\n");
res = benchmark();
console.log("Winner: " + res[0][0].name + " !!!");
count = 1;
res.forEach((r) => {
log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
});
log("\nWinner code:\n");
log(res[0][0].toString());
<textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"></textarea>
Тест будет работать в течение 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)
Firefox (SpiderMonkey Engine):
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)
Существует гораздо более чистое решение:
var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);
Требуется проверка длины, потому что Array
Конструктор ведет себя по-разному, когда он вызывается только с одним аргументом.
Помните, что.slice() не будет работать для двумерных массивов. Вам понадобится такая функция:
function copy(array) {
return array.map(function(arr) {
return arr.slice();
});
}
Это зависит от длины массива. Если длина массива <= 1000 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 мс, в зависимости от случайных чисел).
Если вы установите длину original_array равным 10 000 000, то slice
метод занимает более 60 мс и concat
метод занимает более 20 мс
В ES6 вы можете просто использовать синтаксис Spread.
Пример:
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']
Было несколько способов клонировать массив. Есть 2 типа клонирования массива:
- Мелкая копия
- Глубокая копия
Мелкие копии покрывают только 1-й уровень массива, а на остальные есть ссылки. Если вам нужна настоящая копия вложенных массивов, вам понадобится глубокий клон.
Пример :
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)
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);
Быстрые способы дублирования массива в JavaScript по порядку:
#1: array1copy = [...array1];
#2: array1copy = array1.slice(0);
#3: array1copy = array1.slice();
Если ваши объекты массива содержат некоторый несериализуемый контент JSON (функции, Number.POSITIVE_INFINITY и т. Д.), Лучше использовать
array1copy = JSON.parse(JSON.stringify(array1))
Если вам нужен НАСТОЯЩИЙ клонированный объект / массив в JS с клонированными ссылками на все атрибуты и подобъекты:
export function clone(arr) {
return JSON.parse(JSON.stringify(arr))
}
ВСЕ другие операции не создают клонов, потому что они просто изменяют базовый адрес корневого элемента, а не включенных объектов.
За исключением того, что вы рекурсивно проходите через дерево объектов.
Для простой копии это нормально. Для операций, связанных с адресом хранилища, я предлагаю (и в большинстве других случаев, потому что это быстро!) Ввести преобразование в строку и обратно в полностью новый объект.
Вы можете следовать этому коду. Неизменяемый способ клонирования массива. Это идеальный способ клонирования массива
const array = [1, 2, 3, 4]
const newArray = [...array]
newArray.push(6)
console.log(array)
console.log(newArray)
var arr = [1, 2, 3, 4, 5];
var cloneArr = [];
for(var i = 0; i < arr.length; i++){
cloneArr.push(arr[i]);
}
console.log(cloneArr);
Если вы берете срез , он используется для копирования элементов из массива и создания клона с тем же номером. элементов или меньше нет. элементов.