Chapter 15 of 28 · JavaScript for Beginners — Complete Free Tutorial
DOM Manipulation: Creating, Updating & Removing Elements
Reading the page is step one; changing it is where features appear — lists that grow, banners that dismiss, search results that render. This chapter covers creating nodes, inserting and removing them, updating styles and attributes efficiently, and doing it all without layout thrashing or security holes.
Updating existing elements
const heading = document.querySelector('h1');
heading.textContent = 'Welcome back, Asha'; // safe text swap
heading.classList.add('is-loading');
const badge = document.querySelector('.badge');
badge.textContent = '3';
badge.setAttribute('aria-label', '3 notifications');
Creating elements from scratch
const li = document.createElement('li');
li.className = 'task';
li.textContent = 'Read the DOM chapter';
li.dataset.priority = 'high'; // data-priority="high"
const list = document.querySelector('.tasks');
list.appendChild(li);
// Expected output: the new <li> appears as the last child of the list
Insertion methods
| Method | Places the node… |
|---|---|
parent.appendChild(el) | As the last child |
parent.prepend(el) | As the first child |
el.after(node) | Immediately after el |
el.before(node) | Immediately before el |
parent.append(…nodes) | Multiple children, last (accepts strings too) |
el.replaceWith(node) | In place of el |
Moving is implicit: inserting an element that already lives elsewhere moves it — no copy is made.
Building a list item with structure
function createTask(text) {
const li = document.createElement('li');
li.className = 'task';
const span = document.createElement('span');
span.textContent = text; // textContent escapes for us ✅
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'task__remove';
btn.setAttribute('aria-label', `Remove ${text}`);
btn.textContent = '×';
li.append(span, btn);
return li;
}
document.querySelector('.tasks').appendChild(createTask('Ship release'));
Expected output: a structured, accessible <li> with a labelled remove button — built entirely with DOM APIs, zero HTML strings, zero XSS risk.
innerHTML vs DOM APIs
// ⚠️ convenient for trusted static markup
list.innerHTML = '<li class="task">Static</li>';
// ✅ safe for dynamic/user data — creates real nodes
list.appendChild(createTask(userInput));
Use innerHTML only for markup you authored. For anything containing user or API data, create nodes and set textContent.
Removing elements
const el = document.querySelector('.banner');
el.remove(); // modern one-liner
el.parentElement.removeChild(el); // legacy equivalent
// keep a reference first if you plan to re-insert it
Batching: build off-DOM, insert once
const fragment = document.createDocumentFragment();
for (const item of items) {
fragment.appendChild(createTask(item.name)); // not painted yet
}
list.appendChild(fragment); // ONE insertion → one layout pass
Each insertion can trigger recalculation; building in a DocumentFragment (or a detached element) and inserting once keeps long lists fast.
Styling from JavaScript
const panel = document.querySelector('.panel');
// inline style — highest priority, one property at a time
panel.style.backgroundColor = '#4f46e5';
panel.style.setProperty('--accent', '#f59e0b'); // CSS variables work too
// read computed values
const w = getComputedStyle(panel).width;
// best practice: toggle classes, let CSS hold the values
panel.classList.toggle('is-collapsed');
Rule of thumb: classes for states, inline style only for values computed at runtime (a progress bar's width: 72%, a colour picked by the user). Keep your visual rules in the stylesheet where they belong.
Rendering data: the pattern you will reuse
function renderUsers(users) {
const list = document.querySelector('.users');
list.replaceChildren(); // clear old content (one call)
if (!users.length) {
list.replaceChildren(Object.assign(document.createElement('li'), {
textContent: 'No users yet',
}));
return;
}
const nodes = users.map((u) => {
const li = document.createElement('li');
li.textContent = `${u.name} — ${u.role}`;
return li;
});
list.append(...nodes); // insert all at once
}
Expected output: an empty-state message when there is nothing, otherwise all rows rendered in a single batch — the standard “data → DOM” pipeline behind every list UI.
Performance notes
- Minimise layout-triggering changes: batch reads and writes separately (reading
offsetHeightafter writes forces reflow). - Prefer
replaceChildren()+ one append over many individualremove/appendcalls. - Large lists? Render only what is visible (virtualisation) or paginate — beyond this course's scope, but know the technique exists.
- Store references when an element is used repeatedly; avoid querying the whole document in hot loops.
Common mistakes
- Concatenating user data into HTML strings — XSS (chapter 14's warning, now real).
- Forgetting to remove old listeners — replacing innerHTML destroys nodes (and their listeners) — fine; moving nodes keeps listeners — also fine; the bug is adding the same listener twice.
- Editing DOM inside expensive loops — one write per item triggers N layouts; build first, insert once.
style.display = ''vsnone— setting''removes the inline value so the stylesheet decides again — the correct way to “unhide”.
Practice checklist
- Build a “Add item” flow: input →
createTask()→ append, with an empty state. - Render an array of strings with
replaceChildren+ fragment and time it against per-item appends. - Move the last child of one list to the start of another using
prepend. - Toggle a panel class and verify the CSS transition still animates (classes, not inline styles).
Elements change on command — now let users trigger those changes. Next: Events: Delegation, Bubbling & Listeners.
Frequently Asked Questions
JavaScript is the programming language of the web. It adds behaviour to pages: responding to clicks and input, validating forms, fetching data from APIs, updating the DOM without reloading, and much more. It also runs on servers (Node.js), in desktop and mobile apps, and in build tooling.
They are unrelated languages that share a similar name for historical marketing reasons. Java is a statically-typed compiled language; JavaScript is a dynamically-typed interpreted language designed for the browser. Knowing one does not imply knowledge of the other.
Helpful but not mandatory — HTML gives you the structure JavaScript manipulates and CSS the styling it toggles. If you know basic tags, classes and selectors, you have enough to start; the DOM chapters assume only that much.
Use const by default and let when the value must change. Avoid var entirely in modern code: it is function-scoped instead of block-scoped, hoists differently, and can create accidental globals. This course uses const and let throughout.
The Document Object Model is the browser’s in-memory tree of objects representing your HTML. JavaScript reads and modifies that tree — selecting elements, changing text, adding nodes, attaching event listeners — and the browser re-renders the page to match.
== compares after converting types (so 5 == "5" is true), while === requires the same value AND the same type (5 === "5" is false). Loose equality has famously confusing rules — use === and !== everywhere unless you have a specific reason not to.
JavaScript is single-threaded, so long-running work (timers, fetches, events) is handled by the browser and its callbacks are queued. The event loop moves queued callbacks onto the call stack only when it is empty, with promise callbacks (microtasks) taking priority over timers. It is why asynchronous code never freezes the page.
A Promise is an object representing a future value — pending, fulfilled or rejected — that can be chained and combined (Promise.all). async/await is syntax sugar over promises that lets asynchronous code read top-to-bottom with ordinary try/catch error handling.
Yes. Frameworks are built on exactly these fundamentals — components are functions, state is your variable discipline, effects are events, rendering is DOM work. Developers who learn vanilla first understand what the framework is doing and debug far faster.
Type out the examples instead of copying, build small end-to-end projects (expense tracker, weather app, quiz), handle every UI state (loading, empty, success, error), and revisit fundamentals while reading real code. Consistency beats volume — a little most days outpaces occasional marathons.