Il modo più veloce per duplicare un array in JavaScript: il ciclo slice vs 'for'


634

Per duplicare un array in JavaScript: quale dei seguenti è più veloce da usare?

Metodo Slice

var dup_array = original_array.slice();

For ciclo continuo

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

Conosco entrambi i modi per fare solo una copia superficiale : se original_array contiene riferimenti ad oggetti, gli oggetti non verranno clonati, ma solo i riferimenti verranno copiati e quindi entrambi gli array avranno riferimenti agli stessi oggetti. Ma questo non è il punto di questa domanda.

Sto chiedendo solo della velocità.


3
jsben.ch/#/wQ9RU <= un punto di riferimento per i metodi più comuni per clonare un array
EscapeNetscape

Risposte:


776

Esistono almeno 5 (!) Modi per clonare un array:

  • ciclo continuo
  • fetta
  • Array.from ()
  • concat
  • operatore di diffusione (FASTEST)

C'è stato un grande thread BENCHMARKS , che fornisce le seguenti informazioni:

  • per i browser lampeggiantislice() è il metodo più veloce, concat()è un po 'più lento ed while loopè 2,4 volte più lento.

  • per altri browser while loopè il metodo più veloce, dal momento che quei browser non hanno ottimizzazioni interne per slicee concat.

Questo rimane vero a luglio 2016.

Di seguito sono riportati semplici script che è possibile copiare e incollare nella console del browser ed eseguire più volte per visualizzare l'immagine. Producono millisecondi, più basso è meglio.

mentre loop

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

fetta

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

Si noti che questi metodi cloneranno l'oggetto Array stesso, tuttavia i contenuti dell'array vengono copiati per riferimento e non vengono clonati in profondità.

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

48
@ cept0 nessuna emozione, solo benchmark jsperf.com/new-array-vs-splice-vs-slice/31
Dan

2
@Dan E allora? Risultati del test case: Firefox 30 di notte è ancora ~ 230% più veloce di Chrome. Controlla il codice sorgente di V8 splicee
rimarrai

4
Purtroppo per array brevi la risposta è molto diversa . Ad esempio, clonare un array di ascoltatori prima di chiamare ciascuno di essi. Quelle matrici sono spesso piccole, di solito 1 elemento.
Gman,

6
Hai perso questo metodo:A.map(function(e){return e;});
wcochran

13
Stai scrivendo sui browser per lampeggianti . Il battito di ciglia non è solo un motore di layout, che influisce principalmente sul rendering HTML e quindi non importante? Ho pensato che avremmo preferito parlare di V8, Spidermonkey e amici qui. Solo una cosa che mi ha confuso. Illuminami, se sbaglio.
Neonit,

242

Tecnicamente slice è il modo più veloce. Tuttavia , è ancora più veloce se aggiungi l' 0indice iniziale.

myArray.slice(0);

è più veloce di

myArray.slice();

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


Ed è myArray.slice(0,myArray.length-1);più veloce di myArray.slice(0);?
jave.web,

1
@ jave.web you; ho appena lasciato cadere l'ultimo elemento dell'array. La copia completa è array.slice (0) o array.slice (0, array.length)
Marek Marczak,

137

che dire di es6 way?

arr2 = [...arr1];

23
se convertito con babele:[].concat(_slice.call(arguments))
CHAN,

1
Non sono sicuro da dove argumentsprovenga ... Penso che la tua uscita babel stia combinando alcune caratteristiche diverse. È più probabile che lo sia arr2 = [].concat(arr1).
Sterling Archer,

3
@SterlingArcher arr2 = [].conact(arr1)è diverso da arr2 = [...arr1]. [...arr1]la sintassi convertirà il buco in undefined. Ad esempio arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3,.
tsh

1
Ho provato questo nel mio browser (Chrome 59.0.3071.115) contro la risposta di Dan sopra. Era più di 10 volte più lento di .slice (). n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Harry Stevens,

