← Terug naar tutorials

JavaScript ES6 Features: de belangrijkste vernieuwingen uitgelegd

javascriptes6ecmascript 2015let en constarrow functionsdestructuringmodulesclassespromises

JavaScript ES6 Features: de belangrijkste vernieuwingen uitgelegd

Inleiding

ES6 (ook bekend als ES2015) is een grote update van JavaScript die de taal moderner, expressiever en beter onderhoudbaar maakt. Veel patronen die vroeger omslachtig waren (zoals functies binden, string-concatenatie, prototype-gedoe, callback-hell) kregen met ES6 een duidelijkere syntaxis of betere bouwstenen.

In deze tutorial leer je de belangrijkste ES6-vernieuwingen diepgaand kennen, met praktische voorbeelden, valkuilen en echte commando’s om zelf te testen. De focus ligt op code die je vandaag nog kunt gebruiken in Node.js en moderne browsers.


Voorbereiding: snel testen met Node.js

Node-versie controleren

Open een terminal en controleer je Node.js-versie:

node -v

ES6 wordt al lang breed ondersteund, maar een recente Node-versie is prettig. Als je Node nog niet hebt:

Een klein testbestand maken en uitvoeren

Maak een bestand es6-demo.js:

mkdir es6-tutorial
cd es6-tutorial
touch es6-demo.js
node es6-demo.js

Je kunt ook de Node-REPL gebruiken:

node

1) let en const: block scope en betere intentie

Probleem met var

var is function-scoped, niet block-scoped. Dat leidt tot verrassingen:

function voorbeeldVar() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10, ook buiten het blok
}

Met ES6 krijg je let en const, die block-scoped zijn:

function voorbeeldLet() {
  if (true) {
    let x = 10;
  }
  // console.log(x); // ReferenceError: x is not defined
}

const betekent niet “immutable”

const betekent: de binding kan niet opnieuw worden toegewezen. Het object zelf kan nog steeds muteren:

const user = { naam: "Sam" };
user.naam = "Alex"; // toegestaan
// user = { naam: "Kim" }; // TypeError: Assignment to constant variable

Wil je immutabiliteit, dan kun je bijvoorbeeld Object.freeze gebruiken (met beperkingen: het is “shallow”):

const settings = Object.freeze({ theme: "dark" });
// settings.theme = "light"; // faalt stil in non-strict, of TypeError in strict

Best practice


2) Arrow functions: korter, en vooral: lexicale this

Arrow functions zijn compacter:

const som = (a, b) => a + b;
console.log(som(2, 3)); // 5

Lexicale this (belangrijkste winst)

Bij gewone functies hangt this af van hoe je de functie aanroept. Arrow functions nemen this over uit de omliggende scope.

Voorbeeld met een klassiek probleem:

function Teller() {
  this.waarde = 0;

  setInterval(function () {
    this.waarde++;
    console.log(this.waarde);
  }, 1000);
}

new Teller();

Dit werkt niet zoals je verwacht, omdat this in de callback niet naar de instance wijst.

Oplossing met arrow function:

function Teller() {
  this.waarde = 0;

  setInterval(() => {
    this.waarde++;
    console.log(this.waarde);
  }, 1000);
}

new Teller();

Valkuil: arrow functions als methods

Arrow functions zijn meestal geen goede keuze als objectmethod, omdat je vaak juist dynamische this wilt:

const obj = {
  waarde: 42,
  toon: () => {
    console.log(this.waarde);
  },
};

obj.toon(); // meestal undefined, want this is niet obj

Gebruik dan een gewone method:

const obj2 = {
  waarde: 42,
  toon() {
    console.log(this.waarde);
  },
};

obj2.toon(); // 42

3) Template literals: leesbare strings en multiline

Vroeger:

const naam = "Nora";
const zin = "Hallo " + naam + ", welkom!";

Met template literals:

const naam = "Nora";
const zin = `Hallo ${naam}, welkom!`;

