Maneira mais rápida de duplicar uma matriz em JavaScript - fatia vs. loop 'for'

634

Para duplicar uma matriz em JavaScript: qual das opções a seguir é mais rápida de usar?

Método de fatia

var dup_array = original_array.slice();

For ciclo

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

Sei que as duas maneiras fazem apenas uma cópia superficial : se original_array contiver referências a objetos, os objetos não serão clonados, mas apenas as referências serão copiadas e, portanto, as duas matrizes terão referências aos mesmos objetos. Mas este não é o objetivo desta questão.

Estou perguntando apenas sobre velocidade.

Marco Demaio
fonte
3
jsben.ch/#/wQ9RU <= um ponto de referência para as formas mais comuns de clonar um array
EscapeNetscape

Respostas:

776

Existem pelo menos 5 (!) Maneiras de clonar uma matriz:

  • ciclo
  • fatia
  • Array.from ()
  • concat
  • operador de spread (FASTEST)

Houve um encadeamento de BENCHMARKS , fornecendo as seguintes informações:

  • para navegadores intermitentesslice() é o método mais rápido, concat()um pouco mais lento e while loop2,4x mais lento.

  • para outros navegadores while loopé o método mais rápido, pois esses navegadores não possuem otimizações internas para slicee concat.

Isso permanece verdadeiro em julho de 2016.

Abaixo estão alguns scripts simples que você pode copiar e colar no console do navegador e executar várias vezes para ver a imagem. Eles produzem milissegundos, quanto menor, melhor.

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

fatia

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

Observe que esses métodos clonam o próprio objeto Array, mas o conteúdo do array é copiado por referência e não é profundamente clonado.

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true
Dan
fonte
48
@ cept0 nenhuma emoção, apenas benchmarks jsperf.com/new-array-vs-splice-vs-slice/31
Dan
2
@ Dan Então, o que? Resultados do seu caso de teste: o Firefox 30 noturno ainda é ~ 230% mais rápido que o Chrome. Verifique o código fonte de V8 para splicee você ficará surpreso (enquanto ...)
mate64
4
Infelizmente, para matrizes curtas, a resposta é muito diferente . Por exemplo, clonando uma matriz de ouvintes antes de chamar cada um deles. Essas matrizes geralmente são pequenas, geralmente 1 elemento.
gman
6
Você perdeu este método:A.map(function(e){return e;});
wcochran 7/09/16
13
Você está escrevendo sobre navegadores intermitentes . O piscar não é apenas um mecanismo de layout, afetando principalmente a renderização HTML e, portanto, sem importância? Eu pensei que preferíamos conversar sobre V8, Spidermonkey e amigos aqui. Apenas uma coisa que me confundiu. Esclareça-me, se eu estiver errado.
Neonit 30/10/16
242

Tecnicamente, slice é o caminho mais rápido. No entanto , é ainda mais rápido se você adicionar o 0índice inicial.

myArray.slice(0);

é mais rápido que

myArray.slice();

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

KingKongFrog
fonte
E é myArray.slice(0,myArray.length-1);mais rápido que myArray.slice(0);?
Java.web 17/05
1
@ jave.web you; acabamos de soltar o último elemento da matriz. Cópia completa é Array.slice (0) ou Array.slice (0, array.length)
Marek Marczak
137

e quanto à es6 way?

arr2 = [...arr1];
Yukulélé
fonte
23
se convertido com babel:[].concat(_slice.call(arguments))
CHAN
1
Não sei de onde argumentsvem ... Acho que sua saída do babel está confluindo alguns recursos diferentes. É mais provável que seja arr2 = [].concat(arr1).
Sterling Archer
3
@SterlingArcher arr2 = [].conact(arr1)é diferente de arr2 = [...arr1]. [...arr1]A sintaxe converterá o furo em undefined. Por exemplo arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3,.
tsh
1
Eu testei isso no meu navegador (Chrome 59.0.3071.115) com a resposta de Dan acima. Era mais de 10 vezes mais lento que .slice (). n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Harry Stevens
1
Ainda não será algo clone assim: [{a: 'a', b: {c: 'c'}}]. Se co valor de for alterado na matriz "duplicada", ele será alterado na matriz original, pois é apenas uma cópia referencial, não um clone.
Neurotransmitter
44

