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.


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

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


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

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

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

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.


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


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 .


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


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.


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


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í


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.


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

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.


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)

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

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)


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


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)

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']
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.