Como clonar um array no javascript

admin 0

Introdução

Com uma certa frequência podemos nos deparar com a necessidade de copiar um array no desenvolvimento de sistema, no Javascript existem muitas formas de fazer a mesma coisa e podemos ter problemas se não fizermos isso corretamente.

Para um programador um pouco menos experiente é natural pensar em usar apenas um operador de atribuição e copiar o array:

const barnAnimals = [🐓, 🐑, 🐇];
const barnAnimalsCopy = barnAnimals;

Mas existe um problema nesta abordagem.

Por que não posso usar o operador de atribuição ( = ) para copiar um array?

Arrays em javascript são valores de referência então quando tentamos copiar desta forma ele copiará apenas a referência e não copiará um array novo com os mesmo valores.

Abaixo temos um exemplo para ajudar no entendimento:

const barnAnimals = [🐓, 🐑, 🐇];
const barnAnimalsCopy = barnAnimals;

barnAnimalsCopy.push(🦊);

console.log(barnAnimalsCopy); 
// [🐓, 🐑, 🐇, 🦊]

console.log(barnAnimals);
// [🐓, 🐑, 🐇, 🦊]

No exemplo acima podemos ver o problema, quando alteramos a nossa cópia (barnAnimalsCopy) a alteração é estendida para o array de origem (barnAnimals), Sempre que modificamos um desses arrays, estará modificando ambos os arrays .

Isso acontece pois na verdade estamos copiando apenas o ponteiro para o espaço em memória que o array ocupa, os tipos de referência não contém valores, eles são um ponteiro para o valor da memória.

Arrays são tipos mutáveis ​​em Javascript! Isso significa que o valor da matriz pode ser alterado depois de declarado. Ao contrário do tipos primitivos que são imutáveis, para entender melhor o que é um tipo primitivo e o tipo referência eu recomendo a leitura destes artigos:

What are Primitive and Reference Types in JavaScript?

JavaScript Primitive vs. Reference Values

Primitivo

Agora que já entendemos o problema, vamos para as possíveis soluções.

Copiando um array

Como mencionei no início do artigo existem várias formas de copiar um array, mas antes precisamos entender que existem duas formas de copiá-los, existe a cópia superficial e a cópia profunda.

Cópia superficial

Cópia superficial copia somente o primeiro nível de um array, isso quer dizer que se estivermos trabalhando com array’s multidimensionais, somente o primeiro nível será copiado sem referência ao original, o segundo nível em diante do array ainda continuará trabalhando por referência, exemplo:

const listaMercado = [['banana', 'pera'], 'peixe', 'chocolate'];
const copiaListaMercado = [...listaMercado];

copiaListaMercado[2] = 'pão';

// Podemos ver que somente a copiaListaMercado foi alterado e não a listaMercado
console.log(listaMercado); //[ [ 'banana', 'pera' ], 'peixe', 'chocolate' ]
console.log(copiaListaMercado); //[ [ 'banana', 'pera' ], 'peixe', 'pão' ]

copiaListaMercado[0][0] = 'morango';

console.log(listaMercado); //[ [ 'morango', 'pera' ], 'peixe', 'chocolate' ]
console.log(copiaListaMercado); //[ [ 'morango', 'pera' ], 'peixe', 'pão' ]

No exemplo acima podemos ver que quando alteramos o valor do primeiro nível (linha 4), somente a cópia foi alterada, não modificando o array original.

Porém na linha 10 alteramos o array que está dentro do array, este valor está apontando para a mesma referência, por conta disso ao alteramos na constante copiaListaMercado ele também altera na constante listaMercado.

Formas de cópia superficial

Caso você esteja copiando um array unidimensional a cópia superficial pode ser uma opção, abaixo segue a lista de formas de cópia superficial:

slice()

O método array.slice() em JavaScript é usado para obter a parte de um array do índice inicial ao índice final.

const array = ['banana', 'morango', 'pera', 'melancia'];
const newArray = array.slice()

newArray[0] = 'melão'
console.log(array);
// output: ['banana', 'morango', 'pera', 'melancia']

console.log(newArray);
// output: ['melão', 'morango', 'pera', 'melancia']

concat()

O método array.concat() em JavaScript é usado para concatenar dois ou mais arrays em um.

const array = ['banana', 'morango', 'pera', 'melancia'];
const newArray = [].concat(array);

newArray[0] = 'melão'
console.log(array);
// output: ['banana', 'morango', 'pera', 'melancia']

console.log(newArray);
// output: ['melão', 'morango', 'pera', 'melancia']

from()

O método array.from() em JavaScript cria um novo array a partir do array original. Este método foi introduzido no ES6.

const array = ['banana', 'morango', 'pera', 'melancia'];
const newArray = Array.from(array);

newArray[0] = 'melão'
console.log(array);
// output: ['banana', 'morango', 'pera', 'melancia']

console.log(newArray);
// output: ['melão', 'morango', 'pera', 'melancia']

operador spread

Você pode usar o operador spread (“…”) para clonar um array em JavaScript. O operador de propagação (“…”) está disponível no JavaScript ES6

const array = ['banana', 'morango', 'pera', 'melancia'];
const newArray = [...array]

newArray[0] = 'melão'
console.log(array);
// output: ['banana', 'morango', 'pera', 'melancia']

