cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Bindings: how DOM objects exist in JavaScript

Learn how Web IDL turns browser objects into JavaScript wrappers with conversions, prototypes, live collections, DOMException, and legacy quirks.

By the end, you can
  • 01
    Read bindingsExplain how Web IDL exposes browser capabilities to JavaScript.
  • 02
    Predict conversionDistinguish DOMString, USVString, and sequence conversion.
  • 03
    Debug platform objectsRecognize wrappers, prototypes, live collections, and legacy exceptions.

A browser API is a binding

JavaScript can call document.createElement, read document.body, and build a URL. Those values are not invented by JavaScript alone. The browser exposes its platform objects to JavaScript through a binding layer.

A binding is the agreement that makes a browser capability appear as a JavaScript value, property, method, constructor, or exception. It includes argument conversion, object identity, prototypes, and unusual old behavior kept for compatible websites.

Definition

Web IDL is the language web specifications use to describe an API surface and how that surface maps into JavaScript. The browser implementation may use C++, Rust, or another language behind that surface.

This follows How input reaches your event handlers. Input handlers are JavaScript functions, but their event objects, targets, and document methods arrive through these bindings. Next, Realms, globals & WindowProxy explains why each global gets its own related objects.

Web IDL is the printed manual

Think of a TV remote. You press a button, but the electronics inside the TV do the work. The printed manual says what the button accepts and what it does. JavaScript is the remote; browser implementation objects are the electronics; Web IDL is the manual.

Real-life analogyThe TV remote

You can change the channel without seeing the circuits. The remote still has strict buttons and rules, so the TV knows what each request means.

In real life: A remote button
In JavaScript: A JavaScript method call
In real life: Electronics inside the TV
In JavaScript: Browser implementation objects
In real life: The printed manual
In JavaScript: A Web IDL interface definition
In real life: A button accepts one kind of input
In JavaScript: A binding converts and validates arguments

Where the analogy stops: A browser API can do much more than a remote button. The analogy only explains the boundary between JavaScript and implementation code.

An IDL interface names attributes and operations. In JavaScript, regular operations usually become methods on an interface prototype, while an interface object is usually available on the global object. The specification also states which globals expose it.

This is why API behavior is more exact than a TypeScript declaration alone. Web IDL specifies conversion before an operation runs. If conversion fails, the operation does not run. That detail explains many useful browser edge cases.

Read a small IDL declarationJavaScript
// Simplified Web IDL, not the whole DOM Standard.
interface Element {
  undefined setAttribute(DOMString name, DOMString value);
};

const box = document.createElement("div");
box.setAttribute("data-count", 5);
console.log(box.getAttribute("data-count"));

The first three lines are a deliberately simplified reading aid, not a copy of the DOM Standard. interface Element names the kind of platform object. undefined says setAttribute does its work without returning a useful value.

The two DOMString words describe conversion at the boundary. In the JavaScript call, the number 5 reaches the second parameter and becomes the string "5". The final log prints 5, because the browser stored the converted string as the attribute value.

Read IDL as a compact contract: interface name, member name, return type, then argument types. It describes what JavaScript can observe. It does not promise a particular engine class, memory address, or internal implementation language.

DOMString, USVString, and sequences

Web APIs do not merely receive arbitrary JavaScript values. Each parameter has a Web IDL type. A DOMString is a string of UTF-16 code units, while a USVString is a Unicode scalar-value string that cannot retain a lone surrogate.

A number becomes a DOMStringPop out in the code editor (opens in a new tab)JavaScript
const box = document.createElement("div");box.setAttribute("data-count", 5);console.log(typeof box.getAttribute("data-count"));

Line 1 creates a detached element. Line 2 gives setAttribute the number 5. The binding converts that value to the DOMString "5". Line 3 therefore prints string, not number.

Step through a DOMString conversion
Step 0 of 4Ready
Your turn: follow the blue line

Replay a small model of the DOMString conversion used by setAttribute.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const text = String(value);const attribute = text;console.log(typeof attribute);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
A URL uses USVString textPop out in the code editor (opens in a new tab)JavaScript
const url = new URL("https://shop.example/?q=\uD800");console.log(url.search);

Line 1 gives URL text containing one unmatched surrogate. The URL API converts that input to a USVString, replacing the invalid scalar position with U+FFFD. Line 2 prints ?q=%EF%BF%BD, the percent encoding of that replacement character.

A DOMString uses JavaScript's ordinary string conversion and can retain a lone surrogate code unit. A USVString first makes a DOMString, then replaces each lone surrogate with the replacement character. URL parsing needs scalar values, so it uses the stricter representation.

This is not a general instruction to sanitize every JavaScript string. It is one API-boundary rule. Keep the question narrow: which Web IDL type does this specific parameter name? The answer predicts the conversion before the URL algorithm begins.

A sequence accepts an iterablePop out in the code editor (opens in a new tab)JavaScript
const snacks = new Set(["tea", "cake"]);const blob = new Blob(snacks);console.log(blob.size);

Line 1 creates a Set, which is iterable. Line 2 passes it to Blob; its sequence parameter consumes the iterable into a list of blob parts. Line 3 prints 7, because tea has three UTF-8 bytes and cake has four.

An iterable becomes a copied listPop out in the code editor (opens in a new tab)JavaScript
const letters = new Set(["a", "b"]);
const copied = [...letters];
console.log(copied.join(","));

Line 1 makes a Set, whose iterator yields "a" and then "b". Line 2 spreads that iterable into a new Array. Line 3 prints a,b. A Web IDL sequence follows the same important idea: it accepts an iterable and builds a list of converted values rather than retaining the original Set object as the sequence.

Three conversion ideas
TypeMeaningSmall example
DOMStringA string of UTF-16 code unitssetAttribute turns 5 into "5".
USVStringA string of Unicode scalar valuesA lone surrogate becomes U+FFFD for URL text.
sequence<T>A copied list built from an iterableBlob accepts a Set of parts.

Wrappers and browser objects

Many browsers implement DOM data using native browser objects. JavaScript receives a platform object that stands for that browser object. Developers often call this visible JavaScript object a wrapper.

One DOM node keeps one visible identityPop out in the code editor (opens in a new tab)JavaScript
const firstBody = document.body;const secondBody = document.body;console.log(firstBody === secondBody);

Line 1 reads the body wrapper. Line 2 reads it again. Line 3 prints true. The same underlying document body is observed as the same JavaScript object, so equality and event listeners behave predictably.

This does not require you to know the browser's internal language. The Web IDL standard permits implementation choices. The observable promise is the JavaScript identity and interface behavior, not a particular C++ class layout.

The useful mental model is simple: JavaScript holds a platform object, and browser code owns the platform behavior behind it. Do not serialize a DOM node expecting to recreate its browser identity somewhere else.

A property stays on the same wrapperPop out in the code editor (opens in a new tab)JavaScript
const body = document.body;
body.lessonNote = "tea";

console.log(document.body.lessonNote);
console.log(body === document.body);

delete body.lessonNote;

Line 1 reads the body once. Line 2 adds a temporary ordinary JavaScript property to that visible object. Line 4 reads body again through document.body and prints tea. Line 5 prints true; both names still refer to the same wrapper.

The final delete keeps this small example polite to the page. More importantly, it separates two facts: the property is ordinary JavaScript state on the wrapper, while the wrapper itself represents a stable browser platform object. Browser implementations may use C++ internally, but that is not a second object you manipulate directly from JavaScript.

Step through stable wrapper identity
Step 0 of 5Ready
Your turn: follow the blue line

Replay an instrumented wrapper-identity model. It explains observable JavaScript identity, not a browser's private native layout.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
body.lessonNote = "tea";const again = document.body;console.log(again.lessonNote);console.log(body === again);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Interface objects and prototypes

An interface object is the JavaScript value named after a Web IDL interface, such as HTMLElement. It is a function object, which is why typeof HTMLElement says function.

A function is not always a usable constructorPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof HTMLElement);try {  new HTMLElement();} catch (error) {  console.log(error.name);}

Line 1 prints function. Line 3 tries to construct an HTMLElement directly. HTML elements need browser creation rules, so line 5 prints TypeError after the catch. A function object can expose an interface without accepting new.

Walk upward from bodyPop out in the code editor (opens in a new tab)JavaScript
let current = document.body;for (let step = 0; step < 5; step += 1) {  current = Object.getPrototypeOf(current);  console.log(current.constructor.name);}

Line 1 starts at the body object. Line 3 moves one prototype upward each time. Line 4 prints five constructor names: HTMLBodyElement, HTMLElement, Element, Node, and EventTarget.

Step through the body prototype chain
Step 0 of 13Ready
Your turn: follow the blue line

Step through the interface prototypes above document.body.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
let current = document.body; for (let step = 0; step < 5; step += 1) {  current = Object.getPrototypeOf(current);  names.push(current.constructor.name);} console.log(names.join(" -> "));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

That chain explains inherited methods. An element can call addEventListener because EventTarget behavior sits higher in the chain. It does not mean each body object stores a separate copy of every method.

