La forma más rápida de duplicar una matriz en JavaScript: segmento vs. bucle 'for'

634

Para duplicar una matriz en JavaScript: ¿cuál de los siguientes es más rápido de usar?

Método de corte

var dup_array = original_array.slice();

For lazo

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

Sé que ambas formas solo hacen una copia superficial : si original_array contiene referencias a objetos, los objetos no se clonarán, pero solo se copiarán las referencias y, por lo tanto, ambas matrices tendrán referencias a los mismos objetos. Pero este no es el punto de esta pregunta.

Solo pregunto por la velocidad.

Marco Demaio
fuente
3
jsben.ch/#/wQ9RU <= un punto de referencia para las formas más comunes de clonar una matriz
EscapeNetscape

Respuestas:

776

Hay al menos 5 (!) Formas de clonar una matriz:

  • lazo
  • rebanada
  • Array.from ()
  • concat
  • operador de propagación (MÁS RÁPIDO)

Ha habido un hilo enorme de BENCHMARKS , que proporciona la siguiente información:

  • para los navegadores de parpadeoslice() es el método más rápido, concat()es un poco más lento y while loopes 2.4 veces más lento.

  • para otros navegadores while loopes el método más rápido, ya que esos navegadores no tienen optimizaciones internas para slicey concat.

Esto sigue siendo cierto en julio de 2016.

A continuación hay scripts simples que puede copiar y pegar en la consola de su navegador y ejecutar varias veces para ver la imagen. Producen milisegundos, menor es mejor.

mientras bucle

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

rebanada

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

Tenga en cuenta que estos métodos clonarán el objeto Array en sí, sin embargo, los contenidos de la matriz se copian por referencia y no se clonan en profundidad.

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true
Dan
fuente
48
@ cept0 sin emociones, solo puntos de referencia jsperf.com/new-array-vs-splice-vs-slice/31
Dan
2
@ Dan ¿Y qué? Resultados de su caso de prueba: Firefox 30 todas las noches sigue siendo ~ 230% más rápido que Chrome. Verifique el código fuente de V8 splicey se sorprenderá (mientras ...)
mate64
44
Lamentablemente para las matrices cortas, la respuesta es muy diferente . Por ejemplo, clonar una variedad de oyentes antes de llamar a cada uno de ellos. Esos conjuntos son a menudo pequeños, generalmente de 1 elemento.
gman
66
Te perdiste este método:A.map(function(e){return e;});
wcochran
13
Estás escribiendo sobre los navegadores de parpadeo . ¿No es parpadear solo un motor de diseño, que afecta principalmente la representación HTML y, por lo tanto, no es importante? Pensé que preferiríamos hablar de V8, Spidermonkey y amigos aquí. Solo una cosa que me confundió. Ilumíname, si me equivoco.
Neonit
242

Técnicamente slice es la forma más rápida. Sin embargo , es aún más rápido si agrega el 0índice de inicio.

myArray.slice(0);

es más rápido que

myArray.slice();

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

KingKongFrog
fuente
¿Y es myArray.slice(0,myArray.length-1);más rápido que myArray.slice(0);?
jave.web
1
@ jave.web you; acabo de soltar el último elemento de la matriz. La copia completa es array.slice (0) o array.slice (0, array.length)
Marek Marczak
137

¿qué pasa con es6 way?

arr2 = [...arr1];
Yukulélé
fuente
23
si se convierte con babel:[].concat(_slice.call(arguments))
CHAN
1
No estoy seguro de dónde argumentsviene ... Creo que su salida de babel está combinando algunas características diferentes. Es más probable que lo sea arr2 = [].concat(arr1).
Sterling Archer
3
@SterlingArcher arr2 = [].conact(arr1)es diferente de arr2 = [...arr1]. [...arr1]la sintaxis convertirá el agujero a undefined. Por ejemplo, arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3.
tsh
1
Probé esto en mi navegador (Chrome 59.0.3071.115) contra la respuesta de Dan anterior. Fue más de 10 veces más lento que .slice (). n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Harry Stevens
1
Todavía no lo hará clon algo como esto: [{a: 'a', b: {c: 'c'}}]. Si cse cambia el valor en la matriz "duplicada", cambiará en la matriz original, ya que es solo una copia referencial, no un clon.
Neurotransmisor
44

