Course outline · 0% complete

0/30 lessons0%

Course overview →

Script tags and the DOM tree

lesson 8-1 · ~9 min · 24/30

From lesson 1-2, parsing HTML builds the DOM, a tree of element objects.

The Document Object Model is one node object per element, nested exactly like the HTML.

HTMLDOM
an elementa node object
nested elementschildren
the wrapperthe parent

This unit is about reading and changing that tree from JavaScript.

Getting JavaScript into the page

A page without JavaScript can only ever be a document. The DOM API is what turns it into an application, and every framework that comes later, including React and Vue, ultimately generates the same tree through the same calls taught in this unit.

There is a second reason this lesson matters. When code appears to do nothing, the cause is usually here, because the script ran at the wrong time.

<head>
  <script src="app.js" defer></script>
</head>
AttributeEffect
srcloads and runs a JavaScript file
deferdownload now, run after the HTML is parsed

Without defer, a script in the head runs before the body exists, so code that touches elements finds nothing there.

A script tag can also sit at the end of body, and code can be written inline between <script> tags for quick tests. Using src plus defer in the head is the habit worth building.

Once running, code sees the page through one global object, document, which is the root of the DOM.

The DOM is a tree of objects

The browser turns HTML nesting, the boxes in boxes from lesson 2-2, into a tree. Every element becomes a node object, nested elements become its children, and the wrapper is the parent, with document at the root.

A tree of plain objects is already familiar territory.

const dom = {
  tag: "body",
  children: [
    { tag: "h1", children: [] },
    { tag: "ul", children: [
      { tag: "li", children: [] },
      { tag: "li", children: [] },
    ] },
  ],
};
TermIn this object
rootthe body node
childrenthe children array
leafany node with an empty children

Every DOM operation, whether finding a node, changing it, or adding children, is a walk through this structure. The real one simply has richer nodes, carrying text content, classes, and styles.

documenthtmlheadbodyh1ullili
The DOM as a tree. The gold highlight visits nodes in depth-first order: parent first, then each child's whole subtree.

Walking the toy DOM

A depth-first visit of every node, indenting by depth, which reconstructs the shape of the original HTML.

const dom = {
  tag: "body",
  children: [
    { tag: "h1", children: [] },
    { tag: "ul", children: [
      { tag: "li", children: [] },
      { tag: "li", children: [] },
    ] },
  ],
};

function walk(node, depth) {
  console.log("  ".repeat(depth) + node.tag);
  for (const child of node.children) {
    walk(child, depth + 1);
  }
}

walk(dom, 0);

Output

body
  h1
  ul
    li
    li
NodeDepthIndent
body0none
h1 and ul1two spaces
the two li2four spaces

The node is printed before recursing, which is what makes a parent appear above its children. Passing depth + 1 down is the entire indentation mechanism, and it needs no bookkeeping on the way back up.

Counting nodes by tag

countTag(node, tag) counts how many nodes in the tree, including the starting node, carry the given tag. It shares its recursive shape with walk.

const dom = {
  tag: "body",
  children: [
    { tag: "h1", children: [] },
    { tag: "ul", children: [
      { tag: "li", children: [] },
      { tag: "li", children: [] },
    ] },
  ],
};

function countTag(node, tag) {
  let count = node.tag === tag ? 1 : 0;
  for (const child of node.children) {
    count += countTag(child, tag);
  }
  return count;
}

console.log(countTag(dom, "li"));
console.log(countTag(dom, "body"));
console.log(countTag(dom, "a"));

Output

2
1
0
TagCount
li2
body1
a0

The count starts at 1 or 0 depending on whether this node matches, then adds countTag(child, tag) for every child exactly as walk recursed.

No base case is written out, because a node with no children means the loop never runs and the recursion stops by itself. That is a property of iterating the children rather than an accident.