The interface object and the interface prototype do different jobs. HTMLElement is the global interface object. Its prototype is the shared object in an element's prototype chain. Methods such as element operations live on those prototypes so the browser does not need to place a separate function on every element instance.

The constructor error is useful evidence rather than a dead end. Web IDL interface objects are function objects, but only interfaces with suitable constructor operations can make instances with new. The DOM instead makes elements through document creation methods, where the document can supply the right realm, owner document, and element-specific setup.

Legacy platform objects and live collections

Some older web objects act partly like ordinary JavaScript objects and partly like collections. Web IDL calls platform objects with indexed or named property support legacy platform objects. This behavior exists for compatibility and is not a pattern for new API design.

Live and static DOM queriesPop out in the code editor (opens in a new tab)JavaScript
const list = document.createElement("ul");list.innerHTML = "<li>tea</li>";const live = list.getElementsByTagName("li");const snapshot = list.querySelectorAll("li");list.append(document.createElement("li"));console.log(live.length, snapshot.length);

Line 3 gets a live HTMLCollection. Line 4 gets a static NodeList. Line 5 adds another list item. Line 6 prints 2 1: the live collection updated, while the querySelectorAll result kept its original match list.

Playground: live versus static matches
Live and static queriesJavaScript
const list = document.createElement("ul");
list.innerHTML = "<li>tea</li>";
const live = list.getElementsByTagName("li");
const snapshot = list.querySelectorAll("li");
list.append(document.createElement("li"));
console.log(live.length, snapshot.length);
Observed lengths

Added items: 1

Live HTMLCollection: 1

Static NodeList: 1

Try it yourself

The live collection sees 1 list items. The static NodeList still has 1.

This browser playground models the two query results after adding a list item.
Collection behavior to keep separate
ObjectAfter DOM changesExample
Live HTMLCollectionUpdates when matching DOM changesgetElementsByTagName
Static NodeListKeeps the matches from the queryquerySelectorAll
Legacy platform objectCan expose indexed or named propertiesCollections can act array-like without being arrays.

Live does not mean reactive in a framework sense. Reading the collection later can produce different contents because the DOM changed. Convert it to an Array when you need a stable copy for a later calculation.

Indexed collection access is legacy platform-object behavior: a collection can answer collection[0] even though it is not an Array. Named access is another old compatibility shape. Treat those conveniences as browser API behavior, not as a reason to use arbitrary property indexing in new application APIs.

In the playground, the only changed input is the number of list items. Reset restores one item. The live result follows that input; the static result deliberately stays at the query-time snapshot. That contrast is the safe debugging question to ask when a list appears to move.

DOMException

DOMException is a platform error object with a name and message. It is separate from JavaScript's ordinary Error subclasses, though it has error-like behavior and can be checked with instanceof DOMException.

Create a DOMExceptionPop out in the code editor (opens in a new tab)JavaScript
const stopped = new DOMException("Stopped", "AbortError");console.log(stopped.name, stopped instanceof DOMException);

Line 1 creates an exception with the name AbortError. Line 2 prints AbortError true. Code should normally use the name to identify the web-platform condition, rather than relying on legacy numeric error codes.

An invalid element name has a platform errorPop out in the code editor (opens in a new tab)JavaScript
try {  document.createElement("1abc");} catch (error) {  console.log(error.name);}

Line 2 asks the DOM to create a name that starts with a digit. The DOM rejects that name. Line 4 prints InvalidCharacterError, a DOMException name defined for invalid characters.

A DOMException tells you an API contract was not met. It is not proof that every thrown browser error is a DOMException, and a normal JavaScript TypeError can still be the right error for a different kind of failure.

Inspect a DOMException carefullyPop out in the code editor (opens in a new tab)JavaScript
const problem = new DOMException("Bad name", "InvalidCharacterError");
console.log(problem.name);
console.log(problem.code);
console.log(problem instanceof DOMException);

Line 1 constructs a platform exception with a message and name. Line 2 prints InvalidCharacterError. Line 3 prints the legacy numeric code 5. Line 4 prints true. In application code, prefer the descriptive name; codes exist for compatibility with older APIs.

The DOM's invalid-name example shows a browser-created exception, while this snippet constructs one directly. In both cases, the important branch is a stable name such as InvalidCharacterError, not a browser-specific message string.

The document.all exception

document.all is a compatibility relic. It returns an HTMLAllCollection, so property reads such as document.all.length work. Yet the HTML specification gives it special behavior for old feature-detection code.

