De `Object`-klasse in JavaScript
Dit artikel legt de Object-klasse in JavaScript uit.
Dit artikel legt de Object-klasse in JavaScript uit, inclusief praktische voorbeelden.
YouTube Video
De Object-klasse in JavaScript
Object is een ingebouwd object dat als basis dient voor alle JavaScript-objecten. Veel van de kernfuncties van de taal, zoals het beheren van eigenschappen, overerving (prototypeketen), enumeratie, klonen en bevriezen, worden mogelijk gemaakt door het gedrag van Object.
Object.create
Er zijn meerdere manieren om objecten te maken, en je dient deze passend te gebruiken afhankelijk van je doel.
Objectliteral (meest gebruikelijk)
De volgende code toont de eenvoudigste en meest leesbare manier om een object aan te maken.
1// Create an object using object literal
2const user = {
3 name: "Alice",
4 age: 30,
5 greet() {
6 return `Hello, I'm ${this.name}`;
7 }
8};
9
10console.log(user.greet()); // "Hello, I'm Alice"
- In dit voorbeeld worden eigenschappen en methoden gedefinieerd met behulp van letterlijke waarden (literals). Het is eenvoudig en biedt doorgaans betere prestaties.
new Object()-constructor
De Object-constructor wordt zelden gebruikt, maar het is nuttig om zijn gedrag te begrijpen.
1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
new Object()retourneert een leeg object, maar de literal{}is korter en gebruikelijker.
Het prototype specificeren met Object.create
Object.create wordt gebruikt om een object met een gespecificeerd prototype te maken.
1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
Object.createis ideaal voor op overerving gebaseerde objectontwerpen, waardoor je nauwkeurig controle hebt over de prototypeketen.
Eigenschap attributen en descriptors
Eigenschappen hebben attributen zoals 'value', 'writable', 'enumerable' en 'configurable', welke je in detail kunt beheren met Object.defineProperty.
Basisvoorbeeld van het gebruik van defineProperty
Hieronder volgt een voorbeeld van het definiëren van niet-listeerbare en alleen-lezen eigenschappen met behulp van defineProperty.
1// Define a non-enumerable read-only property
2const person = { name: "Carol" };
3
4Object.defineProperty(person, "id", {
5 value: 12345,
6 writable: false,
7 enumerable: false,
8 configurable: false
9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
- Met
definePropertykun je het gedrag van eigenschappen, zoals opsomming, overschrijven en verwijderen, precies bepalen.
Accessor-eigenschappen (getter / setter)
Met accessors kun je logica toevoegen aan het lezen en schrijven van eigenschappen.
1// Use getter and setter to manage internal state
2const data = {
3 _value: 1,
4 get value() {
5 return this._value;
6 },
7 set value(v) {
8 if (typeof v === "number" && v > 0) {
9 this._value = v;
10 } else {
11 throw new Error("value must be a positive number");
12 }
13 }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
- Met
getterensetterkun je eigenschaps toegang behandelen als een externe API en validatie of neveneffecten toevoegen.
Prototype en overerving (prototype / __proto__ / Object.getPrototypeOf)
Overerving in JavaScript is gebaseerd op de prototypeketen, niet op klassen. Objecten kunnen naar andere objecten verwijzen als hun prototypes.
Object.getPrototypeOf en Object.setPrototypeOf
Het volgende voorbeeld laat zien hoe je prototypes kunt bekijken en instellen.
1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
Object.getPrototypeOfhaalt het prototype van een object op.Object.setPrototypeOfwijzigt het prototype van een bestaand object, maar je moet dit voorzichtig gebruiken omdat het de prestaties kan beïnvloeden.
Belangrijke ingebouwde methoden
We leggen duidelijk de meest gebruikte en belangrijkste methoden uit, geselecteerd uit de instantiefuncties van Object.prototype, evenals de statische methoden die Object bezit.
hasOwnProperty, isPrototypeOf, toString, valueOf
hasOwnProperty, isPrototypeOf, toString en valueOf bepalen het basisgedrag van objecten.
1// Demonstrate prototype methods
2const base = { greet() { return "hello"; } };
3const child = Object.create(base);
4const a = { x: 1 };
5
6console.log(a.hasOwnProperty("x")); // true
7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
8
9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
hasOwnPropertyis een essentiële methode om te controleren of een eigenschap direct op het object zit.isPrototypeOfcontroleert of het doelobject zichzelf als prototype heeft.
Object.keys, Object.values, Object.entries
Object.keys, Object.values en Object.entries retourneren lijsten van de eigen enumerateerbare eigenschappen van een object. Deze zijn handig voor iteratie en transformatie.
1// Keys, values and entries
2const item = { id: 1, name: "Widget", price: 9.99 };
3
4// ["id", "name", "price"]
5console.log(Object.keys(item));
6
7// [1, "Widget", 9.99]
8console.log(Object.values(item));
9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));- Deze worden vaak gebruikt voor het itereren en transformeren van objecten.
Object.assign
Object.assign wordt gebruikt voor het oppervlakkig kopiëren en samenvoegen van objecten. Let op dat prototypes en accessor-eigenschappen niet worden gekopieerd.
1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
- Bij geneste objecten worden alleen de referenties gekopieerd, dus je hebt een andere implementatie nodig voor diep klonen.
Object.freeze, Object.seal, Object.preventExtensions
Object.freeze, Object.seal en Object.preventExtensions regelen de mutabiliteit van objecten.
1// Freeze vs seal vs preventExtensions
2const obj = { a: 1 };
3Object.freeze(obj);
4obj.a = 2; // fails silently or throws in strict mode
5delete obj.a; // fails
6
7const obj2 = { b: 2 };
8Object.seal(obj2);
9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
freezeis het strengst; het voorkomt dat er wijzigingen worden aangebracht aan de eigenschappen van het object.sealvoorkomt het toevoegen of verwijderen van eigenschappen, maar staat toe bestaande waarden te wijzigen.preventExtensionsvoorkomt alleen het toevoegen van nieuwe eigenschappen; bestaande eigenschappen kunnen nog steeds gewijzigd of verwijderd worden.
Object-enumeratie, volgorde en for...in / for...of
for...in enumereert de benamingen van eigenschappen, maar bevat ook eigenschappen uit de prototypeketen, daarom wordt het vaak samen met hasOwnProperty gebruikt. Het combineren van Object.keys() met for...of is veiliger en maakt je bedoelingen duidelijker.
1// Safe enumeration
2const obj = Object.create({ inherited: true });
3obj.own = 1;
4
5for (const key in obj) {
6 if (obj.hasOwnProperty(key)) {
7 console.log("own prop:", key);
8 } else {
9 console.log("inherited prop:", key);
10 }
11}
12
13for (const key of Object.keys(obj)) {
14 console.log("key via Object.keys:", key);
15}- De regels voor het enumereren van eigenschappen zijn vastgelegd in de ECMAScript-specificatie, en soms is de enumeratievolgorde gegarandeerd, soms niet. Als algemene regel worden sleutels die als nummer worden geïnterpreteerd in oplopende volgorde geplaatst, terwijl andere sleutels de invoegvolgorde volgen.
Klonen en diep kopiëren
Er zijn twee soorten objectkopieën: oppervlakkige kopieën met Object.assign of de spread-syntaxis, en diepe kopieën met recursieve methoden. Het is belangrijk om ze op de juiste manier te gebruiken, afhankelijk van de situatie.
Oppervlakkig kopiëren (spread-syntax / Object.assign)
1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
- Bij oppervlakkig kopiëren delen geneste objecten referenties, dus wijzigingen in het originele object kunnen de kopie beïnvloeden.
Eenvoudig diep kopiëren (met kanttekeningen)
Het zogenaamde JSON-trucje is een snelle manier om een diepe kopie te maken, maar heeft nadelen zoals het verliezen van functies, Date, circulaire referenties en undefined-waarden. Voor echt diep klonen moet je een gespecialiseerde bibliotheek gebruiken.
1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
- JSON-gebaseerde methoden zijn handig voor het snel verwerken van eenvoudige gegevens, maar in veelvoorkomende gevallen kan hun werking verkeerd uitpakken.
Mixins en objectcompositie
In plaats van meervoudige overerving wordt vaak het patroon van gedragssamenstelling met mixins gebruikt.
1// Simple mixin function
2const canEat = {
3 eat() { return `${this.name} eats`; }
4};
5const canWalk = {
6 walk() { return `${this.name} walks`; }
7};
8
9function createPerson(name) {
10 const person = { name };
11 return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
- Mixins zijn flexibel, maar je moet oppassen voor naamconflicten en testbaarheid.
Veelvoorkomende valkuilen en beste praktijken
Hier zijn enkele veelvoorkomende valkuilen en beste praktijken.
-
Mutabiliteit Objecten zijn standaard veranderlijk (mutable). Overweeg in applicaties met toestandsbeheer het gebruik van onveranderlijke (immutable) datastructuren via
Object.freezeof een immutable-bibliotheek. -
Prototype-vervuiling Het direct samenvoegen van externe data met een object via
Object.assignof lussen kan onverwachte bijwerkingen veroorzaken met speciale eigenschappen zoals__proto__ofconstructor, wat beveiligingsrisico's oplevert. Filter gebruikersinvoer voordat je deze direct samenvoegt. -
Valkuilen van
for...infor...inenumereert ook prototype-eigenschappen, dus controleer methasOwnProperty.Object.keysgebruiken is duidelijker. -
Misbruik van oppervlakkig kopiëren Overweeg of je diep kopiëren nodig hebt om te voorkomen dat wijzigingen in geneste objecten het originele object beïnvloeden.
Praktisch Voorbeeld: Onveranderlijk Object Updatepatroon
Patronen die een nieuw object retourneren zonder direct de toestand te wijzigen, worden vaak gebruikt in React en vergelijkbare bibliotheken.
1// Immutable update example
2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
3
4// Toggle todo done immutably
5function toggleTodo(state, todoId) {
6 return {
7 ...state,
8 todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
9 };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
- Deze code is een voorbeeld van het aanmaken van een nieuw statusobject zonder het originele
state-object direct te wijzigen. De functietoggleTodokopieert detodos-array en retourneert een nieuw object waarin alleen het doel-element is aangepast, zodat het originelestateongewijzigd blijft. - Onveranderlijke updates verminderen bijwerkingen en maken toestandsbeheer eenvoudiger.
Praktisch Voorbeeld: Veilig Samenvoegen (Let op Prototype-vervuiling)
Negeer __proto__ bij het samenvoegen van externe JSON om prototype-vervuiling te voorkomen.
1// Safe merge ignoring __proto__ keys
2function safeMerge(target, source) {
3 for (const key of Object.keys(source)) {
4 if (key === "__proto__" || key === "constructor") continue;
5 target[key] = source[key];
6 }
7 return target;
8}
9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
- Dit soort bescherming is ook belangrijk in bibliotheken en frameworks.
Prestatiefactoren
Wat prestaties betreft, houd rekening met het volgende:.
- Vermijd veelvuldige prototypewijzigingen (
Object.setPrototypeOf) of dynamisch toevoegen/verwijderen van eigenschappen, want dit belemmert optimalisaties door de engine. - Bij het genereren van veel kleine objecten wordt optimalisatie effectiever als je objecten gebruikt met een uniforme structuur (dezelfde set eigenschappen).
- Diep kopiëren is kostbaar. Beperk het gebruik ervan of overweeg diff-gebaseerde updates.
Samenvatting
Object staat centraal in JavaScript en biedt allerlei mogelijkheden zoals objectcreatie, eigenschapsbeheer, overerving, kopiëren en mutabiliteitsbeheer. Het is belangrijk om API's zoals Object.defineProperty, Object.assign en Object.freeze te begrijpen, en zorgvuldig te ontwerpen om valkuilen zoals prototypevervuiling en oppervlakkige kopieën te vermijden.
Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.