Самый быстрый способ продублировать массив в JavaScript - слайс против цикла 'for'


634

Чтобы дублировать массив в 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 содержит ссылки на объекты, объекты не будут клонированы, а будут скопированы только ссылки, и поэтому оба массива будут иметь ссылки на одни и те же объекты. Но это не главное в этом вопросе.

Я спрашиваю только о скорости.


3
jsben.ch/#/wQ9RU <= тест для самых распространенных способов клонирования массива
EscapeNetscape,

Ответы:


776

Существует как минимум 5 (!) Способов клонировать массив:

  • петля
  • кусочек
  • Array.from ()
  • CONCAT
  • оператор распространения (FASTEST)

Был поток huuuge BENCHMARKS , предоставляющий следующую информацию:

  • Для браузеров Blinkslice() это самый быстрый метод, 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

48
@ cept0 никаких эмоций, только тесты jsperf.com/new-array-vs-splice-vs-slice/31
Дан

2
@ Дэн И что? Результаты вашего теста: Firefox 30 по-прежнему работает на ~ 230% быстрее, чем Chrome. Проверьте исходный код V8, spliceи вы будете удивлены (пока ...)
mate64

4
К сожалению, для коротких массивов ответ сильно отличается . Например, клонирование массива слушателей перед вызовом каждого из них. Эти массивы часто маленькие, обычно 1 элемент.
человек

6
Вы пропустили этот метод:A.map(function(e){return e;});
wcochran

13
Вы пишете о браузерах Blink . Разве моргание - это не просто механизм разметки, который в основном влияет на рендеринг HTML и, следовательно, не важен? Я думал, что мы лучше поговорим о V8, Spidermonkey и друзьях здесь. Просто вещь, которая смутила меня. Просвети меня, если я ошибаюсь.
Неонит

242

Технически slice это самый быстрый способ. Тем не менее , это даже быстрее, если вы добавите 0начальный индекс.

myArray.slice(0);

быстрее чем

myArray.slice();

http://jsperf.com/cloning-arrays/3


А myArray.slice(0,myArray.length-1);быстрее чем myArray.slice(0);?
jave.web

1
@ jave.web, вы только что удалили последний элемент массива. Полная копия: array.slice (0) или array.slice (0, array.length)
Марек Марчак,

137

как насчет es6?

arr2 = [...arr1];

23
если преобразовано с babel:[].concat(_slice.call(arguments))
ЧАН

1
Не уверен, откуда argumentsвзялась ... Я думаю, что ваш вывод из Babel объединяет несколько разных функций. Это более вероятно arr2 = [].concat(arr1).
Стерлинг Арчер

3
@SterlingArcher arr2 = [].conact(arr1)отличается от arr2 = [...arr1]. [...arr1]синтаксис преобразует дыру в undefined. Например, arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3.
TSH

1
Я проверил это в своем браузере (Chrome 59.0.3071.115) против ответа Дэна выше. Это было более чем в 10 раз медленнее, чем .slice (). n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Гарри Стивенс

1
Все равно не буду клонировать что-то подобное [{a: 'a', b: {c: 'c'}}]. Если cзначение изменяется в «дублированном» массиве, оно изменяется в исходном массиве, так как это просто ссылочная копия, а не клон.
Нейромедиатор

44

Самый простой способ глубокого клонирования массива или объекта:

var dup_array = JSON.parse(JSON.stringify(original_array))

56
Важное примечание для начинающих: поскольку это зависит от JSON, это также наследует его ограничения. Среди прочего, это означает, что ваш массив не может содержать undefinedили каких-либо functions. Оба из них будут преобразованы nullдля вас во время JSON.stringifyпроцесса. Другие стратегии, такие как (['cool','array']).slice(), не изменят их, но также не будут глубоко клонировать объекты в массиве. Таким образом, есть компромисс.
Сет Холладей,

27
Очень плохо работает и не работает со специальными объектами, такими как DOM, date, regexp, function ... или прототипированными объектами. Не поддерживает циклические ссылки. Вы никогда не должны использовать JSON для глубокого клонирования.
Yukulélé

