Em qualquer linguagem de programação, existem tipos de dados que os programadores descrevem para os sujeitos, a fim de continuar trabalhando e, se necessário, processá-los. O JavaScript não é uma exceção, pois possui tipos de dados primitivos (
Number
,
String
,
Boolean
,
Symbol
etc.) e de referência (
Array
,
Object
,
Function
,
Maps
,
Sets
etc.). Deve-se notar que os tipos de dados primitivos são imutáveis - seus valores não podem ser modificados, eles só podem ser substituídos por um novo valor completo, mas nos tipos de dados de referência, o oposto é verdadeiro. Por exemplo, declare variáveis do tipo
Number
e
Object
:
let num = 5; let obj = { a: 5 };
Não podemos modificar a variável
num
, podemos apenas reescrever seu valor, mas podemos modificar a variável obj:
let num = 10; let obj = { a: 5, b: 6 };
Como você pode ver, no primeiro caso, substituímos o valor da variável e no segundo, expandimos o objeto. A partir disso, concluímos que tipos de dados primitivos não podem ser expandidos e, com tipos de dados de referência, podemos fazer isso, mesmo com o modificador
const
.
O último pode ser congelado, por exemplo, usando
Object.freeze(obj)
, mas este tópico está além do escopo do artigo (links para o curioso
Object.defineProperty ,
protegendo o objeto de alterações ).
Como os tipos de dados são transmitidos para funções em JavaScript? Cada programador js provavelmente responderá facilmente a essa pergunta, mas, no entanto, digamos: tipos de dados primitivos sempre são passados para uma função apenas por valor, e os referenciados são sempre apenas por referência. E aqui com o último, em algumas situações, surgem problemas.
Vejamos um exemplo:
const arr = [0, 1, 2, 3, 4, 5]; console.log("Array: ", arr);
Nesse caso, simplesmente declaramos uma matriz de números e a exibimos no console. Agora passamos para uma função que retorna uma nova matriz, mas com a adição de algum valor no segundo argumento, ao final da nova matriz:
const arr = [0, 1, 2, 3, 4, 5]; console.log("Old array: ", arr);
Como podemos ver nas conclusões do console, não apenas a nova matriz mudou, mas também a antiga. Isso aconteceu porque na função
insertValToArr
,
insertValToArr
uma matriz a outra
const newArr = arr
e, portanto, criamos um link para uma matriz existente e, quando tentamos modificar uma nova matriz, ela se refere à área de memória da matriz antiga e, grosso modo, a altera. E como as duas matrizes se referem à mesma área de memória, elas terão o mesmo valor. Vamos mudar nossa função para que não possa alterar a matriz antiga:
const arr = [0, 1, 2, 3, 4, 5]; const newArr = insertValToArr(arr, 15); console.log("New array: ", newArr);
A matriz antiga não mudou, porque recebemos cada um de seus elementos e atribuímos individualmente os valores do elemento aos elementos da nova matriz. Agora, o último possui uma área de memória separada e, se você a alterar, isso não afetará a matriz antiga. Mas todos esses são exemplos simples e, em programas reais, é provável que não apenas sejam encontradas matrizes unidimensionais, mas também bidimensionais, menos frequentemente tridimensionais e ainda menos frequentemente quadridimensionais. Principalmente, eles são encontrados na forma de matrizes associativas (tabelas de hash). Em JavaScript, esses são objetos com mais frequência.
Vejamos os métodos padrão para copiar objetos que o JavaScript fornece - Object.assign () é usado para copiar os valores de todas as suas próprias propriedades enumeradas de um ou mais objetos de origem para o objeto de destino. Após a cópia, ele retorna o objeto de destino. Considere-o:
const obj = { a: 1 }; const newObj = Object.assign({}, obj); console.log(newObj);
E, novamente, o antigo problema, nos referimos à mesma área de memória, que leva à modificação de dois objetos de uma só vez - mudar um mudará o outro. O que fazer se precisarmos obter uma cópia de um objeto complexo (com várias ramificações) e, ao mesmo tempo, alterar o objeto, não modificar outro? Responder a essa pergunta é o objetivo deste artigo. Além disso, consideraremos como escrever nosso próprio método para clonagem profunda (cópia) de objetos de qualquer ramificação. Vamos ao código.
Etapa 1: declarar e inicializar o objeto
Z
e também fazer a saída do console para comparação antes e depois da clonagem:
const Z = { a: 5, b: { g: 8, y: 9, t: { q: 48 } }, x: 47, l: { f: 85, p: { u: 89, m: 7 }, s: 71 }, r: { h: 9, a: 'test', s: 'test2' } }; console.log('Z object before cloning: ', Z);