console.log(newArray);
// output: ['melão', 'morango', 'pera', 'melancia']

map()

O método array.map() em JavaScript cria uma nova matriz preenchida com os resultados da chamada da função fornecida em cada elemento da matriz de chamada.

const array = ['banana', 'morango', 'pera', 'melancia'];
const newArray = array.map((element)=>element);

newArray[0] = 'melão'
console.log(array);
// output: ['banana', 'morango', 'pera', 'melancia']

console.log(newArray);
// output: ['melão', 'morango', 'pera', 'melancia']

for

const array = ['banana', 'morango', 'pera', 'melancia'];

let newArray = [];
for (i = 0; i < array.length; i++) {
  newArray[i] = array[i];
}
newArray[0] = 'melão'
console.log(array);
// output: ['banana', 'morango', 'pera', 'melancia']

console.log(newArray);
// output: ['melão', 'morango', 'pera', 'melancia']

while

const array = ['banana', 'morango', 'pera', 'melancia'];
let newArray = [];
let i = -1;

while (++i < array.length) {
  newArray[i] = array[i];
}
newArray[0] = 'melão'
console.log(array);
// output: ['banana', 'morango', 'pera', 'melancia']

console.log(newArray);
// output: ['melão', 'morango', 'pera', 'melancia']

Cópia Profunda

Caso você esteja copiando um array multidimensional a cópia profunda deve ser utilizada, abaixo segue a lista de forma de cópia profunda:

structuredClone()

Para criar um clone profundo de uma matriz em JavaScript, você pode usar o método structuredClone(), que cria um clone profundo de uma matriz usando o algoritmo de clone estruturado. O método structuredClone() é relativamente novo e está disponível no Chrome 98+, Firefox 94+ e Node.js 17+.

const listaMercado = [['banana', 'pera'], 'peixe', 'chocolate'];
const copiaListaMercado = structuredClone(listaMercado);

copiaListaMercado[0][0] = 'morango';

console.log(listaMercado); 
// output: [ [ 'banana', 'pera' ], 'peixe', 'chocolate' ]
console.log(copiaListaMercado); 
// output: [ [ 'morango', 'pera' ], 'peixe', 'pão' ]

JSON.stringify() e JSON.parse()

O método mais popular para matrizes de cópia profunda antes de estruturadoClone() era converter a matriz em uma string JSON e, em seguida, converter a string JSON de volta em uma matriz. A principal desvantagem desse método é seu baixo desempenho e a limitação de trabalhar apenas com conteúdo serializável em JSON.

const listaMercado = [['banana', 'pera'], 'peixe', 'chocolate'];
const copiaListaMercado = JSON.parse(JSON.stringify(listaMercado));

copiaListaMercado[0][0] = 'morango';

console.log(listaMercado); 
// output: [ [ 'banana', 'pera' ], 'peixe', 'chocolate' ]
console.log(copiaListaMercado); 
// output: [ [ 'morango', 'pera' ], 'peixe', 'pão' ]

Porém esta forma pode ser problemática, o JSON.stringify() penas converte um valor JavaScript em uma string JSON. Não há problema em usá-lo com tipos primitivos como Numbers , Strings ou Booleans, mas tipos undefined, Symbol e function podemos ter problemas.

JSON.stringify({ key: undefined });
JSON.stringify({ key: Symbol() });
JSON.stringify({ key: function(){} });
// tudo isso será convertido para "{}"

cloneDeep com lodash

Ao contrário do JSON.stringify()/JSON.parse() o deepClone trabalha com todos os tipos, funções e símbolos sem problemas.

import _ from 'lodash.min.js';

const listaMercado = [['banana', 'pera'], 'peixe', 'chocolate', ()=>{console.log('oi')}];
const copiaListaMercado = _.cloneDeep((listaMercado));

copiaListaMercado[0][0] = 'morango';

console.log(listaMercado); 
// output: [ [ 'banana', 'pera' ], 'peixe', 'chocolate' ]
console.log(copiaListaMercado); 
// output: [ [ 'morango', 'pera' ], 'peixe', 'pão' ]
copiaListaMercado[3]()
// oi

Performance

Após ler tudo isso você deve estar se pergunta, ok, mas qual escolher?
Antes de tomar esta decisão, vamos dar uma olhada em como cada uma destas opções performa.

Para podermos tomar sua decisão final, analisar a performance pode ser um critério importante.

Montei um js benchmark no site http://jsben.ch/, não sei o quanto os teste neste site são confiáveis, pois ele apresenta resultados diferentes com os mesmos dados, porém em meus testes na média o slice se saiu melhor, abaixo segue a média de posição de cada função.

Para ver o desempenho de cada uma delas recomendo você acessar e executar os testes.

Qual escolher?

Para cópias simples e rasas eu escolheria o slice, pois sua performance me pareceu mais consistente que os outros, porém também consideraria o uso do spread e do structuredClone pela legibilidade, o que tornaria mais intuitiva a leitura do código.

Para Cópias simples e profundas, em questão de performance eu escolheria o JSON.stringify() e JSON.parse(), porém pela legibilidade não descartaria o uso do structuredClone, e caso o lodash já estivesse incluso no projeto, utilizaria o cloneDeep dele.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *