DOM Manipulation
DOM (Document Object Model) is a tree that the browser creates from the HTML document. By operating on this tree with JavaScript, you can dynamically change the page's content, structure, and attributes—like picking fruit from a tree, grafting new branches, or cutting dead wood.
Selecting Elements
The first step in DOM manipulation is always "find it first".
| Method | Returns | Description |
|---|---|---|
getElementById('id') |
Single element | Fastest, id is unique |
getElementsByClassName('cls') |
HTMLCollection | Live collection, syncs with DOM changes |
getElementsByTagName('tag') |
HTMLCollection | Same as above |
querySelector('selector') |
Single element | CSS selector, returns first match |
querySelectorAll('selector') |
NodeList | Static collection, doesn't change with DOM |
💡
querySelector family vs getElementBy*: the former uses CSS selectors (more flexible), the latter has slightly better performance. For daily development, prefer querySelector—it's more intuitive.
Example: Comparing Five Selection Methods
HTML
<div id="box" class="card active">Hello</div>
<div class="card">World</div>
<p class="card">JS</p>
<script>
const byId = document.getElementById('box');
console.log('byId:', byId.textContent);
const byClass = document.getElementsByClassName('card');
console.log('byClass count:', byClass.length);
const byTag = document.getElementsByTagName('p');
console.log('byTag:', byTag[0].textContent);
const first = document.querySelector('.card');
console.log('querySelector:', first.textContent);
const all = document.querySelectorAll('.card');
console.log('querySelectorAll count:', all.length);
</script>
Modifying Content
After finding an element, the most common need is to change "what it says".
textContent: reads/writes plain text, safe and efficient.innerHTML: reads/writes HTML strings, can insert tags, but has XSS risk.
⚠️ Never use
innerHTML to insert user-supplied content, otherwise attackers can inject malicious scripts. It's like posting a letter from a stranger directly to the bulletin board—the letter might contain a "bomb".
Example: textContent vs innerHTML
HTML
<div id="safe"></div>
<div id="danger"></div>
<script>
const safe = document.getElementById('safe');
safe.textContent = '<b>This will not be bold</b>';
const danger = document.getElementById('danger');
danger.innerHTML = '<b>This will be bold</b>';
</script>
Modifying Attributes
HTML element attributes (id, class, src, href, etc.) can also be read and written with JS.
| Method | Description |
|---|---|
getAttribute('name') |
Read attribute value |
setAttribute('name', 'value') |
Set attribute |
removeAttribute('name') |
Remove attribute |
💡 Some attributes can be accessed directly with dot notation:
el.id, el.className, el.src. But for custom attributes (data-*), use getAttribute or el.dataset.
Example: Manipulating Link Attributes
HTML
<a id="link" href="https://example.com" target="_blank">Example Link</a>
<script>
const link = document.getElementById('link');
console.log('href:', link.getAttribute('href'));
link.setAttribute('href', 'https://mdn.io');
link.setAttribute('title', 'Click to go to MDN');
link.removeAttribute('target');
console.log('After modification:', link.getAttribute('href'));
</script>
Creating and Inserting Elements
One of the core DOM manipulation abilities: "create" an element out of thin air, then attach it to the tree.
| Method | Description |
|---|---|
createElement('tag') |
Create element node |
createTextNode('text') |
Create text node |
appendChild(child) |
Append to end |
insertBefore(new, ref) |
Insert before ref |
Example: Dynamically Adding List Items
HTML
<ul id="list">
<li>Existing item</li>
</ul>
<script>
const list = document.getElementById('list');
const li = document.createElement('li');
li.textContent = 'Newly added item';
list.appendChild(li);
const li2 = document.createElement('li');
li2.textContent = 'Inserted at the beginning';
list.insertBefore(li2, list.firstChild);
</script>
Removing and Cloning Elements
| Method | Description |
|---|---|
removeChild(child) |
Parent removes child node |
remove() |
Element removes itself (newer API) |
cloneNode(true/false) |
true = deep clone (including children), false = shallow clone |
💡
remove() is simpler than removeChild(), but if you need to get the removed node for further processing, use removeChild()—it returns the removed node.
Example: Removing and Cloning
HTML
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
<script>
const fruits = document.getElementById('fruits');
const banana = fruits.children[1];
const removed = fruits.removeChild(banana);
console.log('Removed:', removed.textContent);
const clone = fruits.cloneNode(true);
document.body.appendChild(clone);
console.log('Cloned the list');
</script>
📖 Summary
- Two camps for selecting elements:
getElementBy*(better performance) andquerySelector*(more flexible). For daily use,querySelectoris sufficient. - Use
textContentfor text changes, useinnerHTMLonly when you need HTML. The latter requires XSS prevention. - Attribute operations:
getAttributeto read,setAttributeto write,removeAttributeto delete. - Standard flow for creating elements:
createElement→ set content/attributes →appendChildto mount. - Two ways to remove elements:
remove()(element removes itself),removeChild()(parent removes it, and you get the node back). cloneNode(true)deep-clones including all descendants,cloneNode(false)clones only the shell.
❓ FAQ
Q What happens if
querySelector returns null?A Calling any property/method on
null will throw a TypeError. So it's best to check before operating: const el = document.querySelector('.xxx'); if (el) { ... }.Q What's the difference between
HTMLCollection and NodeList?A
HTMLCollection is live—it automatically updates when the DOM changes; NodeList from querySelectorAll is a static snapshot. When iterating and adding/removing elements, a static collection is safer, otherwise you might get an infinite loop.Q What happens if you
appendChild a node that already exists?A The node "moves" from its original position to the new position. In the DOM, the same node can only appear once. This is a move, not a copy.
📝 Exercises
- Basic: Create a page that uses
createElementandappendChildto dynamically generate a list containing 5 city names. - Intermediate: Add a delete button to the list from the previous exercise. When clicked, use
removeChildto delete the corresponding list item. - Challenge: Implement a simple "note manager"—an input field + add button. Each time you add a note, create a card with a delete button. Clicking the delete button removes that card.



