Najszybszy sposób na zduplikowanie tablicy w JavaScript - wycinek vs. pętla „for”


634

Aby powielić tablicę w JavaScript: który z poniższych sposobów jest szybszy w użyciu?

Metoda krojenia

var dup_array = original_array.slice();

For pętla

for(var i = 0, len = original_array.length; i < len; ++i)
   dup_array[i] = original_array[i];

Wiem, że oba sposoby wykonują tylko płytką kopię : jeśli tablica_oryginalna zawiera odniesienia do obiektów, obiekty nie zostaną sklonowane, ale tylko odniesienia zostaną skopiowane, a zatem obie tablice będą miały odniesienia do tych samych obiektów. Ale nie o to chodzi w tym pytaniu.

Pytam tylko o szybkość.


3
jsben.ch/#/wQ9RU <= test porównawczy dla najczęstszych sposobów klonowania tablicy
EscapeNetscape

Odpowiedzi:


776

Istnieje co najmniej 5 (!) Sposobów klonowania tablicy:

  • pętla
  • plasterek
  • Array.from ()
  • konkat
  • operator rozrzutu (NAJSZYBSZY)

Pojawił się ogromny wątek BENCHMARKS , zawierający następujące informacje:

  • w przypadku przeglądarek mrugającychslice() jest to najszybsza metoda, concat()jest nieco wolniejsza i while loopjest 2,4 razy wolniejsza.

  • dla innych przeglądarek while loopjest najszybszą metodą, ponieważ przeglądarki te nie mają wewnętrznych optymalizacji dla slicei concat.

Tak jest w lipcu 2016 r.

Poniżej znajdują się proste skrypty, które można skopiować i wkleić w konsoli przeglądarki i uruchomić kilka razy, aby zobaczyć obraz. Generują milisekundy, im niższa, tym lepiej.

pętla 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);

plasterek

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = a.slice();
console.log(new Date() - start);

Należy pamiętać, że te metody sklonują sam obiekt Array, jednak zawartość tablicy jest kopiowana przez odniesienie i nie jest głęboko klonowana.

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true

48
@ cept0 brak emocji, tylko testy porównawcze jsperf.com/new-array-vs-splice-vs-slice/31
Dan.

2
@ Dan Co z tego? Wyniki Twojego przypadku testowego: Firefox 30 na noc jest nadal ~ 230% szybszy niż Chrome. Sprawdź kod źródłowy V8 pod kątem, splicea będziesz zaskoczony (podczas ...)
mate64

4
Niestety w przypadku krótkich tablic odpowiedź jest zupełnie inna . Na przykład klonowanie tablicy słuchaczy przed wywołaniem każdego z nich. Te tablice są często małe, zwykle 1 element.
gman

6
Przegapiłeś tę metodę:A.map(function(e){return e;});
wcochran,

13
Piszesz o przeglądarkach blink . Czy mruganie nie jest tylko aparatem układu, który wpływa głównie na renderowanie HTML, a zatem nie jest ważne? Myślałem, że wolimy tutaj rozmawiać o V8, Spidermonkey i przyjaciołach. Po prostu rzecz, która mnie zdezorientowała. Oświeć mnie, jeśli się mylę.
Neonit

241

Technicznie slice jest to najszybszy sposób. Jednak dodanie 0indeksu początkowego jest jeszcze szybsze .

myArray.slice(0);

jest szybszy niż

myArray.slice();

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


I jest myArray.slice(0,myArray.length-1);szybszy niż myArray.slice(0);?
jave.web

1
@ jave.web you; właśnie upuściłem ostatni element tablicy. Pełna kopia to array.slice (0) lub array.slice (0, array.length)
Marek Marczak

137

co ze sposobem ES6?

arr2 = [...arr1];

23
w przypadku konwersji za pomocą babel:[].concat(_slice.call(arguments))
CHAN

1
Nie jestem pewien, skąd argumentspochodzi ... Myślę, że twoja produkcja babel łączy w sobie kilka różnych funkcji. Jest bardziej prawdopodobne, że tak będzie arr2 = [].concat(arr1).
Sterling Archer

3
@SterlingArcher arr2 = [].conact(arr1)różni się od arr2 = [...arr1]. [...arr1]składnia zamieni dziurę w undefined. Na przykład arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3.
tsh

1
Przetestowałem to w mojej przeglądarce (Chrome 59.0.3071.115) z odpowiedzią Dana powyżej. Było ponad 10 razy wolniejsze niż .slice (). n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Harry Stevens,

