| |

JavaScript 61 🧬 DOM manipulation

const heading = document.querySelector('h1');
const boxes = document.querySelectorAll('.box');

const newEl = document.createElement('div');
newEl.textContent = 'Hello';
document.body.appendChild(newEl);

heading.textContent = 'Updated';
heading.innerHTML = '<span>Rich</span>';
heading.classList.add('active');
heading.classList.remove('hidden');
heading.classList.toggle('highlight');

heading.setAttribute('data-id', '42');
console.log(heading.getAttribute('data-id'));

heading.style.color = 'red';
heading.style.fontSize = '24px';

heading.addEventListener('click', () => {
  console.log('clicked');
});

const parent = document.querySelector('#container');
parent.removeChild(newEl);
newEl.remove();

const clone = heading.cloneNode(true);
parent.appendChild(clone);

The DOM (Document Object Model) is a tree representation of an HTML page. JavaScript can read it, modify it, add to it, and remove from it — which is how every interactive web page works. document.querySelector, createElement, appendChild, addEventListener — these are the tools.

Key point: The DOM is a live tree. When you change it, the page changes. Every update — even one character — triggers the browser to re-render. Efficient DOM manipulation means batching updates and touching the DOM as little as possible.


a – Selecting elements

Before you can change the DOM, you have to find the elements you want.

The four main methods:

MethodReturnsSelector
getElementById(id)One elementID
querySelector(sel)First matchCSS selector
querySelectorAll(sel)NodeListCSS selector
getElementsByClassNameLive HTMLCollectionClass

querySelector — the universal picker:

const heading = document.querySelector('h1');
const firstBox = document.querySelector('.box');
const byId = document.querySelector('#main');
const nested = document.querySelector('.card .title');

Takes any valid CSS selector — tags, classes, IDs, attributes, pseudo-classes.

document.querySelector('input[type="email"]');
document.querySelector('ul > li:first-child');
document.querySelector('a[href^="https"]');

querySelectorAll — every match:

const boxes = document.querySelectorAll('.box');

console.log(boxes.length);
// [ 3 ]

boxes.forEach(box => console.log(box.textContent));

Returns a NodeList — not an array, but iterable. Use forEach, spread it, or Array.from:

const arr = [...boxes];
const arr2 = Array.from(boxes);

getElementById — fastest for IDs:

const main = document.getElementById('main');

Slightly faster than querySelector('#main') because it doesn’t parse a selector.

Live vs static collections:

MethodTypeLive?
querySelectorAllNodeListStatic
getElementsByClassNameHTMLCollectionLive
getElementsByTagNameHTMLCollectionLive
childrenHTMLCollectionLive
const staticList = document.querySelectorAll('.item');
const liveList = document.getElementsByClassName('item');

// Add a new element
document.body.appendChild(makeItem());

console.log(staticList.length);   // original count — unchanged
console.log(liveList.length);     // updated — includes new element

Live collections update automatically — useful sometimes, surprising often.

Selecting within an element:

const card = document.querySelector('.card');
const title = card.querySelector('.title');
const buttons = card.querySelectorAll('button');

You can call querySelector on any element to scope the search.

Selecting forms and inputs:

const form = document.querySelector('form');
console.log(form.elements);        // all inputs, buttons, selects
console.log(form.name.value);      // named input access

Selecting special elements:

document.documentElement;   // <html>
document.head;              // <head>
document.body;              // <body>
document.title;             // <title> text
document.forms;             // all <form>
document.images;            // all <img>
document.links;             // all <a href>

Common patterns:

// Get all buttons in a nav
const navButtons = document.querySelectorAll('nav button');

// Find the currently active tab
const activeTab = document.querySelector('.tab.active');

// Find inputs that aren't disabled
const inputs = document.querySelectorAll('input:not([disabled])');

Handling missing elements:

const el = document.querySelector('.missing');

if (el) {
  el.textContent = 'found';
}

// Or with optional chaining
el?.classList.add('active');

querySelector returns null if nothing matches. Always guard.

Selecting multiple kinds:

const elements = document.querySelectorAll('h1, h2, h3');

CSS selectors let you match multiple patterns at once.


b – Modifying elements

Once you’ve selected an element, you can change its content, attributes, styles, and classes.

Text content:

const el = document.querySelector('.title');

el.textContent = 'New title';
el.textContent = '';   // clear

textContent sets or gets the plain text — safe from HTML injection.

