3. Objecten
3. Objecten
In dit hoofdstuk gaan we dieper in op objecten. Objecten zijn alomtegenwoordig in JavaScript (bijna alles is een object) en het is belangrijk om te begrijpen hoe ze werken en wat de limitaties zijn.
Wat zijn objecten?
Net zoals in Java, C#, ... onderscheiden we in JavaScript twee categorieën: primitieve types en objecten.
Primitieve types zijn dingen die een immutable waarde voorstellen op het laagste niveau van een programmeertaal. Een immutable waarde is een waarde die niet kan veranderen. Dit betekent niet dat er geen bewerkingen uitgevoerd kunnen worden op de primitieve types, maar dat elke bewerking een nieuwe waarde oplevert in plaats van de bestaande waarde te wijzigen. Zodra je een eigenschap of methode oproept op een primitief type, wordt de corresponderende object wrapper automatisch aangemaakt.
| Type | typeof resultaat | Object wrapper |
|---|---|---|
| Null | "object"[1] | Niet van toepassing |
| Undefined | "undefined" | Niet van toepassing |
| Boolean | "boolean" | Boolean |
| Number | "number" | Number |
| BigInt | "bigint" | BigInt |
| String | "string" | String |
| Symbol | "symbol" | Symbol |
Alles andere datatypes zijn objecten, inclusief functies, arrays, datums, .... Elk van deze objecten is een collectie van properties en methodes. Eigenschappen van een object worden properties genoemd, properties die een functie voorstellen worden methodes genoemd.
Objecten initialiseren
Objecten kunnen op verschillende manieren aangemaakt worden, de meest frequent gebruikte manier is een object literal.
Begrip: Object literal
Een object literal is een object dat rechtstreeks in de code gedefiniëerd wordt met behulp van accolades {}.
// Een object literal met waarden
const obj = {prop1: val1, prop2: val2, ...}
// Een object literal zonder waarden
const emptyObj = {}
// Een object met vijf properties
const person = {
firstName: "Alan",
lastName: "Turing",
birthYear: 1912,
deathYear: 1954,
}Methodes
Objecten kunnen methodes bevatten, dit zijn functies die als property van een object gedefinieerd zijn.
Begrip: Methodes in objecten
Een property van een object kan een methode zijn, dit is een functie die als eigenschap van het object gedefinieerd is.
Om gebruik te maken van andere properties binnen het object, kan de speciale variabele this gebruikt worden. Deze variabele verwijst naar de context waarbinnen de methode aangeroepen wordt, in dit geval het object zelf.
const person = {
firstName: "Alan",
lastName: "Turing",
birthYear: 1912,
deathYear: 1954,
age: function () {
return this.deathYear - this.birthYear
},
}This
Zoals hierboven vermeld, kan this gebruikt worden om te verwijzen naar de context waarin een methode aangeroepen wordt. In de meeste gevallen is het duidelijk waarnaar this verwijst, maar in sommige gevallen wordt de betekenis van this aangepast.
Beschouw onderstaand voorbeeld. Hier wordt de setTimeout methode gebruikt om de informatie pas na 1 seconde te loggen.
Omdat we een callback meegeven aan setTimeout, wordt de context van de functie aangepast en verwijst this niet langer naar het person object, maar naar de globale context (window in browsers).
const person = {
firstName: "Alan",
lastName: "Turing",
birthYear: 1912,
deathYear: 1954,
age() {
return this.deathYear - this.birthYear;
},
infoLater() {
setTimeout(function () {
console.log(`${this.firstName} lived ${this.age()} years.`);
}, 1000);
}
}
person2.infoLater()Bovenstaande code produceert dan ook volgende foutmelding:
Merk op dat de foutmelding pas verschijnt voor de tweede keer dat this aangeroepen wordt. Desondanks werkt ook this.firstName niet zoals verwacht, het resultaat is undefined. Aangezien we de voornaam gewoon uitprinten, levert dit geen foutmelding op, voor de age methode proberen we undefined als functie aan te roepen, wat uiteraard niet lukt en een foutmelding oplevert.
this en arrow functies
Om dit probleem op te lossen kunnen we gebruik maken van arrow functies.
Arrow functies hebben geen eigen betekenis voor this, in plaats daarvan erft this de betekenis van de context waarin de arrow functie gedefinieerd is.
const person = {
firstName: "Alan",
lastName: "Turing",
birthYear: 1912,
deathYear: 1954,
age() {
return this.deathYear - this.birthYear;
},
infoLater() {
setTimeout(() => console.log(`${this.firstName} lived ${this.age()} years.`), 1000);
}
}Na deze aanpassing werkt de code zoals verwacht en wordt na 1 seconde volgende output weergegeven:
Alan lived 42 years.Best practice: Arrow functies voor callbacks
Gebruik altijd arrow functies voor callbacks, tenzij je expliciet een andere context wil gebruiken.
Properties en methodes
Begrip: Property accessors
Eigenschappen en methodes van een object kunnen op twee manieren uitgelezen worden, via puntnotatie en via vierkante haken notatie.
De puntnotatie is de handigste manier om properties uit te lezen, maar werkt als je
- De naam van de property kent en deze niet dynamisch is.
- De naam van de property een geldige identifier is (geen spaties, geen speciale tekens, ...), i.e. een geldige variabele naam.
objectnaam.eigenschap;
objectnaam.methode();De vierkante haken notatie is iets omslachtiger, maar laat toe om properties dynamisch uit te lezen of om eigenschappen te gebruiken die een spatie bevatten of andere speciale tekens.
objectnaam['eigenschap met een spatie'];
const methodName = 'foo'
objectnaam[methodName]();Properties aanpassen en toevoegen
Bovenstaande accessors kunnen niet alleen gebruikt worden om properties uit te lezen, maar ook om properties aan te passen of toe te voegen. Als de property al bestaat, wordt de waarde aangepast, anders wordt de property toegevoegd aan het object.
const person = {
firstName: "Alan",
lastName: "Turing",
birthYear: 1912,
deathYear: 1954,
job: ""
}
person.job = "Mathematician"
person.age = function () {
return this.deathYear - this.birthYear
}Geneste properties
Objecten zijn zelden zo eenvoudig als in bovenstaande voorbeelden. Vaak bevatten objecten andere objecten als properties, deze worden geneste objecten genoemd. Aangezien de waarde van de property een nieuw object is, kunnen we de geneste properties op dezelfde manier benaderen als de properties van het hoofdobject, via de puntnotatie of de vierkante haken notatie.
const alan = {
name: {
first: 'Alan',
last: 'Turing',
},
life: {
birth: 1912,
death: 1954,
},
}
alan.name.first
alan['name']['first']Prototype chain
Elk object heeft een prototype, in het prototype zijn alle methodes en properties gedefinieerd die overgeërfd worden van andere objecten. Dit wordt geïmplementeerd door een prototype property in elk object die verwijst naar het bovenliggende object, dit blijft doorgaan tot de prototype property null is.