Multiline is direct mogelijk:

const bericht = `
Beste ${naam},

Dit is een bericht over meerdere regels.
Groeten!
`;
console.log(bericht);

Expressies in ${...}

Je kunt elke expressie gebruiken:

const a = 5;
const b = 7;
console.log(`Som: ${a + b}`); // Som: 12

4) Destructuring: sneller waarden uit objecten en arrays halen

Object destructuring

const user = { id: 1, naam: "Aylin", rol: "admin" };
const { naam, rol } = user;
console.log(naam, rol);

Hernoemen:

const { naam: displayName } = user;
console.log(displayName);

Default values:

const { taal = "nl" } = user;
console.log(taal); // nl (want user.taal ontbreekt)

Diep destructuren:

const data = { profiel: { contact: { email: "a@b.nl" } } };
const {
  profiel: {
    contact: { email },
  },
} = data;

console.log(email);

Array destructuring

const punten = [10, 20, 30];
const [eerste, tweede] = punten;
console.log(eerste, tweede);

Overslaan en rest:

const [a, , c] = punten;
console.log(a, c); // 10 30

const [head, ...tail] = punten;
console.log(head); // 10
console.log(tail); // [20, 30]

Praktisch: destructuring in functieparameters

function printUser({ naam, rol = "user" }) {
  console.log(`${naam} (${rol})`);
}

printUser({ naam: "Bo" }); // Bo (user)

5) Default parameters: minder boilerplate

Vroeger:

function begroet(naam) {
  naam = naam || "vriend";
  return "Hallo " + naam;
}

Met ES6:

function begroet(naam = "vriend") {
  return `Hallo ${naam}`;
}

Let op: default wordt alleen gebruikt bij undefined, niet bij null:

console.log(begroet(undefined)); // Hallo vriend
console.log(begroet(null)); // Hallo null

6) Rest en spread: flexibele argumenten en kopieën

Rest parameters

function telOp(...getallen) {
  return getallen.reduce((acc, n) => acc + n, 0);
}

console.log(telOp(1, 2, 3, 4)); // 10

Rest moet als laatste parameter staan.

Spread voor arrays

Arrays samenvoegen:

const a = [1, 2];
const b = [3, 4];
const samen = [...a, ...b];
console.log(samen); // [1,2,3,4]

Kopie maken (shallow):

const origineel = [{ x: 1 }, { x: 2 }];
const kopie = [...origineel];
kopie[0].x = 99;
console.log(origineel[0].x); // 99 (shallow copy!)

Spread voor objecten

Objecten combineren:

const basis = { host: "localhost", port: 3000 };
const override = { port: 8080 };
const config = { ...basis, ...override };
console.log(config); // port is 8080

Let op: bij conflicts wint de laatste.


7) Enhanced object literals: minder herhaling, betere definities

Property shorthand

const naam = "Iris";
const leeftijd = 29;

const persoon = { naam, leeftijd };
console.log(persoon);

Method shorthand

const reken = {
  som(a, b) {
    return a + b;
  },
};

console.log(reken.som(2, 5));

Computed property names

const key = "status";
const obj = {
  [key]: "actief",
};
console.log(obj.status);

8) Classes: syntactische suiker bovenop prototypes

ES6 classes maken OOP-achtige code leesbaarder, maar onder de motorkap blijft het prototype-gebaseerd.

Basis class

class Gebruiker {
  constructor(naam) {
    this.naam = naam;
  }

  begroet() {
    return `Hallo, ik ben ${this.naam}`;
  }
}

const g = new Gebruiker("Milan");
console.log(g.begroet());

Overerving met extends en super

class Admin extends Gebruiker {
  constructor(naam, rechten) {
    super(naam);
    this.rechten = rechten;
  }

  isSuperAdmin() {
    return this.rechten.includes("root");
  }
}

const a = new Admin("Lina", ["read", "root"]);
console.log(a.begroet());
console.log(a.isSuperAdmin());

Getter en setter

class Temperatuur {
  constructor(celsius) {
    this._c = celsius;
  }

  get celsius() {
    return this._c;
  }

  set celsius(v) {
    if (typeof v !== "number") throw new TypeError("Moet een getal zijn");
    this._c = v;
  }

  get fahrenheit() {
    return this._c * 9 / 5 + 32;
  }
}

const t = new Temperatuur(20);
console.log(t.fahrenheit);
t.celsius = 25;
console.log(t.fahrenheit);

Belangrijke nuance


9) Modules: import en export (basis)

ES6 introduceert een standaardsysteem voor modules. In browsers werkt dit met <script type="module">. In Node.js werkt dit met ESM, vaak via .mjs of "type": "module" in package.json.

Node.js project met ESM opzetten

mkdir es6-modules
cd es6-modules
npm init -y

Pas package.json aan en voeg toe:

{
  "type": "module"
}

Exporteren en importeren

Maak math.js:

export function som(a, b) {
  return a + b;
}

export const PI = 3.14159;

export default function kwadraat(x) {
  return x * x;
}

Maak index.js:

import kwadraat, { som, PI } from "./math.js";

console.log(som(2, 3));
console.log(PI);
console.log(kwadraat(6));

Run:

node index.js

Named vs default exports


10) Promises: beter omgaan met async code

Vroeger deed je veel met callbacks. Promises geven structuur: een async operatie is “pending”, en wordt “fulfilled” of “rejected”.

Een Promise maken

function wacht(ms) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`Klaar na ${ms}ms`), ms);
  });
}

wacht(500).then(console.log);

Fouten afhandelen met .catch

function faal() {
  return new Promise((_, reject) => {
    reject(new Error("Er ging iets mis"));
  });
}

faal()
  .then(() => console.log("Dit gebeurt niet"))
  .catch((err) => console.error("Fout:", err.message));

Promise chaining

wacht(200)
  .then((msg) => {
    console.log(msg);
    return wacht(200);
  })
  .then((msg) => {
    console.log("Tweede:", msg);
  });

Parallel uitvoeren met Promise.all

Promise.all([wacht(200), wacht(300), wacht(100)])
  .then((resultaten) => console.log(resultaten));

Let op: Promise.all faalt meteen als één Promise reject.


11) Map en Set: betere datastructuren dan “plain objects” voor bepaalde taken

Set: unieke waarden

const s = new Set();
s.add("a");
s.add("a");
s.add("b");

console.log(s.size); // 2
console.log(s.has("a")); // true
console.log([...s]); // ["a","b"]

Praktisch: duplicaten verwijderen:

const lijst = [1, 1, 2, 3, 3, 3];
const uniek = [...new Set(lijst)];
console.log(uniek); // [1,2,3]

Map: sleutel-waarde met elke sleutelsoort

Objecten als sleutel:

const m = new Map();
const keyObj = { id: 1 };

m.set(keyObj, "waarde voor object-sleutel");
console.log(m.get(keyObj));
console.log(m.has(keyObj));

Itereren:

for (const [k, v] of m) {
  console.log(k, v);
}

Waarom Map?


12) Iterators en for...of: nette iteratie over iterables

for...of werkt met iterables zoals arrays, strings, maps, sets:

for (const ch of "test") {
  console.log(ch);
}

Met arrays:

const arr = ["x", "y", "z"];
for (const item of arr) {
  console.log(item);
}

Verschil met for...in:


13) Generators: functies die kunnen pauzeren

Generators zijn functies die je kunt pauzeren en hervatten met yield. Ze leveren een iterator op.

function* teller(max) {
  let i = 1;
  while (i <= max) {
    yield i;
    i++;
  }
}

const it = teller(3);
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }

Itereren met for...of:

for (const n of teller(5)) {
  console.log(n);
}

Wanneer nuttig?


14) Symbol: unieke property keys

Symbol maakt unieke identifiers, vaak gebruikt om property names te vermijden die botsen.