An object that looks undefined to old checksPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof document.all);console.log(Boolean(document.all));console.log(typeof document.all.length);

Line 1 prints undefined. Line 2 prints false. Line 3 prints number, because the collection still has a readable length. It is not literally the JavaScript value undefined.

The special [[IsHTMLDDA]] internal slot makes this possible. It affects typeof, Boolean conversion, and loose equality behavior. New code must not use it for feature detection; it exists only so old pages keep working.

In particular, document.all == null is true while strict equality with undefined is false. This is a narrowly specified compatibility exception, not a conversion rule you can recreate with a normal object, Proxy, or custom valueOf method.

Treat the double brackets as specification notation for an internal slot. JavaScript source cannot add [[IsHTMLDDA]] to an object. The HTML Standard gives it only to HTMLAllCollection so old pages that detect ancient browsers still behave as they historically did.

Practical debugging habits

Bindings become useful when an API surprises you. First inspect the API signature and the type of each argument. A number stored as a DOM attribute, a malformed URL string, and a live collection are all ordinary consequences of the binding contract.

When object identity matters, test it directly with strict equality. When inherited behavior matters, inspect prototypes with Object.getPrototypeOf. When a collection changes unexpectedly, check whether you have HTMLCollection or NodeList.

Sort the binding facts
  • 5 becomes "5" for an attribute
  • A lone surrogate becomes U+FFFD in a URL
  • document.body === document.body
  • HTMLElement.prototype holds shared methods
  • A collection changes length after DOM changes
  • Falsy object with typeof equal to undefined
Try it yourself
0 of 6 correct

Put each card in the category that best explains it.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

The published lessons on the DOM tree, node properties, attributes and properties, and the prototype chain give the everyday API practice behind these internals.

Common misconceptions

  • “The DOM is pure JavaScript.” DOM objects are platform objects exposed through a JavaScript binding.
  • “Every interface function accepts new.” An interface object can be a function and still throw when constructed.
  • “All DOM collections are arrays.” They can be collection objects with different identity and liveness rules.
  • “document.all is undefined.” It has selected undefined-like behavior, but remains an object-like collection.
  • “DOMException is just Error.” It is a distinct web-platform interface with observable names.

These distinctions save debugging time because they point to the correct layer. A conversion problem belongs at the API boundary. A prototype problem belongs in interface inheritance. A moving query result belongs in collection liveness.

Practice exercises

Exercise 1 · Warm-upPredict an attribute type

What type does the final log print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const box = document.createElement("div");
box.setAttribute("data-count", 5);
console.log(typeof box.getAttribute("data-count"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upName the replacement

    A URL receives a lone surrogate. What value replaces it during USVString conversion?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeChoose the live query

      Your shopping list adds items after the query. Which API should you expect to update?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeRead the exception name

        What is the name of the created exception?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const stopped = new DOMException("Stopped", "AbortError");
        console.log(stopped.name, stopped instanceof DOMException);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain document.all

          Why can this old collection be falsy while still having a length?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeApply it to an app

            A form value changes shape after a browser API call. What should you inspect before adding a workaround?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              Use the binding contract to predict values instead of treating browser behavior as magic.

              Bindings and Web IDL quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What is Web IDL for?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log(new URL("https://shop.example/?q=\uD800").search);

                Choose an answer to see the explanation.

              3. Question 3 of 8Why does new HTMLElement() throw?

                Choose an answer to see the explanation.

              4. Question 4 of 8What does a live HTMLCollection do after a matching element is added?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does the DOMException snippet print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const error = new DOMException("Stopped", "AbortError");
                console.log(error.name, error instanceof DOMException);

                Choose an answer to see the explanation.

              6. Question 6 of 8Why is document.all unusual?

                Choose an answer to see the explanation.

              7. Question 7 of 8What does a Web IDL sequence require from a JavaScript argument?

                Choose an answer to see the explanation.

              8. Question 8 of 8Which value should application code usually branch on for a DOMException?

                Choose an answer to see the explanation.

              Key takeaways

              • Web IDL describes how browser APIs become JavaScript objects and methods.
              • DOMString, USVString, and sequences each convert JavaScript input differently.
              • DOM nodes are stable platform objects with interface prototypes.
              • Live collections can change after DOM changes, unlike static query results.
              • DOMException names describe web-platform failure conditions.
              • document.all is a deliberate legacy compatibility exception.

              Remember the one-liner.
              A browser binding is the rulebook that lets JavaScript talk to platform objects.

              Coming next: Realms, globals & WindowProxy.

              CompleteFrontend Clear concepts. Working examples.