Artigo original: How to clone an array in JavaScript

O JavaScript tem diversas maneiras de fazer qualquer coisa. Eu já escrevi sobre 10 maneiras de escrever pipes/compor em JavaScript (em inglês), e agora estamos falando sobre arrays.

1. Operador spread (cópia superficial)

Desde o surgimento do ES6, esse é o método mais popular. É uma sintaxe breve e vocês verão que isso é incrivelmente útil ao usar bibliotecas como o React e o Redux.

numbers = [1, 2, 3];
numbersCopy = [...numbers];

Observação: essa não é uma cópia de arrays multidimensionais segura. Valores de arrays/objetos são copiados por referência em vez de por valor.

Esta forma está ok

numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] e [1, 2, 3, 4]
// numbers não é alterado

Já esta não está

nestedNumbers = [[1], [2]];
numbersCopy = [...nestedNumbers];

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// Os dois foram alterados, pois compartilham as referências

2. O bom e velho laço for() (cópia superficial)

Imagino que essa abordagem é a menos popular, dado que a programação funcional agora está tão na moda em nosso círculo.

Pura ou impura, declarativa ou imperativa, ela faz o trabalho!

numbers = [1, 2, 3];
numbersCopy = [];

for (i = 0; i < numbers.length; i++) {
  numbersCopy[i] = numbers[i];
}

Observação: essa não é uma cópia segura dos arrays multidimensionais. Como se está usando o operador =, ela atribuirá objetos/arrays por referência em vez de por valor.

Esta forma está ok

numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] e [1, 2, 3, 4]
// numbers não é alterado

Já esta não está

nestedNumbers = [[1], [2]];
numbersCopy = [];

for (i = 0; i < nestedNumbers.length; i++) {
  numbersCopy[i] = nestedNumbers[i];
}

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// Ambos foram alterados por compartilharem referências

3. O bom e velho laço while() (cópia superficial)

O mesmo que ocorre com o laço for—impuro, imperativo, blá, blá, blá… funciona!

numbers = [1, 2, 3];
numbersCopy = [];
i = -1;

while (++i < numbers.length) {
  numbersCopy[i] = numbers[i];
}

Observação: Isso também atribui objetos/arrays por referência em vez de por valor.

Esta forma está ok

numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] e [1, 2, 3, 4]
// numbers não é alterado

Já esta não está

nestedNumbers = [[1], [2]];
numbersCopy = [];

i = -1;

while (++i < nestedNumbers.length) {
  numbersCopy[i] = nestedNumbers[i];
}

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// Ambos foram alterados por compartilharem referências

4. Array.map (cópia superficial)

Já de volta ao território moderno, encontramos a função map. Com raiz na matemática, map é o conceito de transformar um conjunto em outro tipo de conjunto, embora preserve a estrutura.

Em inglês, isso significa que o Array.map retorna um array do mesmo comprimento toda vez.

Para duplicar uma lista de números, use map com uma função double.

numbers = [1, 2, 3];
double = (x) => x * 2;

numbers.map(double);

E quanto à clonagem?

Verdade, este artigo tem a ver com a clonagem de arrays. Para duplicar um array, retorne o próprio elemento em sua chamada de map.

numbers = [1, 2, 3];
numbersCopy = numbers.map((x) => x);

Se você quiser ser um pouco mais matemático, (x) => x é chamada de identidade. Ela retorna o parâmetro como ele foi fornecido.

map(identity) clona uma lista.

identity = (x) => x;
numbers.map(identity);
// [1, 2, 3]

Observação: Isso também atribui objetos/arrays por referência em vez de por valor.

5. Array.filter (cópia superficial)

Esta função retorna um array, assim como map, mas não é garantido que com filter o array seja do mesmo tamanho.

E se você estiver filtrando pelos números pares?

[1, 2, 3].filter((x) => x % 2 === 0);
// [2]

O comprimento do array de entrada foi 3, mas o comprimento resultante foi 1.

Se o predicado do seu filter sempre retornar true, no entanto, você obtém uma cópia!

numbers = [1, 2, 3];
numbersCopy = numbers.filter(() => true);

Como todos os elementos passam no teste, todos são retornados.

Observação: Isso também atribui objetos/arrays por referência em vez de por valor.

6. Array.reduce (cópia superficial)

Eu quase me sinto mal de usar reduce para clonar um array, porque ele serve para coisas muito mais significantes do que isso. Mas vamos lá…

numbers = [1, 2, 3];

numbersCopy = numbers.reduce((newArray, element) => {
  newArray.push(element);

  return newArray;
}, []);

reduce transforma um valor inicial ao fazer um laço para percorrer uma lista.

Aqui, o valor inicial é um array vazio. Preencheremos o array com cada elemento ao progredir. Esse array deve ser retornado da função a ser usada na próxima iteração.

Observação: Isso também atribui objetos/arrays por referência em vez de por valor.

7. Array.slice (cópia superficial)

slice retorna uma cópia superficial de um array com base nos índices de início/fim que você fornecer.

Se quisermos os primeiros 3 elementos:

[1, 2, 3, 4, 5].slice(0, 3);
// [1, 2, 3]
// Iniciando no índice 0 e parando antes do índice 3

Se quisermos todos os elementos, simplesmente não oferecemos parâmetros

numbers = [1, 2, 3, 4, 5];
numbersCopy = numbers.slice();
// [1, 2, 3, 4, 5]

Observação: Essa é uma cópia superficial. Por isso, ela também atribui objetos/arrays por referência em vez de por valor.

8. JSON.parse e JSON.stringify (cópia profunda)

JSON.stringify transforma um objeto em uma string.

JSON.parse transforma uma string em um objeto.

Combinando-os, você pode transformar um objeto em uma string e depois inverter o processo para criar uma nova estrutura de dados.

Observação: Essa é uma cópia segura de objetos/arrays profundamente aninhados!

nestedNumbers = [[1], [2]];
numbersCopy = JSON.parse(JSON.stringify(nestedNumbers));

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);

// [[1], [2]]
// [[1, 300], [2]]
// Esses dois arrays são completamente separados!

9. Array.concat (cópia superficial)

concat combina arrays com valores ou outros arrays.

[1, 2, 3].concat(4); // [1, 2, 3, 4]
[1, 2, 3].concat([4, 5]); // [1, 2, 3, 4, 5]

Se você não fornecer nada ou um array vazio, uma cópia superficial é retornada.

[1, 2, 3].concat(); // [1, 2, 3]
[1, 2, 3].concat([]); // [1, 2, 3]

Observação: Isso também atribui objetos/arrays por referência em vez de por valor.

10. Array.from (cópia superficial)

Isso pode transformar objetos iteráveis em um array. Fornecer um array retorna uma cópia superficial.

numbers = [1, 2, 3];
numbersCopy = Array.from(numbers);
// [1, 2, 3]

Observação: Isso também atribui objetos/arrays por referência em vez de por valor.

Conclusão

E aí? Foi divertido?

Tentei mostrar clonagens de apenas 1 passo. Você encontrará mais formas se você usar diversos métodos e técnicas.