1
Nadal nie będzie klonem coś takiego: [{a: 'a', b: {c: 'c'}}]. Jeśli cwartość zostanie zmieniona w „zduplikowanej” tablicy, zmieni się ona w oryginalnej tablicy, ponieważ jest to tylko kopia referencyjna, a nie klon.
Neurotransmitter

44

Najłatwiejszy sposób na głębokie klonowanie tablicy lub obiektu:

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

56
Ważna uwaga dla początkujących: ponieważ zależy to od JSON, dziedziczy to również jego ograniczenia. Oznacza to między innymi, że twoja tablica nie może zawierać undefinedani żadnych functions. Oba zostaną przekonwertowane nullna Ciebie podczas JSON.stringifyprocesu. Inne strategie, takie jak (['cool','array']).slice()nie zmienią ich, ale również nie będą głęboko klonować obiektów w tablicy. Więc jest kompromis.
Seth Holladay,

27
Bardzo zła perf i nie działa ze specjalnymi obiektami, takimi jak DOM, data, wyrażenie regularne, funkcja ... lub prototypowanymi obiektami. Nie obsługuj cyklicznych odniesień. Nigdy nie powinieneś używać JSON do głębokiego klonowania.
Yukulélé,

17
najgorszy możliwy sposób! Używaj tylko, jeśli w przypadku niektórych problemów wszystkie inne nie działają. Jest powolny, wymaga dużych zasobów i ma wszystkie ograniczenia JSON wspomniane już w komentarzach. Nie mogę sobie wyobrazić, jak zdobył 25 głosów.
Lukas Liesis

2
Głęboko kopiuje tablice z prymitywami, a właściwościami są tablice z kolejnymi prymitywami / tablicami. Do tego jest w porządku.
Drenai,

4
Przetestowałem to w mojej przeglądarce (Chrome 59.0.3071.115) z odpowiedzią Dana powyżej. Było prawie 20 razy wolniejsze niż .slice (). n = 1000*1000; start = + new Date(); a = Array(n); var b = JSON.parse(JSON.stringify(a)) console.log(new Date() - start); // 221
Harry Stevens,

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

3
Proszę wyjaśnić, co to robi.
Jed Fox

8
Ten fragment kodu może odpowiedzieć na pytanie, ale nie zawiera kontekstu wyjaśniającego, w jaki sposób i dlaczego. Rozważ dodanie zdania lub dwóch w celu wyjaśnienia swojej odpowiedzi.
brandonscript

32
Nienawidzę tego rodzaju komentarzy. To oczywiste, co robi!
EscapeNetscape

6
Prosta odpowiedź na proste pytania, bez wielkiej historii do przeczytania. Lubię tego rodzaju odpowiedzi +1
Achim,

15
„Pytam tylko o prędkość” - ta odpowiedź nie wskazuje prędkości. To jest główne zadawane pytanie. brandonscript ma dobrą rację. Potrzebne są dodatkowe informacje, aby uznać to za odpowiedź. Ale gdyby to było prostsze pytanie, byłaby to doskonała odpowiedź.
TamusJRoyce

26

Przygotowałem szybkie demo: http://jsbin.com/agugo3/edit

Moje wyniki w Internet Explorerze 8 to 156, 782 i 750, co wskazuje, że slicew tym przypadku jest znacznie szybsze.


Nie zapomnij o dodatkowych kosztach śmieciarza, jeśli musisz to zrobić bardzo szybko. Kopiowałem każdą sąsiednią tablicę dla każdej komórki w moich automatach komórkowych za pomocą wycinka i było to znacznie wolniejsze niż ponowne użycie poprzedniej tablicy i kopiowanie wartości. Chrome wskazał, że około 40% całkowitego czasu poświęcono na zbieranie śmieci.
drake7707,

21

a.map(e => e)to kolejna alternatywa dla tej pracy. Na dzień dzisiejszy .map()jest bardzo szybki (prawie tak szybki .slice(0)) w Firefoksie, ale nie w Chrome.

Z drugiej strony, jeśli tablica jest wielowymiarowa, ponieważ tablice są obiekty i przedmioty są typy referencyjne, żadna z metod plasterek lub Concat będzie leczyć ... Więc jeden właściwy sposób klonowania tablicę jest wynalazkiem Array.prototype.clone()jako następuje.

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


Nieźle, ale niestety to nie działa, jeśli masz Object w swojej tablicy: \ JSON.parse (JSON.stringify (myArray)) działa lepiej w tym przypadku.
GBMan

17

🏁 Najszybszy sposób na klonowanie tablicy

