Objectos são os blocos fundamentais do JavaScript. Um objecto é uma colecção de propriedades, e uma propriedade é uma associação entre uma chave (ou nome) e um valor. Quase todos os objectos em JavaScript são instâncias de Object
que se situa no topo da cadeia do protótipo.
Introdução
Como sabe, o operador de atribuição não cria uma cópia de um objecto, apenas lhe atribui uma referência, vejamos o seguinte código:
let obj = { a: 1, b: 2,};let copy = obj;obj.a = 5;console.log(copy.a);// Result // a = 5;
O obj
variável é um recipiente para o novo objecto inicializado. A variável copy
está a apontar para o mesmo objecto e é uma referência a esse objecto. Assim, basicamente este { a: 1, b: 2, }
objecto está a dizer: Há agora duas maneiras de ter acesso a mim. Tem de passar pela variável obj
ou pela variável copy
de uma ou de outra forma que ainda me atinge e tudo o que me fizer através destas vias (gateways) afectar-me-á.
A imutabilidade é amplamente falada nos dias de hoje e tem de ouvir esta chamada! Este método remove qualquer forma de imutabilidade e pode levar a bugs caso o objecto original seja usado por outra parte do seu código.
A forma ingénua de copiar objectos
A forma ingénua de copiar objectos é fazer looping através do objecto original e copiar cada propriedade uma após a outra. Vamos dar uma olhada neste código:
function copy(mainObj) { let objCopy = {}; // objCopy will store a copy of the mainObj let key; for (key in mainObj) { objCopy = mainObj; // copies each property to the objCopy object } return objCopy;}const mainObj = { a: 2, b: 5, c: { x: 7, y: 4, },}console.log(copy(mainObj));
Questões Inerentes
-
objCopy
objecto tem um novoObject.prototype
método diferente domainObj
método de protótipo de objecto, que não é o que queremos. Queremos uma cópia exacta do objecto original. - Os descritores de propriedade não são copiados. Um descritor “gravável” com valor definido para ser falso será verdadeiro no
objCopy
object. - O código acima apenas copia propriedades enumeradas de
mainObj
. - Se uma das propriedades do objecto original for um objecto em si, então será partilhada entre a cópia e o original fazendo com que as suas respectivas propriedades apontem para o mesmo objecto.
Objectos de Cópia Rápida
Diz-se que um objecto é copiado pouco profundo quando as propriedades de nível superior da fonte são copiadas sem qualquer referência e existe uma propriedade de origem cujo valor é um objecto e é copiado como referência. Se o valor da fonte for uma referência a um objecto, apenas copia esse valor de referência para o objecto de destino.
Uma cópia rasa duplicará as propriedades de nível superior, mas o objecto aninhado é partilhado entre o original (fonte) e a cópia (alvo).
Using Object.assign() method
O método Object.assign() é usado para copiar os valores de todas as propriedades próprias enumeradas de um ou mais objectos-fonte para um objecto-alvo.
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy);// Result - { a: 1, b: 2 }
Bem, isto faz o trabalho até agora. Fizemos uma cópia de obj
. Vamos ver se a imutabilidade existe:
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy); // result - { a: 1, b: 2 }objCopy.b = 89;console.log(objCopy); // result - { a: 1, b: 89 }console.log(obj); // result - { a: 1, b: 2 }
No código acima, alterámos o valor da propriedade 'b'
em objCopy
objecto para 89
e quando registamos o objecto modificado objCopy
na consola, as alterações aplicam-se apenas a objCopy
. A última linha de código verifica que o objecto obj
ainda está intacto e não mudou. Isto implica que criámos com sucesso uma cópia do objecto fonte sem quaisquer referências a ele.
Pitfall of Object.assign()
Não tão depressa! Embora tenhamos criado com sucesso uma cópia e tudo pareça estar a funcionar bem, lembram-se que discutimos a cópia superficial? Vejamos este exemplo:
let obj = { a: 1, b: { c: 2, },}let newObj = Object.assign({}, obj);console.log(newObj); // { a: 1, b: { c: 2} }obj.a = 10;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 1, b: { c: 2} }newObj.a = 20;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 20, b: { c: 2} }newObj.b.c = 30;console.log(obj); // { a: 10, b: { c: 30} }console.log(newObj); // { a: 20, b: { c: 30} }// Note: newObj.b.c = 30; Read why..
Porquê obj.b.c = 30?
p>Bem, isso é uma armadilha de Object.assign()
Object.assign
só faz cópias rasas. Ambos newObj.b
e obj.b
partilham a mesma referência ao objecto devido a não terem sido feitas cópias individuais, em vez disso foi copiada uma referência ao objecto. Qualquer alteração feita a qualquer propriedade do objecto aplica-se a todas as referências que utilizem o objecto. Como podemos corrigir isto? Continue a ler… temos uma correcção na secção seguinte.
Nota: As propriedades na cadeia de protótipos e as propriedades não utilizáveis não podem ser copiadas. Ver aqui:
let someObj = { a: 2,}let obj = Object.create(someObj, { b: { value: 2, }, c: { value: 3, enumerable: true, },});let objCopy = Object.assign({}, obj);console.log(objCopy); // { c: 3 }
-
someObj
está na cadeia de protótipos do objecto para que não possa ser copiado. -
property b
é uma propriedade não enumerável. -
property c
tem um descritor de propriedade enumerável que permite que seja enumerável. É por isso que foi copiado.
Deep Copying Objects
Uma cópia profunda irá duplicar cada objecto que encontrar. A cópia e o objecto original não partilharão nada, por isso será uma cópia do original. Aqui está a solução para o problema que encontramos usando Object.assign()
. Vamos explorar.
Usando JSON.parse(JSON.stringify(object));
Isto resolve o problema que tínhamos anteriormente. Agora newObj.b
tem uma cópia e não uma referência! Esta é uma forma de copiar objectos em profundidade. Aqui está um exemplo:
let obj = { a: 1, b: { c: 2, },}let newObj = JSON.parse(JSON.stringify(obj));obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } } (New Object Intact!)
Immutable: ✓
Pitfall
Felizmente, este método não pode ser usado para copiar métodos de objectos definidos pelo utilizador. Ver abaixo.
Cópia de métodos de objectos
Um método é uma propriedade de um objecto que é uma função. Nos exemplos até agora, ainda não copiámos um objecto com um método. Vamos tentar isso agora e usar os métodos que aprendemos a fazer cópias.
let obj = { name: 'scotch.io', exec: function exec() { return true; },}let method1 = Object.assign({}, obj);let method2 = JSON.parse(JSON.stringify(obj));console.log(method1); //Object.assign({}, obj)/* result{ exec: function exec() { return true; }, name: "scotch.io"}*/console.log(method2); // JSON.parse(JSON.stringify(obj))/* result{ name: "scotch.io"}*/
O resultado mostra que Object.assign()
pode ser usado para copiar métodos enquanto JSON.parse(JSON.stringify(obj))
não pode ser usado.
Copiar objectos circulares
Objectos circulares são objectos que têm propriedades que se referem a si próprios. Vamos usar os métodos de copiar objectos que aprendemos até agora para fazer cópias de um objecto circular e ver se funciona.
Utilizar JSON.parse(JSON).stringify(object))
Tentemos JSON.parse(JSON.stringify(object))
:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj = JSON.parse(JSON.stringify(obj));console.log(newObj);
Aqui está o resultado:
JSON.parse(JSON.stringify(obj))
claramente não funciona para objectos circulares.
Using Object.assign()
P>Tentemos Object.assign()
:
// circular objectlet obj = { a: 'a', b: { c: 'c', d: 'd', },}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj2 = Object.assign({}, obj);console.log(newObj2);
Aqui está o resultado:
Object.assign()
funciona bem para copiar objectos circulares pouco profundos mas não funcionaria para uma cópia profunda. Sinta-se à vontade para explorar o circular object tree
na consola do seu navegador. Tenho a certeza de que encontrará aí um monte de trabalho interessante.
Using Spread Elements ( … )
ES6 já tem elementos de descanso para atribuição de desestruturação de array e elementos de spread para array literalmente implementados. Dê uma vista de olhos à implementação de elementos de propagação num array aqui:
const array = ;const newArray = ;console.log(newArray);// Result //
A propriedade de propagação para objectos literais é actualmente uma proposta de Fase 3 para ECMAScript. As propriedades de propagação nos inicializadores de objectos copiam as próprias propriedades enumeráveis de um objecto fonte para o objecto alvo. O exemplo abaixo mostra como seria fácil copiar um objecto uma vez a proposta aceite.
let obj = { one: 1, two: 2,}let newObj = { ...z };// { one: 1, two: 2 }
Nota: Isto só será eficaz para cópia rasa
Conclusão
Copiar objectos em JavaScript pode ser bastante assustador especialmente se for novo no JavaScript e não souber o seu caminho em torno da linguagem. Espero que este artigo o tenha ajudado a compreender e evitar futuras armadilhas que poderá encontrar ao copiar objectos. Se tiver alguma biblioteca ou peça de código que alcance um melhor resultado, sinta-se bem-vindo a partilhar com a comunidade. Feliz codificação!