La forma más fácil de clonar profundamente una matriz u objeto:

var dup_array = JSON.parse(JSON.stringify(original_array))
Vladimir Kharlampidi
fuente
56
Nota importante para principiantes: como esto depende de JSON, esto también hereda sus limitaciones. Entre otras cosas, eso significa que su matriz no puede contener undefinedni ningún functions. Ambos serán convertidos a nullusted durante el JSON.stringifyproceso. Otras estrategias, como (['cool','array']).slice()no las cambiarán, pero tampoco clonarán objetos en la matriz. Entonces hay una compensación.
Seth Holladay
27
Muy mal rendimiento y no funciona con objetos especiales como DOM, date, regexp, function ... u objetos prototipados. No admite referencias cíclicas. Nunca debe usar JSON para clones profundos.
Yukulélé
17
peor manera posible! Úselo solo si por algún problema el resto no funciona. Es lento, sus recursos son intensos y tiene todas las limitaciones de JSON ya mencionadas en los comentarios. No puedo imaginar cómo obtuvo 25 votos positivos.
Lukas Liesis
2
Copia en profundidad las matrices con primitivas, y donde las propiedades son matrices con primitivas / matrices adicionales. Para eso está bien.
Drenai
44
Probé esto en mi navegador (Chrome 59.0.3071.115) contra la respuesta de Dan anterior. Fue casi 20 veces más lento 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
fuente
3
Por favor explique qué hace esto.
Jed Fox
8
Si bien este fragmento de código puede responder la pregunta, no proporciona ningún contexto para explicar cómo o por qué. Considere agregar una o dos oraciones para explicar su respuesta.
brandonscript
32
Odio este tipo de comentarios. ¡Es obvio lo que hace!
EscapeNetscape
66
Una respuesta simple para preguntas simples, no hay una gran historia para leer. Me gusta este tipo de respuestas +1
Achim
15
"Solo pregunto por la velocidad": esta respuesta no indica la velocidad. Esa es la pregunta principal que se hace. Brandonscript tiene un buen punto. Se necesita más información para considerar esto como una respuesta. Pero si fuera una pregunta más simple, esta sería una excelente respuesta.
TamusJRoyce
26

Puse una demostración rápida: http://jsbin.com/agugo3/edit

Mis resultados en Internet Explorer 8 son 156, 782 y 750, lo que indicaría que slicees mucho más rápido en este caso.

Lincoln
fuente
No olvide el costo adicional del recolector de basura si tiene que hacer esto muy rápido. Estaba copiando cada matriz vecina para cada celda en mi autómata celular usando una división y fue mucho más lento que reutilizar una matriz anterior y copiar los valores. Chrome indicó que aproximadamente el 40% del tiempo total se dedicó a la recolección de basura.
drake7707
21

a.map(e => e)Es otra alternativa para este trabajo. A partir de hoy .map()es muy rápido (casi tan rápido como .slice(0)) en Firefox, pero no en Chrome.

Por otro lado, si una matriz es multidimensional, ya que las matrices son objetos y los objetos son los tipos de referencia, ninguno de los métodos de división o de concat será una cura ... Así que una forma adecuada de la clonación de un array es una invención de Array.prototype.clone()lo sigue.

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
fuente
No está mal, pero desafortunadamente esto no funciona si tiene Object en su matriz: \ JSON.parse (JSON.stringify (myArray)) funciona mejor en este caso.
GBMan
17

🏁 La forma más rápida de clonar una matriz

