Le moyen le plus rapide de dupliquer un tableau en JavaScript - tranche vs boucle 'for'

634

Pour dupliquer un tableau en JavaScript: lequel des éléments suivants est le plus rapide à utiliser?

Méthode de tranche

var dup_array = original_array.slice();

For boucle

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

Je sais que les deux façons ne font qu'une copie superficielle : si original_array contient des références à des objets, les objets ne seront pas clonés, mais seules les références seront copiées, et donc les deux tableaux auront des références aux mêmes objets. Mais ce n'est pas le but de cette question.

Je ne demande que de la vitesse.

Marco Demaio
la source
3
jsben.ch/#/wQ9RU <= une référence pour les moyens les plus courants de cloner un tableau
EscapeNetscape

Réponses:

776

Il existe au moins 5 (!) Façons de cloner un tableau:

  • boucle
  • tranche
  • Array.from ()
  • concat
  • opérateur de propagation (FASTEST)

Il y a eu un thread huuuge BENCHMARKS , fournissant les informations suivantes:

  • pour les navigateurs clignotantsslice() est la méthode la plus rapide, concat()un peu plus lente et while loop2,4 fois plus lente.

  • pour les autres navigateurs while loopest la méthode la plus rapide, car ces navigateurs n'ont pas d'optimisations internes pour sliceet concat.

Cela reste vrai en juillet 2016.

Vous trouverez ci-dessous des scripts simples que vous pouvez copier-coller dans la console de votre navigateur et exécuter plusieurs fois pour voir l'image. Ils produisent des millisecondes, plus c'est bas, mieux c'est.

en boucle

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

tranche

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

Veuillez noter que ces méthodes cloneront l'objet Array lui-même, cependant le contenu du tableau est copié par référence et n'est pas cloné en profondeur.

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true
Dan
la source
48
@ cept0 aucune émotion, juste des repères jsperf.com/new-array-vs-splice-vs-slice/31
Dan
2
@Dan Et alors? Les résultats de votre scénario de test: Firefox 30 la nuit est toujours ~ 230% plus rapide que Chrome. Vérifiez le code source de V8 spliceet vous serez surpris (pendant que ...)
mate64
4
Malheureusement pour les tableaux courts, la réponse est très différente . Par exemple, cloner un tableau d'écouteurs avant d'appeler chacun d'eux. Ces tableaux sont souvent petits, généralement 1 élément.
gman
6
Vous avez manqué cette méthode:A.map(function(e){return e;});
wcochran
13
Vous écrivez sur les navigateurs clignotants . Le clignotement n'est-il pas seulement un moteur de mise en page, affectant principalement le rendu HTML, et donc sans importance? Je pensais que nous préférerions parler du V8, de Spidermonkey et de ses amis ici. Juste une chose qui m'a dérouté. Éclairez-moi, si je me trompe.
Neonit
242

Techniquement, slice c'est le moyen le plus rapide. Cependant , il est encore plus rapide si vous ajoutez l' 0index de début.

myArray.slice(0);

est plus rapide que

myArray.slice();

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

KingKongFrog
la source
Et est myArray.slice(0,myArray.length-1);plus rapide que myArray.slice(0);?
jave.web
1
@ jave.web you; ve vient de supprimer le dernier élément du tableau. La copie complète est array.slice (0) ou array.slice (0, array.length)
Marek Marczak
137

qu'en est-il de l'es6?

arr2 = [...arr1];
Yukulélé
la source
23
si converti avec babel:[].concat(_slice.call(arguments))
CHAN
1
Je ne sais pas d'où argumentsça vient ... Je pense que votre sortie babel regroupe quelques fonctionnalités différentes. C'est plus probable arr2 = [].concat(arr1).
Sterling Archer
3
@SterlingArcher arr2 = [].conact(arr1)est différent de arr2 = [...arr1]. [...arr1]la syntaxe convertira le trou en undefined. Par exemple arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3,.
tsh
1
J'ai testé cela dans mon navigateur (Chrome 59.0.3071.115) par rapport à la réponse de Dan ci-dessus. Il était plus de 10 fois plus lent que .slice (). n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Harry Stevens
1
Encore ne sera pas quelque chose de clone comme ceci: [{a: 'a', b: {c: 'c'}}]. Si cla valeur de est modifiée dans le tableau "dupliqué", elle changera dans le tableau d'origine, car ce n'est qu'une copie référentielle, pas un clone.
Neurotransmetteur du
44

