Esta é uma postagem simples e rápida sobre técnicas de JavaScript. Abordaremos diferentes métodos para combinar/fundir duas matrizes JS e os prós e contras de cada abordagem.

Vamos começar com o cenário:

var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var b = [ "foo", "bar", "baz", "bam", "bun", "fun" ];


A simples concatenação de a e b seria, obviamente, o senhor:

[
   1, 2, 3, 4, 5, 6, 7, 8, 9,
   "foo", "bar", "baz", "bam" "bun", "fun"
]


concat(..)

A abordagem mais comum é:

var c = a.concat( b );

a; // [1,2,3,4,5,6,7,8,9]
b; // ["foo","bar","baz","bam","bun","fun"]

c; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]


Como o senhor pode ver, c é um novo array que representa a combinação dos dois a e b matrizes, deixando o a e b intocado. Simples, não é?

E se a tiver 10.000 itens, e o b é de 10.000 itens? c é agora de 20.000 itens, o que constitui basicamente o dobro do uso de memória do a e do b.

“Sem problemas!”, diz o senhor. É só cancelar a configuração do a e b portanto, eles são coletados pelo lixo, certo? Problema resolvido!

a = b = null; // `a` and `b` can go away now


Não. Por apenas alguns pequenos arrays, isso é bom. Mas para grandes arrays, ou repetir esse processo regularmente várias vezes, ou trabalhar em ambientes com memória limitada, isso deixa muito a desejar.

Inserção em loop

OK, vamos apenas acrescentar um arrayao conteúdo do outro, usando Array#push(..):

// `b` onto `a`
for (var i=0; i < b.length; i++) {
    a.push( b[i] );
}

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

b = null;


Agora, a tem o resultado tanto do original quanto do a mais o conteúdo do b.

Parece que é melhor para a memória.

Mas e se o senhor a fosse pequeno e o b era comparativamente muito grande? Por motivos de memória e velocidade, o senhor provavelmente gostaria de usar o menor a para a frente do b em vez do mais longo b no final do a. Não há problema, basta substituir o push(..) por unshift(..) e faça um loop na direção oposta:

// `a` into `b`:
for (var i=a.length-1; i >= 0; i--) {
    b.unshift( a[i] );
}

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

a = null;


Truques funcionais

UnparaFelizmente, for os loops são feios e mais difíceis de manter. Podemos fazer algo melhor?

Aqui está nossa primeira tentativa, usando Array#reduce:

// `b` onto `a`:
a = b.reduce( function(coll,item){
    coll.push( item );
    return coll;
}, a );

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

// or `a` into `b`:
b = a.reduceRight( function(coll,item){
    coll.unshift( item );
    return coll;
}, b );

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]


Array#reduce(..) e Array#reduceRight(..) são bons, mas são um pouco desajeitados. ES6 => arrow-functions as reduzirão um pouco, mas ainda será necessária uma chamada de função por item, o que é lamentável.

E quanto a:

// `b` onto `a`:
a.push.apply( a, b );

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

// or `a` into `b`:
b.unshift.apply( b, a );

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]


Isso é muito mais agradável, certo? Especialmente porque o unshift(..) aqui não precisa se preocupar com a ordem inversa, como nas tentativas anteriores. O operador de propagação do ES6 será ainda melhor: a.push( ...b ) ou b.unshift( ...a ).

Mas as coisas não são tão cor-de-rosa quanto parecem. Em ambos os casos, a passagem do a ou b para apply(..)(ou através do ... spread operator) significa que a matriz está sendo distribuída como argumentos para a função.

O primeiro grande problema é que estamos efetivamente dobrando o tamanho (temporariamente, é claro!) da coisa que está sendo anexada, essencialmente copiando seu conteúdo para a pilha para a chamada de função. Além disso, diferentes mecanismos JS têm diferentes limitações dependentes da implementação para o número de argumentos que podem ser passados.

Portanto, se o array que está sendo adicionado tiver um milhão de itens, é quase certo que o senhor excederá o tamanho da pilha permitida para esse push(..) ou unshift(..) chamada. Ugh. Isso funcionará muito bem para alguns milhares de elementos, mas o senhor precisa ter cuidado para não exceder um limite razoavelmente seguro.

Nota: O senhor pode tentar a mesma coisa com o splice(..), mas o senhor terá as mesmas conclusões que com push(..) / unshift(..).

Uma opção seria usar essa abordagem, mas agrupar os segmentos no tamanho máximo seguro:

function combineInto(a,b) {
    var len = a.length;
    for (var i=0; i < len; i=i+5000) {
        b.unshift.apply( b, a.slice( i, i+5000 ) );
    }
}


Espere, estamos retrocedendo em termos de legibilidade (e talvez até de desempenho!). Vamos parar antes de perdermos todos os ganhos que tivemos até agora.

Resumo

Array#concat(..) é a abordagem comprovada e verdadeira para combinar dois (ou mais!) arrays. Mas o perigo oculto é que o senhor está criando um novo array em vez de modificar um dos existentes.

Há opções que modificam no local, mas elas têm várias desvantagens.

Considerando os vários prós e contras, talvez a melhor de todas as opções (incluindo outras não mostradas) seja o reduce(..) e o reduceRight(..).

Seja qual for sua escolha, provavelmente é uma boa ideia pensar criticamente sobre sua estratégia de mesclagem de array, em vez de tomá-la como garantida.

Kyle Simpson

Sobre Kyle Simpson

Kyle Simpson é um engenheiro de software orientado para a Web, amplamente aclamado por sua série de livros “You Don’t Know JS” e por quase 1 milhão de horas vistas de seus cursos on-line. O superpoder de Kyle é fazer perguntas melhores, e ele acredita profundamente no uso máximo das ferramentas minimamente necessárias para qualquer tarefa. Como um “tecnólogo centrado no ser humano”, ele é apaixonado por unir humanos e tecnologia, desenvolvendo organizações de engenharia para resolver os problemas certos, de maneiras mais simples. Kyle sempre lutará pelas pessoas por trás dos pixels.