Objecten zijn de fundamentele blokken van JavaScript. Een object is een verzameling eigenschappen, en een eigenschap is een associatie tussen een sleutel (of naam) en een waarde. Bijna alle objecten in JavaScript zijn instanties van Object
, dat bovenaan de prototype-keten staat.
Inleiding
Zoals je weet, maakt de toewijzingsoperator geen kopie van een object, het wijst er alleen een verwijzing naar toe, laten we eens kijken naar de volgende code:
let obj = { a: 1, b: 2,};let copy = obj;obj.a = 5;console.log(copy.a);// Result // a = 5;
De obj
variabele is een container voor het nieuwe object dat geïnitialiseerd is. De copy
variabele wijst naar hetzelfde object en is een verwijzing naar dat object. Dus eigenlijk zegt dit { a: 1, b: 2, }
object: Er zijn nu twee manieren om toegang tot mij te krijgen. Je moet door de obj
variabele of de copy
variabele heen hoe dan ook je komt nog steeds bij mij en alles wat je via deze manieren (gateways) met mij doet zal mij beïnvloeden.
Immuteerbaarheid wordt tegenwoordig veel over gesproken en je moet naar deze oproep luisteren! Deze methode verwijdert elke vorm van onveranderlijkheid en zou tot bugs kunnen leiden als het originele object door een ander deel van je code wordt gebruikt.
De naïeve manier van objecten kopiëren
De naïeve manier van objecten kopiëren is door het originele object te lussen en elke eigenschap de een na de ander te kopiëren. Laten we eens naar deze code kijken:
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));
Inherente problemen
-
objCopy
object heeft een nieuweObject.prototype
methode die verschilt van demainObj
object prototype methode, en dat is niet wat we willen. We willen een exacte kopie van het originele object. - Eigenschapdescriptors worden niet gekopieerd. Een “writable” descriptor met waarde false wordt true in het
objCopy
object. - De bovenstaande code kopieert alleen enumerable properties van
mainObj
. - Als een van de properties in het originele object zelf een object is, dan wordt het gedeeld tussen de kopie en het origineel waardoor hun respectievelijke properties naar hetzelfde object wijzen.
Slim kopiëren van objecten
Een object wordt ondiep gekopieerd als de top-level bron-eigenschappen worden gekopieerd zonder enige verwijzing en er een bron-eigenschap bestaat waarvan de waarde een object is en wordt gekopieerd als een verwijzing. Als de bronwaarde een verwijzing naar een object is, wordt alleen die referentiewaarde gekopieerd naar het doelobject.
Een ondiepe kopie zal de eigenschappen op het hoogste niveau dupliceren, maar het geneste object wordt gedeeld tussen het origineel(bron) en de kopie(doel).
Gebruik van de Object.assign() methode
De Object.assign() methode wordt gebruikt om de waarden van alle telbare eigen eigenschappen van een of meer bron-objecten naar een doel-object te kopiëren.
let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy);// Result - { a: 1, b: 2 }
Wel, dit doet het werk tot nu toe. We hebben een kopie gemaakt van obj
. Laten we eens kijken of onveranderlijkheid bestaat:
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 }
In de bovenstaande code, hebben we de waarde van de eigenschap 'b'
in objCopy
object veranderd in 89
en wanneer we het gewijzigde objCopy
object in de console loggen, gelden de veranderingen alleen voor objCopy
. De laatste lijn van de code controleert of het obj
object nog intact is en niet veranderd is. Dit betekent dat we met succes een kopie van het bronobject hebben gemaakt zonder verwijzingen ernaar.
Pitfall of Object.assign()
Niet zo snel! We hebben met succes een kopie gemaakt en alles lijkt goed te werken, maar weet je nog dat we het hadden over ondiep kopiëren? Laten we eens naar dit voorbeeld kijken:
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..
Waarom is obj.b.c = 30?
Wel, dat is een valkuil van Object.assign()
Object.assign
maakt alleen ondiepe kopieën. Zowel newObj.b
als obj.b
delen dezelfde referentie naar het object omdat er geen individuele kopieën werden gemaakt, maar een referentie naar het object werd gekopieerd. Elke wijziging aan een eigenschap van het object geldt voor alle verwijzingen die het object gebruiken. Hoe kunnen we dit oplossen? Lees verder… we hebben een oplossing in de volgende sectie.
Note: Eigenschappen op de prototype-keten en niet-telbare eigenschappen kunnen niet worden gekopieerd. Zie hier:
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
staat op obj’s prototype-keten en zou dus niet worden gekopieerd. -
property b
is een niet-telbare property. -
property c
heeft een enumerable property descriptor waardoor het wel telbaar is. Daarom is het gekopieerd.
Diep kopiëren van objecten
Een diepe kopie zal elk object dat het tegenkomt dupliceren. De kopie en het originele object zullen niets delen, dus zal het een kopie van het origineel zijn. Hier is de oplossing voor het probleem dat we tegenkwamen met Object.assign()
. Laten we eens kijken.
Gebruik JSON.parse(JSON.stringify(object));
Dit lost het probleem op dat we eerder hadden. Nu heeft newObj.b
een kopie en geen referentie! Dit is een manier om objecten diep te kopiëren. Hier is een voorbeeld:
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
Gelukkig genoeg kan deze methode niet worden gebruikt om door de gebruiker gedefinieerde object methoden te kopiëren. Zie hieronder.
Kopiëren van objectmethoden
Een methode is een eigenschap van een object die een functie is. In de voorbeelden tot nu toe hebben we nog geen object met een methode gekopieerd. Laten we dat nu eens proberen en de methoden die we hebben geleerd gebruiken om kopieën te maken.
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"}*/
Het resultaat laat zien dat Object.assign()
gebruikt kan worden om methoden te kopiëren, terwijl JSON.parse(JSON.stringify(obj))
niet gebruikt kan worden.
Copying Circular Objects
Circulaire objecten zijn objecten die eigenschappen hebben die naar zichzelf verwijzen. Laten we de methoden voor het kopiëren van objecten die we tot nu toe hebben geleerd gebruiken om kopieën te maken van een cirkelvormig object en kijken of het werkt.
Gebruik JSON.parse(JSON.stringify(object))
Laten we JSON.parse(JSON.stringify(object))
proberen:
// 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);
Hier is het resultaat:
JSON.parse(JSON.stringify(obj))
werkt duidelijk niet voor cirkelvormige objecten.
Gebruik Object.assign()
Laten we Object.assign()
eens proberen:
// 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);
Hier is het resultaat:
Object.assign()
werkt prima voor het ondiep kopiëren van cirkelvormige objecten, maar zou niet werken voor het diep kopiëren. Voel je vrij om de circular object tree
op je browser console te onderzoeken. Ik weet zeker dat je daar veel interessant werk zult vinden.
Gebruik spreidingselementen ( … )
ES6 heeft al rest-elementen voor array destructureringstoewijzing en spreidingselementen voor array-literalen geïmplementeerd. Kijk hier naar de implementatie van spread elementen op een array:
const array = ;const newArray = ;console.log(newArray);// Result //
De spread eigenschap voor object-literals is momenteel een Stage 3 voorstel voor ECMAScript. Spread properties in object initializers kopieert eigen enumerable properties van een bron object naar het doel object. Het onderstaande voorbeeld laat zien hoe eenvoudig het zou zijn om een object te kopiëren als het voorstel eenmaal is aanvaard.
let obj = { one: 1, two: 2,}let newObj = { ...z };// { one: 1, two: 2 }
Note: Dit zal alleen effectief zijn voor oppervlakkig kopiëren
Conclusie
Het kopiëren van objecten in JavaScript kan behoorlijk ontmoedigend zijn, vooral als je nieuw bent in JavaScript en je weg in de taal niet kent. Hopelijk heeft dit artikel je geholpen het te begrijpen en toekomstige valkuilen te vermijden die je kunt tegenkomen bij het kopiëren van objecten. Als je een bibliotheek of stuk code hebt dat een beter resultaat oplevert, voel je dan welkom om het met de gemeenschap te delen. Veel plezier met coderen