Zrobiłem tę bardzo prostą funkcję narzędzia do testowania czasu potrzebnego do sklonowania tablicy. Nie jest w 100% wiarygodny, jednak może dać ci ogólny pomysł na to, ile czasu zajmuje klonowanie istniejącej tablicy:

function clone(fn) {
    const arr = [...Array(1000000)];
    console.time('timer');
    fn(arr);
    console.timeEnd('timer');
}

I przetestowałem inne podejście:

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

AKTUALIZACJA :
Uwaga: spośród nich jedynym sposobem głębokiego klonowania tablicy jest użycie JSON.parse(JSON.stringify(arr)).

To powiedziawszy, nie używaj powyższego, jeśli tablica może zawierać funkcje, ponieważ zwróci null.
Dziękuję @GilEpshtain za tę aktualizację .


2
Próbowałem przeprowadzić analizę porównawczą twojej odpowiedzi i otrzymałem bardzo różne wyniki: jsben.ch/o5nLG
mesqueeb

@mesqueeb, testy mogą się zmienić, oczywiście w zależności od komputera. Jednak możesz zaktualizować odpowiedź o wynik testu. Dobra robota!
Lior Elrom

Bardzo podoba mi się twoja odpowiedź, jednak próbuję twojego testu i dostaję, że arr => arr.slice()jest najszybszy.
Gil Epshtain

1
@LiorElrom, twoja aktualizacja jest nieprawidłowa, ponieważ nie można serializować metod. Na przykład: JSON.parse(JSON.stringify([function(){}]))wyświetli[null]
Gil Epshtain

1
Niezły benchmark. Przetestowałem to na moim Macu w 2 przeglądarek: Chrome i Safari Version 81.0.4044.113 wersji 13.1 (15609.1.20.111.8) i najszybciej jest operacja spread: [...arr]ze 4.653076171875msw Chrome i 8.565msSafari. Druga szybko w Chrome jest funkcja plaster arr.slice()z 6.162109375msa drugi jest w Safari [].concat(arr)z 13.018ms.
edufinn

7

Spójrz na: link . Nie chodzi o szybkość, ale o komfort. Poza tym, jak widać, można używać wycinka (0) tylko na typach pierwotnych .

Aby utworzyć niezależną kopię tablicy zamiast kopii odwołania do niej, możesz użyć metody wycinania tablic.

Przykład:

Aby utworzyć niezależną kopię tablicy zamiast kopii odwołania do niej, możesz użyć metody wycinania tablic.

var oldArray = ["mip", "map", "mop"];
var newArray = oldArray.slice();

Aby skopiować lub sklonować obiekt:

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

Źródło: link


1
Prymitywne typy komentarz odnosi się do forpętli w pytaniu, jak również.
user113716,

4
gdybym kopiował tablicę obiektów, oczekiwałbym, że nowa tablica będzie odwoływać się do tych samych obiektów, a nie klonować obiekty.
lincolnk,

7

Jak powiedział @Dan „Ta odpowiedź szybko staje się przestarzała. Użyj testów porównawczych, aby sprawdzić rzeczywistą sytuację”, jest jedna konkretna odpowiedź od jsperf, która nie ma odpowiedzi dla siebie: podczas gdy :

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

miał 960.589 operacji na sekundę z drugim miejscem a.concat()na poziomie 578,129 operacji na sekundę, co stanowi 60%.

To najnowszy 64-bitowy Firefox (40).


@aleclarson stworzył nowy, bardziej niezawodny test porównawczy.


1
Naprawdę powinieneś połączyć jsperf. Ta, o której myślisz, jest zepsuta, ponieważ nowa tablica jest tworzona w każdym przypadku testowym, z wyjątkiem testu „while loop”.
aleclarson

1
Zrobiłem nowy jsperf, który jest dokładniejszy: jsperf.com/clone-array-3
aleclarson

60% co? 60% szybciej?
Peter Mortensen,

1
@PeterMortensen: 587192 to ~ 60% (61,1 ...) z 960589.
serv-inc

7

Sposób ECMAScript 2015 z Spreadoperatorem:

Podstawowe przykłady:

var copyOfOldArray = [...oldArray]
var twoArraysBecomeOne = [...firstArray, ..seccondArray]

Wypróbuj w konsoli przeglądarki:

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

Bibliografia


Prawdopodobnie jedyną rzeczą, która jest szybka w przypadku spreadu, jest jego wpisanie. To jest zdecydowanie mniej wydajne niż inne sposoby robienia tego.
XT_Nova