Elke object heeft een eigen prototype, Array.prototype voor arrays, Function.prototype voor functies, Object.prototype voor gewone objecten, ...
Monkey patching
Monkey patching is het toevoegen van nieuwe methodes of properties aan bestaande objecten via hun prototype. Dit is doorgaans een slecht idee omdat dit problemen kan geven met forward compatibiliteit (toekomstige versies van de taal kunnen de toegevoegde methodes of properties al gebruiken, wat kan leiden tot onverwachte resultaten) en compatibiliteit met andere libraries. Desondanks is het hier wel nuttig om prototype inheritance te demonstreren.
// Voeg een nieuwe methode toe aan alle objecten.
Object.prototype.objectName = 'UNKNOWN';
Object.prototype.debug = function() {
console.log(`-------------${this.objectName}-------------`);
console.log(this)
console.log('--------------------------------------------');
}
const alan = {
objectName: 'Alan Turing Object',
name: {
first: 'Alan',
last: 'Turing',
},
life: {
birth: 1912,
death: 1954,
},
age() {
return this.life.death - this.life.birth;
},
accomplishments: [
'Turing Machine',
'Enigma Codebreaker',
],
}
alan.debug();Deze code produceert volgende output:
-------------Alan Turing object-------------
{
objectName: "Alan Turing object",
name: {
first: "Alan",
last: "Turing",
},
life: {
birth: 1912,
death: 1954,
},
age: [Function: age],
accomplishments: [ "Turing Machine", "Enigma Codebreaker" ],
}
--------------------------------------------Dezelfde aanpak kan gebruikt worden om nieuwe methodes toe te voegen aan complexere types zoals arrays.
Array.prototype.keepEven = function() {
return this.filter(i => i % 2 === 0);
}Itereren over objecten
Zoals vermeld werd in het vorig hoofdstuk, kunnen we de for...in lus gebruiken om over de properties van een object te itereren. Deze lus itereert echter niet enkel over de eigen properties van het object, maar ook over de properties die geërfd worden via de prototype chain.
// Het bovenstaande alan object uit het monkey patching voorbeeld
for (const property in alan) {
console.log(property + ':', alan[property]);
}Als we dit gebruiken om te itereren over bovenstaand alan object, krijgen we volgend resultaat. Merk op dat zowel debug als objectName weergegeven worden, deze zijn immers toegevoegd aan het prototype van alle objecten.
firstName: Alan
name: Turing
birthYear: 1912
deathYear: 1954
age: [Function: age]
objectName: UNKNOWN
debug: [Function]Via de hasOwnProperty methode kunnen we controleren of een property tot het object zelf behoort, als de property geërfd is via de prototype chain, zal deze methode false teruggeven.
for (const property in alan) {
if (alan.hasOwnProperty(property)) {
console.log(' ' + property + ':', alan[property]);
}
}Alhoewel het bovenstaande werkt, is het beter om gebruik te maken van de ingebouwde methodes van het Object object.
Begrip: Itereren over de keys en values van een object
Via de Object.keys(), Object.values() en Object.entries() methodes kan je respectievelijk de keys, values of key-value paren van een object ophalen als een array. Deze arrays kunnen vervolgens geïtereerd worden met behulp van bijvoorbeeld een for...of lus.
const foo = {
a: 1,
b: 2,
c: 3,
}
// Itereren over de keys
for (const key of Object.keys(foo)) {
console.log(key) // 'a', 'b', 'c'
}
// Itereren over de values
for (const value of Object.values(foo)) {
console.log(value) // 1, 2, 3
}
// Itereren over de key-value paren
for (const pair of Object.entries(foo)) {
console.log(pair) // ['a', 1], ['b', 2], ['c', 3]
}Deconstructing
Deconstructing laat toe om eenvoudig properties van een object of elementen van een array toe te wijzen aan variabelen.
Objecten
Begrip: Deconstructing objects
Via deconstructing kunnen één of meer properties van een object eenvoudig toegewezen worden aan variabelen. Hierbij is
- de volgorde niet belangrijk
- de naam van de variabele moet overeenkomen met de naam van de property in het object
const alan = {
firstName: 'Alan',
name: 'Turing',
birthYear: 1912,
deathYear: 1954,
age() {
return this.deathYear - this.birthYear;
},
}
// Deconstructing van het object.
// Merk op dat de age en deathYear properties niet gedeconstructed worden.
// Merk op dat name eerste komt in de objectdeinitie, maar alse laatste in de deconstructing.
const { birthYear, firstName, name } = alan;Arrays
Begrip: Deconstructing arrays
Via deconstructing kunnen één of meer elementen van een array eenvoudig toegewezen worden aan variabelen. Hierbij is
- de volgorde wel belangrijk
- de naam van de variabele is niet belangrijk
const accomplishments = ['Turing machine', 'Enigma code breaking', 'Turing test'];
const [firstAccomplishment, , accomplishment3] = accomplishments;
const [theFirstAccomplishment] = accomplishments;Ingebouwde objecten
JavaScript bevat verschillende ingebouwde objecten die frequent gebruikt worden, hieronder tonen we een overzicht van enkele objecten die veelvuldig gebruikt worden doorheen de JavaScript lessen. Voor de specifieke documentatie van deze objecten, verwijzen we naar de MDN-documentatie.
Voorbeeldcode
Uitgewerkt lesvoorbeeld met commentaar
Omwille van de manier waarop de eerste versie van JavaScript geïmplementeerd was, geeft
typeof nullhet resultaat "object". Dit is een gekend probleem waar ooit een fix voor voorzien was (in ES5.1), maar deze fix is uiteindelijk afgewezen omdat dit leide tot heel wat compatibiliteitsproblemen met bestaande code. ↩︎