Articles

bitsofcode

Jag har nyligen skrivit om vad exakt DOM och shadow DOM är och hur de skiljer sig åt. För att sammanfatta är Document Object Model en objektbaserad representation av ett HTML-dokument och ett gränssnitt för att manipulera detta objekt. Shadow DOM kan ses som en ”light”-version av DOM. Det är också en objektbaserad representation av HTML-element, men inte av ett fullständigt fristående dokument. Istället gör shadow DOM det möjligt för oss att separera vårt DOM i mindre, inkapslade bitar som kan användas i flera HTML-dokument.

En annan liknande term som du kanske har stött på är ”virtual DOM”. Även om konceptet har funnits i flera år, blev det mer populärt genom att det användes i ramverket React. I den här artikeln kommer jag att ta upp exakt vad det virtuella DOM är, hur det skiljer sig från det ursprungliga DOM och hur det används.

Varför behöver vi ett virtuellt DOM?

För att förstå varför konceptet med det virtuella DOM uppstod, ska vi återgå till det ursprungliga DOM. Som jag nämnde finns det två delar i DOM – den objektbaserade representationen av HTML-dokumentet och API:et för att manipulera detta objekt.

Till exempel, låt oss ta detta enkla HTML-dokument med en oordnad lista och ett listobjekt.

<!doctype html><html lang="en"> <head></head> <body> <ul class="list"> <li class="list__item">List item</li> </ul> </body></html>

Detta dokument kan representeras som följande DOM-träd:

  • html
    • head lang=”en”
    • body
      • ul class=”list”
        • li class=”list__item”
          • ”List item”

Vad sägs om att vi vill ändra innehållet i det första listobjektet till "List item one" och även lägga till ett andra listobjekt. För att göra detta måste vi använda DOM API:erna för att hitta de element vi vill uppdatera, skapa de nya elementen, lägga till attribut och innehåll och slutligen uppdatera själva DOM-elementen.

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);

DOM var inte gjord för detta…

När den första specifikationen för DOM släpptes 1998 byggde och hanterade vi webbsidor på väldigt olika sätt. Man var mycket mindre beroende av DOM API:erna för att skapa och uppdatera sidans innehåll så ofta som vi gör idag.

Enkla metoder som document.getElementsByClassName() går bra att använda i liten skala, men om vi uppdaterar flera element på en sida med några sekunders mellanrum kan det börja bli riktigt dyrt att hela tiden fråga och uppdatera DOM.

Även på grund av hur API:erna är uppbyggda är det oftast enklare att utföra dyrare operationer där vi uppdaterar större delar av dokumentet än att hitta och uppdatera specifika element. Om vi återgår till vårt exempel med listan är det på vissa sätt enklare att ersätta hela den oordnade listan med en ny än att ändra de specifika elementen.

const list = document.getElementsByClassName("list");list.innerHTML = `<li class="list__item">List item one</li><li class="list__item">List item two</li>`;

I just det här exemplet är prestandaskillnaden mellan metoderna förmodligen obetydlig. När webbsidan blir större blir det dock viktigare att bara välja och uppdatera det som behövs.

… men den virtuella DOM:n fanns!

Den virtuella DOM:n skapades för att lösa dessa problem med att ofta behöva uppdatera DOM:n på ett mer prestandamässigt sätt. Till skillnad från DOM eller shadow DOM är det virtuella DOM inte en officiell specifikation, utan snarare en ny metod för gränssnitt mot DOM.

Ett virtuellt DOM kan ses som en kopia av det ursprungliga DOM. Denna kopia kan ofta manipuleras och uppdateras utan att använda DOM API:erna. När alla uppdateringar har gjorts i den virtuella DOM:n kan vi titta på vilka specifika ändringar som behöver göras i den ursprungliga DOM:n och göra dem på ett målinriktat och optimerat sätt.

Hur ser en virtuell DOM ut?

