Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

27 chapters
⌕
Beginner9›
B1Setup & mental modelB2Types & valuesB3Operators & flowB4Functions (first half)B5Objects & arrays (first half)B6DOM & eventsB7Basic asyncB8Errors & tools★Cheat page
Intermediate10›
I1Scope & functions, properlyI2Objects deeplyI3Prototypes & OOPI4Async, properlyI5Modules & toolingI6Regex, dates & APIsI7Error handlingI8Real-time connectionsI9Offline & storage★Cheat page
Advanced10›
A1Engine & memoryA2Advanced asyncA3MetaprogrammingA4Types & dataA5Patterns & architectureA6PerformanceA7SecurityA8EcosystemA9Testing★Cheat page
/ search[ ] chaptert top

JavaScript levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
B6

DOM & events

The DOM is a live tree of objects — and JS can poke every branch of it.

Everything below runs against a real, live sandbox on this page, not a simulation — the buttons genuinely call querySelector, classList, appendChild and friends against the little page snippet right above them. Push every button before reading on; watching it happen is most of the lesson.

Live DOM playground

Sandbox page

Click a button below to mutate me.

  • Item 1
  • Item 2

Every click below is logged with the exact DOM call that ran.

Selecting elements

Call Returns
document.getElementById(id)one element, or null — no # prefix
document.querySelector(css)the first match for any CSS selector, or null
document.querySelectorAll(css)a NodeList of every match — not a real array, but it has .forEach
document.getElementById("de-text");         // exact id match
document.querySelector("#de-text");         // same element, CSS-selector syntax
document.querySelector(".btn");             // the FIRST element with class "btn"
document.querySelectorAll(".btn");          // every element with class "btn"
Rule querySelector/ querySelectorAll take real CSS selectors, so anything you can write in a stylesheet works here too — "ul li:last-child", "[data-active]", "input[type=email]". That flexibility is why they've mostly replaced the older, narrower getElementsByClassName/getElementsByTagName.

Reading and changing content

Property Reads/writes Watch out for
el.textContentplain text onlythe safe default — never parses HTML
el.innerHTMLmarkup, parsed as HTMLuser-supplied text through here is an XSS hole — see the security chapter
el.getAttribute(name) / setAttribute(name, v)any HTML attribute, always as a stringuse for custom/data-* attributes
el.classList.add(), .remove(), .toggle(), .contains()the modern way to manage classes — no manual string splitting
el.textContent = "hello <b>there</b>";  // literal text — tags show up as text, not bold
el.innerHTML = "hello <b>there</b>";     // actually renders as bold

el.setAttribute("data-user-id", "42");
el.getAttribute("data-user-id");            // "42" — always a string, even for numbers

el.classList.add("active");
el.classList.toggle("open");                // on if it was off, off if it was on
el.classList.contains("active");            // true

Creating, appending, removing

const li = document.createElement("li");   // exists only in memory so far
li.textContent = "New item";
listEl.appendChild(li);                     // now it's actually in the page

listEl.removeChild(li);                     // gone from the page (still exists in memory until GC'd)
li.remove();                                // modern shorthand — no need to know the parent
⚠ appendChild moves, it doesn't copy If li is already somewhere in the page and you appendChild it again elsewhere, it's relocated, not duplicated — an element can only exist at one spot in the tree at a time. Need it in two places? Use el.cloneNode(true) (the true means "deep clone, children included") and append the clone.

Events — addEventListener and the event object

button.addEventListener("click", function (event) {
  console.log(event.type);          // "click"
  console.log(event.target);        // the exact element that was clicked
  console.log(event.currentTarget); // the element the LISTENER is attached to
});

target and currentTarget only differ when events bubble — a click starts at the exact element you tapped and travels upward through every ancestor that's listening. target stays fixed at where it started; currentTarget is always whichever element's listener is currently running. That bubbling is what makes event delegation work: put one listener on a parent list instead of one on every item, and check event.target inside it to see which item was actually clicked.

preventDefault — stopping the browser's own reaction

Some elements have a built-in behavior for certain events — a form submits and reloads the page, an <a> navigates. event.preventDefault() cancels that specific default without stopping the event from continuing to bubble or running your own handler.

form.addEventListener("submit", function (event) {
  event.preventDefault();          // stop the page reload
  const data = new FormData(form); // now handle it yourself — fetch(), validation, etc.
});
Rule preventDefault() stops the browser's built-in reaction. stopPropagation() stops the event from bubbling further up the tree. They solve two different problems and it's common to need only one of them.

Forms and input values

input.value;                 // the current text — always a string, even for type="number"
input.value = "";            // clearing it programmatically

checkbox.checked;            // boolean — .value on a checkbox is NOT what's checked
select.value;                // the selected <option>'s value

input.addEventListener("input", e => console.log(e.target.value));  // fires on every keystroke
input.addEventListener("change", e => console.log(e.target.value)); // fires once, on blur/commit

input vs change trips a lot of people up: input is for "react live, as they type" (a character counter, live search); change is for "react once they're done" (a select dropdown, a checkbox, a field that loses focus).

Practice this layer

Opens in the editor — write it, run it, and check it against real tests.

Build a mini event emitter4 tests · beginner
←previousObjects & arrays (first half)↑ CovernextBasic async→