3
Podaj kilka linków na temat swojego argumentu.
Marian07

6

To zależy od przeglądarki. Jeśli spojrzysz na post na blogu Array.prototype.slice vs. ręczne tworzenie tablic , istnieje przybliżony przewodnik po wydajności każdego z nich:

Wpisz opis zdjęcia tutaj

Wyniki:

Wpisz opis zdjęcia tutaj


1
argumentsnie jest odpowiednią tablicą i używa, callaby wymusić sliceuruchomienie kolekcji. wyniki mogą być mylące.
lincolnk,

Tak, chciałem wspomnieć, że w moim poście te statystyki prawdopodobnie zmieniłyby się teraz wraz z poprawą broswersów, ale daje to ogólny pomysł.
kyndigs,

2
@diugalde Myślę, że jedyną sytuacją, w której kodowanie obrazu jest dopuszczalne, jest to, że kod jest potencjalnie niebezpieczny i nie należy go wklejać. W tym przypadku jest to jednak niedorzeczne.
Florian Wendelborn,

6

Istnieje o wiele czystsze rozwiązanie:

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

Kontrola długości jest wymagana, ponieważ Arraykonstruktor zachowuje się inaczej, gdy jest wywoływany z dokładnie jednym argumentem.


2
Ale czy jest najszybszy?
Chris Wesseling

14
splice()Może bardziej semantyczny niż . Ale naprawdę, zastosuj, a to wszystko jest prawie intuicyjne.
Michael Piefel

pokazuje najwolniejszą wydajność na chrome- jsperf.com/new-array-vs-splice-vs-slice/113
chrismarx

3
Możesz użyć Array.ofi zignorować długość:Array.of.apply(Array, array)
Oriol,

6

Pamiętaj, że .slice () nie będzie działać dla tablic dwuwymiarowych. Potrzebujesz takiej funkcji:

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

3
W Javascript nie ma tablic dwuwymiarowych. Są tylko tablice zawierające tablice. To, co próbujesz zrobić, to głęboka kopia, która nie jest wymagana w pytaniu.
Aloso

5

To zależy od długości tablicy. Jeśli długość tablicy wynosi <= 1 000 000, metody slicei concatzajmują mniej więcej ten sam czas. Ale gdy podasz szerszy zakres, concatmetoda wygrywa.

Na przykład wypróbuj ten kod:

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

Jeśli ustawisz długość original_array na 1 000 000, slicemetoda iconcat metoda zajmą mniej więcej ten sam czas (3-4 ms, w zależności od liczb losowych).

Jeśli ustawisz długość pierwotnej tablicy na 10 000 000, wówczas slicemetoda zajmuje ponad 60 ms, a concatmetoda trwa ponad 20 ms.


dup.pushjest źle w a5, zamiast tego dup[i] = należy użyć
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 ] ]

Aby uniknąć tych scenariuszy, użyj

        const arr = ['1', '2', '3'];

        const cloneArr = Array.from(arr);

Warto zwrócić uwagę na to, jak zmiana cloneNums[0][0]w twoim przykładzie propagowała zmianę nums[0][0]- ale dzieje się tak, ponieważ nums[0][0]jest to obiekt, do którego odwołanie jest kopiowane cloneNumsprzez operator rozkładania. Wszystko to znaczy, że to zachowanie nie wpłynie na kod, w którym kopiujemy według wartości (literały int, string itp.).
Aditya, poseł

1

Czas testu!

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>

Test będzie działał przez 10 sekund od momentu kliknięcia przycisku.

Moje wyniki:

Chrome (silnik 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 (silnik SpiderMonkey):

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)

Kod zwycięzcy:

function clone1(arr) {
    return arr.slice(0);
}

Zwycięski silnik:

SpiderMonkey (Mozilla / Firefox)


1

Szybkie sposoby duplikowania tablicy w JavaScript w kolejności:

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

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

#3: array1copy = array1.slice();

Jeśli obiekty tablicowe zawierają pewną zawartość, która nie jest serializowana JSON (funkcje, Number.POSITIVE_INFINITY itp.), Lepiej użyć

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


0

Możesz śledzić ten kod. Niezmienny sposób klonowania tablicy. Jest to idealny sposób na klonowanie macierzy


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

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

0

W ES6 możesz po prostu użyć składni Spread .

Przykład:

let arr = ['a', 'b', 'c'];
let arr2 = [...arr];

Pamiętaj, że operator rozkładania generuje zupełnie nową tablicę, więc modyfikowanie jednej nie wpłynie na drugą.

Przykład:

arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.