DOM manipulation best practices and performance optimization

Alex Chang Feb 2026
1 tab
// Creating elements
const div = document.createElement('div');
div.id = 'container';
div.className = 'box rounded';
div.textContent = 'Hello World';

// Setting attributes
div.setAttribute('data-id', '123');
div.setAttribute('aria-label', 'Content container');

// Getting attributes
const id = div.getAttribute('data-id');
const hasAttr = div.hasAttribute('data-id');

// Removing attributes
div.removeAttribute('data-id');

// Appending to DOM
document.body.appendChild(div);

// Creating complex structures
const article = document.createElement('article');
const heading = document.createElement('h2');
heading.textContent = 'Article Title';
const paragraph = document.createElement('p');
paragraph.textContent = 'Article content...';

article.appendChild(heading);
article.appendChild(paragraph);
document.body.appendChild(article);

// Using DocumentFragment (efficient for multiple elements)
const fragment = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i + 1}`;
  fragment.appendChild(li);
}

// Single reflow instead of 100
document.getElementById('list').appendChild(fragment);

// innerHTML (use cautiously - XSS risk)
const container = document.getElementById('container');
container.innerHTML = '<h1>Title</h1><p>Content</p>';

// Safer: textContent
const text = document.getElementById('text');
text.textContent = 'Safe text content'; // No HTML parsed

// insertAdjacentHTML - insert at specific positions
const target = document.getElementById('target');

target.insertAdjacentHTML('beforebegin', '<div>Before target</div>');
target.insertAdjacentHTML('afterbegin', '<p>At start of target</p>');
target.insertAdjacentHTML('beforeend', '<p>At end of target</p>');
target.insertAdjacentHTML('afterend', '<div>After target</div>');

// classList manipulation
const element = document.getElementById('element');

element.classList.add('active', 'highlight');
element.classList.remove('inactive');
element.classList.toggle('visible');

if (element.classList.contains('active')) {
  console.log('Element is active');
}

element.classList.replace('old-class', 'new-class');

// Style manipulation
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.fontSize = '16px';

// Multiple styles at once
Object.assign(element.style, {
  color: 'red',
  backgroundColor: 'blue',
  fontSize: '16px',
  padding: '1rem'
});

// Data attributes
const item = document.getElementById('item');

// Set data attributes
item.dataset.userId = '123';
item.dataset.userName = 'Alice';
item.dataset.itemType = 'product';

// Read data attributes
console.log(item.dataset.userId); // '123'
console.log(item.dataset.userName); // 'Alice'

// Equivalent to:
item.setAttribute('data-user-id', '123');
const userId = item.getAttribute('data-user-id');

// Removing elements
const oldElement = document.getElementById('old');
oldElement.remove(); // Modern way

// Or using parent
oldElement.parentNode.removeChild(oldElement);

// Replacing elements
const newElement = document.createElement('div');
newElement.textContent = 'New content';
oldElement.replaceWith(newElement);

// Cloning elements
const original = document.getElementById('template');
const clone = original.cloneNode(true); // true = deep clone
document.body.appendChild(clone);

// Querying DOM
const single = document.getElementById('unique-id');
const first = document.querySelector('.class-name');
const all = document.querySelectorAll('.items');
const byTag = document.getElementsByTagName('div');
const byClass = document.getElementsByClassName('box');

// Iterating NodeList
all.forEach(element => {
  console.log(element.textContent);
});

// Converting to array
const array = Array.from(all);
const spread = [...all];

// Finding elements relative to another
const parent = element.parentElement;
const children = element.children;
const firstChild = element.firstElementChild;
const lastChild = element.lastElementChild;
const next = element.nextElementSibling;
const previous = element.previousElementSibling;

// Closest ancestor matching selector
const form = element.closest('form');
const container2 = element.closest('.container');

// Check if element matches selector
if (element.matches('.active')) {
  console.log('Element has active class');
}

// Performance: Batch DOM changes
// Bad - causes multiple reflows
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div');
  div.textContent = i;
  document.body.appendChild(div); // Reflow each time
}

// Good - single reflow
const html = [];
for (let i = 0; i < 100; i++) {
  html.push(`<div>${i}</div>`);
}
document.body.innerHTML += html.join('');

// Better - using fragment
const frag = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div');
  div.textContent = i;
  frag.appendChild(div);
}
document.body.appendChild(frag);

// Read/write separation (prevent layout thrashing)
// Bad - interleaved reads and writes
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
  const height = box.offsetHeight; // Read (triggers layout)
  box.style.height = height + 10 + 'px'; // Write
});

// Good - batch reads, then writes
const heights = [];
boxes.forEach(box => {
  heights.push(box.offsetHeight);
});
boxes.forEach((box, i) => {
  box.style.height = heights[i] + 10 + 'px';
});

// Virtual scrolling for large lists
class VirtualList {
  constructor(container, items, itemHeight) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleCount = Math.ceil(container.offsetHeight / itemHeight);
    this.render();

    container.addEventListener('scroll', () => this.render());
  }

  render() {
    const scrollTop = this.container.scrollTop;
    const startIndex = Math.floor(scrollTop / this.itemHeight);
    const endIndex = startIndex + this.visibleCount;

    const visibleItems = this.items.slice(startIndex, endIndex);

    this.container.innerHTML = '';
    visibleItems.forEach((item, index) => {
      const div = document.createElement('div');
      div.textContent = item;
      div.style.position = 'absolute';
      div.style.top = (startIndex + index) * this.itemHeight + 'px';
      div.style.height = this.itemHeight + 'px';
      this.container.appendChild(div);
    });

    this.container.style.height = this.items.length * this.itemHeight + 'px';
  }
}

// Sanitizing HTML (prevent XSS)
function sanitizeHTML(html) {
  const div = document.createElement('div');
  div.textContent = html; // Treats as text, not HTML
  return div.innerHTML;
}

// Or use DOMPurify library for rich HTML
// const clean = DOMPurify.sanitize(dirtyHTML);

// Template element
const template = document.getElementById('item-template');
const clone2 = template.content.cloneNode(true);

// Modify clone
clone2.querySelector('.title').textContent = 'New Item';
clone2.querySelector('.description').textContent = 'Description';

document.getElementById('items').appendChild(clone2);

// Mutation Observer - watch DOM changes
const observer = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    console.log('Type:', mutation.type);
    console.log('Target:', mutation.target);
  });
});

observer.observe(document.body, {
  childList: true,
  subtree: true,
  attributes: true,
  characterData: true
});

// Stop observing
// observer.disconnect();

// Intersection Observer - visibility tracking
const intersectionObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('Element is visible');
      entry.target.classList.add('visible');
    }
  });
});

document.querySelectorAll('.lazy-load').forEach(el => {
  intersectionObserver.observe(el);
});
1 file · javascript Explain with highlit

DOM manipulation modifies HTML structure using JavaScript methods like createElement(), appendChild(), and removeChild(). I minimize reflows by batching DOM changes together. Using DocumentFragment groups multiple changes before inserting into DOM. The innerHTML property replaces content but risks XSS attacks without sanitization. Prefer textContent for text-only updates. The insertAdjacentHTML() method inserts HTML at specific positions. I use classList.add(), classList.remove(), classList.toggle() for CSS class manipulation. The dataset property accesses data attributes. Virtual scrolling and lazy rendering improve performance for large lists. Understanding browser reflow and repaint cycles prevents performance issues. Modern frameworks use virtual DOM for efficient updates.