Hice esta función de utilidad muy simple para probar el tiempo que lleva clonar una matriz. No es 100% confiable, sin embargo, puede darle una idea general de cuánto tiempo lleva clonar una matriz existente:

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

Y probado enfoque diferente:

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

ACTUALIZACIÓN :
Nota: de todos ellos, la única forma de clonar en profundidad una matriz es mediante el uso JSON.parse(JSON.stringify(arr)).

Dicho esto, no use lo anterior si su matriz puede incluir funciones, ya que volverá null.
Gracias @GilEpshtain por esta actualización .

Lior Elrom
fuente
2
Intenté comparar su respuesta y obtuve resultados muy diferentes: jsben.ch/o5nLG
mesqueeb
@mesqueeb, las pruebas pueden cambiar, dependiendo de su máquina, por supuesto. Sin embargo, no dude en actualizar la respuesta con el resultado de su prueba. ¡Buen trabajo!
Lior Elrom
Me gusta mucho su respuesta, sin embargo, pruebo su prueba y obtengo que arr => arr.slice()es la más rápida.
Gil Epshtain
1
@LiorElrom, su actualización no es correcta, debido al hecho de que los métodos no son serializables. Por ejemplo: JSON.parse(JSON.stringify([function(){}]))saldrá[null]
Gil Epshtain
1
Buen punto de referencia. He probado esto en mi Mac en 2 navegadores: Chrome Versión 81.0.4044.113 y Safari Versión 13.1 (15609.1.20.111.8) y la operación de propagación más rápida: [...arr]con 4.653076171875msChrome y 8.565msSafari. El segundo rápido en Chrome es la función de división arr.slice()con 6.162109375msy en Safari el segundo es [].concat(arr)con 13.018ms.
edufinn
7

Echa un vistazo a: enlace . No se trata de velocidad, sino de comodidad. Además, como puede ver, solo puede usar el segmento (0) en tipos primitivos .

Para hacer una copia independiente de una matriz en lugar de una copia de la referencia, puede usar el método de división de matriz.

Ejemplo:

Para hacer una copia independiente de una matriz en lugar de una copia de la referencia, puede usar el método de división de matriz.

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

Para copiar o clonar un objeto:

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

Fuente: enlace

Margus
fuente
1
El comentario de tipos primitivos se aplica también al forbucle en la pregunta.
user113716
44
si estuviera copiando una matriz de objetos, esperaría que la nueva matriz haga referencia a los mismos objetos en lugar de clonarlos.
lincolnk
7

Como @Dan dijo "Esta respuesta se vuelve obsoleta rápidamente. Use puntos de referencia para verificar la situación real", hay una respuesta específica de jsperf que no ha tenido una respuesta por sí misma: mientras que :

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

tuvo 960,589 operaciones / segundo con el segundo puesto a.concat()en 578,129 operaciones / segundo, que es 60%.

Este es el último Firefox (40) de 64 bits.


@aleclarson creó un nuevo punto de referencia más confiable.

serv-inc
fuente
1
Realmente deberías vincular el jsperf. El que está pensando está roto, porque se crea una nueva matriz en cada caso de prueba, excepto la prueba 'while loop'.
aleclarson
1
Hice un nuevo jsperf que es más preciso: jsperf.com/clone-array-3
aleclarson
60% que? 60% más rápido?
Peter Mortensen
1
@PeterMortensen: 587192 es ~ 60% (61.1 ...) de 960589.
serv-inc
7

Forma ECMAScript 2015 con el Spreadoperador:

Ejemplos básicos:

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

Prueba en la consola del navegador:

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

Referencias

Marian07
fuente
Probablemente lo único que es rápido con la propagación es escribirlo. Es mucho menos eficaz que otras formas de hacerlo.
XT_Nova
3
Proporcione algunos enlaces sobre su argumento.
Marian07
6