1
Ancora non lo farà qualcosa di clonare in questo modo: [{a: 'a', b: {c: 'c'}}]. Se cil valore di viene modificato nella matrice "duplicata", cambierà nella matrice originale, poiché è solo una copia referenziale, non un clone.
Neurotrasmettitore

44

Il modo più semplice per eseguire la clonazione profonda di array o oggetti:

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

56
Nota importante per i principianti: poiché dipende da JSON, questo eredita anche i suoi limiti. Tra le altre cose, ciò significa che l'array non può contenere undefinedné alcuno function. Entrambi verranno convertiti in nullper te durante il JSON.stringifyprocesso. Altre strategie, come ad esempio (['cool','array']).slice()non le modificheranno, ma non cloneranno in profondità oggetti all'interno dell'array. Quindi c'è un compromesso.
Seth Holladay,

27
Perf molto male e non funziona con oggetti speciali come DOM, date, regexp, function ... o oggetti prototipati. Non supportare riferimenti ciclici. Non dovresti mai usare JSON per il clone profondo.
Yukulélé,

17
peggior modo possibile! Utilizzare solo se per qualche problema tutti gli altri non funzionano. È lento, le risorse sono intense e ha tutti i limiti JSON già menzionati nei commenti. Non riesco a immaginare come abbia ottenuto 25 voti positivi.
Lukas Liesis,

2
Copia in profondità matrici con primitive e dove le proprietà sono matrici con ulteriori primitive / matrici. Per quello è ok.
Drenai,

4
Ho provato questo nel mio browser (Chrome 59.0.3071.115) contro la risposta di Dan sopra. Era quasi 20 volte più lento di .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
Per favore, spiega cosa fa questo.
Jed Fox,

8
Sebbene questo frammento di codice possa rispondere alla domanda, non fornisce alcun contesto per spiegare come o perché. Valuta di aggiungere una o due frasi per spiegare la tua risposta.
brandonscript,

32
Odio questo tipo di commenti. È ovvio cosa fa!
EscapeNetscape,

6
Una risposta semplice per una semplice domanda, nessuna grande storia da leggere. Mi piace questo tipo di risposte +1
Achim

15
"Chiedo solo informazioni sulla velocità": questa risposta non fornisce indicazioni sulla velocità. Questa è la domanda principale che viene posta. brandonscript ha un buon punto. Sono necessarie ulteriori informazioni per considerare questa una risposta. Ma se fosse una domanda più semplice, questa sarebbe una risposta eccellente.
TamusJRoyce

26

Ho messo insieme una breve demo: http://jsbin.com/agugo3/edit

I miei risultati su Internet Explorer 8 sono 156, 782 e 750, il che indicherebbe slice è molto più veloce in questo caso.


Non dimenticare il costo aggiuntivo del Garbage Collector se devi farlo molto velocemente. Stavo copiando ogni array vicino per ogni cella nei miei automi cellulari usando slice ed era molto più lento rispetto al riutilizzo di un array precedente e alla copia dei valori. Chrome ha indicato che circa il 40% del tempo totale è stato dedicato alla raccolta dei rifiuti.
drake7707,

21

a.map(e => e)è un'altra alternativa per questo lavoro. Ad oggi .map()è molto veloce (quasi altrettanto veloce .slice(0)) in Firefox, ma non in Chrome.

D'altra parte, se un array è multidimensionale, poiché gli array sono oggetti e gli oggetti sono tipi di riferimento, nessuno dei metodi fetta o concat sarà una cura ... Quindi un modo corretto di clonazione una matrice è un'invenzione Array.prototype.clone()come segue.

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


Non male, ma sfortunatamente questo non funziona se hai Object nel tuo array: \ JSON.parse (JSON.stringify (myArray)) funziona meglio in questo caso.
GBMan

17

🏁 Il modo più veloce per clonare un array