const geheim = Symbol("geheim");

const obj = {
  [geheim]: 123,
  publiek: 456,
};

console.log(obj.publiek); // 456
console.log(obj[geheim]); // 123

Symbols verschijnen niet standaard in Object.keys:

console.log(Object.keys(obj)); // ["publiek"]
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(geheim)]

15) Nieuwe string- en array-methodes (selectie)

Strings

console.log("Hallo".startsWith("Ha")); // true
console.log("Hallo".endsWith("lo")); // true
console.log("Hallo".includes("all")); // true
console.log("ha".repeat(3)); // "hahaha"

Arrays

Array.from:

const set = new Set([1, 2, 3]);
console.log(Array.from(set)); // [1,2,3]

find en findIndex:

const users = [{ id: 1 }, { id: 2 }, { id: 3 }];
const u = users.find((x) => x.id === 2);
console.log(u); // { id: 2 }

const idx = users.findIndex((x) => x.id === 3);
console.log(idx); // 2

16) Praktische mini-case: configuratie, destructuring, defaults, spread, modules

Hier combineren we meerdere ES6-features in één klein voorbeeld.

Stap 1: project opzetten

mkdir es6-mini-case
cd es6-mini-case
npm init -y

Zet ESM aan in package.json:

{
  "type": "module"
}

Stap 2: config.js

const defaultConfig = {
  host: "127.0.0.1",
  port: 3000,
  features: {
    logging: true,
    cache: false,
  },
};

export function maakConfig(overrides = {}) {
  // shallow merge voor top-level
  const merged = { ...defaultConfig, ...overrides };

  // handmatige merge voor nested object (features), ook shallow
  merged.features = { ...defaultConfig.features, ...overrides.features };

  return merged;
}

export default defaultConfig;

Stap 3: index.js

import defaultConfig, { maakConfig } from "./config.js";

const cliOverrides = {
  port: 8080,
  features: { cache: true },
};

const config = maakConfig(cliOverrides);

// destructuring met defaults
const {
  host,
  port,
  features: { logging, cache },
} = config;

console.log("Default:", defaultConfig);
console.log(`Server start op ${host}:${port}`);
console.log("logging:", logging, "cache:", cache);

Run:

node index.js

Wat je hier leert:


17) Veelgemaakte fouten en aandachtspunten

1) Shallow copies met spread

Spread ({...obj} en [...arr]) kopieert alleen de bovenste laag. Geneste objecten blijven dezelfde referentie houden. Als je “deep copy” nodig hebt, moet je bewust kiezen voor een strategie, bijvoorbeeld:

2) this en arrow functions

3) const geeft geen immutabiliteit

const beschermt tegen her-toewijzing, niet tegen mutatie. Combineer met immutabele patronen als dat je doel is.

4) Modules en padnamen

In ESM moet je in Node doorgaans de extensie meegeven:

import { som } from "./math.js";

Zonder .js werkt het vaak niet zoals je verwacht.


18) Samenvatting: wat ES6 je vooral oplevert

Met ES6 krijg je:

Als je deze features consequent toepast, schrijf je code die korter, duidelijker en robuuster is, en die beter schaalbaar blijft in grotere projecten.


Oefeningen (aanrader)

  1. Vervang in een bestaand script alle var door let/const en los scope-bugs op.
  2. Schrijf een module logger.js met een default export en minstens twee named exports.
  3. Maak een functie mergeDeep die in elk geval één niveau diep objecten merge’t (zoals bij features in de mini-case).
  4. Bouw een kleine Set-gebaseerde deduplicatie voor een lijst met e-mails en valideer met includes, startsWith, endsWith.
  5. Schrijf een generator die paginagewijs resultaten oplevert (bijvoorbeeld arrays van 10 items per yield).

Als je wilt, kun je jouw huidige code of een klein fragment delen; dan kan ik aanwijzen welke ES6-features het meest winst opleveren en hoe je ze veilig refactort.