Depende del navegador. Si mira en la publicación del blog Array.prototype.slice vs creación manual de matriz , hay una guía aproximada para el rendimiento de cada uno:

Ingrese la descripción de la imagen aquí

Resultados:

Ingrese la descripción de la imagen aquí

Kyndigs
fuente
1
argumentsno es una matriz adecuada y está usando callpara forzar la sliceejecución de la colección Los resultados pueden ser engañosos.
Lincoln
Sí, quise mencionar que en mi publicación estas estadísticas probablemente cambiarían ahora con la mejora de los hermanos, pero da una idea general.
kyndigs
2
@diugalde Creo que la única situación en la que es aceptable publicar código como imagen es cuando el código es potencialmente peligroso y no se debe copiar y pegar. En este caso, sin embargo, es bastante ridículo.
Florian Wendelborn
6

Hay una solución mucho más limpia:

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

Se requiere la verificación de longitud, porque el Arrayconstructor se comporta de manera diferente cuando se llama con exactamente un argumento.

ciembor
fuente
2
¿Pero es el más rápido?
Chris Wesseling
14
Más semántico que splice(), tal vez. Pero realmente, aplique y esto es todo menos intuitivo.
Michael Piefel
muestra el rendimiento más lento en Chrome- jsperf.com/new-array-vs-splice-vs-slice/113
chrismarx
3
Puede usar Array.ofe ignorar la duración:Array.of.apply(Array, array)
Oriol
6

Recuerde .slice () no funcionará para matrices bidimensionales. Necesitará una función como esta:

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}
martinedwards
fuente
3
En Javascript no hay matrices bidimensionales. Solo hay matrices que contienen matrices. Lo que intenta hacer es una copia profunda que no se requiere en la pregunta.
Aloso
5

Depende de la longitud de la matriz. Si la longitud de la matriz es <= 1,000,000, los métodos slicey concattardan aproximadamente el mismo tiempo. Pero cuando das un rango más amplio, el concatmétodo gana.

Por ejemplo, intente este código:

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 establece la longitud de original_array en 1,000,000, el slicemétodo yconcat método demoran aproximadamente el mismo tiempo (3-4 ms, dependiendo de los números aleatorios).

Si establece la longitud de original_array en 10,000,000, entonces el slicemétodo toma más de 60 ms y el concatmétodo toma más de 20 ms.

Gor
fuente
dup.pushestá equivocado a5, en su lugar se dup[i] = debe usar
4esn0k
3

Una solución simple:

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

Entonces, para evitar que ocurran estos escenarios, use

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

        const cloneArr = Array.from(arr);
Anki
fuente
Es válido señalar cómo el cambio cloneNums[0][0]en su ejemplo propagó el cambio nums[0][0], pero eso es porque nums[0][0]efectivamente es un objeto cuya referencia es copiada cloneNumspor el operador de propagación. Todo lo que quiere decir, este comportamiento no afectará el código donde estamos copiando por valor (int, string, etc. literales).
Aditya MP
1

Tiempo de referencia!

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>

El punto de referencia se ejecutará durante 10 segundos desde que hace clic en el botón.

Mis resultados:

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

Código de ganador:

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

Motor ganador:

SpiderMonkey (Mozilla / Firefox)

Zibri
fuente
1

Formas rápidas de duplicar una matriz en JavaScript en orden:

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

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

#3: array1copy = array1.slice();

Si sus objetos de matriz contienen algún contenido no serializable JSON (funciones, Number.POSITIVE_INFINITY, etc.), mejor usar

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

MuhammadUmarFarooq
fuente
0

Puedes seguir este código. Forma inmutable matriz clon. Esta es la manera perfecta de clonar en matriz


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

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

En ES6, simplemente puede utilizar la sintaxis Spread .

Ejemplo:

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

Tenga en cuenta que el operador de propagación genera una matriz completamente nueva, por lo que modificar una no afectará a la otra.

Ejemplo:

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