DOM Manipulation & Events

Difficulty: Intermediate

Question

How do you efficiently manipulate the DOM in JavaScript? Explain key DOM methods and event handling.

Answer

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.

Code examples

DOM Selection and Manipulation

// 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.

Efficient Batch DOM Updates

// 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).

Event Handling

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).

Key points

Concepts covered

DOM API, querySelector, Event Listeners, createElement, DocumentFragment, Performance