A maneira mais fácil de clonar profundamente Matriz ou Objeto:

var dup_array = JSON.parse(JSON.stringify(original_array))
Vladimir Kharlampidi
fonte
56
Nota importante para iniciantes: como isso depende do JSON, isso também herda suas limitações. Entre outras coisas, isso significa que sua matriz não pode conter undefinedou nenhum functions. Ambos serão convertidos nullpara você durante o JSON.stringifyprocesso. Outras estratégias, como (['cool','array']).slice()não as alteram, mas também não clonam objetos profundamente dentro da matriz. Portanto, há uma troca.
Seth Holladay
27
Muito mau desempenho e não funciona com objetos especiais como DOM, data, expressão regular, função ... ou objetos com prototipagem. Não suporte referências cíclicas. Você nunca deve usar JSON para clone profundo.
Yukulélé
17
pior maneira possível! Use apenas se, para algum problema, todos os outros não funcionarem. É lento, os recursos são intensos e tem todas as limitações do JSON já mencionadas nos comentários. Não consigo imaginar como conseguiu 25 votos positivos.
Lukas Liesis
2
Ele copia em profundidade matrizes com primitivas e onde as propriedades são matrizes com outras primitivas / matrizes. Para isso está tudo bem.
Drenai 6/08/16
4
Eu testei isso no meu navegador (Chrome 59.0.3071.115) com a resposta de Dan acima. Era quase 20 vezes mais 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
fonte
3
Por favor, explique o que isso faz.
Jed Fox
8
Embora esse trecho de código possa responder à pergunta, ele não fornece nenhum contexto para explicar como ou por quê. Considere adicionar uma ou duas frases para explicar sua resposta.
brandonscript
32
Eu odeio esse tipo de comentário. É óbvio o que faz!
EscapeNetscape 24/10
6
Uma resposta simples para perguntas simples, sem grandes histórias para ler. Eu gosto desse tipo de respostas +1
Achim
15
"Estou perguntando apenas sobre velocidade" - Esta resposta não indica a velocidade. Essa é a principal pergunta que está sendo feita. brandonscript tem um bom argumento. São necessárias mais informações para considerar isso uma resposta. Mas se fosse uma pergunta mais simples, essa seria uma excelente resposta.
TamusJRoyce
26

Eu montei uma demonstração rápida: http://jsbin.com/agugo3/edit

Meus resultados no Internet Explorer 8 são 156, 782 e 750, o que indicaria que sliceé muito mais rápido nesse caso.

Lincolnk
fonte
Não se esqueça do custo adicional do coletor de lixo se você precisar fazer isso muito rapidamente. Eu estava copiando cada matriz vizinha para cada célula em meus autômatos celulares usando fatia e era muito mais lento que reutilizar uma matriz anterior e copiar os valores. O Chrome indicou que cerca de 40% do tempo total foi gasto na coleta de lixo.
drake7707
21

a.map(e => e)é outra alternativa para este trabalho. A partir de hoje, .map()é muito rápido (quase tão rápido quanto .slice(0)) no Firefox, mas não no Chrome.

Por outro lado, se uma matriz é multi-dimensional, uma vez que as matrizes são objectos e objectos são tipos de referência, nenhum dos métodos ou fatia concat será uma cura ... Portanto, uma forma adequada de clonagem de uma matriz é uma invenção de Array.prototype.clone()quanto 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));

Redu
fonte
Nada mal, mas infelizmente isso não funciona se você tiver um objeto em sua matriz: \ JSON.parse (JSON.stringify (myArray)) funciona melhor nesse caso.
GBMan 24/04
17

Way A maneira mais rápida de clonar uma matriz