17
худший из возможных способов! Используйте только в том случае, если для какого-либо вопроса все остальные не работают. Он медленный, требует больших ресурсов и имеет все ограничения JSON, уже упомянутые в комментариях. Не могу представить, как он получил 25 голосов.
Лукас Лиезис

2
Он глубоко копирует массивы с примитивами, а где свойства - это массивы с дополнительными примитивами / массивами. Для этого все нормально.
Дренай

4
Я проверил это в своем браузере (Chrome 59.0.3071.115) против ответа Дэна выше. Это было почти в 20 раз медленнее, чем .slice (). n = 1000*1000; start = + new Date(); a = Array(n); var b = JSON.parse(JSON.stringify(a)) console.log(new Date() - start); // 221
Гарри Стивенс

29
var cloned_array = [].concat(target_array);

3
Пожалуйста, объясните, что это делает.
Джед Фокс

8
Хотя этот фрагмент кода может ответить на вопрос, он не предоставляет никакого контекста, объясняющего, как и почему. Попробуйте добавить предложение или два, чтобы объяснить свой ответ.
brandonscript

32
Я ненавижу такие комментарии. Очевидно, что он делает!
EscapeNetscape

6
Простой ответ для простых вопросов, никакой большой истории для чтения. Мне нравятся такие ответы +1
Ахим

15
«Я спрашиваю только о скорости» - этот ответ не указывает на скорость. Это главный вопрос, который задают. У brandonscript есть хороший момент. Требуется больше информации, чтобы считать это ответом. Но если бы это был более простой вопрос, это был бы отличный ответ.
TamusJRoyce

26

Я собрал короткую демонстрацию: http://jsbin.com/agugo3/edit

Мои результаты в Internet Explorer 8 - 156, 782 и 750, что говорит о sliceтом, что в этом случае гораздо быстрее.


Не забывайте о дополнительных затратах на сборщик мусора, если вам придется делать это очень быстро. Я копировал каждый соседний массив для каждой ячейки в своих клеточных автоматах, используя слайс, и это было намного медленнее, чем повторное использование предыдущего массива и копирование значений. Хром показал, что около 40% общего времени было потрачено на сбор мусора.
drake7707

21

a.map(e => e)другая альтернатива для этой работы. На сегодняшний день .map()очень быстро (почти так же быстро, как .slice(0)) в Firefox, но не в Chrome.

С другой стороны, если массив является многомерным, так как массивы являются объектами и объектами являются ссылочными типами, ни один из ломтиков или 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));


Неплохо, но, к сожалению, это не работает, если в вашем массиве есть Object: \ JSON.parse (JSON.stringify (myArray)) работает лучше в этом случае.
GBMan

17

🏁 Самый быстрый способ клонировать массив

Я сделал эту очень простую служебную функцию, чтобы проверить время, необходимое для клонирования массива. Он не на 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));

ОБНОВЛЕНИЕ :
Примечание: из всех них единственным способом глубокого клонирования массива является использование JSON.parse(JSON.stringify(arr)).

Тем не менее, не используйте вышеперечисленное, если ваш массив может включать функции, поскольку он будет возвращаться null.
Спасибо @GilEpshtain за это обновление .


2
Я попытался сравнить ваш ответ, и я получил совсем другие результаты: jsben.ch/o5nLG
mesqueeb

@mesqueeb, тесты могут измениться, в зависимости от вашей машины, конечно. Тем не менее, не стесняйтесь обновлять ответ результатами теста. Хорошо сделано!
Лиор Элром

Мне очень нравится ваш ответ, однако я пробую ваш тест и получу arr => arr.slice()самый быстрый.
Гил Эпштейн

1
@LiorElrom, ваше обновление неверно из-за того, что методы не сериализуемы. Например: JSON.parse(JSON.stringify([function(){}]))выйдет[null]
Гил Эпштейн

1
Хороший тест. Я проверил это на своем Mac в двух браузерах: Chrome версии 81.0.4044.113 и Safari версии 13.1 (15609.1.20.111.8), и самая быстрая операция распространения: [...arr]с 4.653076171875msChrome и 8.565msSafari. Второй быстрый в Chrome есть функция ломтика arr.slice()с 6.162109375msи в Safari второй находится [].concat(arr)с 13.018ms.
edufinn