HTML content:

el.innerHTML = '<strong>Bold</strong>';

innerHTML parses HTML. Powerful but dangerous with user input — XSS injection is real.

// ❌ Never do this with user input
el.innerHTML = userComment;

// ✅ Use textContent for plain text
el.textContent = userComment;

// ✅ Or sanitize
el.innerHTML = DOMPurify.sanitize(userComment);

innerText vs textContent:

FeatureinnerTexttextContent
Reads rendered text
Includes hidden text
Respects CSS
PerformanceSlowerFaster
Use forVisible textRaw content
el.innerText = 'Visible text';

Prefer textContent for reads and writes unless you specifically need rendered text.

Attributes:

el.setAttribute('data-id', '42');
el.getAttribute('data-id');
el.hasAttribute('data-id');
el.removeAttribute('data-id');

Direct property access:

img.src = '/new.png';
input.value = 'hello';
a.href = 'https://example.com';
checkbox.checked = true;

Most HTML attributes are exposed as properties.

dataset for data-* attributes:

// <div data-user-id="42" data-role="admin">
el.dataset.userId;   // "42"
el.dataset.role;     // "admin"

el.dataset.userId = '99';

data-user-id becomes dataset.userId — kebab-case to camelCase.

Classes:

el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open');
el.classList.contains('active');
el.classList.replace('old', 'new');

Full class control:

el.className = 'box active';       // replace all
console.log(el.className);          // 'box active'

Styles:

el.style.color = 'red';
el.style.fontSize = '24px';
el.style.backgroundColor = '#f0f0f0';

CSS property names use camelCase for hyphenated ones.

el.style.cssText = 'color: red; font-size: 24px;';

Or use the CSS text format — replaces all inline styles.

Reading computed styles:

const styles = getComputedStyle(el);
console.log(styles.color);
console.log(styles.fontSize);

getComputedStyle returns the final computed value — including from CSS files.

Creating elements:

const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello';

const text = document.createTextNode('plain text');

Cloning elements:

const clone = el.cloneNode(true);   // deep clone — includes children
const shallow = el.cloneNode(false); // shallow — element only

Removing elements:

el.remove();                          // modern
parent.removeChild(el);               // older

Replacing elements:

parent.replaceChild(newEl, oldEl);

Inserting elements:

parent.appendChild(child);                // at end
parent.insertBefore(child, reference);    // before reference
parent.prepend(child);                    // at start (modern)
parent.append(child);                     // at end (modern)
reference.before(child);                  // before reference
reference.after(child);                   // after reference

Modern insertion methods:

el.append('text', otherEl, anotherEl);   // multiple
el.prepend('first');
el.before('before');
el.after('after');
el.replaceWith(newEl);

Bulk DOM updates:

const fragment = document.createDocumentFragment();

for (const item of items) {
  const li = document.createElement('li');
  li.textContent = item;
  fragment.appendChild(li);
}

ul.appendChild(fragment);

DocumentFragment batches updates — one DOM insertion instead of many.

Templates:

<template id="row">
  <tr>
    <td class="name"></td>
    <td class="email"></td>
  </tr>
</template>
const template = document.getElementById('row');
const clone = template.content.cloneNode(true);
clone.querySelector('.name').textContent = 'Alice';

The correct way to update text:

// ❌ Slow — one DOM write per call
el.textContent = 'a';
el.textContent = 'b';
el.textContent = 'c';

// ✅ Fast — single write
el.textContent = 'c';

Batch updates with a fragment:

function renderList(items) {
  const frag = document.createDocumentFragment();
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    frag.appendChild(li);
  });
  list.innerHTML = '';
  list.appendChild(frag);
}

c – Events and DOM interaction

Events let your page respond to user actions — clicks, keypresses, scrolling, input.

addEventListener — the standard:

el.addEventListener('click', () => {
  console.log('clicked');
});

The event object:

el.addEventListener('click', (event) => {
  console.log(event.type);         // 'click'
  console.log(event.target);       // the clicked element
  console.log(event.currentTarget);// the element with listener
  console.log(event.clientX);      // mouse coordinates
});

Common events:

CategoryEvents
Mouseclick, dblclick, mousedown, mouseup, mousemove, mouseover, mouseout
Keyboardkeydown, keyup, keypress (deprecated)
Formsubmit, change, input, focus, blur
DocumentDOMContentLoaded, load, beforeunload
Touchtouchstart, touchend, touchmove
Scrollscroll, wheel
Dragdragstart, drag, dragend, drop