Etapa 2: atribua o objeto
Z
ao objeto
refToZ
para mostrar a diferença entre a atribuição normal e a clonagem profunda:
const refToZ = Z;
Etapa 3: atribua o objeto
Z
objeto
Y
usando a função
deepClone
e adicione uma nova propriedade ao objeto
Y
Depois disso, exiba esses dois objetos no console:
const Y = deepClone(Z); function deepClone(obj) { const clObj = {}; for(const i in obj) { if (obj[i] instanceof Object) { clObj[i] = deepClone(obj[i]); continue; } clObj[i] = obj[i]; } return clObj; } Y.addnlProp = { fd: 45 }; console.log('Z object after cloning: ', Z); console.log('Y object: ', Y);


No console, vemos claramente que, alterando o objeto
Y
, adicionando uma nova propriedade, não
addnlProp
o objeto
Z
e o último não terá a propriedade
addnlProp
em seu corpo.
Etapa 4: altere a propriedade
x
, que está no corpo dos objetos
Z
e
Y
e
Y
novamente, exiba os dois objetos no console:
Yx = 76; console.log('Y object: ', Y); console.log('Z object: ', Z);


Alterando a mesma propriedade no objeto
Y
, não afetamos a propriedade no corpo
Z
Etapa 5: na última etapa, para comparação, simplesmente adicionamos a propriedade
addToZ
com o valor 100 ao objeto
refToZ
e exibimos os três objetos no console:
refToZ.addToZ = 100; console.log('refToZ object: ', refToZ); console.log('Z object: ', Z); console.log('Y object: ', Y);



Alterando o objeto
refToZ
também alteramos
Z
, mas
Y
não
Y
afetado. A partir disso, concluímos que nossa função cria um novo objeto independente com propriedades e seus valores a partir de um objeto existente (o código para implementar a função
deepClone
pode ser encontrado no
CodePen ).
Vamos nos concentrar na implementação desta função. O último encontra qualquer aninhamento do objeto, mesmo sem saber. Como ela faz isso? O fato é que, neste caso, usamos o conhecido algoritmo para gráficos - pesquisa em profundidade. Um objeto é um gráfico que possui uma ou várias ramificações, que por sua vez podem ter suas ramificações etc. Para encontrarmos tudo, precisamos entrar em cada ramo e avançar em sua profundidade, para que possamos encontrar todos os nós no gráfico e obter seus valores. A pesquisa profunda pode ser implementada de duas maneiras: recursão e uso de um loop. O segundo pode ser mais rápido, uma vez que não preencherá a pilha de chamadas, o que, por sua vez, faz a recursão. Em nossa implementação da função
deepClone
usamos uma combinação de recursão com um loop. Se você quiser ler livros sobre algoritmos, recomendo que você inicie o Aditya Bhargava “Grokayem algoritmos” ou um Thomas Kormen “Algoritmos: construção e análise” mais aprofundados.
Para resumir, recordamos os tipos de dados em JavaScript e como eles são transmitidos para funções. Considerado um exemplo simples de clonagem independente de uma matriz unidimensional simples. Consideramos uma das implementações de linguagem padrão para copiar objetos e, como resultado, escrevemos uma função pequena (em tamanho) para clonagem profunda independente de objetos complexos. Uma função semelhante pode encontrar sua aplicação no lado do servidor (Nó js), o que é mais provável, assim como no cliente. Espero que este artigo tenha sido útil para você. Vejo você de novo.