Namnet ”virtuell DOM” tenderar att öka mystiken kring vad konceptet egentligen är. I själva verket är ett virtuellt DOM bara ett vanligt Javascript-objekt.

Låt oss återgå till DOM-trädet som vi skapade tidigare:

  • html
    • head lang=”en”
    • body
      • ul class=”list”
        • li class=”list__item”
          • ”List item”

Trädet kan också representeras som ett Javascript-objekt.

const vdom = { tagName: "html", children: } // end ul ] } // end body ]} // end html

Vi kan se detta objekt som vår virtuella DOM. Precis som det ursprungliga DOM är det en objektbaserad representation av vårt HTML-dokument. Men eftersom det är ett vanligt Javascript-objekt kan vi manipulera det fritt och ofta utan att röra det faktiska DOM förrän vi behöver det.

Istället för att använda ett objekt för hela objektet är det vanligare att arbeta med små delar av det virtuella DOM. Vi kan till exempel arbeta med en list-komponent, som skulle korrespondera med vårt oordnade listelement.

Under huven på det virtuella DOM

När vi nu har sett hur ett virtuellt DOM ser ut, hur fungerar det för att lösa prestandaproblemen och användbarhetsproblemen med DOM?

Som jag nämnde kan vi använda den virtuella DOM:n för att peka ut de specifika ändringar som behöver göras i DOM:n och göra dessa specifika uppdateringar ensamma. Låt oss gå tillbaka till vårt exempel med den oordnade listan och göra samma ändringar som vi gjorde med hjälp av DOM API:et.

Det första vi skulle göra är att göra en kopia av det virtuella DOM:et som innehåller de ändringar vi vill göra. Eftersom vi inte behöver använda DOM API:et kan vi faktiskt bara skapa ett nytt objekt helt och hållet.

const copy = { tagName: "ul", attributes: { "class": "list" }, children: };

Detta copy används för att skapa vad som kallas en ”diff” mellan det ursprungliga virtuella DOM:et, i det här fallet list, och det uppdaterade. En diff kan se ut ungefär så här:

const diffs = 

Denna diff innehåller instruktioner för hur man uppdaterar det faktiska DOM. När alla diffs har samlats in kan vi göra ändringar i DOM:n i batch och bara göra de uppdateringar som behövs.

Till exempel kan vi slinga oss igenom varje diff och antingen lägga till ett nytt barn eller uppdatera ett gammalt beroende på vad diff:n specificerar.

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); }})

Bemärk att det här är en väldigt förenklad och avskalad version av hur ett virtuellt DOM skulle kunna fungera och att det finns en hel del fall som jag inte har täckt här.

Den virtuella DOM:n och ramverk

Det är vanligare att arbeta med den virtuella DOM:n via ett ramverk, snarare än att interagera med den direkt som jag visade i exemplet ovan.

Ramverk som React och Vue använder konceptet virtuell DOM:n för att göra mer prestandauppdateringar till DOM:n. Vår list-komponent kan till exempel skrivas i React på följande sätt:

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);

Om vi vill uppdatera vår lista kan vi bara skriva om hela listmallen och anropa ReactDOM.render() igen och skicka in den nya listan.

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);

Eftersom React använder sig av det virtuella DOM:et uppdateras endast de delar som faktiskt förändras, även om vi återskapar hela mallen. Om vi tittar på våra utvecklarverktyg när ändringen sker kommer vi att se de specifika elementen och de specifika delarna av elementen som ändras.

The DOM vs the virtual DOM

För att sammanfatta är det virtuella DOM ett verktyg som gör det möjligt för oss att använda gränssnittet med DOM-element på ett enklare och mer prestandamässigt sätt. Det är en Javascript-objektrepresentation av DOM, som vi kan ändra så ofta som vi behöver. Ändringar som görs i det här objektet sammanställs sedan, och ändringar i den faktiska DOM:n är målinriktade och görs mer sällan.