DOM Manipulation and Events
Understand DOM rendering and event manipulation. Study DOM traversal, event capturing, event bubbling, delegation, and event listener configurations.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What are the different ways to select DOM elements in JavaScript?
JavaScript provides several methods to select elements from the DOM, each with different return types and use cases.
| Method | Returns | Notes |
|---|---|---|
| getElementById() | Single element | Fastest, returns null if not found |
| getElementsByClassName() | Live HTMLCollection | Updates automatically when DOM changes |
| getElementsByTagName() | Live HTMLCollection | Selects by tag name |
| querySelector() | Single element (first match) | Accepts any CSS selector |
| querySelectorAll() | Static NodeList | Does NOT update automatically, supports forEach |
// getElementById
const header = document.getElementById('header');
// getElementsByClassName - live collection
const items = document.getElementsByClassName('item');
console.log(items.length); // updates if items are added/removed
// getElementsByTagName
const allDivs = document.getElementsByTagName('div');
// querySelector - first match, any CSS selector
const firstButton = document.querySelector('.btn');
const nestedItem = document.querySelector('ul > li.active');
// querySelectorAll - static NodeList, all matches
const allButtons = document.querySelectorAll('.btn');
allButtons.forEach(btn => console.log(btn.textContent)); // NodeList supports forEach directly
// Converting HTMLCollection to array for array methods
const itemsArray = Array.from(items);
itemsArray.map(item => item.textContent);
// Live vs static difference
const liveList = document.getElementsByClassName('box');
const staticList = document.querySelectorAll('.box');
document.body.innerHTML += '<div class="box"></div>';
console.log(liveList.length); // increased automatically
console.log(staticList.length); // stays the same, was a snapshot
Q2. How do you create, modify, and remove DOM elements dynamically?
JavaScript provides methods to build up new elements, change existing ones, and clean up elements that are no longer needed.
// Creating an element
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello World';
// Setting attributes
div.setAttribute('data-id', '123');
div.id = 'myCard';
// Adding to the DOM
document.body.appendChild(div);
// Alternative: append (allows multiple nodes/strings, no return value needed)
document.body.append(div, 'some text');
// Inserting at a specific position
const reference = document.getElementById('reference');
reference.before(div); // insert before reference
reference.after(div); // insert after reference
reference.parentNode.insertBefore(div, reference); // older API equivalent
// Modifying content
div.textContent = 'Updated text'; // safe, treats content as plain text
div.innerHTML = '<strong>Bold</strong> text'; // parses HTML, XSS risk with user input
// Modifying styles and classes
div.style.color = 'blue';
div.style.backgroundColor = '#f0f0f0';
div.classList.add('active');
div.classList.remove('hidden');
div.classList.toggle('selected');
div.classList.contains('active'); // true
// Removing elements
div.remove(); // modern, direct removal
// Older way:
// div.parentNode.removeChild(div);
// Cloning an element
const clone = div.cloneNode(true); // true = deep clone (includes children)
document.body.appendChild(clone);
// Clearing all children efficiently
const container = document.getElementById('container');
container.replaceChildren(); // removes all children, modern approach
// Older way: while (container.firstChild) container.removeChild(container.firstChild);
Q3. How do you add and remove event listeners in JavaScript?
addEventListener() attaches a function to run when an event occurs, and removeEventListener() detaches it. Both require the exact same function reference to work together.
const button = document.querySelector('button');
// Adding an event listener
function handleClick(event) {
console.log('Button clicked!', event.target);
}
button.addEventListener('click', handleClick);
// Removing - MUST use the same named function reference
button.removeEventListener('click', handleClick);
// This does NOT work - anonymous functions can't be removed
button.addEventListener('click', () => console.log('clicked'));
button.removeEventListener('click', () => console.log('clicked')); // different reference, does nothing
// Adding multiple listeners for the same event - all execute
button.addEventListener('click', () => console.log('Handler 1'));
button.addEventListener('click', () => console.log('Handler 2'));
// Listener options
button.addEventListener('click', handleClick, {
once: true, // automatically removes itself after firing once
passive: true, // tells browser this won't call preventDefault (perf boost for scroll/touch)
capture: false // whether to listen during capture phase (default: bubble phase)
});
// Old-style (inline / on-property) - generally avoided in modern code
button.onclick = function() {
console.log('Only one handler possible this way - overwrites previous');
};
// Cleanup pattern for dynamically created elements
function setupTemporaryListener(element) {
const handler = () => console.log('Temp click');
element.addEventListener('click', handler);
return () => element.removeEventListener('click', handler); // cleanup function
}
const cleanup = setupTemporaryListener(button);
// later: cleanup();
Q4. What is event delegation and why is it useful?
Event delegation is a technique of attaching a single event listener to a parent element instead of individual listeners on each child, relying on event bubbling to catch events from children.
// WITHOUT delegation - a listener per item, doesn't work for future items
const items = document.querySelectorAll('.list-item');
items.forEach(item => {
item.addEventListener('click', () => console.log('Item clicked'));
});
// Problem: new items added later won't have this listener
// WITH delegation - one listener on the parent
const list = document.querySelector('.list');
list.addEventListener('click', function(event) {
if (event.target.classList.contains('list-item')) {
console.log('Item clicked:', event.target.textContent);
}
});
// This automatically works for dynamically added items too
const newItem = document.createElement('li');
newItem.className = 'list-item';
newItem.textContent = 'New Item';
list.appendChild(newItem); // click on this works without adding a new listener
// Using closest() for more robust matching (handles nested elements)
list.addEventListener('click', function(event) {
const item = event.target.closest('.list-item');
if (item) {
console.log('Item clicked:', item.textContent);
}
});| Benefit | Explanation |
|---|---|
| Better performance | One listener instead of hundreds for large lists |
| Works with dynamic content | New elements automatically covered, no re-binding needed |
| Less memory usage | Fewer listener objects retained in memory |
Q5. What is the difference between event bubbling and event capturing?
These are the two phases of DOM event propagation, describing the order in which nested elements receive an event.
| Phase | Direction | Default behavior |
|---|---|---|
| Capturing | Top (document) down to the target element | Off by default; opt-in with { capture: true } |
| Bubbling | Target element up to the top (document) | Default phase for most listeners |
// HTML structure: <div id='outer'><div id='inner'><button id='btn'></button></div></div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const btn = document.getElementById('btn');
// Bubbling phase listeners (default)
outer.addEventListener('click', () => console.log('Outer (bubble)'));
inner.addEventListener('click', () => console.log('Inner (bubble)'));
btn.addEventListener('click', () => console.log('Button (bubble)'));
// Clicking the button logs (bubbling goes target -> up):
// Button (bubble)
// Inner (bubble)
// Outer (bubble)
// Capturing phase listeners
outer.addEventListener('click', () => console.log('Outer (capture)'), { capture: true });
inner.addEventListener('click', () => console.log('Inner (capture)'), { capture: true });
// Full order when both phases have listeners (capture goes down first, then bubble goes up):
// Outer (capture)
// Inner (capture)
// Button (bubble)
// Inner (bubble)
// Outer (bubble)
// Stopping propagation
btn.addEventListener('click', (event) => {
event.stopPropagation(); // prevents the event from bubbling further up
console.log('Button clicked, propagation stopped');
});
// Now 'Inner (bubble)' and 'Outer (bubble)' won't fire
// stopImmediatePropagation also stops OTHER listeners on the SAME element
btn.addEventListener('click', (e) => {
e.stopImmediatePropagation();
});
btn.addEventListener('click', () => {
console.log('This never runs due to stopImmediatePropagation');
});
Q6. How do you prevent default browser behavior for an event?
event.preventDefault() stops the browser's built-in default action for an event, such as following a link or submitting a form, while still allowing the event to propagate.
// Preventing form submission to handle it with JavaScript instead
const form = document.querySelector('form');
form.addEventListener('submit', function(event) {
event.preventDefault(); // stops the page from reloading
const formData = new FormData(form);
console.log('Form data:', Object.fromEntries(formData));
// Send via fetch() instead
});
// Preventing a link from navigating
const link = document.querySelector('a');
link.addEventListener('click', function(event) {
event.preventDefault();
console.log('Link clicked but navigation prevented');
});
// Preventing right-click context menu
document.addEventListener('contextmenu', function(event) {
event.preventDefault();
});
// Preventing certain key presses
document.addEventListener('keydown', function(event) {
if (event.key === 'F5') {
event.preventDefault(); // block page refresh via F5
}
});
// Difference between preventDefault() and stopPropagation()
btn.addEventListener('click', (event) => {
event.preventDefault(); // stops the BROWSER's default action
event.stopPropagation(); // stops the event from BUBBLING to parents
// These solve different problems and are often used together but are not the same thing
});
// Checking if preventDefault was called
function handler(event) {
event.preventDefault();
console.log(event.defaultPrevented); // true
Q7. How do you work with the event object's target, currentTarget, and this?
These three often get confused but refer to different things during event handling, especially important when using event delegation.
| Property | Refers to |
|---|---|
| event.target | The actual element that triggered the event (deepest element) |
| event.currentTarget | The element the listener is attached to |
| this (in regular function) | Same as event.currentTarget in a normal function listener |
// HTML: <ul id='list'><li>Item 1</li><li>Item 2</li></ul>
const list = document.getElementById('list');
list.addEventListener('click', function(event) {
console.log('target:', event.target); // the <li> that was clicked
console.log('currentTarget:', event.currentTarget); // always the <ul> (where listener is attached)
console.log('this:', this); // also the <ul>, same as currentTarget
});
// With arrow functions, 'this' does NOT refer to currentTarget
// (arrow functions inherit 'this' from their enclosing lexical scope)
list.addEventListener('click', (event) => {
console.log('this in arrow function:', this); // NOT the list element
console.log('use event.currentTarget instead:', event.currentTarget);
});
// Practical delegation example using target vs currentTarget
list.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
event.target.classList.toggle('selected'); // only the clicked li
}
console.log('Always the ul:', event.currentTarget);
});
Q8. How do you handle form input events and validate form data in JavaScript?
JavaScript listens to input-related events to track and validate user input in real time, before or instead of relying solely on native HTML validation.
| Event | Fires when |
|---|---|
| input | Every time the value changes (each keystroke) |
| change | When the value is committed (on blur for text, immediately for select/checkbox) |
| submit | Form is submitted |
| focus / blur | Element gains / loses focus |
const emailInput = document.querySelector('#email');
const form = document.querySelector('form');
const errorMsg = document.querySelector('#error');
// Real-time validation on every keystroke
emailInput.addEventListener('input', function(event) {
const value = event.target.value;
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
if (!isValid && value.length > 0) {
errorMsg.textContent = 'Please enter a valid email';
emailInput.classList.add('invalid');
} else {
errorMsg.textContent = '';
emailInput.classList.remove('invalid');
}
});
// Full form validation on submit
form.addEventListener('submit', function(event) {
event.preventDefault();
const formData = new FormData(form);
const errors = [];
if (!formData.get('name')) errors.push('Name is required');
if (!formData.get('email')) errors.push('Email is required');
if (errors.length > 0) {
console.log('Validation errors:', errors);
return;
}
console.log('Form is valid, submitting:', Object.fromEntries(formData));
});
// Using built-in HTML5 validation API
emailInput.addEventListener('input', function() {
if (emailInput.validity.typeMismatch) {
emailInput.setCustomValidity('Please enter a valid email address');
} else {
emailInput.setCustomValidity('');
}
});
// Debounced input handler for performance (e.g. live search)
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', debounce((event) => {
console.log('Searching for:', event.target.value);
}, 300));
Q9. What is the difference between innerHTML, innerText, and textContent?
| Property | Parses HTML? | Respects CSS visibility? | Performance |
|---|---|---|---|
| innerHTML | Yes - renders tags as elements | N/A (sets raw markup) | Slower, triggers reflow/reparse |
| innerText | No - plain text only | Yes - ignores hidden elements, aware of CSS | Slower - triggers layout/reflow to compute visible text |
| textContent | No - plain text only | No - includes hidden text too | Fastest - no layout calculation needed |
// HTML: <div id='box'>Hello <span style='display:none'>Hidden</span> World</div>
const box = document.getElementById('box');
console.log(box.innerHTML); // 'Hello <span style="display:none">Hidden</span> World'
console.log(box.innerText); // 'Hello World' - skips hidden text
console.log(box.textContent); // 'Hello Hidden World' - includes hidden text
// Setting values
box.innerHTML = '<strong>Bold</strong>'; // renders as bold text
box.textContent = '<strong>Bold</strong>'; // renders literally as the string '<strong>Bold</strong>'
// XSS risk with innerHTML and user input
const userInput = '<img src=x onerror="alert(1)">';
box.innerHTML = userInput; // DANGEROUS - executes the script
box.textContent = userInput; // SAFE - displays as plain text
// Performance tip: use textContent when you don't need to parse HTML
// and don't need to account for visibility (most common case for plain text)
Q10. How do you optimize DOM manipulation performance when updating many elements?
Direct DOM manipulation in a loop causes repeated reflow/repaint, which is expensive. Batching changes minimizes the number of times the browser needs to recalculate layout.
// SLOW - triggers a reflow on every iteration (100 separate DOM insertions)
const list = document.getElementById('list');
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
list.appendChild(li); // reflow happens 100 times
}
// FAST - use DocumentFragment to batch DOM insertions
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, fragment is off-DOM
}
list.appendChild(fragment); // single reflow for all 100 items
// FAST alternative - build HTML string and set once
let html = '';
for (let i = 0; i < 100; i++) {
html += `<li>Item ${i}</li>`;
}
list.innerHTML = html; // single reflow, but be cautious with untrusted data (XSS)
// AVOID - reading layout properties inside a loop that also writes (layout thrashing)
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const height = box.offsetHeight; // read forces layout calculation
box.style.height = height + 10 + 'px'; // write invalidates layout
// repeating this in a loop causes forced synchronous layout each iteration
});
// BETTER - separate all reads from all writes
const heights = Array.from(boxes).map(box => box.offsetHeight); // all reads first
boxes.forEach((box, i) => {
box.style.height = heights[i] + 10 + 'px'; // all writes after
});
// Using requestAnimationFrame for visual updates
function updateUI() {
requestAnimationFrame(() => {
// DOM updates synced with the browser's paint cycle
list.style.transform = 'translateX(100px)';
});
}
DOM Manipulation and Events
Understand DOM rendering and event manipulation. Study DOM traversal, event capturing, event bubbling, delegation, and event listener configurations.
What are the different ways to select DOM elements in JavaScript?
JavaScript provides several methods to select elements from the DOM, each with different return types and use...
How do you create, modify, and remove DOM elements dynamically?
JavaScript provides methods to build up new elements, change existing ones, and clean up elements that are no...
How do you add and remove event listeners in JavaScript?
addEventListener() attaches a function to run when an event occurs, and removeEventListener() detaches it. Bot...
What is event delegation and why is it useful?
Event delegation is a technique of attaching a single event listener to a parent element instead of individual...
What is the difference between event bubbling and event capturing?
These are the two phases of DOM event propagation, describing the order in which nested elements receive an ev...
How do you prevent default browser behavior for an event?
event.preventDefault() stops the browser's built-in default action for an event, such as following a link or s...
How do you work with the event object's target, currentTarget, and this?
These three often get confused but refer to different things during event handling, especially important when...
How do you handle form input events and validate form data in JavaScript?
JavaScript listens to input-related events to track and validate user input in real time, before or instead of...
What is the difference between innerHTML, innerText, and textContent?
PropertyParses HTML?Respects CSS visibility?PerformanceinnerHTMLYes - renders tags as elementsN/A (sets raw ma...
How do you optimize DOM manipulation performance when updating many elements?
Direct DOM manipulation in a loop causes repeated reflow/repaint, which is expensive. Batching changes minimiz...