Performance & Optimization
Learn how to optimize your YakaJS applications for maximum performance. This guide covers caching, debouncing, lazy loading, virtual scrolling, and other performance techniques.
Cache your DOM queries to avoid repeated lookups.
// Bad: Query every timefunction updateUI() { _('#header').text('New Title'); _('#header').addClass('active'); _('#header').on('click', handler);}// Good: Cache the selectionconst header = _('#header');function updateUI() { header.text('New Title'); header.addClass('active'); header.on('click', handler);}Limit how often a function can fire using _.debounce().
// Debounce search input_('#search').on('input', _.debounce(function() { const query = _(this).val(); performSearch(query);}, 300)); // Wait 300ms after user stops typing// Debounce window resize_(window).on('resize', _.debounce(function() { recalculateLayout();}, 250));// Debounce scroll events_(window).on('scroll', _.debounce(function() { checkScrollPosition();}, 100));Ensure a function runs at most once per time period using _.throttle().
// Throttle scroll handler (max once per 100ms)_(window).on('scroll', _.throttle(function() { updateScrollIndicator();}, 100));// Throttle mouse move_(document).on('mousemove', _.throttle(function(e) { updateCursorPosition(e.pageX, e.pageY);}, 50));// Throttle resize_(window).on('resize', _.throttle(function() { adjustLayout();}, 200));// Debounce: Wait for pause in events// Use for: Search input, form validation, save buttons_.debounce(fn, 300);// Throttle: Limit execution rate// Use for: Scroll, resize, mouse move_.throttle(fn, 100);Render only visible items in large lists for better performance.
// Create virtual scroll containerconst virtualList = _('#large-list').virtualScroll({ itemHeight: 50, // Height of each item items: 10000, // Total number of items renderItem: function(index) { return `<div class="item">Item ${index + 1}</div>`; }, buffer: 5 // Extra items to render above/below viewport});// Update items dynamicallyvirtualList.update({ items: 20000, renderItem: function(index) { return `<div class="item">Updated Item ${index + 1}</div>`; }});_('#list').virtualScroll({ itemHeight: 60, // Fixed height per item items: 5000, // Total items container: window, // Scroll container (default: element itself) buffer: 10, // Buffer items (default: 5) threshold: 0, // Load threshold in pixels renderItem: function(index, data) { return `<div>${data[index].name}</div>`; }, onScroll: function(startIndex, endIndex) { console.log(`Showing items ${startIndex} to ${endIndex}`); }});Load images and content only when needed.
// Enable lazy loading for images_('img[data-src]').lazyLoad({ threshold: 200, // Load 200px before entering viewport effect: 'fadeIn', // Fade in when loaded placeholder: 'data:image/svg+xml,...' // Placeholder image});// Manual lazy loading_('img[data-src]').each(function() { const img = _(this); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { img.attr('src', img.attr('data-src')); img.removeAttr('data-src'); observer.disconnect(); } }); }); observer.observe(this);});// Load content when scrolled into view_('.lazy-content').lazyLoad({ load: function(element) { const url = _(element).attr('data-url'); _().ajax({ url: url, method: 'GET', success: function(data) { _(element).html(data); } }); }});Minimize reflows by batching DOM changes.
// Bad: Multiple reflows_('#container').append('<div>1</div>');_('#container').append('<div>2</div>');_('#container').append('<div>3</div>');// Good: Single reflowconst items = ['<div>1</div>', '<div>2</div>', '<div>3</div>'];_('#container').append(items.join(''));// Best: Use document fragmentconst fragment = document.createDocumentFragment();for (let i = 0; i < 100; i++) { const div = document.createElement('div'); div.textContent = `Item ${i}`; fragment.appendChild(div);}_('#container').append(fragment);Use event delegation for better performance with dynamic content.
// Bad: Bind to each item (slow with many items)_('.item').on('click', handleClick);// Good: Delegate to container (fast, works with dynamic items)_('#container').on('click', '.item', handleClick);// Delegation with multiple selectors_('#app').on('click', '.btn, .link, .card', function(e) { console.log('Clicked:', _(this).attr('class'));});Batch multiple AJAX requests for better performance.
// Bad: Multiple individual requestsusers.forEach(user => { _().ajax({ url: `/api/users/${user.id}`, method: 'GET' });});// Good: Single batched requestconst userIds = users.map(u => u.id);_().ajax({ url: '/api/users/batch', method: 'POST', data: { ids: userIds }, success: function(usersData) { // Handle all users at once }});Cache function results to avoid repeated calculations.
// Memoize expensive calculationsconst memoize = function(fn) { const cache = {}; return function(...args) { const key = JSON.stringify(args); if (key in cache) return cache[key]; const result = fn.apply(this, args); cache[key] = result; return result; };};// Use memoizationconst expensiveCalc = memoize(function(n) { console.log('Calculating...'); return n * n * n;});console.log(expensiveCalc(5)); // Calculatesconsole.log(expensiveCalc(5)); // Returns cachedOptimize animations for smooth 60fps performance.
// Use CSS transforms instead of top/left_('#element').animate({ transform: 'translateX(100px)', // Hardware accelerated duration: 300});// Avoid animating properties that trigger reflow// Bad (triggers reflow)_('#element').animate({ width: '200px', height: '300px' });// Good (GPU accelerated)_('#element').animate({ transform: 'scale(1.5)' });// Use requestAnimationFrame for custom animationsfunction smoothScroll() { const start = window.pageYOffset; const target = 1000; const duration = 1000; const startTime = performance.now(); function animate(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); const easeProgress = progress * (2 - progress); // Ease out window.scrollTo(0, start + (target - start) * easeProgress); if (progress < 1) { requestAnimationFrame(animate); } } requestAnimationFrame(animate);}Prevent memory leaks and optimize memory usage.
// Clean up event listenersconst element = _('#element');element.on('click', handler);// Later, remove when doneelement.off('click', handler);// Remove all event listenerselement.off();// Clean up when removing elements_('#container').find('.item').each(function() { _(this).off(); // Remove all event listeners}).remove();// Clear references to prevent memory leakslet cachedData = null;function cleanup() { cachedData = null; _(window).off('resize', handleResize);}Preload resources for faster subsequent loads.
// Preload imagesfunction preloadImages(urls) { urls.forEach(url => { const img = new Image(); img.src = url; });}preloadImages([ '/images/hero.jpg', '/images/logo.png', '/images/background.jpg']);// Prefetch API data_().ajax({ url: '/api/data', method: 'GET', priority: 'high', cache: true});Load code only when needed.
// Dynamic import for featuresasync function loadFeature() { const module = await import('./feature.js'); module.initialize();}// Load on user interaction_('#advanced-button').on('click', async function() { _(this).text('Loading...'); await loadFeature(); _(this).text('Feature Loaded!');});Use YakaJS performance utilities to measure and optimize.
// Measure execution timeconst start = performance.now();performComplexOperation();const end = performance.now();console.log(`Operation took ${end - start}ms`);// Use performance mark APIperformance.mark('operation-start');performComplexOperation();performance.mark('operation-end');performance.measure('operation', 'operation-start', 'operation-end');// Log performance metricsconst measure = performance.getEntriesByName('operation')[0];console.log(`Duration: ${measure.duration}ms`);// Badfor (let i = 0; i < 100; i++) { _('#list').append(`<li>Item ${i}</li>`);}// Goodlet html = '';for (let i = 0; i < 100; i++) { html += `<li>Item ${i}</li>`;}_('#list').html(html);// Slow_('div .item .title');// Fast_('#container .item .title');// Fastest_('#container').find('.item').find('.title');// Bad: Causes multiple reflows_('.item').each(function() { const height = _(this).height(); // Read _(this).css('margin-top', height); // Write});// Good: Batch reads, then writesconst heights = [];_('.item').each(function() { heights.push(_(this).height()); // All reads});_('.item').each(function(i) { _(this).css('margin-top', heights[i]); // All writes});// Search with debounce_('#search').on('input', _.debounce(function() { performExpensiveSearch(_(this).val());}, 300));// For lists with 1000+ items_('#large-list').virtualScroll({ itemHeight: 50, items: 10000, renderItem: (i) => `<div>Item ${i}</div>`});