JavaScript Practice #1 DOM Manipulation Basics
The final track of the JavaScript series — Practice. Here we bring together the tools sharpened in Basics, Intermediate, and Advanced to build dynamic web pages in vanilla JavaScript.
A 6-post series.
- #1 DOM manipulation basics ← this post
- #2 Event handling and delegation
- #3 fetch and async UI
- #4 Working with forms — validation, FormData
- #5 Local storage and lightweight state management
- #6 Building a small app — Todo or live search
This post covers the basics of touching the DOM — finding elements, changing content/attributes/classes, creating new elements and inserting them.
What is the DOM? #
DOM (Document Object Model) is the browser’s conversion of HTML into an object tree that JavaScript can manipulate. Tags like <h1>, <p> all become JavaScript objects.
<body>
<h1 id="title">hello</h1>
<p class="message">body</p>
</body>JavaScript sees:
document
└─ html
└─ body
├─ h1#title "hello"
└─ p.message "body"document is the entry point; you reach every element through the tree structure.
Finding elements — the querySelector family
#
The most-used method.
// CSS selector — single
const title = document.querySelector('#title');
const firstP = document.querySelector('p');
const greenBtn = document.querySelector('.btn.green');
// CSS selector — many (NodeList)
const allP = document.querySelectorAll('p');
const items = document.querySelectorAll('.item');CSS selectors work directly — intuitive. In the past, getElementById, getElementsByClassName, and getElementsByTagName were used separately, but modern code uses querySelector almost exclusively.
querySelectorAll’s NodeList — not an array
#
const items = document.querySelectorAll('.item');
items.length; // OK — has length
items.forEach(...); // OK — forEach works
items.map(...); // ✗ no map
items.filter(...); // ✗ no filter
// to a real array
const arr = [...items]; // OK — spread
const arr2 = Array.from(items); // OK
forEach exists on NodeList, but map/filter/reduce don’t. Convert with spread or Array.from when you need array methods.
Parent/child/sibling — tree traversal #
const el = document.querySelector('.item');
// parent
el.parentElement;
el.parentNode; // nearly identical (edge cases differ)
// children
el.children; // HTMLCollection — elements only
el.childNodes; // NodeList — includes text/comments
el.firstElementChild;
el.lastElementChild;
// siblings
el.nextElementSibling;
el.previousElementSibling;
// closest ancestor (including self)
el.closest('.container');closest is used often — the heart of the event delegation pattern (#2).
Changing content — textContent vs innerHTML
#
const el = document.querySelector('#title');
// text only — safe
el.textContent = 'new title';
// parsed as HTML — can be dangerous
el.innerHTML = '<strong>new title</strong>';textContent inserts plain text only. Safe even with user input or external data.
innerHTML parses as HTML. The <strong> tag really renders as bold, but inserting untrusted data leaves you vulnerable to XSS.
const userInput = '<img src=x onerror="alert(1)">';
el.textContent = userInput; // safe — just a string
el.innerHTML = userInput; // ✗ script executes
Default to textContent. When you really need HTML, use innerHTML carefully and only there.
Working with attributes #
Regular attributes #
const link = document.querySelector('a');
link.href; // direct property access
link.href = 'https://...';
link.getAttribute('href'); // same
link.setAttribute('href', 'https://...');
link.removeAttribute('href');
link.hasAttribute('target'); // true/false
For common attributes like href, src, id — direct dot access is shorter. Less-known attributes or dynamic keys — getAttribute/setAttribute.
Data attributes — data-*
#
For embedding JavaScript-side data in HTML.
<button data-id="42" data-action="delete">delete</button>const btn = document.querySelector('button');
btn.dataset.id; // '42' (always string)
btn.dataset.action; // 'delete'
btn.dataset.id = '100'; // updates attribute → HTML changes
Mind camelCase conversion — HTML’s data-user-name becomes dataset.userName in JS.
Working with classes — classList
#
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open'); // remove if present, add if not
el.classList.toggle('disabled', isDisabled); // force with the second arg
el.classList.contains('active'); // boolean
el.classList.replace('a', 'b');classList is the modern standard tool. Safer and more readable than the old el.className = 'a b c' (string-level) approach.
Working with styles #
Inline styles #
const el = document.querySelector('.box');
el.style.color = 'red';
el.style.backgroundColor = '#eee'; // CSS background-color is camelCase in JS
el.style.transform = 'translateX(10px)';But don’t overuse inline styles. Toggling a CSS class is almost always cleaner.
el.classList.add('error');
// CSS defines .error { color: red; ... }
CSS variables #
CSS custom properties (--var) can be set dynamically with setProperty.
el.style.setProperty('--main-color', '#ff5733');
const value = getComputedStyle(el).getPropertyValue('--main-color');Fits theme switching and dynamic styling.
Creating and inserting elements #
Create a new element #
const div = document.createElement('div');
div.className = 'item';
div.textContent = 'new item';Inserting — append / prepend / before / after
#
const parent = document.querySelector('.list');
const item = document.createElement('li');
parent.append(item); // append to end
parent.prepend(item); // prepend to start
existingChild.before(item); // before existing child
existingChild.after(item); // after existing child
existingChild.replaceWith(item); // replace child
The old methods (appendChild, insertBefore) work too — but the new ones are shorter and clearer, so modern code uses these.
Many elements at once #
parent.append(li1, li2, li3);
parent.append('text works too', divEl, '\n');It accepts strings too, auto-converting them to text nodes.
Removing — remove
#
el.remove();Much shorter than the old el.parentNode.removeChild(el).
Efficiency — DocumentFragment
#
Adding many elements one by one triggers reflow each insertion — expensive. DocumentFragment acts as a temporary container.
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.append(li); // gather in fragment first
}
list.append(fragment); // attach to the real tree once — single reflow
Differences become visible around 100 items. Most cases don’t need this — but worth knowing for large lists.
innerHTML + template literals — short but careful
#
list.innerHTML = items
.map((item) => `<li class="item">${item.name}</li>`)
.join('');Very short — but if ${item.name} is user input, you have an XSS risk. Use only for trusted data, or after escaping.
Element info #
el.tagName; // 'DIV' (uppercase)
el.id;
el.className; // string — prefer classList
el.getBoundingClientRect(); // on-screen position/size
el.offsetWidth;
el.offsetHeight;
el.offsetTop;
el.offsetLeft;
el.scrollTop;
el.scrollLeft;Comes up often when handling layout or scrolling.
Using closest
#
The method we touched on in tree traversal. It comes up so often in event handling that it’s worth a second mention.
list.addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (!item) return;
const id = item.dataset.id;
// ...
});Click anywhere inside the list — find the nearest .item and pull id. Foundation of the event delegation pattern we cover next.
Wrap-up #
What we covered:
- DOM is the JavaScript interface that turns HTML into an object tree
querySelector/querySelectorAllare the modern standard- NodeList isn’t an array — use spread/Array.from when you need array methods
textContentis safe; useinnerHTMLonly with trusted data- Manage
data-*attributes withdataset classList.add/remove/toggleare the class standardappend/prepend/before/afterare modern insertion- For big lists, batch with
DocumentFragment closestfor the nearest ancestor
In the next post (#2 Event Handling and Delegation) we cover the event object, bubbling and capturing, and efficient event-delegation patterns.