Самый быстрый способ продублировать массив в 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.