Removing listeners:

function handleClick() { ... }

el.addEventListener('click', handleClick);
el.removeEventListener('click', handleClick);

You must pass the same function reference to remove.

Once option:

el.addEventListener('click', handler, { once: true });

Fires once, then auto-removes.

Event delegation:

Instead of attaching to each child, attach to a parent:

list.addEventListener('click', (event) => {
  if (event.target.matches('.delete-btn')) {
    const id = event.target.dataset.id;
    deleteItem(id);
  }
});

One listener handles all children — efficient and dynamic-proof.

Preventing default:

form.addEventListener('submit', (event) => {
  event.preventDefault();
  // handle manually
});

Stopping propagation:

el.addEventListener('click', (event) => {
  event.stopPropagation();
});

Prevents the event from bubbling to ancestors.

Event phases:

┌──────────────────────────────────────────────┐
│  Capturing (top → down)                      │
│         │                                    │
│         ▼                                    │
│  Target                                      │
│         │                                    │
│         ▼                                    │
│  Bubbling (bottom → top)                     │
└──────────────────────────────────────────────┘
el.addEventListener('click', handler);          // bubble (default)
el.addEventListener('click', handler, true);    // capture
el.addEventListener('click', handler, { capture: true });

Ready state:

document.addEventListener('DOMContentLoaded', () => {
  // DOM ready — safe to query
});

Or use defer on script tags:

<script src="app.js" defer></script>

MutationObserver — watch DOM changes:

const observer = new MutationObserver((mutations) => {
  mutations.forEach(m => console.log(m.type));
});

observer.observe(target, {
  childList: true,
  attributes: true,
  subtree: true
});

observer.disconnect();

IntersectionObserver — visibility detection:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('visible');
    }
  });
});

observer.observe(el);

ResizeObserver — element size changes:

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    console.log(entry.contentRect.width);
  }
});

observer.observe(el);

Common event pattern — form handling:

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const data = Object.fromEntries(new FormData(form));

  try {
    await fetch('/api', {
      method: 'POST',
      body: JSON.stringify(data)
    });
  } catch (err) {
    console.error(err);
  }
});

Common event pattern — input validation:

input.addEventListener('input', (event) => {
  const value = event.target.value;
  isValid = value.length >= 3;
  feedback.textContent = isValid ? '✓' : 'Too short';
});

Common event pattern — keyboard shortcuts:

document.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') closeModal();
  if (event.ctrlKey && event.key === 's') {
    event.preventDefault();
    save();
  }
});

Complete Example Session

// ============================================
// PART 1: SELECT
// ============================================

const heading = document.querySelector('h1');
console.log(heading?.tagName);
// [ 'H1' ]

const boxes = document.querySelectorAll('.box');
console.log(boxes.length);
// [ N ]

// ============================================
// PART 2: CREATE
// ============================================

const newEl = document.createElement('div');
newEl.textContent = 'Hello';
document.body.appendChild(newEl);

// ============================================
// PART 3: TEXT
// ============================================

heading.textContent = 'Updated';
// [ heading now shows "Updated" ]

// ============================================
// PART 4: HTML
// ============================================

heading.innerHTML = '<span>Rich</span>';
// [ heading contains a <span> ]

// ============================================
// PART 5: CLASSES
// ============================================

heading.classList.add('active');
heading.classList.remove('hidden');
heading.classList.toggle('highlight');

// ============================================
// PART 6: ATTRIBUTES
// ============================================

heading.setAttribute('data-id', '42');
console.log(heading.getAttribute('data-id'));
// [ '42' ]

// ============================================
// PART 7: STYLE
// ============================================

heading.style.color = 'red';
heading.style.fontSize = '24px';

// ============================================
// PART 8: EVENTS
// ============================================

heading.addEventListener('click', () => {
  console.log('clicked');
});

// ============================================
// PART 9: REMOVE
// ============================================

const parent = document.querySelector('#container');
parent.removeChild(newEl);
newEl.remove();

// ============================================
// PART 10: CLONE
// ============================================

const clone = heading.cloneNode(true);
parent.appendChild(clone);

// ============================================
// PART 11: DATASET
// ============================================

// <div data-user-id="42" data-role="admin">
el.dataset.userId = '99';
console.log(el.dataset.role);
// [ 'admin' ]

