Understanding the JavaScript Runtime Environment and DOM Nodes
The JavaScript Engine, WebAPIs, and the Event Loop
The JavaScript engine is only one of several tools browsers use to deliver a user interface that is both dynamic and able to asynchronously interact with multiple databases and events. Other main components include WebAPIs (including the DOM), two queues, and the Event Loop.
In this article, we’ll first look at the components of the JS Runtime Environment and understand how the JS engine processes asynchronous callbacks differently from synchronous ones.
We’ll then dive deeper into the Document Object Model (DOM) and DOM Nodes, appreciating the prototypal chain of some of the most important node types.
1. The JS Runtime Environment
Download PDF of JS Runtime Environment
Each browser (e.g. Chrome, Firefox, etc) incorporates its own unique JavaScript runtime environment, but this environment has several common components:
- A. The JavaScript Engine (which includes a Call Stack and Memory Heap)
- B. WebAPIs (including the DOM and Web Storage),
- C. Two queues (Tasks and Microtasks), and
- D. The Event Loop.
A. The JavaScript Engine
The JavaScript Engine is what we typically associate with executing JS code, and accessing behavior contained in .js files. This engine has two components: a call stack and a memory heap. The engine is single-threaded, which means it can only sequentially parse code, line by line, and can thereby get blocked if it encounters an endless loop or otherwise gets into some kind of interminable subroutine.
Every new function execution context is pushed onto the Call Stack, and then popped off when returning from that function. When executing the call stack, variables like objects and function definitions are stored in the Memory Heap, which is also part of the JS Engine.
B. WebAPIs
WebAPIs (also called Browser APIs) are part of the browser, but not part of the JS engine. They provide additional functionality that we can access through the JS engine, and allow us to mimic a multi-threaded environment, particularly through the Web Wokers API, which is one of the many subsets of WebAPIs.
The JS engine may have to interact directly with the Document Object Model (the DOM), which is an object representation of the web page that the user sees. We’ll get into some more DOM details later on in this article, but the DOM is fundamental and also considered part of WebAPIs.
Another noteworthy WebAPI is the Web Storage API, which includes sessionStorage
and localStorage
. These provide more persistent ways of storing data than the JS Engine’s Memory Heap, and can be accessed via the Window API, yet another essential WebAPI.
Whenever the JS engine encounters an asynchronous callback, it sends this callback to be processed by the WebAPIs environment.
More specifically, the Web Workers API handles our asynchronous callbacks behind the scenes–including AJAX requests, timebased events, and event listeners.
C. Microtasks and Tasks Queues
The Web Workers API sends settled promises to the back of the Microtasks queue (also sometimes called the Jobs queue), and any triggered events (e.g. clicks, loads, etc) and expired setTimeout
s / setInterval
s to the back of the Tasks queue (also sometimes called the Callback or Messages queue).
D. Event Loop
Finally, the Event Loop continuously cycles, checking to see whenever there’s either 1) a break in the call stack execution, or 2) no more frames left on the call stack. In either case, the Event Loop checks to see if there are any callbacks in the Microtasks queue, and sends each one to be executed on the call stack, from front to back (first in first out, or FIFO). The Event Loop then checks for and sends any callbacks in the Tasks queue to the call stack, also FIFO.
Because microtask callbacks are pushed on the call stack before the event loop gets to task callbacks, that means that any microtask created by a microtask will also get pushed onto the call stack and executed before any tasks. This could create a situation where the call stack gets blocked by an endless loop of microtasks.
2. DOM and DOM Nodes
The DOM is an in-memory object that represents the structure and content of what gets displayed by a browser.
The DOM contains a tree of “node” objects nested at various levels (i.e. each node can contain other nodes). Developers can use a scripting language like JS to not only read data from these DOM nodes, but also update, move, add, or remove them, thereby also dynamically impacting the browser display.
Each DOM node inherits methods and properties depending on its type (i.e. the constructor that created it).
Mentally prepare yourself to visualize the prototypal inheritance chain for these DOM nodes, and some of their behaviors.
Download PDF of DOM Prototypal Chain
Yes, the figure above is both intense and intimidating. But I’ve got some good news and some bad news. The bad news is that this is just the tip of the iceberg, and there’s actually a lot more.
The good news is that you don’t need to memorize any of it–there are great docs like MDN to consult, and you probably will remember the most important methods anyway just by using them often.
But the above gives you an idea of some of the basics: the most important class is EventTarget, which inherits directly from the Object class and has three very important children: Node, Window, and XMLHttpRequestEventTarget.
A. Node
The Node class is itself the parent of the four main node types, which are: CharacterData, DocumentType, Element, and Document.
Of course, the most important of these nodes are the last two: Element is the ancestor of all HTML element types, and Document is the ancestor of the all-important document
object.
B. Window
The Window class has several useful static methods and properties, and is the constructor for the window
instance, which is the global object within the browser environment.
In normal mode, this
within the global scope refers to window
. (In strict mode, this
in the global scope is undefined
).
Any new functions or other variables created in the global scope are actually attached as properties onto this instance of Window (i.e. the lowercase window
object), including document
itself. The localStorage
and sessionStorage
mentioned previously can also be accessed from the window
object.
C. XMLHttpRequestEventTarget
The unwieldy and impractically-named XMLHttpRequestEventTarget class is the parent of XMLHttpRequest, which is the constructor for all XMLHttpRequest instances. This used to be the main way to handle AJAX requests, prior to the development of the more convenient Fetch API, which is yet another type of WebAPI.
Three main caveats
- The above diagram is is the prototypal chain of the DOM as implemented in Chrome.
- JS doesn’t technically have classes, but prototypes. By “classes” above, what I really mean is constructors, i.e. functions whose
.prototype
property point to the object that is the model for all instances created from that constructor. Check this if you need a refresher on JS’s prototypal inheritance model. - Although the prototypal chain in the diagram above is a node tree itself, it should not be confused with an HTML page’s DOM tree, where a node of one type could contain children of other node types, too.
In a specific page’s DOM tree, for example, a<div>
element node could contain (and often contains) a #text node as a child, even though every #text node is an instance ofText
, and (as seen in the diagram above)Text
is technically in the same “generation” asHTMLElement
, which is the parent ofHTMLDivElement
, which is the constructor/class from which all<div>
nodes are made.
In other words, the diagram shows the inheritance structure for each prototype’s methods and properties and is true for all HTML pages, while a DOM tree instead shows the page structure for the nodes as they currently exist on that page. They both provide hierarchies, but the former indicates behavioral inheritance and the latter indicates position on a specific page.