bitsofcode
Ik heb onlangs geschreven over wat het DOM en het schaduw DOM precies zijn en hoe ze verschillen. Kort samengevat is het Document Object Model een op objecten gebaseerde weergave van een HTML-document en een interface om dat object te manipuleren. Het schaduw DOM kan worden gezien als een “lite” versie van het DOM. Het is ook een object-gebaseerde representatie van HTML elementen, maar niet van een volledig standalone document. In plaats daarvan stelt het schaduw DOM ons in staat om ons DOM op te splitsen in kleinere, ingekapselde stukjes die over HTML-documenten heen kunnen worden gebruikt.
Een andere soortgelijke term die je misschien bent tegengekomen is het “virtuele DOM”. Hoewel het concept al enkele jaren bestaat, is het populairder geworden door het gebruik ervan in het React-framework. In dit artikel zal ik precies behandelen wat het virtuele DOM is, hoe het verschilt van het oorspronkelijke DOM, en hoe het wordt gebruikt.
Waarom hebben we een virtueel DOM nodig?
Om te begrijpen waarom het concept van het virtuele DOM is ontstaan, laten we het oorspronkelijke DOM nog eens bekijken. Zoals ik al zei, bestaat het DOM uit twee delen – de object-gebaseerde representatie van het HTML-document en de API om dat object te manipuleren.
Nemen we bijvoorbeeld dit eenvoudige HTML-document met een ongeordende lijst en één lijst-item.
<!doctype html><html lang="en"> <head></head> <body> <ul class="list"> <li class="list__item">List item</li> </ul> </body></html>
Dit document kan worden weergegeven als de volgende DOM-boom:
- html
- head lang=”en”
- body
- ul class=”list”
- li class=”list__item”
- “List item”
- li class=”list__item”
- ul class=”list”
Laten we zeggen dat we de inhoud van het eerste lijst item willen wijzigen in "List item one"
en ook een tweede lijst item willen toevoegen. Om dit te doen, moeten we de DOM API’s gebruiken om de elementen te vinden die we willen bijwerken, de nieuwe elementen te maken, attributen en inhoud toe te voegen, en tenslotte de DOM-elementen zelf bij te werken.
const listItemOne = document.getElementsByClassName("list__item");listItemOne.textContent = "List item one";const list = document.getElementsByClassName("list");const listItemTwo = document.createElement("li");listItemTwo.classList.add("list__item");listItemTwo.textContent = "List item two";list.appendChild(listItemTwo);
Het DOM was hier niet voor gemaakt…
Toen de eerste specificatie voor het DOM in 1998 werd vrijgegeven, bouwden en beheerden we webpagina’s op heel andere wijze. Er was veel minder vertrouwen in de DOM API’s om de pagina-inhoud zo frequent te maken en bij te werken als we vandaag doen.
Eenvoudige methoden zoals document.getElementsByClassName()
zijn prima te gebruiken op kleine schaal, maar als we meerdere elementen op een pagina om de paar seconden bijwerken, kan het echt duur worden om constant het DOM te bevragen en bij te werken.
Even verder, vanwege de manier waarop de API’s zijn opgezet, is het meestal eenvoudiger om duurdere operaties uit te voeren waarbij we grotere delen van het document bijwerken dan om de specifieke elementen te vinden en bij te werken. Terugkomend op ons lijst voorbeeld, is het in sommige opzichten eenvoudiger om de hele ongeordende lijst te vervangen door een nieuwe dan om de specifieke elementen te wijzigen.
const list = document.getElementsByClassName("list");list.innerHTML = `<li class="list__item">List item one</li><li class="list__item">List item two</li>`;
In dit specifieke voorbeeld is het verschil in prestatie tussen de methoden waarschijnlijk onbeduidend. Maar als de webpagina groter wordt, wordt het belangrijker om alleen te selecteren en bij te werken wat nodig is.
… maar het virtuele DOM was er wel!
Het virtuele DOM is gemaakt om deze problemen van het vaak moeten bijwerken van het DOM op een meer performante manier op te lossen. In tegenstelling tot het DOM of het schaduw DOM, is het virtuele DOM geen officiële specificatie, maar eerder een nieuwe methode om te interfacen met het DOM.
Een virtueel DOM kan worden gezien als een kopie van het oorspronkelijke DOM. Deze kopie kan vaak worden gemanipuleerd en bijgewerkt, zonder gebruik te maken van de DOM API’s. Zodra alle updates aan het virtuele DOM zijn uitgevoerd, kunnen we bekijken welke specifieke wijzigingen in het oorspronkelijke DOM moeten worden aangebracht en deze doelgericht en geoptimaliseerd uitvoeren.
Hoe ziet een virtueel DOM eruit?
De naam “virtueel DOM” heeft de neiging om het mysterie van wat het concept eigenlijk is, nog groter te maken. In feite is een virtueel DOM gewoon een normaal Javascript object.
Laten we nog eens kijken naar de DOM boom die we eerder hebben gemaakt:
- html
- head lang=”en”
- body
- ul class=”list”
- li class=”list__item”
- “Lijst item”
- li class=”list__item”
- ul class=”list”
Deze boom kan ook worden weergegeven als een Javascript object.
const vdom = { tagName: "html", children: } // end ul ] } // end body ]} // end html
We kunnen dit object zien als ons virtuele DOM. Net als het originele DOM, is het een object-gebaseerde representatie van ons HTML document. Maar omdat het een gewoon Javascript-object is, kunnen we het vrijelijk en vaak manipuleren zonder het eigenlijke DOM aan te raken totdat we het nodig hebben.
In plaats van één object voor het hele object te gebruiken, is het gebruikelijker om met kleine secties van het virtuele DOM te werken. We kunnen bijvoorbeeld werken aan een list
component, die zou coresponderen met onze unordered list element.
const list = { tagName: "ul", attributes: { "class": "list" }, children: };
Onder de motorkap van het virtuele DOM
Nu we hebben gezien hoe een virtueel DOM eruit ziet, hoe werkt het om de prestaties en bruikbaarheid problemen van het DOM op te lossen?
Zoals ik al zei, kunnen we het virtuele DOM gebruiken om de specifieke veranderingen die in het DOM moeten worden aangebracht eruit te pikken en alleen die specifieke updates uit te voeren. Laten we teruggaan naar ons ongeordende lijst voorbeeld en dezelfde wijzigingen maken die we deden met behulp van de DOM API.
Het eerste wat we zouden doen is een kopie maken van de virtuele DOM, met daarin de wijzigingen die we willen maken. Omdat we de DOM API’s niet hoeven te gebruiken, kunnen we eigenlijk gewoon een nieuw object maken.
const copy = { tagName: "ul", attributes: { "class": "list" }, children: };
Deze copy
wordt gebruikt om een zogenaamde “diff” te maken tussen de oorspronkelijke virtuele DOM, in dit geval de list
, en de bijgewerkte. Een diff zou er ongeveer zo uit kunnen zien:
const diffs =
Deze diff geeft instructies voor het bijwerken van de eigenlijke DOM. Zodra alle diffs zijn verzameld, kunnen we batchgewijs wijzigingen aanbrengen in de DOM, door alleen de updates te maken die nodig zijn.
Bijvoorbeeld kunnen we door elke diff lopen en een nieuw kind toevoegen of een oud kind bijwerken, afhankelijk van wat de diff specificeert.
const domElement = document.getElementsByClassName("list");diffs.forEach((diff) => { const newElement = document.createElement(diff.newNode.tagName); /* Add attributes ... */ if (diff.oldNode) { // If there is an old version, replace it with the new version domElement.replaceChild(diff.newNode, diff.index); } else { // If no old version exists, create a new node domElement.appendChild(diff.newNode); }})
Merk op dat dit een sterk vereenvoudigde en uitgeklede versie is van hoe een virtuele DOM zou kunnen werken en dat er veel gevallen zijn die ik hier niet heb behandeld.
De virtuele DOM en frameworks
Het is gebruikelijker om met de virtuele DOM te werken via een framework, in plaats van er direct mee te interfacen zoals ik in het voorbeeld hierboven heb laten zien.
Frameworks zoals React en Vue gebruiken het virtuele DOM-concept om meer performante updates van de DOM te maken. Bijvoorbeeld, onze list
component kan op de volgende manier in React worden geschreven.
import React from 'react';import ReactDOM from 'react-dom';const list = React.createElement("ul", { className: "list" }, React.createElement("li", { className: "list__item" }, "List item"));ReactDOM.render(list, document.body);
Als we onze lijst willen bijwerken, kunnen we gewoon de hele lijsttemplate herschrijven, en ReactDOM.render()
opnieuw aanroepen, waarbij we de nieuwe lijst doorgeven.
const newList = React.createElement("ul", { className: "list" }, React.createElement("li", { className: "list__item" }, "List item one"), React.createElement("li", { className: "list__item" }, "List item two"););setTimeout(() => ReactDOM.render(newList, document.body), 5000);
Omdat React de virtuele DOM gebruikt, ook al herrenderen we de hele template, alleen de delen die daadwerkelijk veranderen worden bijgewerkt. Als we naar onze ontwikkelaarstools kijken wanneer de wijziging plaatsvindt, zien we de specifieke elementen en de specifieke delen van de elementen die veranderen.
Het DOM vs het virtuele DOM
Om samen te vatten, het virtuele DOM is een hulpmiddel dat ons in staat stelt om te interfacen met DOM-elementen op een eenvoudigere en meer performante manier. Het is een Javascript-objectrepresentatie van het DOM, die we zo vaak als nodig kunnen wijzigen. Wijzigingen aan dit object worden dan verzameld, en wijzigingen aan het eigenlijke DOM worden gericht en minder vaak gemaakt.