// ============================================
// PART 12: QUERY WITHIN
// ============================================

const card = document.querySelector('.card');
const title = card.querySelector('.title');

// ============================================
// PART 13: FRAGMENT
// ============================================

const frag = document.createDocumentFragment();
for (let i = 0; i < 3; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  frag.appendChild(li);
}
document.querySelector('ul').appendChild(frag);

// ============================================
// PART 14: EVENT DELEGATION
// ============================================

list.addEventListener('click', (event) => {
  if (event.target.matches('.delete')) {
    event.target.closest('li').remove();
  }
});

// ============================================
// PART 15: PREVENT DEFAULT
// ============================================

form.addEventListener('submit', (event) => {
  event.preventDefault();
});

// ============================================
// PART 16: DOMContentLoaded
// ============================================

document.addEventListener('DOMContentLoaded', () => {
  console.log('ready');
});

// ============================================
// PART 17: GETCOMPUTEDSTYLE
// ============================================

const styles = getComputedStyle(heading);
console.log(styles.color);
// [ 'rgb(255, 0, 0)' ]

// ============================================
// PART 18: MUTATIONOBSERVER
// ============================================

const observer = new MutationObserver((mutations) => {
  console.log('DOM changed');
});
observer.observe(document.body, { childList: true, subtree: true });

// ============================================
// PART 19: INSERT MODERN
// ============================================

el.before('before');
el.after('after');
el.replaceWith(otherEl);

// ============================================
// PART 20: FULL SCRIPT
// ============================================

const heading61 = document.querySelector('h1');
const boxes61 = document.querySelectorAll('.box');

const newEl61 = document.createElement('div');
newEl61.textContent = 'Hello';
document.body.appendChild(newEl61);

heading61.textContent = 'Updated';
heading61.innerHTML = '<span>Rich</span>';
heading61.classList.add('active');
heading61.classList.remove('hidden');
heading61.classList.toggle('highlight');

heading61.setAttribute('data-id', '42');
console.log(heading61.getAttribute('data-id'));

heading61.style.color = 'red';
heading61.style.fontSize = '24px';

heading61.addEventListener('click', () => {
  console.log('clicked');
});

const parent61 = document.querySelector('#container');
parent61.removeChild(newEl61);
newEl61.remove();

const clone61 = heading61.cloneNode(true);
parent61.appendChild(clone61);

Quick Reference

Selecting

MethodReturns
getElementById(id)One element
querySelector(sel)First match
querySelectorAll(sel)NodeList
getElementsByClassName(cls)Live HTMLCollection
getElementsByTagName(tag)Live HTMLCollection
el.closest(sel)Nearest ancestor
el.matches(sel)Boolean

Content

PropertyPurpose
textContentPlain text (safe)
innerTextVisible text
innerHTMLHTML (dangerous)
outerHTMLElement + content
valueForm input value

Classes

MethodPurpose
classList.add(...)Add classes
classList.remove(...)Remove classes
classList.toggle(cls)Toggle
classList.contains(cls)Check
classList.replace(a, b)Replace
classNameAll classes as string

Attributes

MethodPurpose
setAttribute(name, val)Set
getAttribute(name)Get
hasAttribute(name)Check
removeAttribute(name)Remove
dataset.*data-* access

Styles

MethodPurpose
style.prop = valInline style
style.cssText = '...'Replace all
getComputedStyle(el)Final computed

Creating and Inserting

MethodPurpose
createElement(tag)Create element
createTextNode(str)Create text
createDocumentFragment()Batch container
cloneNode(deep)Clone
appendChild(node)Add at end
prepend(node)Add at start
append(...)Add multiple
insertBefore(new, ref)Before reference
before(node)Before element
after(node)After element
replaceWith(node)Replace

Removing

MethodPurpose
el.remove()Modern
parent.removeChild(el)Older

Events

MethodPurpose
addEventListener(type, fn)Attach
removeEventListener(type, fn)Detach
event.preventDefault()Stop default
event.stopPropagation()Stop bubbling
event.targetTriggered element
event.currentTargetListener element

Common Events

CategoryEvents
Mouseclick, mouseenter, mouseleave
Keyboardkeydown, keyup
Formsubmit, input, change
DocumentDOMContentLoaded, load

Observers

ObserverPurpose
MutationObserverDOM changes
IntersectionObserverVisibility
ResizeObserverSize changes

