Introduction
As JavaScript developers transitioning to full-stack development with Python, you already understand the basics of DOM manipulation. This session will focus on advanced patterns and optimization techniques that will help you build more performant web applications. We'll explore how these patterns integrate with backend Python frameworks and the broader full-stack architecture.
By the end of this session, you'll have a deeper understanding of efficient DOM manipulation techniques that go beyond the basics, allowing you to create responsive and smooth user experiences regardless of which backend technology you're using.
Understanding DOM Performance
Before diving into advanced patterns, it's crucial to understand what makes DOM operations expensive and how browsers process them.
The Critical Rendering Path
- JavaScript: Execute scripts that might modify the DOM
- Style Calculations: Compute styles based on CSS rules
- Layout: Calculate the geometry of elements (reflow)
- Paint: Fill in pixels for each element
- Composite: Layer elements together to render on screen
Key Insight
Modifying the DOM can trigger a partial or complete re-execution of this rendering pipeline. The deeper in the pipeline the change affects, the more expensive it becomes.
Most Expensive DOM Operations
- Layout Thrashing: Repeatedly forcing the browser to recalculate layouts
- Forced Synchronous Layout: Reading layout properties immediately after writing to the DOM
- Excessive DOM Depth: Deep DOM trees take longer to process
- Layout-triggering CSS Properties: Properties like width, height, top, left, etc.
- Excessive DOM Size: Too many elements can slow down all operations
Batch DOM Operations
One of the most effective patterns for optimizing DOM performance is batching operations to minimize browser reflows and repaints.
Document Fragments
Use DocumentFragment as an off-screen DOM container for preparing multiple DOM operations before a single insertion.
Inefficient Approach
// Inefficient: causes multiple reflows
const list = document.querySelector('ul');
for (let i = 0; i < 100; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
list.appendChild(item); // Causes reflow on each iteration
}
Optimized Approach
// Efficient: single reflow
const list = document.querySelector('ul');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
fragment.appendChild(item);
}
list.appendChild(fragment); // Only one reflow
Avoiding Layout Thrashing
Layout thrashing occurs when you repeatedly force the browser to perform layout calculations by alternating between reading and writing to the DOM.
Layout Thrashing Example
// Bad pattern: alternating reads and writes
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const width = box.offsetWidth; // Read (forces layout)
box.style.width = (width * 2) + 'px'; // Write
const height = box.offsetHeight; // Read (forces another layout)
box.style.height = (height * 2) + 'px'; // Write
});
Optimized Batching
// Good pattern: batch reads, then writes
const boxes = document.querySelectorAll('.box');
const dimensions = [];
// Read phase (all layout calculations done together)
boxes.forEach(box => {
dimensions.push({
width: box.offsetWidth,
height: box.offsetHeight
});
});
// Write phase (all DOM updates done together)
boxes.forEach((box, i) => {
box.style.width = (dimensions[i].width * 2) + 'px';
box.style.height = (dimensions[i].height * 2) + 'px';
});
Pro Tip
Consider using libraries like FastDOM which automatically batch DOM reads and writes to prevent layout thrashing.
Advanced Event Delegation
Event delegation is a technique that leverages event bubbling to handle events at a higher level in the DOM rather than attaching listeners to individual elements.
Benefits of Event Delegation
- Memory Efficiency: Fewer event listeners means less memory usage
- Dynamic Elements: Works with elements added to the DOM after initial page load
- Less Code: Cleaner implementation with less setup code
- Better Performance: Especially for large numbers of similar elements
Without Event Delegation
// Inefficient for many elements
document.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', function(e) {
// Handle menu item click
console.log('Menu item clicked:', this.textContent);
});
});
// New items won't have listeners
const newItem = document.createElement('li');
newItem.className = 'menu-item';
newItem.textContent = 'New Item';
document.querySelector('.menu').appendChild(newItem);
With Event Delegation
// Single event listener
document.querySelector('.menu').addEventListener('click', function(e) {
// Check if clicked element or its parent is a menu item
const menuItem = e.target.closest('.menu-item');
if (menuItem) {
console.log('Menu item clicked:', menuItem.textContent);
}
});
// New items will work automatically
const newItem = document.createElement('li');
newItem.className = 'menu-item';
newItem.textContent = 'New Item';
document.querySelector('.menu').appendChild(newItem);
Advanced Event Delegation Pattern
For complex applications, you can implement a more sophisticated event delegation system:
// Event delegation manager
class EventManager {
constructor(rootElement = document) {
this.rootElement = rootElement;
this.handlersByEventType = {};
}
on(eventType, selector, handler) {
if (!this.handlersByEventType[eventType]) {
this.handlersByEventType[eventType] = [];
// Create the delegated event handler
this.rootElement.addEventListener(eventType, (e) => {
this.handlersByEventType[eventType].forEach(entry => {
const matchingElements = Array.from(
this.rootElement.querySelectorAll(entry.selector)
);
let element = e.target;
while (element && element !== this.rootElement) {
if (matchingElements.includes(element)) {
entry.handler.call(element, e, element);
}
element = element.parentElement;
}
});
});
}
this.handlersByEventType[eventType].push({ selector, handler });
return this;
}
off(eventType, selector, handler) {
if (!this.handlersByEventType[eventType]) return this;
this.handlersByEventType[eventType] = this.handlersByEventType[eventType]
.filter(entry => {
return !(
entry.selector === selector &&
(!handler || entry.handler === handler)
);
});
return this;
}
}
// Usage example
const events = new EventManager();
// Multiple delegated handlers
events
.on('click', '.btn-delete', function(e, element) {
console.log('Delete clicked', element.dataset.id);
e.preventDefault();
})
.on('click', '.btn-edit', function(e, element) {
console.log('Edit clicked', element.dataset.id);
e.preventDefault();
})
.on('change', '.item-checkbox', function(e, element) {
console.log('Checkbox changed', element.checked);
});
DOM Recycling and Virtualization
For applications that need to display large datasets or lists, creating and destroying DOM elements can be expensive. DOM recycling and virtualization techniques reuse existing DOM elements instead of creating new ones.
DOM Recycling for Lists
The concept is simple: instead of removing elements from the DOM when they're no longer needed, hide them and reuse them when new data arrives.
class RecycledList {
constructor(containerElement, itemTemplateId, itemHeight = 40) {
this.container = containerElement;
this.itemTemplate = document.getElementById(itemTemplateId).content;
this.itemHeight = itemHeight;
this.items = [];
this.visibleItems = [];
this.pool = []; // Recycled DOM elements
this.visibleRange = { start: 0, end: 0 };
}
setItems(items) {
this.items = items;
this.container.style.height = `${items.length * this.itemHeight}px`;
this.updateVisibleItems();
}
updateVisibleItems() {
const containerRect = this.container.getBoundingClientRect();
const scrollTop = this.container.scrollTop;
// Calculate visible range
const startIndex = Math.floor(scrollTop / this.itemHeight);
const endIndex = Math.min(
this.items.length - 1,
Math.ceil((scrollTop + containerRect.height) / this.itemHeight)
);
// Get currently visible items
const currentlyVisible = {};
this.visibleItems.forEach(item => {
currentlyVisible[item.index] = item;
});
const newVisibleItems = [];
// Add new visible items
for (let i = startIndex; i <= endIndex; i++) {
if (currentlyVisible[i]) {
// Keep existing visible item
newVisibleItems.push(currentlyVisible[i]);
delete currentlyVisible[i];
} else {
// Create new visible item
const itemData = this.items[i];
const element = this.pool.length > 0
? this.pool.pop()
: this.createItemElement();
// Position the element
element.style.transform = `translateY(${i * this.itemHeight}px)`;
// Update content
this.updateItemElement(element, itemData);
// Add to visible items
newVisibleItems.push({ index: i, element, data: itemData });
}
}
// Recycle items no longer visible
Object.values(currentlyVisible).forEach(item => {
this.container.removeChild(item.element);
this.pool.push(item.element);
});
this.visibleItems = newVisibleItems;
}
createItemElement() {
const element = document.importNode(this.itemTemplate, true).firstElementChild;
element.style.position = 'absolute';
element.style.width = '100%';
element.style.height = `${this.itemHeight}px`;
this.container.appendChild(element);
return element;
}
updateItemElement(element, data) {
// Override this method to update element content based on data
element.textContent = JSON.stringify(data);
}
}
// Usage example
const listContainer = document.querySelector('.virtual-list-container');
const virtualList = new RecycledList(listContainer, 'item-template');
// Extend the class to customize item rendering
class UserList extends RecycledList {
updateItemElement(element, user) {
element.querySelector('.user-name').textContent = user.name;
element.querySelector('.user-email').textContent = user.email;
element.querySelector('.user-avatar').src = user.avatar;
}
}
// Handle scroll events
listContainer.addEventListener('scroll', () => {
virtualList.updateVisibleItems();
});
// Load data
fetch('/api/users')
.then(response => response.json())
.then(users => {
virtualList.setItems(users);
});
When to Use Virtualization
Implement virtualization when you have lists with more than a few hundred items. For smaller lists, the complexity may not be worth the performance gain.
Component-Based Architecture Without Frameworks
While frameworks like React, Vue, and Angular provide robust component models, you can implement a similar architecture using vanilla JavaScript. This is particularly useful when integrating with Python backends that may have their own templating systems.
Simple Component Pattern
class Component {
constructor(props = {}) {
this.props = props;
this.state = {};
this.element = null;
}
setState(newState) {
const prevState = { ...this.state };
this.state = { ...this.state, ...newState };
this.update(prevState);
}
update(prevState) {
// Override in subclasses
}
render() {
// Override in subclasses
return document.createElement('div');
}
mount(container) {
this.element = this.render();
container.appendChild(this.element);
this.afterMount();
return this;
}
afterMount() {
// Hook for after mounting
}
unmount() {
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
}
}
// Example component implementation
class TodoItem extends Component {
constructor(props) {
super(props);
this.state = {
completed: props.completed || false
};
}
toggleComplete() {
this.setState({ completed: !this.state.completed });
if (this.props.onToggle) {
this.props.onToggle(this.props.id, this.state.completed);
}
}
update(prevState) {
if (prevState.completed !== this.state.completed) {
this.element.classList.toggle('completed', this.state.completed);
this.element.querySelector('input').checked = this.state.completed;
}
}
render() {
const item = document.createElement('li');
item.className = 'todo-item';
if (this.state.completed) {
item.classList.add('completed');
}
item.innerHTML = `
${this.props.text}
`;
item.querySelector('input').addEventListener('change', () => {
this.toggleComplete();
});
item.querySelector('.delete-btn').addEventListener('click', () => {
if (this.props.onDelete) {
this.props.onDelete(this.props.id);
}
});
return item;
}
}
// Usage
const todoList = document.getElementById('todo-list');
const todos = [
{ id: 1, text: 'Learn advanced DOM patterns', completed: false },
{ id: 2, text: 'Build a component system', completed: true }
];
todos.forEach(todo => {
new TodoItem({
id: todo.id,
text: todo.text,
completed: todo.completed,
onToggle: (id, completed) => {
console.log(`Todo ${id} changed to ${completed ? 'completed' : 'active'}`);
},
onDelete: (id) => {
console.log(`Delete todo ${id}`);
}
}).mount(todoList);
});
Observer Pattern for DOM Updates
The Observer pattern (or Pub/Sub) can be extremely useful for managing DOM updates in response to data changes, especially in applications connected to Python backends.
class Observable {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
return () => {
this.observers = this.observers.filter(obs => obs !== fn);
};
}
notify(data) {
this.observers.forEach(fn => fn(data));
}
}
// Example: Todo List with Observer pattern
class TodoStore extends Observable {
constructor() {
super();
this.todos = [];
}
addTodo(text) {
const todo = {
id: Date.now(),
text,
completed: false
};
this.todos = [...this.todos, todo];
this.notify(this.todos);
}
toggleTodo(id) {
this.todos = this.todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
);
this.notify(this.todos);
}
removeTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id);
this.notify(this.todos);
}
}
class TodoListView {
constructor(container, store) {
this.container = container;
this.store = store;
this.unsubscribe = this.store.subscribe(todos => this.render(todos));
// Form for adding new todos
this.form = document.createElement('form');
this.form.innerHTML = `
`;
this.form.addEventListener('submit', e => {
e.preventDefault();
const input = this.form.querySelector('input');
if (input.value.trim()) {
this.store.addTodo(input.value.trim());
input.value = '';
}
});
this.list = document.createElement('ul');
this.container.appendChild(this.form);
this.container.appendChild(this.list);
}
render(todos) {
// Clear previous items
this.list.innerHTML = '';
// Create fragment for batch update
const fragment = document.createDocumentFragment();
todos.forEach(todo => {
const li = document.createElement('li');
li.className = todo.completed ? 'completed' : '';
li.innerHTML = `
${todo.text}
`;
li.querySelector('input').addEventListener('change', () => {
this.store.toggleTodo(todo.id);
});
li.querySelector('.delete').addEventListener('click', () => {
this.store.removeTodo(todo.id);
});
fragment.appendChild(li);
});
this.list.appendChild(fragment);
}
destroy() {
// Clean up subscriptions when view is destroyed
this.unsubscribe();
}
}
// Usage
const todoStore = new TodoStore();
const todoListView = new TodoListView(
document.getElementById('todo-container'),
todoStore
);
// Simulate receiving data from Python backend
function syncWithBackend() {
fetch('/api/todos')
.then(response => response.json())
.then(todos => {
todos.forEach(todo => todoStore.addTodo(todo.text));
});
}
Integration with Python Backends
This pattern works well with Python frameworks like Flask or Django. You can use AJAX to fetch data from your API endpoints and then update your store, which will automatically trigger DOM updates through the observer pattern.
Performance Monitoring
Monitoring DOM performance is crucial for identifying optimization opportunities. Here are some techniques and tools to help you measure and improve performance.
Key Performance Metrics
| Metric | Description | Ideal Target |
|---|---|---|
| First Contentful Paint (FCP) | Time until first content is rendered | < 1.8s |
| Largest Contentful Paint (LCP) | Time until largest content element is visible | < 2.5s |
| First Input Delay (FID) | Time from user interaction to browser response | < 100ms |
| Cumulative Layout Shift (CLS) | Unexpected layout shifts during page loading | < 0.1 |
| Time to Interactive (TTI) | Time until page is fully interactive | < 3.8s |
| Framerate | Frames per second during animations/scrolling | 60fps |
Performance Monitoring Code
// Simple performance monitoring utilities
const PerformanceMonitor = {
// Start timing an operation
startMeasure(label) {
performance.mark(`${label}-start`);
},
// End timing and log result
endMeasure(label) {
performance.mark(`${label}-end`);
performance.measure(label, `${label}-start`, `${label}-end`);
const measure = performance.getEntriesByName(label)[0];
console.log(`${label}: ${measure.duration.toFixed(2)}ms`);
// Cleanup
performance.clearMarks(`${label}-start`);
performance.clearMarks(`${label}-end`);
performance.clearMeasures(label);
return measure.duration;
},
// Monitor frame rate during an operation
monitorFrameRate(durationMs = 5000, callback) {
let frameCount = 0;
let startTime = performance.now();
function countFrame() {
frameCount++;
const currentTime = performance.now();
const elapsed = currentTime - startTime;
if (elapsed < durationMs) {
requestAnimationFrame(countFrame);
} else {
const fps = (frameCount / elapsed) * 1000;
console.log(`Average FPS: ${fps.toFixed(1)}`);
if (callback) callback(fps);
}
}
requestAnimationFrame(countFrame);
},
// Check for layout thrashing
detectLayoutThrashing() {
// Override common properties that trigger layout
const layoutTriggeringProps = [
'offsetTop', 'offsetLeft', 'offsetWidth', 'offsetHeight',
'clientTop', 'clientLeft', 'clientWidth', 'clientHeight',
'getComputedStyle'
];
let layoutReads = 0;
let layoutReadWriteSequences = 0;
let lastOperationType = null;
// Monkey patch to detect reads
layoutTriggeringProps.forEach(prop => {
if (prop === 'getComputedStyle') {
const original = window.getComputedStyle;
window.getComputedStyle = function() {
layoutReads++;
if (lastOperationType === 'write') {
layoutReadWriteSequences++;
console.warn('Layout thrashing detected: Reading style after write');
}
lastOperationType = 'read';
return original.apply(this, arguments);
};
} else {
const elementProto = Element.prototype;
const originalDescriptor = Object.getOwnPropertyDescriptor(elementProto, prop);
if (originalDescriptor && originalDescriptor.get) {
Object.defineProperty(elementProto, prop, {
get: function() {
layoutReads++;
if (lastOperationType === 'write') {
layoutReadWriteSequences++;
console.warn(`Layout thrashing detected: Reading ${prop} after write`);
}
lastOperationType = 'read';
return originalDescriptor.get.apply(this);
}
});
}
}
});
// Detect writes
const originalSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function() {
lastOperationType = 'write';
return originalSetAttribute.apply(this, arguments);
};
const originalStyleSetter = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'style').set;
Object.defineProperty(HTMLElement.prototype, 'style', {
set: function() {
lastOperationType = 'write';
return originalStyleSetter.apply(this, arguments);
}
});
// Report stats periodically
setInterval(() => {
console.log(`Layout reads: ${layoutReads}, Layout thrashing sequences: ${layoutReadWriteSequences}`);
layoutReads = 0;
layoutReadWriteSequences = 0;
}, 5000);
}
};
// Usage examples
document.addEventListener('DOMContentLoaded', () => {
// Example: Measure rendering performance
PerformanceMonitor.startMeasure('initial-render');
renderComplexTable();
PerformanceMonitor.endMeasure('initial-render');
// Example: Monitor framerate during scrolling
document.querySelector('.scroll-container').addEventListener('scroll', () => {
PerformanceMonitor.monitorFrameRate(2000, fps => {
if (fps < 30) {
console.warn('Scrolling performance is poor');
}
});
});
// Example: Detect layout thrashing in development
if (process.env.NODE_ENV === 'development') {
PerformanceMonitor.detectLayoutThrashing();
}
});
Leveraging Browser DevTools for DOM Optimization
Modern browser developer tools provide powerful features for analyzing DOM performance.
Key DevTools Features
- Performance Panel: Record and analyze rendering, scripting, and painting
- Memory Panel: Detect memory leaks and analyze heap snapshots
- Layers Panel: Visualize composited layers
- Paint Flashing: Visualize areas being repainted
- FPS Meter: Monitor frame rate in real-time
DevTools Performance Analysis Workflow
- Open DevTools and navigate to the Performance panel
- Click "Record" and perform the action you want to analyze
- Click "Stop" and analyze the resulting timeline
- Look for long tasks, layout recalculations, and excessive painting
- Use the "Bottom-Up" and "Call Tree" tabs to identify problematic code
- Optimize and repeat to confirm improvements
Integrating with Python Backends
As you transition to full-stack development with Python, understanding how frontend DOM patterns integrate with backend frameworks is essential.
Flask Integration
// Example: Fetching data from a Flask API
class FlaskApiClient {
constructor(baseUrl = '') {
this.baseUrl = baseUrl;
this.csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
}
async get(endpoint) {
const response = await fetch(`${this.baseUrl}${endpoint}`);
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json();
}
async post(endpoint, data) {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json();
}
}
// Integrating with component architecture
class UserList extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
loading: true,
error: null
};
this.api = new FlaskApiClient('/api');
}
async loadUsers() {
try {
this.setState({ loading: true, error: null });
const users = await this.api.get('/users');
this.setState({ users, loading: false });
} catch (error) {
this.setState({ error: error.message, loading: false });
}
}
afterMount() {
this.loadUsers();
}
render() {
const container = document.createElement('div');
container.className = 'user-list-container';
if (this.state.loading) {
container.innerHTML = 'Loading users...';
} else if (this.state.error) {
container.innerHTML = `${this.state.error}`;
} else {
const fragment = document.createDocumentFragment();
const list = document.createElement('ul');
list.className = 'user-list';
this.state.users.forEach(user => {
const item = document.createElement('li');
item.className = 'user-item';
item.innerHTML = `
`;
list.appendChild(item);
});
fragment.appendChild(list);
container.appendChild(fragment);
}
return container;
}
}
// Usage
new UserList().mount(document.getElementById('app'));
Django Integration
// Handling Django CSRF protection
function getCsrfToken() {
return document.querySelector('[name=csrfmiddlewaretoken]')?.value;
}
// Django API client
class DjangoApiClient {
async request(url, options = {}) {
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
credentials: 'same-origin'
};
const response = await fetch(url, {...defaultOptions, ...options});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
async get(url) {
return this.request(url);
}
async post(url, data) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data)
});
}
async put(url, data) {
return this.request(url, {
method: 'PUT',
body: JSON.stringify(data)
});
}
async delete(url) {
return this.request(url, {
method: 'DELETE'
});
}
}
// Django form submission with optimization
class EnhancedForm {
constructor(formElement) {
this.form = formElement;
this.api = new DjangoApiClient();
this.setupForm();
}
setupForm() {
this.form.addEventListener('submit', async (e) => {
e.preventDefault();
try {
await this.submitForm();
} catch (error) {
this.showError(error.message);
}
});
}
getFormData() {
const formData = new FormData(this.form);
const data = {};
for (let [key, value] of formData.entries()) {
data[key] = value;
}
return data;
}
async submitForm() {
// Show loading state
this.form.classList.add('loading');
this.clearErrors();
// Get form data
const data = this.getFormData();
try {
// Submit to Django backend
const response = await this.api.post(this.form.action, data);
// Handle successful response
if (response.success) {
this.showSuccess(response.message || 'Form submitted successfully');
// Redirect if specified
if (response.redirect) {
window.location.href = response.redirect;
}
} else if (response.errors) {
// Handle field errors
this.showFieldErrors(response.errors);
}
} finally {
// Remove loading state
this.form.classList.remove('loading');
}
}
clearErrors() {
// Remove existing error messages
this.form.querySelectorAll('.error-message').forEach(el => el.remove());
this.form.querySelectorAll('.field-error').forEach(el => {
el.classList.remove('field-error');
});
}
showFieldErrors(errors) {
// Efficiently add error messages using document fragment
const fragment = document.createDocumentFragment();
Object.entries(errors).forEach(([field, message]) => {
const input = this.form.querySelector(`[name="${field}"]`);
if (input) {
input.classList.add('field-error');
const errorElement = document.createElement('div');
errorElement.className = 'error-message';
errorElement.textContent = message;
// Insert after the input
input.parentNode.insertBefore(errorElement, input.nextSibling);
}
});
this.form.appendChild(fragment);
}
showError(message) {
const errorContainer = this.form.querySelector('.form-error') ||
document.createElement('div');
errorContainer.className = 'form-error';
errorContainer.textContent = message;
if (!errorContainer.parentNode) {
this.form.insertBefore(errorContainer, this.form.firstChild);
}
}
showSuccess(message) {
const successContainer = this.form.querySelector('.form-success') ||
document.createElement('div');
successContainer.className = 'form-success';
successContainer.textContent = message;
if (!successContainer.parentNode) {
this.form.insertBefore(successContainer, this.form.firstChild);
}
}
}
// Usage
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('form.enhanced').forEach(form => {
new EnhancedForm(form);
});
});
Exercises
Exercise 1: Performance Optimization
Take the following code and optimize it for DOM performance:
// Original code to optimize
function renderTable(data) {
const table = document.getElementById('data-table');
table.innerHTML = '';
data.forEach(row => {
const tr = document.createElement('tr');
// Create and append cells one by one
const nameCell = document.createElement('td');
nameCell.textContent = row.name;
tr.appendChild(nameCell);
const valueCell = document.createElement('td');
valueCell.textContent = row.value;
tr.appendChild(valueCell);
const percentCell = document.createElement('td');
percentCell.textContent = row.percentage + '%';
tr.appendChild(percentCell);
// Calculate and set width for bar visualization
const barCell = document.createElement('td');
const bar = document.createElement('div');
bar.className = 'bar';
bar.style.width = (barCell.offsetWidth * (row.percentage / 100)) + 'px';
bar.style.backgroundColor = row.color;
barCell.appendChild(bar);
tr.appendChild(barCell);
table.appendChild(tr);
});
}
Exercise 2: Implement a Component System
Create a simple component system that includes:
- A base Component class with lifecycle methods (init, render, mount, update, unmount)
- State management within components
- Event handling and delegation
- Parent-child component relationships
Use this system to build a simple to-do list application that connects to a Python/Flask backend.
Exercise 3: Virtual List Implementation
Implement a virtual list component that:
- Only renders elements visible in the viewport
- Recycles DOM nodes as the user scrolls
- Efficiently handles a dataset of 10,000 items
- Maintains smooth 60fps scrolling
Summary and Best Practices
Key DOM Optimization Principles
- Minimize DOM Manipulations: Batch operations and use document fragments
- Avoid Layout Thrashing: Separate read and write operations
- Use Event Delegation: Attach listeners to containers instead of individual elements
- Optimize Rendering: Recycle DOM elements and implement virtualization for large lists
- Leverage Component Patterns: Organize code in a modular, maintainable way
- Measure Performance: Use browser tools to identify bottlenecks
- Update Efficiently: Only update what has changed
Connecting to Python Backends
When working with Python backends like Flask or Django:
- Use async/await for API requests to keep the UI responsive
- Handle CSRF protection properly in your requests
- Consider the balance between server-side and client-side rendering
- Implement proper error handling for API requests
- Use optimistic UI updates for better user experience