Eu criei essa função de utilitário muito simples para testar o tempo necessário para clonar uma matriz. Não é 100% confiável, no entanto, pode fornecer uma ideia geral sobre quanto tempo leva para clonar uma matriz existente:

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

E testou uma abordagem 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));

UPDATE :
Nota: dentre todas, a única maneira de clonar profundamente uma matriz é usando JSON.parse(JSON.stringify(arr)).

Dito isto, não use o acima se sua matriz pode incluir funções, pois ela retornará null.
Obrigado @ GilEpshtain por esta atualização .

Lior Elrom
fonte
2
Eu tentei aferição sua resposta e eu tenho resultados muito diferentes: jsben.ch/o5nLG
mesqueeb
@mesqueeb, os testes podem mudar, dependendo da sua máquina, é claro. No entanto, fique à vontade para atualizar a resposta com o resultado do teste. Bom trabalho!
Lior Elrom 25/02/19
Eu gosto muito da sua resposta, no entanto, eu tento o seu teste e faço com que arr => arr.slice()seja o mais rápido.
Gil Epshtain
1
@LiorElrom, sua atualização não está correta, devido ao fato de os métodos não serem serializáveis. Por exemplo: JSON.parse(JSON.stringify([function(){}]))irá produzir[null]
Gil Epshtain
1
Nice benchmark. Eu testei isso no meu Mac em 2 navegadores: Chrome versão 81.0.4044.113 e Safari versão 13.1 (15609.1.20.111.8) e mais rápida é a operação disseminação: [...arr]com 4.653076171875msno Chrome e 8.565msno Safari. O segundo jejum no Chrome é a função de fatia arr.slice()com 6.162109375mse no Safari o segundo é [].concat(arr)com 13.018ms.
edufinn 23/04
7

Dê uma olhada no: link . Não se trata de velocidade, mas de conforto. Além disso, como você pode ver, você só pode usar a fatia (0) em tipos primitivos .

Para fazer uma cópia independente de uma matriz em vez de uma cópia da referência a ela, você pode usar o método de fatia da matriz.

Exemplo:

Para fazer uma cópia independente de uma matriz em vez de uma cópia da referência a ela, você pode usar o método de fatia da matriz.

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

Para copiar ou clonar um 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);

Fonte: link

Margus
fonte
1
O comentário de tipos primitivos também se aplica ao forloop da pergunta.
user113716
4
se eu estivesse copiando uma matriz de objetos, esperaria que a nova matriz fizesse referência aos mesmos objetos em vez de cloná-los.
lincolnk
7

Como o @Dan disse: "Esta resposta fica desatualizada rapidamente. Use benchmarks para verificar a situação real", há uma resposta específica do jsperf que não teve uma resposta por si só: enquanto :

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

teve 960.589 ops / s com o a.concat()segundo colocado em 578.129 ops / s, que é de 60%.

Este é o último Firefox (40) 64 bits.


A @aleclarson criou uma nova referência mais confiável.

serv-inc
fonte
1
Você realmente deve vincular o jsperf. O que você está pensando está quebrado, porque uma nova matriz é criada em todos os casos de teste, exceto no teste 'while loop'.
Aleclarson
1
Eu fiz um novo JSPerf que é mais preciso: jsperf.com/clone-array-3
aleclarson
60% o que? 60% mais rápido?
22618 Peter Mortensen
1
@PeterMortensen: 587192 é ~ 60% (61,1 ...) de 960589.
serv-inc
7

Maneira ECMAScript 2015 com o Spreadoperador:

Exemplos básicos:

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

Experimente no console do 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);

Referências

Marian07
fonte
Provavelmente, a única coisa que é rápida com a propagação é digitá-la. É muito menos eficiente do que outras maneiras de fazê-lo.
XT_Nova 26/0118
3
Forneça alguns links sobre o seu argumento.
Marian07
6

Depende do navegador. Se você procurar na postagem do blog Array.prototype.slice x criação manual de matriz , há um guia aproximado para o desempenho de cada uma:

Digite a descrição da imagem aqui

Resultados:

Digite a descrição da imagem aqui

Kyndigs
fonte
1
argumentsnão é uma matriz adequada e ele está usando callpara forçar slicea execução na coleção. os resultados podem ser enganosos.
lincolnk
Sim, eu quis mencionar que, no meu post, essas estatísticas provavelmente mudariam agora com a melhoria dos irmãos, mas isso dá uma idéia geral.
kyndigs
2
@diugalde Acho que a única situação em que a postagem de um código como imagem é aceitável é quando o código é potencialmente perigoso e não deve ser copiado e colado. Nesse caso, porém, é bastante ridículo.
Florian Wendelborn
6

Existe uma solução muito mais limpa:

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

A verificação do comprimento é necessária, porque o Arrayconstrutor se comporta de maneira diferente quando é chamado com exatamente um argumento.

ciembor
fonte
2
Mas é o mais rápido?
31514 Chris Wesseling
14
Mais semântico do que splice(), talvez. Mas, na verdade, aplique e isso é tudo, menos intuitivo.
Michael Piefel
mostra o mais lento o desempenho em cromo jsperf.com/new-array-vs-splice-vs-slice/113
chrismarx
3
Você pode usar Array.ofe ignorar o comprimento:Array.of.apply(Array, array)
Oriol 12/11
6

Lembre-se de que .slice () não funcionará para matrizes bidimensionais. Você precisará de uma função como esta:

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}
martinedwards
fonte
3
Em Javascript, não existem matrizes bidimensionais. Existem apenas matrizes contendo matrizes. O que você está tentando fazer é uma cópia profunda que não é necessária na pergunta.
Aloso 4/17
5

Depende do comprimento da matriz. Se o comprimento da matriz for <= 1.000.000, os métodos slicee concatlevarão aproximadamente o mesmo tempo. Mas quando você fornece uma faixa maior, o concatmétodo vence.

Por exemplo, tente 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();

Se você definir o comprimento da matriz original como 1.000.000, o slicemétodo e o concatmétodo levarão aproximadamente o mesmo tempo (3 a 4 ms, dependendo dos números aleatórios).

Se você definir o comprimento da matriz original como 10.000.000, o slicemétodo terá mais de 60 ms e o concatmétodo terá mais de 20 ms.

Gor
fonte
dup.pushestá no errado a5, em vez dup[i] = deve ser usado
4esn0k
3

Uma solução simples:

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

Portanto, para evitar que esses cenários aconteçam, use

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

        const cloneArr = Array.from(arr);
Anki
fonte
É válido ressaltar como a alteração cloneNums[0][0]no seu exemplo propagou a alteração para nums[0][0]- mas isso é porque ele nums[0][0]é efetivamente um objeto cuja referência é copiada cloneNumspelo operador de propagação. Tudo o que quer dizer, esse comportamento não afetará o código onde estamos copiando por valor (int, string etc literais).
Aditya MP
1

Hora de referência!

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>

O valor de referência será executado por 10s desde que você clique no botão.

Meus resultados:

Chrome (mecanismo 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 (mecanismo 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 do vencedor:

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

Motor vencedor:

SpiderMonkey (Mozilla / Firefox)

Zibri
fonte
1

Maneiras rápidas de duplicar uma matriz em JavaScript na ordem:

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

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

#3: array1copy = array1.slice();

Se os objetos da matriz contiverem algum conteúdo JSON não serializável (funções, Number.POSITIVE_INFINITY etc.) melhor para usar

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

MuhammadUmarFarooq
fonte
0

Você pode seguir este código. Clone de matriz de maneira imutável. Essa é a maneira perfeita de clonar matrizes


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

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

No ES6, você pode simplesmente utilizar a sintaxe do Spread .

Exemplo:

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

Observe que o operador spread gera uma matriz completamente nova, portanto, modificar uma não afetará a outra.

Exemplo:

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