Best Practices

Do This:

// Use querySelector for flexibility
const el = document.querySelector('.card');       // ✅

// Use textContent for user data
el.textContent = userInput;                       // ✅

// Batch DOM updates with fragments
const frag = document.createDocumentFragment();   // ✅

// Use event delegation for lists
list.addEventListener('click', handler);          // ✅

// Guard against missing elements
el?.classList.add('active');                      // ✅

// Use DOMContentLoaded
document.addEventListener('DOMContentLoaded', fn); // ✅

// Remove listeners to prevent leaks
el.removeEventListener('click', handler);         // ✅

// Use classList instead of className
el.classList.add('active');                       // ✅

Don’t Do This:

// Don't use innerHTML with user input
el.innerHTML = userInput;                         // ❌ XSS

// Don't query in a loop
for (let i = 0; i < 1000; i++) {
  document.querySelector('.item');                // ❌ slow
}

// Don't attach listeners to every list item
items.forEach(item => item.addEventListener(...)); // ⚠️  use delegation

// Don't update DOM in a loop
for (const x of arr) {
  el.textContent += x;                            // ❌ many reflows
}

// Don't use document.write
document.write('<p>Hello</p>');                   // ❌ obsolete

// Don't forget to remove observers
const obs = new MutationObserver(fn);
// obs.disconnect() when done                   // ✅

Common Pitfalls

PitfallProblemSolution
querySelectorAll in loopSlowCache result
innerHTML with user inputXSSUse textContent
Missing null checkTypeErrorUse ?. or if
Attaching listeners per itemMany listenersEvent delegation
Updating DOM in loopReflow stormBatch with fragment
Live vs static confusionWrong .lengthKnow which is which
Forgetting to removeMemory leakRemove on cleanup
innerText performanceSlow readsUse textContent

Real-World Examples

1. Select and Modify

const h = document.querySelector('h1');
h.textContent = 'Hello';

2. Create and Add

const div = document.createElement('div');
div.textContent = 'New';
document.body.appendChild(div);

3. Add Class

el.classList.add('active');

4. Toggle Class

el.classList.toggle('open');

5. Set Attribute

el.setAttribute('data-id', '42');

6. Set Style

el.style.color = 'red';

7. Add Event Listener

el.addEventListener('click', () => console.log('clicked'));

8. Remove Element

el.remove();

9. Clone

const clone = el.cloneNode(true);

10. Data Attribute

el.dataset.userId = '99';
console.log(el.dataset.userId);
// [ '99' ]

11. Query Within

const card = document.querySelector('.card');
const title = card.querySelector('.title');

12. Document Fragment

const frag = document.createDocumentFragment();
for (const x of items) {
  const li = document.createElement('li');
  li.textContent = x;
  frag.appendChild(li);
}
list.appendChild(frag);

13. Event Delegation

list.addEventListener('click', (e) => {
  if (e.target.matches('.delete')) {
    e.target.closest('li').remove();
  }
});

14. Prevent Default

form.addEventListener('submit', (e) => {
  e.preventDefault();
});

15. DOMContentLoaded

document.addEventListener('DOMContentLoaded', () => {
  // ready
});

16. Get Computed Style

const color = getComputedStyle(el).color;

17. Mutation Observer

const obs = new MutationObserver(list => {
  console.log(list.length);
});
obs.observe(target, { childList: true });

18. Insert with Modern API

el.before(otherEl);
el.after(otherEl);

19. Replace

oldEl.replaceWith(newEl);

20. Full Script

const heading61 = document.querySelector('h1');
const boxes61 = document.querySelectorAll('.box');

const newEl61 = document.createElement('div');
newEl61.textContent = 'Hello';
document.body.appendChild(newEl61);

heading61.textContent = 'Updated';
heading61.innerHTML = '<span>Rich</span>';
heading61.classList.add('active');
heading61.classList.remove('hidden');
heading61.classList.toggle('highlight');

heading61.setAttribute('data-id', '42');
console.log(heading61.getAttribute('data-id'));

heading61.style.color = 'red';
heading61.style.fontSize = '24px';

heading61.addEventListener('click', () => {
  console.log('clicked');
});

const parent61 = document.querySelector('#container');
parent61.removeChild(newEl61);
newEl61.remove();

const clone61 = heading61.cloneNode(true);
parent61.appendChild(clone61);

Visual: DOM Tree