Ho creato questa semplice utility per testare il tempo necessario per clonare un array. Non è affidabile al 100%, tuttavia può darti un'idea generale di quanto tempo ci vuole per clonare un array esistente:

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

E testato un approccio diverso:

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

AGGIORNAMENTO :
Nota: tra tutti, l'unico modo per clonare in profondità un array è usare JSON.parse(JSON.stringify(arr)).

Detto questo, non utilizzare quanto sopra se l'array potrebbe includere funzioni in quanto tornerà null.
Grazie @GilEpshtain per questo aggiornamento .


2
Ho provato a confrontare la tua risposta e ho ottenuto risultati molto diversi: jsben.ch/o5nLG
mesqueeb

@mesqueeb, i test potrebbero cambiare, a seconda della tua macchina ovviamente. Tuttavia, sentiti libero di aggiornare la risposta con il risultato del test. Bel lavoro!
Lior Elrom,

Mi piace molto la tua risposta, tuttavia provo il tuo test e ottengo che arr => arr.slice()sia il più veloce.
Gil Epshtain,

1
@LiorElrom, l'aggiornamento non è corretto a causa del fatto che i metodi non sono serializzabili. Ad esempio: JSON.parse(JSON.stringify([function(){}]))uscirà[null]
Gil Epshtain il

1
Bel punto di riferimento. Ho provato questo sul mio Mac in 2 browser: Chrome versione 81.0.4044.113 e Safari versione 13.1 (15609.1.20.111.8) e l'operazione di diffusione più veloce è: [...arr]con 4.653076171875msChrome e 8.565msSafari. Il secondo veloce in Chrome è la funzione slice arr.slice()con 6.162109375mse in Safari il secondo è [].concat(arr)con 13.018ms.
edufinn,

7

Dai un'occhiata a: link . Non si tratta di velocità, ma di comfort. Inoltre, come puoi vedere, puoi usare solo slice (0) su tipi primitivi .

Per creare una copia indipendente di un array anziché una copia del riferimento ad esso, è possibile utilizzare il metodo slice dell'array.

Esempio:

Per creare una copia indipendente di un array anziché una copia del riferimento ad esso, è possibile utilizzare il metodo slice dell'array.

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

Per copiare o clonare un oggetto:

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

Fonte: link


1
Il commento sui tipi primitivi si applica anche al forciclo nella domanda.
user113716

4
se stavo copiando un array di oggetti, mi aspetterei che il nuovo array faccia riferimento agli stessi oggetti anziché clonarli.
Lincoln,

7

Come ha detto @Dan "Questa risposta diventa obsoleta rapidamente. Usa i benchmark per verificare la situazione reale", c'è una risposta specifica da jsperf che non ha avuto una risposta per sé: while :

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

aveva 960.589 operazioni al secondo con il secondo classificato a.concat() a 578.129 ops / sec, che è del 60%.

Questo è l'ultimo Firefox (40) a 64 bit.


@aleclarson ha creato un nuovo benchmark più affidabile.


1
Dovresti davvero collegare jsperf. Quello a cui stai pensando è rotto, perché in ogni caso di test viene creato un nuovo array, ad eccezione del test "while loop".
aleclarson,

1
Ho realizzato un nuovo jsperf più preciso: jsperf.com/clone-array-3
aleclarson,

60% cosa? 60% più veloce?
Peter Mortensen,

1
@PeterMortensen: 587192 è ~ 60% (61.1 ...) di 960589.
serv-inc,

7

ECMAScript 2015 con il Spread operatore:

Esempi di base:

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

Prova nella console del browser:

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

Riferimenti


Probabilmente l'unica cosa che è veloce con lo spread è digitarlo. È sicuramente meno performante rispetto ad altri modi di farlo.
XT_Nova,

3
Fornisci alcuni link sul tuo argomento.
Marian07,

6

Dipende dal browser. Se guardi nel post del blog Array.prototype.slice vs creazione manuale dell'array , c'è una guida approssimativa alle prestazioni di ciascuno:

