Difficulty: Intermediate
How do you efficiently manipulate the DOM in JavaScript? Explain key DOM methods and event handling.
The Document Object Model (DOM) is a tree representation of an HTML document. JavaScript interacts with it through the DOM API to read, modify, create, and delete elements.
Key selection methods: getElementById, querySelector (CSS selector), querySelectorAll (returns NodeList), getElementsByClassName and getElementsByTagName (return live HTMLCollections).
For manipulation: createElement, appendChild, insertBefore, removeChild, replaceChild, and the modern append, prepend, before, after, replaceWith, remove methods. innerHTML and textContent set content (innerHTML parses HTML, textContent is safer and faster).
Performance tips: Batch DOM operations using DocumentFragment or building strings before innerHTML. Minimize layout thrashing by reading all measurements before writing. Use event delegation instead of attaching listeners to many elements.
// Assume this HTML exists:
// <div id="app">
// <ul class="list"></ul>
// </div>
// Selection methods
const app = document.getElementById('app');
const list = document.querySelector('.list');
const items = document.querySelectorAll('li'); // NodeList
// Creating elements
const li = document.createElement('li');
li.textContent = 'New Item';
li.classList.add('item', 'active');
li.setAttribute('data-id', '1');
li.style.color = 'blue';
// Appending
list.appendChild(li);
// Modern methods
const li2 = document.createElement('li');
li2.textContent = 'Second Item';
list.append(li2); // append at end
// list.prepend(li2); // add at beginning
// li.before(li2); // add before li
// li.after(li2); // add after li
// li.replaceWith(li2); // replace li with li2
// li.remove(); // remove from DOM
console.log(list.innerHTML);
console.log(list.children.length);
querySelector uses CSS selectors. createElement + textContent + classList is the safe way to build elements. Modern methods (append, prepend, before, after) are cleaner than older APIs.
// BAD: causes reflow for each append
function addItemsBad(count) {
const list = document.querySelector('.list');
for (let i = 0; i < count; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
list.appendChild(li); // reflow on every append!
}
}
// GOOD: DocumentFragment batches updates
function addItemsGood(count) {
const list = document.querySelector('.list');
const fragment = document.createDocumentFragment();
for (let i = 0; i < count; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li); // no reflow
}
list.appendChild(fragment); // single reflow
}
// ALSO GOOD: build HTML string
function addItemsHTML(count) {
const list = document.querySelector('.list');
const html = Array.from({ length: count },
(_, i) => `<li>Item ${i}</li>`
).join('');
list.innerHTML = html; // single parse + reflow
}
console.log('DocumentFragment approach: 1 reflow');
console.log('innerHTML approach: 1 reflow');
console.log('Individual appendChild: N reflows');
DocumentFragment is a lightweight container that is not part of the DOM tree. Appending it triggers a single reflow. innerHTML is fast but be careful with user input (XSS risk).
const button = document.createElement('button');
button.textContent = 'Click Me';
// addEventListener (preferred)
function handleClick(event) {
console.log('Type:', event.type);
console.log('Target:', event.target.textContent);
console.log('Timestamp:', Math.round(event.timeStamp));
}
button.addEventListener('click', handleClick);
// Remove listener (must pass same function reference)
// button.removeEventListener('click', handleClick);
// Once option - auto-removes after first trigger
button.addEventListener('dblclick', () => {
console.log('Double clicked! (only fires once)');
}, { once: true });
// AbortController for cleanup
const controller = new AbortController();
button.addEventListener('mouseenter', () => {
console.log('Hovered');
}, { signal: controller.signal });
// Remove all listeners attached with this signal
// controller.abort();
console.log('Event listener options: capture, once, passive, signal');
addEventListener supports options: once (auto-remove), capture (capture phase), passive (no preventDefault for scroll perf), and signal (AbortController cleanup).
DOM API, querySelector, Event Listeners, createElement, DocumentFragment, Performance