JS Производительность
Производительность JavaScript — совокупность практик оптимизации времени выполнения кода, потребления памяти и отзывчивости UI: измерение узких мест, минимизация блокировки главного потока, эффективная работа с DOM и памятью.
Зачем нужно
Медленный JavaScript прямо влияет на пользовательский опыт: задержки в UI, низкий FPS анимаций, высокое потребление памяти. Оптимизация без измерения бесполезна — сначала профилируй, затем оптимизируй. Знание техник позволяет писать код, дружелюбный к V8 и браузерному рендер-пайплайну.
Где используется
- Анимации и интерактивные интерфейсы (60fps)
- Обработка больших массивов данных
- Рендеринг длинных списков
- Оптимизация частых обработчиков событий
- Снижение нагрузки при первой загрузке страницы
Основной контент
Измерение производительности
// Performance API
performance.mark('start');
// ... код для измерения ...
performance.mark('end');
performance.measure('операция', 'start', 'end');
const [measure] = performance.getEntriesByName('операция');
console.log(`Время: ${measure.duration.toFixed(2)}ms`);
// Быстрый замер
console.time('sort');
const arr = Array.from({ length: 100000 }, () => Math.random);
arr.sort((a, b) => a - b);
console.timeEnd('sort');
Оптимизация DOM
// Плохо: множество reflow (перерасчёт layout)
const list = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Пункт ${i}`;
list.appendChild(li); // reflow на каждой итерации
}
// Хорошо: DocumentFragment — один reflow
const fragment = document.createDocumentFragment;
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Пункт ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment);
// Избегать чтение layout-свойств в цикле
// Плохо: forced synchronous layout
elements.forEach(el => {
const height = el.offsetHeight; // чтение → reflow
el.style.width = height + 'px'; // запись → invalidate
});
// Хорошо: сначала все чтения, потом все записи
const heights = elements.map(el => el.offsetHeight);
elements.forEach((el, i) => { el.style.width = heights[i] + 'px'; });
Throttle и Debounce для событий
// Debounce — выполнить после паузы
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout( => fn(...args), delay);
};
}
// Throttle — не чаще раза в период
function throttle(fn, limit) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn(...args);
}
};
}
window.addEventListener('resize', throttle(handleResize, 100));
input.addEventListener('input', debounce(search, 300));
Мемоизация и кэширование
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const fib = memoize(function(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
requestAnimationFrame для анимаций
// Плохо: setInterval не синхронизирован с рендером
setInterval(animate, 16);
// Хорошо: rAF выполняется перед каждым кадром
function animate(timestamp) {
// обновление на основе timestamp
element.style.transform = `translateX(${timestamp % 200}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Частые ошибки
- Оптимизация без профилирования — используйте DevTools Performance и Memory Profiler, чтобы найти реальные узкие места, а не предполагаемые.
- Чтение layout-свойств (
offsetWidth,scrollTop) в цикле — вызывает forced synchronous layout (layout thrashing). Вынесите чтение за пределы цикла. - Утечки памяти через замыкания и EventListener — слушатели событий, хранящие ссылки на DOM-узлы, мешают сборщику мусора. Удаляйте через
removeEventListener.
V8 internals — как JIT влияет на ваш код
JavaScript исполняется двухпластово: Ignition (интерпретатор байт-кода) + TurboFan (оптимизирующий компилятор). Понимание этой пары — основа всех оптимизаций.
- Ignition vs TurboFan — основная пара V8, через которую проходит весь JS
- Inline Cache и hidden classes — почему форма объекта решает скорость доступа
- Monomorphic Polymorphic Megamorphic — три состояния IC, главный регулятор скорости
- Деоптимизация в V8 — почему «оптимизированный» код вдруг тормозит
- OSR -- On-Stack Replacement — оптимизация горячих циклов внутри холодной функции
- Не мешай интерпретатору — список того, что ломает оптимизацию
Бенчмарки и измерения
- Бенчмарки JS -- как правильно — почему ваш
console.timeврёт - V8 Native Syntax —
%OptimizeFunctionOnNextCall,%DebugPrintи компания - Profiling Node -- инструменты —
--inspect,--prof, clinic.js - Сложность алгоритмов в JS — Big-O vs константы, когда что важнее
Что реально оптимизировать
- Что убивает перформанс — топ-killers из практики AsForJS
- 10 мифов об оптимизации JS — список устаревших советов
- Императивный код и процессор — почему
forиногда в 10x быстрее.map.filter.reduce - for vs forEach vs reduce — сравнение циклов с замером деоптов
- Switch vs IF в V8 — switch — синтаксический сахар над if
- Array SMI vs Double — типы elements массива в V8
- Async Function -- скрытая стоимость — каждое await не бесплатно
Утечки памяти и GC
- Garbage Collection в V8 — generational GC: Scavenger + Mark-Sweep
- Утечки памяти и GC в Node — главные источники, поиск через 3-snapshot
Связанные темы
- _MOC JavaScript
- Debounce и Throttle для DOM-событий
- MutationObserver -- наблюдение за DOM
- WeakMap и WeakSet
- _MOC Производительность