┌──────────────────────────────────────────────┐
│                   document                   │
│                       │                      │
│                       ▼                      │
│                      html                    │
│                    /      \                  │
│                 head      body               │
│                 │          │                 │
│               title       div                │
│                          /   \               │
│                        h1    ul              │
│                              / | \           │
│                            li li li          │
│                                              │
└──────────────────────────────────────────────┘

Visual: Live vs Static Collections

┌──────────────────────────────────────────────┐
│  querySelectorAll  → NodeList (static)       │
│                                              │
│  Captures current matches only               │
│  Later changes don't affect it               │
│                                              │
│  const list = querySelectorAll('.x');        │
│  list.length → N                             │
│  addMore();                                  │
│  list.length → N (unchanged)                 │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  getElementsByClassName → HTMLCollection     │
│                     (live)                   │
│                                              │
│  Reflects current DOM                        │
│  const list = getElementsByClassName('x');   │
│  list.length → N                             │
│  addMore();                                  │
│  list.length → N+M (updated)                 │
│                                              │
└──────────────────────────────────────────────┘

Visual: Event Flow

┌──────────────────────────────────────────────┐
│  html                                        │
│   │  ↑                                       │
│   ▼  │                                       │
│  body    ← capture: top → down               │
│   │  ↑                                       │
│   ▼  │                                       │
│  div                                        │
│   │  ↑                                       │
│   ▼  │                                       │
│  button   ← target                           │
│   │  ↑                                       │
│   ▼  │                                       │
│   ──────  ← bubble: bottom → top             │
│                                              │
│  event.stopPropagation() stops at that point │
│  event.preventDefault() stops default action │
│                                              │
└──────────────────────────────────────────────┘

Visual: Event Delegation

┌──────────────────────────────────────────────┐
│  Instead of:                                 │
│                                              │
│  <ul>                                        │
│    <li> a <button>X</button></li>            │
│    <li> b <button>X</button></li>            │
│    <li> c <button>X</button></li>            │
│    ... 100 more                              │
│                                              │
│  Attaching 100 listeners ❌                  │
│                                              │
│  Do this:                                    │
│                                              │
│  ul.addEventListener('click', e => {         │
│    if (e.target.matches('button')) {         │
│      e.target.closest('li').remove();        │
│    }                                         │
│  });                                         │
│                                              │
│  One listener handles all ✅                 │
│                                              │
└──────────────────────────────────────────────┘

Summary

ConceptMethodExample
Select onequerySelectorquerySelector('h1')
Select manyquerySelectorAllquerySelectorAll('.box')
By IDgetElementByIdgetElementById('main')
TexttextContentel.textContent = 'x'
HTMLinnerHTMLel.innerHTML = '<b>x</b>'
ClassesclassListel.classList.add('x')
AttributessetAttributeel.setAttribute('id', '1')
Data attrsdatasetel.dataset.userId
Stylestyle.propel.style.color = 'red'
CreatecreateElementcreateElement('div')
AppendappendChildparent.appendChild(el)
Modern insertappend, before, afterel.before(x)
Removeremoveel.remove()
ClonecloneNodeel.cloneNode(true)
FragmentcreateDocumentFragmentBatch inserts
ListenaddEventListenerel.addEventListener(...)
DelegateParent listenerlist.addEventListener(...)
ObserveMutationObserverWatch changes

Key takeaways:

  • querySelector and querySelectorAll accept any CSS selector — they’re the modern standard
  • getElementById is fastest for IDs
  • textContent is safe; innerHTML is dangerous with user input
  • classList is cleaner than manipulating className strings
  • dataset provides clean access to data-* attributes
  • createDocumentFragment batches DOM inserts into one reflow
  • Use event delegation — one listener on a parent beats many on children
  • preventDefault stops default behavior; stopPropagation stops bubbling
  • Live collections (getElementsByClassName) update automatically; static (querySelectorAll) don’t
  • addEventListener is the correct way to bind events — never onclick = ...
  • DOMContentLoaded ensures the DOM is ready before you query it
  • Observe changes with MutationObserver, IntersectionObserver, ResizeObserver
  • Cache queries — don’t re-query in a loop

Remember: DOM manipulation is how you make pages interactive. Select with querySelector, change with textContent or classList, create with createElement, insert with appendChild or fragments, listen with addEventListener. Batch updates, delegate events, and never trust user input in innerHTML. Master the DOM, and every web page becomes your playground.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!