DOM Manipulation: innerHTML XSS, clone & DocumentFragment
innerHTML se hack ho jayega agar user input daala. textContent safe hai. cloneNode listeners copy nahi karta. insertAdjacentHTML behtaar hai.
document.createElement('div') creates an element NOT attached to the DOM — you must append it later. It exists only in memory until you explicitly add it to the page.
document.createTextNode('hello') creates a text node — rarely needed because textContent is simpler and does the same job.
element.textContent = 'hello' is the simplest way to set text — no text node creation needed, no HTML parsing, no XSS risk.
Elements created with createElement are in memory only — invisible until appended to the DOM. You can set attributes, classes, and styles on the element before appending, which means zero reflows until it's attached.
// Create element — NOT in DOM yet
const div = document.createElement('div');
div.className = 'card';
div.id = 'card-1';
div.textContent = 'Hello Hyderabad!'; // Simple text setting
div.style.color = 'blue';
// Not visible yet! Must append to DOM
document.body.appendChild(div); // NOW it appears
// createTextNode — rarely needed
const textNode = document.createTextNode('Raw text');
div.appendChild(textNode); // Adds text AFTER existing content
// textContent vs createTextNode:
// div.textContent = 'Hello'; ✅ Simple, replaces all children
// div.appendChild(createTextNode('Hello')); ❌ Verbose, appends instead of replacingtextContent: Returns ALL text including hidden elements. FAST — no reflow. Safe from XSS.
innerText: Returns only VISIBLE text. SLOW — triggers reflow to check visibility! Use sparingly.
innerHTML: Parses HTML string into DOM nodes. SLOWEST. XSS RISK with user input!
innerHTML security: element.innerHTML = userInput allows script injection if userInput contains HTML.
ALWAYS use textContent for user text. Use innerHTML only with trusted HTML (your own templates, not user data).
// ⚠️ XSS ATTACK — NEVER do this!
const userInput = '<img src=x onerror=alert("hacked!")>';
const div = document.getElementById('output');
div.innerHTML = userInput; // CODE EXECUTES! XSS vulnerability!
// ✅ SAFE: textContent escapes everything
div.textContent = userInput; // Shows the raw text. No HTML parsing.
// Performance comparison:
// textContent: Reads text from DOM tree directly. No layout. O(n).
// innerText: Must determine visibility for each element → REFLOW! O(n × layout).
// innerHTML: Serializes DOM to HTML string (read) or parses HTML to DOM (write).
// Reading text from hidden elements
const hidden = document.querySelector('.hidden-element');
hidden.textContent; // Returns text even if hidden ✅
hidden.innerText; // Returns "" if element is display:none ❌
// When innerHTML IS appropriate (trusted HTML only)
const template = '<div class="card"><h2>Title</h2><p>Content</p></div>';
container.innerHTML = template; // OK — this is YOUR template, not user input
append(...nodes): Accepts multiple arguments AND strings. Does NOT return the appended node.
appendChild(node): Single node only. RETURNS the appended node.
prepend(), before(), after(), replaceWith(): Modern methods — more intuitive than insertBefore.
remove(): Removes element from DOM — no parent reference needed.
removeChild(child): Returns removed child — need parent reference.
cloneNode(false): Shallow — copies element only, no children.
cloneNode(true): Deep — copies element + all descendants. EVENT LISTENERS ARE NEVER CLONED!
// append vs appendChild
const list = document.getElementById('list');
const li1 = document.createElement('li');
const li2 = document.createElement('li');
list.append(li1, li2, 'text node'); // Multiple! Strings auto-converted!
list.appendChild(li1); // Single only. Returns li1.
// Modern insertion methods
const newItem = document.createElement('li');
list.prepend(newItem); // Insert as FIRST child
li1.before(newItem); // Insert BEFORE li1 (sibling)
li1.after(newItem); // Insert AFTER li1 (sibling)
li1.replaceWith(newItem); // Replace li1 with newItem
// Remove
li1.remove(); // Simple. No return.
list.removeChild(li2); // Need parent. Returns li2.
// Clone — EVENT LISTENERS NOT COPIED!
const original = document.getElementById('card');
original.addEventListener('click', () => console.log('clicked'));
const shallow = original.cloneNode(false); // Empty card, no children
const deep = original.cloneNode(true); // Full copy with children
deep.addEventListener('click', () => console.log('clicked'));
// Must re-attach listeners! They don't clone!XSS (Cross-Site Scripting): Injecting malicious scripts into a webpage through user input.
Attack vector: userInput contains <script> or <img onerror=...> or <svg onload=...>.
innerHTML does NOT execute <script> tags (browser security). BUT it DOES execute onerror, onload, onfocus on elements.
The attack flow: User enters malicious input → stored in DB → displayed via innerHTML → script runs.
Prevention: ALWAYS sanitize user input. Use textContent for text. Use DOMPurify library for HTML.
// XSS attack vectors that WORK with innerHTML:
// Vector 1: img onerror
const attack1 = '<img src="x" onerror="alert(document.cookies)">';
div.innerHTML = attack1; // EXECUTES! Image fails, onerror runs.
// Vector 2: svg onload
const attack2 = '<svg onload="alert(1)">';
div.innerHTML = attack2; // EXECUTES on load!
// Vector 3: autofocus + onfocus
const attack3 = '<input autofocus onfocus="alert(1)">';
div.innerHTML = attack3; // EXECUTES when input gets focus!
// ❌ innerHTML does NOT execute <script> tags
const attack4 = '<script>alert(1)</script>';
div.innerHTML = attack4; // Does NOT execute (browser blocks it)
// But this is NOT safe — other vectors still work!
// ✅ Defense 1: textContent (escapes everything)
div.textContent = userInput; // 100% safe. Shows raw text.
// ✅ Defense 2: DOMPurify (if you need HTML)
// const clean = DOMPurify.sanitize(userInput);
// div.innerHTML = clean; // Strips all dangerous HTML
insertAdjacentHTML(position, html): Parse and insert HTML at a specific position — faster than innerHTML for partial updates.
Positions: beforebegin (before element), afterbegin (first child), beforeend (last child), afterend (after element).
innerHTML is faster for LARGE HTML strings (browser's C++ HTML parser vs JS loop).
createElement is safer and more flexible for small numbers of elements.
insertAdjacentHTML does NOT destroy existing children (unlike innerHTML which replaces all).
// insertAdjacentHTML — 4 positions
const card = document.getElementById('card');
card.insertAdjacentHTML('beforebegin', '<div>Before card</div>');
card.insertAdjacentHTML('afterbegin', '<div>First child</div>');
card.insertAdjacentHTML('beforeend', '<div>Last child</div>');
card.insertAdjacentHTML('afterend', '<div>After card</div>');
// ✅ insertAdjacentHTML: Adds WITHOUT destroying existing children
card.insertAdjacentHTML('beforeend', '<p>New paragraph</p>');
// Existing children remain! No re-parsing!
// ❌ innerHTML += : Destroys and re-creates ALL children
card.innerHTML += '<p>New paragraph</p>';
// 1. Serializes existing DOM to string
// 2. Appends new HTML to string
// 3. Re-parses ENTIRE string back to DOM
// 4. ALL event listeners on existing children LOST!
DocumentFragment: A lightweight container for batch DOM operations. Append all children to the fragment first, then append the fragment to the DOM once — single reflow instead of many.
// DocumentFragment — batch DOM operations
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = 'Item ' + i;
fragment.appendChild(li); // No reflow — not in DOM yet!
}
list.appendChild(fragment); // ONE reflow for all 100 items!
// Fragment disappears — its children move directly into list
// Performance summary:
// Large HTML (100+ elements): innerHTML = faster (C++ parser)
// Small updates (1-5 elements): createElement = safer, same speed
// Partial updates: insertAdjacentHTML = best (no destruction)
// Batch operations: DocumentFragment = best (single reflow)
// User text: textContent = always (XSS-safe)
Lo kar liya — Key Points:
- ✅ document.createElement creates elements in memory — they're invisible until appended to the DOM
- ✅ textContent is safe from XSS and fastest for text; innerText triggers reflow; innerHTML parses HTML (XSS risk)
- ✅ NEVER use innerHTML with user input — use textContent for text or DOMPurify for HTML
- ✅ append accepts multiple arguments and strings; appendChild accepts single node and returns it
- ✅ cloneNode(true) copies element and children but NEVER copies event listeners
- ✅ insertAdjacentHTML inserts HTML at specific positions WITHOUT destroying existing children
- ✅ innerHTML += destroys all existing event listeners — use insertAdjacentHTML or append instead
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login