Inserisci qui la descrizione dell'immagine

risultati:

Inserisci qui la descrizione dell'immagine


1
argumentsnon è un array corretto e sta usando callper forzare slicel'esecuzione nella raccolta. i risultati potrebbero essere fuorvianti.
Lincoln,

Sì, intendevo menzionare che nel mio post queste statistiche probabilmente cambieranno ora con il miglioramento dei broswer, ma dà un'idea generale.
kyndigs,

2
@diugalde Penso che l'unica situazione in cui pubblicare il codice come immagine sia accettabile sia quando il codice è potenzialmente pericoloso e non dovrebbe essere incollato. In questo caso, tuttavia, è abbastanza ridicolo.
Florian Wendelborn,

6

C'è una soluzione molto più pulita:

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

È necessario il controllo della lunghezza, poiché il Arraycostruttore si comporta in modo diverso quando viene chiamato con esattamente un argomento.


2
Ma è il più veloce?
Chris Wesseling,

14
Più semantico di splice(), forse. Ma davvero, applica e questo è quasi intuitivo.
Michael Piefel,


3
È possibile utilizzare Array.ofe ignorare la lunghezza:Array.of.apply(Array, array)
Oriol

6

Ricorda .slice () non funzionerà con array bidimensionali. Avrai bisogno di una funzione come questa:

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

3
In Javascript non ci sono matrici bidimensionali. Esistono solo array contenenti array. Quello che stai cercando di fare è una copia profonda che non è richiesta nella domanda.
Aloso

5

Dipende dalla lunghezza dell'array. Se la lunghezza dell'array è <= 1.000.000, i metodi slicee concatimpiegano all'incirca allo stesso tempo. Ma quando dai una gamma più ampia, ilconcat metodo vince.

Ad esempio, prova questo codice:

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

Se si imposta la lunghezza di original_array su 1.000.000, il slicemetodo econcat metodo impiegano all'incirca lo stesso tempo (3-4 ms, a seconda dei numeri casuali).

Se si imposta la lunghezza di original_array su 10.000.000, il slicemetodo richiede oltre 60 ms e il concatmetodo richiede oltre 20 ms.


dup.pushè sbagliato a5, invece dup[i] = dovrebbe essere usato
4esn0k

3

Una soluzione semplice:

original = [1,2,3]
cloned = original.map(x=>x)

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 ] ]

Quindi, per evitare che si verifichino questi scenari, utilizzare

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

        const cloneArr = Array.from(arr);

È una cosa valida sottolineare come il cambiamento cloneNums[0][0]nel tuo esempio abbia propagato la modifica a nums[0][0]- ma questo è perché si nums[0][0]tratta effettivamente di un oggetto il cui riferimento viene copiato cloneNumsdall'operatore spread. Ciò significa che questo comportamento non influirà sul codice in cui stiamo copiando per valore (int, string etc etc letterals).
Aditya MP,

1

Tempo di benchmark!

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>

Il benchmark verrà eseguito per 10 secondi da quando si fa clic sul pulsante.

I miei risultati:

Chrome (motore 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)

Codice del vincitore:

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

Motore vincitore:

SpiderMonkey (Mozilla / Firefox)


1

Modi rapidi per duplicare un array in JavaScript in ordine:

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

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

#3: array1copy = array1.slice();

Se gli oggetti dell'array contengono contenuti JSON non serializzabili (funzioni, Number.POSITIVE_INFINITY, ecc.) Che è meglio usare

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


0

Puoi seguire questo codice. Clone di array a modalità immutabile. Questo è il modo perfetto per eseguire la clonazione di array


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

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

0

In ES6, puoi semplicemente utilizzare la sintassi Spread .

Esempio:

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

Si noti che l'operatore spread genera un array completamente nuovo, quindi la modifica di uno non influirà sull'altro.

Esempio:

arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.