7

Взгляните на: ссылку . Дело не в скорости, а в комфорте. Кроме того, как вы можете видеть, вы можете использовать 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);

Источник: ссылка


1
Примитив типа комментарий относится к forпетле в вопросе.
user113716 20.10.10

4
если бы я копировал массив объектов, я бы ожидал, что новый массив будет ссылаться на те же объекты, а не клонировать объекты.
Линкольн

7

Как сказал @Dan: «Этот ответ быстро устареет. Используйте тесты для проверки реальной ситуации», есть один конкретный ответ от jsperf, у которого не было ответа для себя: while :

var i = a.length;
while(i--) { b[i] = a[i]; }

было 960 589 операций в секунду, второе место - 578 a.concat()129 операций в секунду, что составляет 60%.

Это последний Firefox (40) 64 бит.


@aleclarson создал новый, более надежный тест.


1
Вы должны действительно связать jsperf. Тот, о котором вы думаете, не работает, потому что новый массив создается в каждом тестовом примере, кроме теста while.
aleclarson

1
Я сделал новый jsperf, более точный: jsperf.com/clone-array-3
aleclarson,

60% что? На 60% быстрее?
Питер Мортенсен

1
@PeterMortensen: 587192 составляет ~ 60% (61,1 ...) от 960589.
-инк

7

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);

Ссылки


Вероятно, единственное, что быстро с распространением, это набрать его. Это менее продуктивно, чем другие способы сделать это.
XT_Nova

3
Пожалуйста, предоставьте несколько ссылок о вашем аргументе.
Marian07

6

Это зависит от браузера. Если вы посмотрите в записи блога Array.prototype.slice против ручного создания массива , есть приблизительное руководство по производительности каждого из них:

Введите описание изображения здесь

Результаты:

Введите описание изображения здесь


1
argumentsне является правильным массивом, и он использует, callчтобы заставить sliceработать на коллекции. результаты могут вводить в заблуждение.
Линкольн

Да, я хотел упомянуть, что в моем посте эта статистика, вероятно, теперь изменится с улучшением работы броузеров, но это дает общее представление.
kyndigs

2
@diugalde Я думаю, что единственная ситуация, когда публикация кода в виде рисунка является приемлемой, - это когда код потенциально опасен и не должен копироваться. В этом случае, хотя, это довольно смешно.
Флориан Вендельборн

6

Существует гораздо более чистое решение:

var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);

Проверка длины обязательна, потому что Arrayконструктор ведет себя по-разному, когда он вызывается только с одним аргументом.


2
Но это самый быстрый?
Крис Весселинг,

14
Более семантически, чем splice(), возможно. Но на самом деле, применять, и это все, но интуитивно понятно.
Майкл Пифель

показывает самую низкую
chrismarx

3
Вы можете использовать Array.ofи игнорировать длину:Array.of.apply(Array, array)
Oriol

6

Помните, что .slice () не будет работать для двумерных массивов. Вам понадобится такая функция:

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}

3
В Javascript нет двухмерных массивов. Есть только массивы, содержащие массивы. То, что вы пытаетесь сделать, это глубокая копия, которая не требуется в этом вопросе.
Aloso

5

Это зависит от длины массива. Если длина массива <= 1000000, то 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 мс.


dup.pushнеправильно a5, вместо этого dup[i] = следует использовать
4esn0k


2
        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, string и т. Д.).
Адитья М.П.

1

Контрольное время!

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)


1

Быстрые способы дублировать массив в JavaScript в порядке:

#1: array1copy = [...array1];

#2: array1copy = array1.slice(0);

#3: array1copy = array1.slice();

Если ваши объекты массива содержат некоторое JSON-несериализуемое содержимое (функции, Number.POSITIVE_INFINITY и т. Д.), Лучше использовать

array1copy = JSON.parse(JSON.stringify(array1))


0

Вы можете следовать этому коду. Неизменным образом массив клонов. Это идеальный способ клонирования массива


const array = [1, 2, 3, 4]

const newArray = [...array]
newArray.push(6)
console.log(array)
console.log(newArray)

0

В 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']
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.