intro-frontend-course

Lecture 3: Basic Performance Optimization

Importance of Performance Optimization

Performance optimization is crucial for enhancing user experience, improving SEO, and increasing user retention. Slow websites can frustrate users, leading to higher bounce rates and lower conversion rates. Optimizing your JavaScript code and other web assets ensures your web applications run smoothly and efficiently.

Optimizing JavaScript Code

  1. Minimizing Reflows and Repaints
    • Reflows and repaints occur when changes to the DOM require the browser to re-render parts of the page. Minimizing these helps improve performance.
    • Avoid direct DOM manipulation inside loops. Instead, make changes off-DOM and reinsert elements in a single operation.

Real-World Example: Dynamically Adding List Items

const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
  1. Debouncing and Throttling Events
    • Debouncing and throttling help limit the rate at which functions are executed. This is particularly useful for handling events like scroll and resize.

Debounce Example:

function debounce(func, delay) {
  let debounceTimer;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => func.apply(context, args), delay);
  };
}

window.addEventListener('resize', debounce(() => {
  console.log('Resize event debounced');
}, 250));

Throttle Example:

function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function() {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
}

window.addEventListener('scroll', throttle(() => {
  console.log('Scroll event throttled');
}, 200));
  1. Reducing Memory Leaks
    • Memory leaks can cause an application to use more memory over time, leading to performance issues.
    • Ensure event listeners are properly removed and avoid global variables.

Real-World Example: Event Listeners

const element = document.getElementById('myElement');
function handleClick() {
  console.log('Element clicked');
}
element.addEventListener('click', handleClick);

// Later in the code
element.removeEventListener('click', handleClick);

For more on minimizing reflows and repaints, see Google Developers: Rendering Performance.

Efficient DOM Manipulation

  1. Using DocumentFragment
    • DocumentFragment is a lightweight container for DOM elements. It can be used to batch DOM updates, reducing reflows.

Real-World Example: Appending Multiple Elements

const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = `Item ${i}`;
  fragment.appendChild(div);
}
document.body.appendChild(fragment);
  1. Batch Updates to the DOM
    • Make multiple changes to the DOM in one go to minimize reflows and repaints.

Real-World Example: Updating List Items

const list = document.getElementById('list');
let items = '';
for (let i = 0; i < 1000; i++) {
  items += `<li>Item ${i}</li>`;
}
list.innerHTML = items;

Optimizing Network Requests

  1. Lazy Loading of Resources
    • Lazy loading defers the loading of non-essential resources until they are needed. This improves initial load times.

Real-World Example: Lazy Loading Images

<img src="placeholder.jpg" data-src="image.jpg" class="lazy">
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const lazyImages = document.querySelectorAll('.lazy');
    lazyImages.forEach(img => {
      img.src = img.dataset.src;
    });
  });
</script>
  1. Using Service Workers for Caching
    • Service Workers can cache resources, enabling offline access and faster load times for repeat visits.

Real-World Example: Caching with Service Workers

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/index.html',
        '/styles.css',
        '/script.js',
        '/image.jpg'
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

For more on lazy loading, refer to MDN Web Docs: Lazy loading.

Optimizing Assets

  1. Minifying CSS, JavaScript, and HTML
    • Minification removes unnecessary characters from code, reducing file size and improving load times.

Real-World Example: Minifying JavaScript

# Using a tool like UglifyJS for JavaScript
uglifyjs script.js -o script.min.js
  1. Image Optimization Techniques
    • Compress images and use modern formats like WebP for better performance.

Real-World Example: Image Compression

# Using a tool like imagemin for image compression
imagemin input.jpg > output.jpg
  1. Using Modern Image Formats
    • WebP and AVIF offer better compression rates than JPEG and PNG.

Real-World Example: Using WebP

<picture>
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Example Image">
</picture>

For image optimization, see Google Developers: Image Optimization.

Tools for Performance Optimization

  1. Lighthouse
    • Lighthouse is an open-source, automated tool for improving the quality of web pages. It provides audits for performance, accessibility, SEO, and more.

Real-World Example: Using Lighthouse CLI

# Using Lighthouse CLI
lighthouse https://example.com --output html --output-path report.html
  1. WebPageTest
    • WebPageTest is a free tool that provides detailed performance data, including load time, first paint, and more.
  2. Chrome DevTools
    • Chrome DevTools offers a suite of performance analysis tools. The Performance tab helps identify bottlenecks in your code.

For more on using Lighthouse, visit Google Developers: Lighthouse.

Practical Examples

Example 1: Improving the Performance of a Sample Application

  1. Before Optimization
document.addEventListener('scroll', () => {
  console.log('User scrolled');
  // Expensive operation here
});
  1. After Optimization
const handleScroll = throttle(() => {
  console.log('User scrolled');
  // Expensive operation here
}, 200);

document.addEventListener('scroll', handleScroll);

Example 2: Optimizing Image Loading

  1. Before Optimization
<img src="large-image.jpg" alt="Large Image">
  1. After Optimization with Lazy Loading
<img src="placeholder.jpg" data-src="large-image.jpg" class="lazy" alt="Large Image">
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const lazyImages = document.querySelectorAll('.lazy');
    lazyImages.forEach(img => {
      img.src = img.dataset.src;
    });
  });
</script>

Example 3: Real-World Scenario: Optimizing E-Commerce Website

Before Optimization:

document.querySelectorAll('.product').forEach(product => {
  product.addEventListener('click', () => {
    // Fetch product details
    fetch(`/product-details/${product.id}`)
      .then(response => response.json())
      .then(data => {
        // Update product details section
        document.getElementById('product-details').innerHTML = data.description;
      });
  });
});

After Optimization with Debouncing:

const productElements = document.querySelectorAll('.product');
const handleProductClick = debounce((event) => {
  const productId = event.target.id;
  fetch(`/product-details/${productId}`)
    .then(response => response.json())
    .then(data => {
      document.getElementById('product-details').innerHTML = data.description;
    });
}, 300);

productElements.forEach(product => {
  product.addEventListener('click', handleProductClick);
});

Video Resources:

By applying these performance optimization techniques, you can significantly improve the speed and responsiveness of your web applications, leading to a