Moyen le plus simple de cloner en profondeur un tableau ou un objet:

var dup_array = JSON.parse(JSON.stringify(original_array))
Vladimir Kharlampidi
la source
56
Remarque importante pour les débutants: car cela dépend de JSON, cela hérite également de ses limites. Entre autres choses, cela signifie que votre tableau ne peut pas contenir undefinedde functions. Ces deux éléments seront convertis nullpour vous au cours du JSON.stringifyprocessus. D'autres stratégies, telles que celles (['cool','array']).slice()qui ne les modifieront pas, mais ne clonent pas profondément les objets dans le tableau. Il y a donc un compromis.
Seth Holladay
27
Très mauvais perf et ne fonctionne pas avec des objets spéciaux comme DOM, date, regexp, fonction ... ou des objets prototypés. Ne supporte pas les références cycliques. Vous ne devez jamais utiliser JSON pour un clone profond.
Yukulélé
17
pire moyen possible! Utilisez uniquement si pour certains problèmes, tous les autres ne fonctionnent pas. C'est lent, ses ressources sont intenses et il a toutes les limitations JSON déjà mentionnées dans les commentaires. Je ne peux pas imaginer comment il a obtenu 25 votes positifs.
Lukas Liesis
2
Il copie en profondeur les tableaux avec des primitives, et où les propriétés sont des tableaux avec d'autres primitives / tableaux. Pour ça c'est ok.
Drenai
4
J'ai testé cela dans mon navigateur (Chrome 59.0.3071.115) par rapport à la réponse de Dan ci-dessus. Il était presque 20 fois plus lent que .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);
Sajjad Shirazy
la source
3
Veuillez expliquer ce que cela signifie.
Jed Fox
8
Bien que cet extrait de code puisse répondre à la question, il ne fournit aucun contexte pour expliquer comment ou pourquoi. Pensez à ajouter une ou deux phrases pour expliquer votre réponse.
brandonscript
32
Je déteste ce genre de commentaires. C'est évident ce que ça fait!
EscapeNetscape du
6
Une réponse simple pour une simple question, pas de grande histoire à lire. J'aime ce genre de réponses +1
Achim
15
"Je ne demande que la vitesse" - Cette réponse ne donne aucune indication sur la vitesse. Telle est la principale question posée. brandonscript a un bon point. Plus d'informations sont nécessaires pour considérer cela comme une réponse. Mais s'il s'agissait d'une question plus simple, ce serait une excellente réponse.
TamusJRoyce
26

J'ai monté une démo rapide: http://jsbin.com/agugo3/edit

Mes résultats sur Internet Explorer 8 sont 156, 782 et 750, ce qui indiquerait que slicec'est beaucoup plus rapide dans ce cas.

lincolnk
la source
N'oubliez pas le coût supplémentaire du ramasse-miettes si vous devez le faire très rapidement. Je copiais chaque tableau voisin pour chaque cellule de mes automates cellulaires en utilisant une tranche et c'était beaucoup plus lent que de réutiliser un tableau précédent et de copier les valeurs. Chrome a indiqué qu'environ 40% du temps total était consacré à la collecte des ordures.
drake7707
21

a.map(e => e)est une autre alternative pour ce travail. À ce jour .map()est très rapide (presque aussi rapide que .slice(0)) dans Firefox, mais pas dans Chrome.

D'un autre côté, si un tableau est multidimensionnel, puisque les tableaux sont des objets et que les objets sont des types de référence, aucune des méthodes slice ou concat ne sera un remède ... Donc, une façon appropriée de cloner un tableau est une invention de Array.prototype.clone()as suit.

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

Redu
la source
Pas mal, mais malheureusement cela ne fonctionne pas si vous avez un objet dans votre tableau: \ JSON.parse (JSON.stringify (myArray)) fonctionne mieux dans ce cas.
GBMan
17

🏁 Le moyen le plus rapide de cloner un tableau

