Clonagem profunda independente de objetos em JavaScript

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); // output: Array: [0, 1, 2, 3, 4, 5] 

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); // "Old array: " [0, 1, 2, 3, 4, 5] const newArr = insertValToArr(arr, 15); console.log("New array: ", newArr); // output: "New array: " [0, 1, 2, 3, 4, 5, 15] console.log("Old array: ", arr); // output: "Old array: " [0, 1, 2, 3, 4, 5, 15] function insertValToArr(arr, val) { const newArr = arr; newArr.push(val); return newArr; } 

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); // output: "New array: " [0, 1, 2, 3, 4, 5, 15] console.log("Old array: ", arr); // output: "Old array: " [0, 1, 2, 3, 4, 5] function insertValToArr(arr, val) { const newArr = []; arr.forEach((value, ind) => { newArr[ind] = value}); newArr.push(val); return 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); // output: { a: 1, b: 2 } console.log(obj); // output: { a: 1, b: 2 } 

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

imagem

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

imagem

imagem

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

imagem

imagem

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

imagem

imagem

imagem

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.

Source: https://habr.com/ru/post/pt480786/


All Articles