DEV Community

Cover image for 9 Essential JavaScript Performance Techniques Every Developer Should Master for Faster Web Apps
Nithin Bharadwaj
Nithin Bharadwaj

Posted on

9 Essential JavaScript Performance Techniques Every Developer Should Master for Faster Web Apps

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Optimizing frontend performance requires deliberate strategies. I've found these nine JavaScript techniques essential for maintaining smooth user experiences. Each addresses specific runtime bottlenecks while keeping code maintainable.

DOM manipulation remains a common performance pitfall. Batching changes reduces costly browser recalculations. Here's how I handle grouped updates:

class DOMBatcher {
  constructor() {
    this.updateQueue = [];
  }

  queueChange(element, property, value) {
    this.updateQueue.push({ element, property, value });

    if (!this.rafPending) {
      this.rafPending = true;
      requestAnimationFrame(() => {
        const fragment = document.createDocumentFragment();
        this.updateQueue.forEach(change => {
          change.element.style[change.property] = change.value;
          fragment.appendChild(change.element.cloneNode(true));
        });
        document.getElementById('target').appendChild(fragment);
        this.updateQueue = [];
        this.rafPending = false;
      });
    }
  }
}

// Usage
const batcher = new DOMBatcher();
batcher.queueChange(document.getElementById('box'), 'opacity', '0.5');
batcher.queueChange(document.getElementById('panel'), 'height', '300px');
Enter fullscreen mode Exit fullscreen mode

Event management needs special attention with frequent interactions. I throttle resize handlers and debounce search inputs to balance responsiveness and efficiency:

function throttle(fn, interval) {
  let lastExecution = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastExecution >= interval) {
      fn.apply(this, args);
      lastExecution = now;
    }
  };
}

function debounce(fn, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Implementation
window.addEventListener('resize', throttle(() => {
  console.log('Adjusted layout');
}, 200));

searchInput.addEventListener('input', debounce(() => {
  console.log('Search query processed');
}, 300));
Enter fullscreen mode Exit fullscreen mode

Large datasets demand virtualization. I render only visible items using scroll position calculations:

class ListVirtualizer {
  constructor(container, itemHeight, renderItem) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.renderItem = renderItem;
    this.visibleItems = [];
    this.data = [];

    container.addEventListener('scroll', throttle(this.updateView.bind(this), 50));
  }

  setData(newData) {
    this.data = newData;
    this.updateView();
  }

  updateView() {
    const scrollTop = this.container.scrollTop;
    const visibleCount = Math.ceil(this.container.clientHeight / this.itemHeight);
    const startIndex = Math.floor(scrollTop / this.itemHeight);
    const endIndex = startIndex + visibleCount + 2;

    // Recycle visible items
    this.visibleItems.forEach(item => item.element.remove());
    this.visibleItems = [];

    // Render new items
    for (let i = startIndex; i <= endIndex && i < this.data.length; i++) {
      const item = this.renderItem(this.data[i], i);
      item.element.style.position = 'absolute';
      item.element.style.top = `${i * this.itemHeight}px`;
      this.container.appendChild(item.element);
      this.visibleItems.push(item);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Memory leaks often come from overlooked references. I implement cleanup routines for components:

class Component {
  constructor() {
    this.handlers = [];
    this.cleanupActions = [];
  }

  addEventListener(target, type, listener) {
    target.addEventListener(type, listener);
    this.handlers.push({ target, type, listener });
  }

  setInterval(callback, interval) {
    const id = setInterval(callback, interval);
    this.cleanupActions.push(() => clearInterval(id));
  }

  destroy() {
    this.handlers.forEach(({ target, type, listener }) => {
      target.removeEventListener(type, listener);
    });
    this.cleanupActions.forEach(action => action());
  }
}
Enter fullscreen mode Exit fullscreen mode

Web Workers handle intensive tasks without blocking the UI. Here's my communication pattern:

// Main thread
const worker = new Worker('processor.js');
worker.postMessage(largeDataset);

worker.onmessage = (event) => {
  console.log('Processed:', event.data);
};

// processor.js
self.onmessage = (event) => {
  const result = intensiveCalculation(event.data);
  self.postMessage(result);
};
Enter fullscreen mode Exit fullscreen mode

Event delegation simplifies dynamic content. I attach one listener per container:

document.getElementById('gallery').addEventListener('click', (event) => {
  if (event.target.classList.contains('thumbnail')) {
    showFullImage(event.target.dataset.id);
  }
});
Enter fullscreen mode Exit fullscreen mode

Lazy loading resources improves initial load. I use IntersectionObserver for elements:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
}, { threshold: 0.1 });

document.querySelectorAll('.lazy-image').forEach(img => {
  observer.observe(img);
});
Enter fullscreen mode Exit fullscreen mode

Animations perform best with GPU acceleration. I prioritize transform and opacity:

function animateElement(element) {
  // Optimal
  element.style.transition = 'transform 0.3s';
  element.style.transform = 'scale(1.1)';

  // Avoid
  // element.style.left = `${currentLeft + 10}px`;
}
Enter fullscreen mode Exit fullscreen mode

Data structures significantly impact performance. For frequent key operations, I prefer Map over Object:

// Object approach
const objCache = {};
objCache[key] = value;
delete objCache[key];

// Map approach
const mapCache = new Map();
mapCache.set(key, value);
mapCache.delete(key);
Enter fullscreen mode Exit fullscreen mode

These techniques collectively maintain application responsiveness. Through careful implementation, I've consistently achieved smooth interactions even in complex applications. The key is profiling first to identify actual bottlenecks before applying optimizations. Each project has unique requirements, but these patterns provide a solid foundation for runtime efficiency.

📘 Checkout my latest ebook for free on my channel!

Be sure to like, share, comment, and subscribe to the channel!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Top comments (0)