J'ai fait cette fonction utilitaire très simple pour tester le temps qu'il faut pour cloner un tableau. Il n'est pas fiable à 100%, mais il peut vous donner une idée globale du temps nécessaire pour cloner un tableau existant:

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

Et testé une approche différente:

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

MISE À JOUR :
Remarque: sur tous, la seule façon de cloner en profondeur un tableau est d'utiliser JSON.parse(JSON.stringify(arr)).

Cela dit, n'utilisez pas ce qui précède si votre tableau peut inclure des fonctions car il reviendra null.
Merci @GilEpshtain pour cette mise à jour .

Lior Elrom
la source
2
J'ai essayé de comparer votre réponse et j'ai obtenu des résultats très différents: jsben.ch/o5nLG
mesqueeb
@mesqueeb, les tests peuvent changer, selon votre machine bien sûr. Cependant, n'hésitez pas à mettre à jour la réponse avec votre résultat de test. Bon travail!
Lior Elrom
J'aime beaucoup votre réponse, mais j'essaie votre test et j'obtiens que arr => arr.slice()c'est le plus rapide.
Gil Epshtain
1
@LiorElrom, votre mise à jour n'est pas correcte, car les méthodes ne sont pas sérialisables. Par exemple: JSON.parse(JSON.stringify([function(){}]))affichera[null]
Gil Epshtain le
1
Belle référence. J'ai testé cela sur mon Mac dans 2 navigateurs: Chrome Version 81.0.4044.113 et Safari Version 13.1 (15609.1.20.111.8) et le plus rapide est l'opération de propagation: [...arr]avec 4.653076171875msdans Chrome et 8.565msdans Safari. Le deuxième rapide dans Chrome est la fonction tranche arr.slice()avec 6.162109375mset dans Safari le second est [].concat(arr)avec 13.018ms.
edufinn
7

Jetez un oeil à: lien . Ce n'est pas une question de vitesse, mais de confort. De plus, comme vous pouvez le voir, vous ne pouvez utiliser la tranche (0) que sur les types primitifs .

Pour créer une copie indépendante d'un tableau plutôt qu'une copie de la référence, vous pouvez utiliser la méthode de tranche de tableau.

Exemple:

Pour créer une copie indépendante d'un tableau plutôt qu'une copie de la référence, vous pouvez utiliser la méthode de tranche de tableau.

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

Pour copier ou cloner un objet:

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

Source: lien

Margus
la source
1
Le commentaire des types primitifs s'applique également à la forboucle de la question.
user113716
4
si je copiais un tableau d'objets, je m'attendrais à ce que le nouveau tableau fasse référence aux mêmes objets plutôt que de cloner les objets.
lincolnk
7

Comme @Dan l'a dit "Cette réponse devient rapidement obsolète. Utilisez des repères pour vérifier la situation réelle", il y a une réponse spécifique de jsperf qui n'a pas eu de réponse pour elle-même: tandis que :

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

avait 960 589 ops / sec avec le runnerup a.concat()à 578,129 ops / sec, qui est 60%.

Il s'agit du dernier Firefox (40) 64 bits.


@aleclarson a créé une nouvelle référence plus fiable.

serv-inc
la source
1
Vous devriez vraiment lier le jsperf. Celui auquel vous pensez est cassé, car un nouveau tableau est créé dans chaque cas de test, à l'exception du test de la boucle while.
aleclarson
1
J'ai fait un nouveau jsperf qui est plus précis: jsperf.com/clone-array-3
aleclarson
60% quoi? 60% plus rapide?
Peter Mortensen
1
@PeterMortensen: 587192 est ~ 60% (61,1 ...) de 960589.
serv-inc
7

ECMAScript 2015 façon avec l' Spreadopérateur:

Exemples de base:

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

Essayez dans la console du navigateur:

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

Références

Marian07
la source
La seule chose qui est rapide avec le spread est probablement de le taper. Il est beaucoup moins performant que d'autres façons de le faire.
XT_Nova
3
Veuillez fournir quelques liens sur votre argument.
Marian07
6

Cela dépend du navigateur. Si vous regardez dans le billet de blog Array.prototype.slice vs la création manuelle de tableaux , il existe un guide approximatif des performances de chacun:

Entrez la description de l'image ici

Résultats:

Entrez la description de l'image ici

kyndigs
la source
1
argumentsn'est pas un tableau approprié et il utilise callpour forcer l' sliceexécution sur la collection. les résultats peuvent être trompeurs.
lincolnk
Ouais, je voulais dire que dans mon article que ces statistiques changeraient probablement maintenant avec l'amélioration des broswers, mais cela donne une idée générale.
kyndigs
2
@diugalde Je pense que la seule situation où la publication de code sous forme d'image est acceptable est lorsque le code est potentiellement dangereux et ne doit pas être copié-collé. Dans ce cas cependant, c'est assez ridicule.
Florian Wendelborn
6

Il existe une solution beaucoup plus propre:

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

La vérification de la longueur est requise, car le Arrayconstructeur se comporte différemment lorsqu'il est appelé avec exactement un argument.

ciembor
la source
2
Mais est-ce le plus rapide?
Chris Wesseling du
14
Plus sémantique que splice(), peut-être. Mais vraiment, appliquer et c'est tout sauf intuitive.
Michael Piefel
affiche les performances les plus lentes sur chrome- jsperf.com/new-array-vs-splice-vs-slice/113
chrismarx
3
Vous pouvez utiliser Array.ofet ignorer la longueur:Array.of.apply(Array, array)
Oriol
6

N'oubliez pas que .slice () ne fonctionnera pas pour les tableaux à deux dimensions. Vous aurez besoin d'une fonction comme celle-ci:

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}
martinedwards
la source
3
En Javascript, il n'y a pas de tableaux à deux dimensions. Il y a juste des tableaux contenant des tableaux. Ce que vous essayez de faire est une copie complète qui n'est pas requise dans la question.
Aloso
5

Cela dépend de la longueur du tableau. Si la longueur du tableau est <= 1 000 000, les méthodes sliceet concatprennent environ le même temps. Mais quand vous donnez une gamme plus large, la concatméthode gagne.

Par exemple, essayez ce code:

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

Si vous définissez la longueur de original_array sur 1 000 000, la sliceméthode etconcat méthode prennent approximativement le même temps (3 à 4 ms, selon les nombres aléatoires).

Si vous définissez la longueur de original_array sur 10 000 000, la sliceméthode prend plus de 60 ms et la concatméthode prend plus de 20 ms.

Gor
la source
dup.pushest inacceptable a5, au lieu dup[i] = devrait être utilisé
4esn0k
3

Une solution simple:

original = [1,2,3]
cloned = original.map(x=>x)
Caio Santos
la source
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 ] ]

Donc, pour éviter que ces scénarios ne se produisent, utilisez

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

        const cloneArr = Array.from(arr);
Anki
la source
C'est une chose valable de souligner comment la modification cloneNums[0][0]de votre exemple a propagé la modification nums[0][0]- mais c'est parce que nums[0][0]c'est effectivement un objet dont la référence est copiée dans cloneNumspar l'opérateur de propagation. Tout cela pour dire que ce comportement n'affectera pas le code où nous copions par valeur (int, chaîne etc littéraux).
Aditya MP
1

Temps de référence!

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>

Le benchmark fonctionnera pendant 10 secondes depuis que vous cliquez sur le bouton.

Mes résultats:

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

Code gagnant:

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

Moteur gagnant:

SpiderMonkey (Mozilla / Firefox)

Zibri
la source
1

Des moyens rapides de dupliquer un tableau en JavaScript dans l'ordre:

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

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

#3: array1copy = array1.slice();

Si vos objets tableau contiennent du contenu non sérialisable JSON (fonctions, Number.POSITIVE_INFINITY, etc.), mieux utiliser

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

MuhammadUmarFarooq
la source
0

Vous pouvez suivre ce code. Clone de tableau de façon immuable. C'est le moyen idéal pour cloner un tableau


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

const newArray = [...array]
newArray.push(6)
console.log(array)
console.log(newArray)
Shuvro
la source
0

Dans ES6, vous pouvez simplement utiliser la syntaxe Spread .

Exemple:

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

Veuillez noter que l'opérateur d'étalement génère un tout nouveau tableau, donc la modification de l'un n'affectera pas l'autre.

Exemple:

arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
balfonso
la source