DOM, Events and Async
DOM
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DOM Example</title> <style> .main-content { padding: 20px; max-width: 800px; margin: 0 auto; } .description { color: #666; } .button-group { margin: 20px 0; } </style></head><body> <div id="container" class="main-content"> <h1>Welcome to my page</h1> <p class="description">This is a sample DOM structure</p> <div class="button-group"> <button id="myButton">Click me!</button> </div> <img src="sample.jpg" alt="Sample image" data-custom="value"> </div>
<script> // DOM manipulation examples const button = document.getElementById('myButton'); button.addEventListener('click', () => { alert('Button clicked!'); }); </script></body></html>
DOM (Document Object Model) is like a tree structure that represents your webpage. It allows JavaScript to:
- Read webpage content
- Change elements and text
- Update styles
- React to user actions
// Examplesdocument.getElementById('myButton') // Find elementdocument.querySelector('.myClass') // Find first matching elementelement.innerHTML = 'New text' // Change contentelement.style.color = 'red' // Change style
// # is used to select id// . is used to select class
DOM Tree
Window │ ▼ Document │ ▼ HTML ┌────────┴────────┐ ▼ ▼ HEAD BODY ┌───────┼────────┐ ┌────┴────┐ ▼ ▼ ▼ ▼ ▼ meta style title div div [charset="UTF-8"] [id] [class] meta [class] │ [name="viewport"] │ ▼ link ▼ h1,img,p [rel="stylesheet"] p,a [data-attr] [href, class] [src, alt]
DOM Methods
Common DOM manipulation methods include:
// Finding Elementsdocument.getElementById('myId') // Returns single element by IDdocument.getElementsByClassName('myClass') // Returns HTMLCollection of elementsdocument.getElementsByTagName('div') // Returns HTMLCollection of elementsdocument.querySelector('.myClass') // Returns first matching elementdocument.querySelectorAll('div.myClass') // Returns NodeList of all matching elements
// Creating & Modifying Elementsdocument.createElement('div') // Creates new elementelement.appendChild(childElement) // Adds child elementelement.removeChild(childElement) // Removes child elementelement.replaceChild(newChild, oldChild) // Replaces child elementelement.cloneNode(true) // Clones element (true = deep clone)
// Modifying Contentelement.textContent = 'New text' // Updates text (safer)element.innerHTML = '<span>New HTML</span>' // Updates HTML contentelement.setAttribute('class', 'newClass') // Sets attributeelement.getAttribute('class') // Gets attributeelement.removeAttribute('class') // Removes attribute
// Stylingelement.style.backgroundColor = 'red' // Direct style modificationelement.classList.add('newClass') // Adds classelement.classList.remove('oldClass') // Removes class
NodeList
A NodeList is a collection of nodes returned by methods like querySelectorAll()
or properties like childNodes
. It’s similar to an array but has some important differences.
// Getting a NodeListconst paragraphs = document.querySelectorAll('p');const childNodes = element.childNodes;
// Accessing Elementsconst firstParagraph = paragraphs[0]; // Using indexconst length = paragraphs.length; // Getting lengthparagraphs.item(1); // Using item() method
// Converting to Arrayconst array = Array.from(paragraphs); // Modern wayconst arrayAlt = [...paragraphs]; // Spread operatorconst oldArray = Array.prototype.slice.call(paragraphs); // Old way
// Iterating// 1. forEach methodparagraphs.forEach(paragraph => { paragraph.classList.add('highlight');});
// 2. for...of loopfor (const paragraph of paragraphs) { paragraph.style.color = 'blue';}
// 3. Traditional for loopfor (let i = 0; i < paragraphs.length; i++) { paragraphs[i].textContent = `Paragraph ${i + 1}`;}
Events
Events are actions or occurrences that happen in a web browser, such as user interactions or system updates. JavaScript can detect and respond to these events.
Event Handling
// 1. Adding Event Listenerselement.addEventListener('click', function(event) { console.log('Clicked!');});
// 2. Removing Event Listenersconst handler = (event) => console.log('Clicked!');element.addEventListener('click', handler);element.removeEventListener('click', handler);
// 3. Event Object Propertieselement.addEventListener('click', (event) => { console.log(event.target); // Element that triggered the event console.log(event.currentTarget); // Element that listener is attached to console.log(event.type); // Type of event (e.g., 'click') console.log(event.timeStamp); // Time when event occurred
event.preventDefault(); // Prevent default behavior event.stopPropagation(); // Stop event bubbling});
Common Events
// Mouse Eventselement.addEventListener('click', () => {}); // Mouse clickelement.addEventListener('dblclick', () => {}); // Double clickelement.addEventListener('mouseenter', () => {}); // Mouse enters elementelement.addEventListener('mouseleave', () => {}); // Mouse leaves elementelement.addEventListener('mousemove', () => {}); // Mouse moves
// Keyboard Eventsdocument.addEventListener('keydown', (e) => { // Key pressed console.log(e.key); // Key value console.log(e.code); // Physical key});document.addEventListener('keyup', () => {}); // Key released
// Form Eventsform.addEventListener('submit', (e) => { // Form submission e.preventDefault(); // Prevent form submission});input.addEventListener('change', () => {}); // Input value changesinput.addEventListener('input', () => {}); // Real-time input changesinput.addEventListener('focus', () => {}); // Input receives focusinput.addEventListener('blur', () => {}); // Input loses focus
// Document/Window Eventswindow.addEventListener('load', () => {}); // Page fully loadeddocument.addEventListener('DOMContentLoaded', () => {}); // DOM readywindow.addEventListener('resize', () => {}); // Window resizedwindow.addEventListener('scroll', () => {}); // Page scrolled
Event Propagation
Events in the DOM bubble up through the ancestor elements (event bubbling) and can also capture down (event capturing).
// Event Bubbling (default)parent.addEventListener('click', () => { console.log('Parent clicked');});child.addEventListener('click', () => { console.log('Child clicked'); // Outputs: Child clicked, then Parent clicked});
// Event Capturing (third parameter true)parent.addEventListener('click', () => { console.log('Parent clicked');}, true);child.addEventListener('click', () => { console.log('Child clicked'); // Outputs: Parent clicked, then Child clicked}, true);
// Stop Propagationchild.addEventListener('click', (e) => { e.stopPropagation(); // Prevents event from bubbling up console.log('Child only');});
Event Delegation
Event delegation is a technique of handling events on multiple elements using a single event listener on their parent.
// Instead of adding listeners to each buttondocument.getElementById('button-container').addEventListener('click', (e) => { if (e.target.matches('button')) { console.log('Button clicked:', e.target.textContent); }});
// Example HTML:// <div id="button-container">// <button>Button 1</button>// <button>Button 2</button>// <button>Button 3</button>// </div>
Async
- JavaScript is synchronous and single-threaded, meaning it can only execute one task at a time.
- Execution of code is blocked until the current task is completed.
- example:
- synchronous code is useful when registering a user in a database and displaying a success message only after the database operation is complete.
- Asynchronous programming is a technique that allows multiple tasks to be performed simultaneously without blocking the main thread.
- Execution of code is not blocked, allowing other tasks to run in the background.
- It’s essential for handling time-consuming tasks like network requests, file operations, and animations without freezing the page.
- example:
- Asynchronous code is useful when fetching data from a server and updating the UI without blocking the main thread.
┌────────── JavaScript Engine ─────────┐ ┌────── Web APIs ─────┐│ │ │ ││ ┌─────────┐ ┌──────────┐ │ │ • DOM ││ │ │ │ │ │ │ • setTimeout ││ │ Heap │ │ Call │ │ │ • fetch ││ │ │ │ Stack │ │ │ • addEventListener ││ │ │ │ │ │ │ • XMLHttpRequest ││ │ │ │ fn2() │ │ │ • geolocation ││ │ │ │ fn1() │ │ │ • localStorage ││ │ │ │ main() │ │ │ ││ │ │ │ global │ │ │ ││ └─────────┘ └──────────┘ │ │ │└──────────────────────────────────────┘ └─────────────────────┘
┌──────────────────┐ ┌─────────────────────────┐ ┌──────────────────────┐│ │ │ │ │ Microtask Queue ││ Event Loop │ │ Callback Queue │ │ [Promise] ││ ↺ │ │ [fn3()][fn4()][fn5()] │ │ [queueMicrotask] ││ │ │ │ │ │└──────────────────┘ └─────────────────────────┘ └──────────────────────┘
- Event Loop controls the execution of tasks based on the priority of the tasks.
- Microtask Queue has higher priority than Callback Queue.
- Microtask Queue includes Promises and
queueMicrotask
,fetch. - Callback Queue includes all the callbacks from the Web APIs.
Example
setTimeout(() => { console.log('callback');}, 0);
Promise.resolve().then(() => console.log('promise'));
console.log('synchronous');
// Output: synchronous promise callback// Explanation:// 1. 'synchronous' logs first (direct execution)// 2. 'promise' logs second (microtask queue priority)// 3. 'callback' logs last (callback queue)
Promise.resolve().then(() => console.log(1));setTimeout(() => console.log(2), 0);
queueMicrotask(() =>{ console.log(3) queueMicrotask(() => console.log(4))})
console.log(5);
// Output: 5 1 3 4 2
Promise
- A Promise is an object that represents a value that may not be available yet but will be at some point.
- It can be in one of three states: pending, fulfilled, or rejected.
- A Promise can be created using the
Promise
constructor. - A Promise can be resolved or rejected using the
resolve
andreject
methods. - A Promise can be chained using the
then
method. - A Promise can be handled using the
catch
method.
const promise = new Promise((resolve, reject) => { // ...});
Creation
- A Promise is created using the
Promise
constructor. - The constructor takes a function as an argument, which takes two parameters:
resolve
andreject
. resolve
is a function that is called when the Promise is fulfilled.reject
is a function that is called when the Promise is rejected.
usecase:
- Simulating network delay
- Fetching data from a server
- Reading from a file
// Basic Promise Creationconst promise = new Promise((resolve, reject) => { // Async operation const success = true;
if (success) { resolve('Operation successful!'); } else { reject(new Error('Operation failed!')); }});
// Promise that fetches datafunction fetchUserData(userId) { return new Promise((resolve, reject) => { fetch(`https://api.example.com/users/${userId}`) .then(response => response.json()) .then(data => resolve(data)) .catch(error => reject(error)); });}
Consuming Promises
- Promises can be consumed using the
then
method. - The
then
method takes two arguments: a function that is called when the Promise is fulfilled and a function that is called when the Promise is rejected. - The
catch
method can be used to handle errors. - The
finally
method can be used to perform cleanup after the Promise is resolved or rejected.
usecase:
- Handling asynchronous operations
- Chaining operations
- Error handling
// Using .then() and .catch()promise .then(result => { console.log('Success:', result); }) .catch(error => { console.error('Error:', error); }) .finally(() => { console.log('Promise completed'); });
// Async/await syntax (modern approach)async function handlePromise() { try { const result = await promise; console.log('Success:', result); } catch (error) { console.error('Error:', error); }}
// Real-world exampleasync function loadUserData() { try { const userData = await fetchUserData(1); const data = await userData.json(); // Convert response to JSON takes time console.log('User data:', data); } catch (error) { console.error('Failed to load user:', error); }}
Promisification
- Promisification is the process of converting a function that takes a callback into a function that returns a promise.
- It’s useful for handling asynchronous operations in a more readable and manageable way.
function getCurrentPosition() { return new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition(resolve, reject); });}
async function getLocation() { try { const position = await getCurrentPosition(); console.log(position); } catch (error) { console.error(error); }}
// UsagegetLocation(); // Output: position object or error// Do not forget to handle errors!
Fetch
The Fetch API provides a modern interface for making HTTP requests. It returns Promises, making it easier to handle asynchronous operations.
Basic Usage
// Basic GET requestfetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));
// Using async/awaitasync function getData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data); } catch (error) { console.error('Error:', error); }}
Request Options
// POST request with JSON datafetch('https://api.example.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer your-token-here' }, body: JSON.stringify({ title: 'New Post', content: 'Post content' })});
// Form dataconst formData = new FormData();formData.append('file', fileInput.files[0]);
fetch('https://api.example.com/upload', { method: 'POST', body: formData});
Response Handling
fetch('https://api.example.com/data') .then(response => { // Check if response is ok (status 200-299) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }
// Check response type and handle accordingly const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return response.json(); } else if (contentType && contentType.includes('text/plain')) { return response.text(); } else if (contentType && contentType.includes('application/pdf')) { return response.blob(); } throw new TypeError("Unsupported data format"); }) .then(data => console.log(data)) .catch(error => console.error('Error:', error));