Оптимизация JS-кода охватывает несколько уровней: предотвращение утечек памяти, избегание layout thrashing в браузере, использование мемоизации для дорогих вычислений, debounce/throttle для частых событий, Web Workers для CPU-интенсивных задач. V8 лучше оптимизирует предсказуемый код — не меняй форму объектов и типы переменных после создания.
// УТЕЧКА 1: Event listener без cleanup
class Timer {
start() {
// Каждый вызов start() добавляет новый listener
window.addEventListener('resize', this.handleResize)
}
// Нет метода stop() с removeEventListener!
}
// ИСПРАВЛЕНИЕ:
class Timer {
constructor() {
this.handleResize = this.handleResize.bind(this)
}
start() {
window.addEventListener('resize', this.handleResize)
}
stop() {
window.removeEventListener('resize', this.handleResize)
}
}
// УТЕЧКА 2: Замыкание держит ссылку на большой объект
function createLeak() {
const hugeData = new Array(1000000).fill('data')
return function() {
// hugeData никогда не будет собран GC
// даже если мы не используем его внутри
console.log('done')
}
}
// ИСПРАВЛЕНИЕ: убираем ссылку
function noLeak() {
let hugeData = new Array(1000000).fill('data')
const result = processData(hugeData)
hugeData = null // позволяем GC собрать данные
return function() {
console.log(result)
}
}
// УТЕЧКА 3: Unbounded cache (кэш без лимита)
const cache = {} // растёт бесконечно!
// ИСПРАВЛЕНИЕ: WeakMap (автоочистка) или LRU-кэш с лимитом
const weakCache = new WeakMap() // ключи-объекты освобождаются автоматически// ПЛОХО: чтение и запись DOM вперемешку — каждое чтение форсирует reflow
function badLayout(elements) {
elements.forEach(el => {
const height = el.offsetHeight // READ — forceлayout reflow
el.style.height = height * 2 + 'px' // WRITE
// Следующая итерация: снова READ после WRITE — браузер обязан пересчитать!
})
}
// ХОРОШО: сначала все reads, потом все writes (batching)
function goodLayout(elements) {
// Фаза чтения
const heights = elements.map(el => el.offsetHeight)
// Фаза записи (браузер может батчить)
elements.forEach((el, i) => {
el.style.height = heights[i] * 2 + 'px'
})
}// НЕ используй setInterval для анимаций
setInterval(() => {
element.style.left = (x++) + 'px'
}, 16) // может не совпасть с частотой экрана, вызывает jank
// requestAnimationFrame — синхронизирован с частотой обновления экрана
function animate() {
element.style.left = (x++) + 'px'
if (x < 100) {
requestAnimationFrame(animate) // следующий кадр
}
}
requestAnimationFrame(animate)function memoize(fn) {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
return cache.get(key) // возвращаем кэшированный результат
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// Для рекурсивных функций — мемоизация значительно ускоряет
const fib = memoize(function(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
})
// fib(40): без мемоизации ~1 млрд вызовов, с мемоизацией — 40// ПЛОХО: V8 не может оптимизировать полиморфный код
function process(obj) {
return obj.value * 2
}
process({ value: 1, name: 'a' }) // форма 1
process({ value: 2, extra: true }) // форма 2 — V8 деоптимизирует!
// ХОРОШО: всегда одна форма объекта
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
// Все Point имеют одинаковую форму — V8 генерирует оптимальный машинный код
// ПЛОХО: изменение типа переменной
let x = 42 // integer
x = 'hello' // string — V8 деоптимизирует переменную!
// ХОРОШО: стабильные типы
const count = 42
const text = 'hello'// Главный поток (main.js)
// Тяжёлые вычисления блокируют UI — используй Worker
const worker = new Worker('worker.js')
worker.postMessage({ data: largeArray })
worker.onmessage = (event) => {
console.log('Результат из Worker:', event.data.result)
}
// worker.js — выполняется в отдельном потоке
self.onmessage = (event) => {
const { data } = event.data
const result = heavyComputation(data) // не блокирует UI
self.postMessage({ result })
}Структурируй ответ по категориям: память, рендеринг, вычисления. Приведи конкретные примеры утечек (event listeners, замыкания). Обязательно упомяни мемоизацию и объясни, где она применима (чистые функции, дорогие вычисления). Упомяни requestAnimationFrame вместо setInterval для анимаций. Если спрашивают про большие объёмы данных — скажи про Web Workers.
Мемоизация с лимитом кэша, демонстрация утечки памяти и исправление, замер производительности
// ===== МЕМОИЗАЦИЯ =====
console.log('=== Мемоизация ===')
function memoize(fn, maxSize = 100) {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log('Кэш hit:', key)
return cache.get(key)
}
// LRU: если кэш полон — удаляем самый старый элемент
if (cache.size >= maxSize) {
const firstKey = cache.keys().next().value
cache.delete(firstKey)
}
const result = fn.apply(this, args)
cache.set(key, result)
console.log('Кэш miss, вычислено:', result)
return result
}
}
// Тяжёлая функция — Фибоначчи
function slowFib(n) {
if (n <= 1) return n
return slowFib(n - 1) + slowFib(n - 2)
}
// Мемоизированная версия
const fastFib = memoize(function fib(n) {
if (n <= 1) return n
return fastFib(n - 1) + fastFib(n - 2)
})
console.log('\nfib(10):')
console.log(fastFib(10)) // вычисляет
console.log('\nfib(10) снова:')
console.log(fastFib(10)) // из кэша
// Замер разницы в производительности
console.log('\n=== Замер производительности ===')
let t0, t1
// БЕЗ мемоизации
t0 = Date.now()
const resultSlow = slowFib(35)
t1 = Date.now()
console.log('slowFib(35):', resultSlow, '| Время:', t1 - t0, 'ms')
// С мемоизацией
const memoFib = memoize(function fibM(n) {
if (n <= 1) return n
return memoFib(n - 1) + memoFib(n - 2)
})
t0 = Date.now()
const resultFast = memoFib(35)
t1 = Date.now()
console.log('memoFib(35):', resultFast, '| Время:', t1 - t0, 'ms')
// ===== ПАТТЕРН УТЕЧКИ ПАМЯТИ И ИСПРАВЛЕНИЕ =====
console.log('\n=== Утечка памяти: пример и исправление ===')
// УТЕЧКА: unbounded cache
function createLeakyService() {
const requestCache = {} // никогда не очищается!
return {
getData(key) {
if (!requestCache[key]) {
requestCache[key] = { data: new Array(1000).fill(key), timestamp: Date.now() }
}
return requestCache[key]
},
// нет метода для очистки кэша!
getCacheSize() {
return Object.keys(requestCache).length
}
}
}
// ИСПРАВЛЕНИЕ: кэш с TTL и лимитом
function createSafeService(maxItems = 50, ttlMs = 60000) {
const cache = new Map()
function cleanup() {
const now = Date.now()
for (const [key, entry] of cache.entries()) {
if (now - entry.timestamp > ttlMs) {
cache.delete(key)
}
}
// Также соблюдаем лимит по размеру (LRU)
while (cache.size > maxItems) {
const oldestKey = cache.keys().next().value
cache.delete(oldestKey)
}
}
return {
getData(key) {
cleanup() // очищаем устаревшие записи
if (cache.has(key)) {
return cache.get(key).data
}
const data = { value: key, computed: Date.now() }
cache.set(key, { data, timestamp: Date.now() })
return data
},
getCacheSize() {
return cache.size
}
}
}
const leaky = createLeakyService()
const safe = createSafeService(3) // лимит 3 для демо
// Симулируем много запросов
for (let i = 0; i < 10; i++) {
leaky.getData(`key-${i}`)
safe.getData(`key-${i}`)
}
console.log('Leaky кэш размер:', leaky.getCacheSize()) // 10 (растёт)
console.log('Safe кэш размер:', safe.getCacheSize()) // 3 (ограничен)
// ===== МЕМОИЗАЦИЯ С РАЗНЫМИ ТИПАМИ АРГУМЕНТОВ =====
console.log('\n=== Мемоизация сложных аргументов ===')
const memoizeDeep = memoize((arr, multiplier) => {
console.log(' Вычисление...')
return arr.map(x => x * multiplier)
})
console.log(memoizeDeep([1, 2, 3], 2)) // вычисляет
console.log(memoizeDeep([1, 2, 3], 2)) // из кэша
console.log(memoizeDeep([1, 2, 3], 3)) // новые аргументы — вычисляетОптимизация JS-кода охватывает несколько уровней: предотвращение утечек памяти, избегание layout thrashing в браузере, использование мемоизации для дорогих вычислений, debounce/throttle для частых событий, Web Workers для CPU-интенсивных задач. V8 лучше оптимизирует предсказуемый код — не меняй форму объектов и типы переменных после создания.
// УТЕЧКА 1: Event listener без cleanup
class Timer {
start() {
// Каждый вызов start() добавляет новый listener
window.addEventListener('resize', this.handleResize)
}
// Нет метода stop() с removeEventListener!
}
// ИСПРАВЛЕНИЕ:
class Timer {
constructor() {
this.handleResize = this.handleResize.bind(this)
}
start() {
window.addEventListener('resize', this.handleResize)
}
stop() {
window.removeEventListener('resize', this.handleResize)
}
}
// УТЕЧКА 2: Замыкание держит ссылку на большой объект
function createLeak() {
const hugeData = new Array(1000000).fill('data')
return function() {
// hugeData никогда не будет собран GC
// даже если мы не используем его внутри
console.log('done')
}
}
// ИСПРАВЛЕНИЕ: убираем ссылку
function noLeak() {
let hugeData = new Array(1000000).fill('data')
const result = processData(hugeData)
hugeData = null // позволяем GC собрать данные
return function() {
console.log(result)
}
}
// УТЕЧКА 3: Unbounded cache (кэш без лимита)
const cache = {} // растёт бесконечно!
// ИСПРАВЛЕНИЕ: WeakMap (автоочистка) или LRU-кэш с лимитом
const weakCache = new WeakMap() // ключи-объекты освобождаются автоматически// ПЛОХО: чтение и запись DOM вперемешку — каждое чтение форсирует reflow
function badLayout(elements) {
elements.forEach(el => {
const height = el.offsetHeight // READ — forceлayout reflow
el.style.height = height * 2 + 'px' // WRITE
// Следующая итерация: снова READ после WRITE — браузер обязан пересчитать!
})
}
// ХОРОШО: сначала все reads, потом все writes (batching)
function goodLayout(elements) {
// Фаза чтения
const heights = elements.map(el => el.offsetHeight)
// Фаза записи (браузер может батчить)
elements.forEach((el, i) => {
el.style.height = heights[i] * 2 + 'px'
})
}// НЕ используй setInterval для анимаций
setInterval(() => {
element.style.left = (x++) + 'px'
}, 16) // может не совпасть с частотой экрана, вызывает jank
// requestAnimationFrame — синхронизирован с частотой обновления экрана
function animate() {
element.style.left = (x++) + 'px'
if (x < 100) {
requestAnimationFrame(animate) // следующий кадр
}
}
requestAnimationFrame(animate)function memoize(fn) {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
return cache.get(key) // возвращаем кэшированный результат
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// Для рекурсивных функций — мемоизация значительно ускоряет
const fib = memoize(function(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
})
// fib(40): без мемоизации ~1 млрд вызовов, с мемоизацией — 40// ПЛОХО: V8 не может оптимизировать полиморфный код
function process(obj) {
return obj.value * 2
}
process({ value: 1, name: 'a' }) // форма 1
process({ value: 2, extra: true }) // форма 2 — V8 деоптимизирует!
// ХОРОШО: всегда одна форма объекта
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
// Все Point имеют одинаковую форму — V8 генерирует оптимальный машинный код
// ПЛОХО: изменение типа переменной
let x = 42 // integer
x = 'hello' // string — V8 деоптимизирует переменную!
// ХОРОШО: стабильные типы
const count = 42
const text = 'hello'// Главный поток (main.js)
// Тяжёлые вычисления блокируют UI — используй Worker
const worker = new Worker('worker.js')
worker.postMessage({ data: largeArray })
worker.onmessage = (event) => {
console.log('Результат из Worker:', event.data.result)
}
// worker.js — выполняется в отдельном потоке
self.onmessage = (event) => {
const { data } = event.data
const result = heavyComputation(data) // не блокирует UI
self.postMessage({ result })
}Структурируй ответ по категориям: память, рендеринг, вычисления. Приведи конкретные примеры утечек (event listeners, замыкания). Обязательно упомяни мемоизацию и объясни, где она применима (чистые функции, дорогие вычисления). Упомяни requestAnimationFrame вместо setInterval для анимаций. Если спрашивают про большие объёмы данных — скажи про Web Workers.
Мемоизация с лимитом кэша, демонстрация утечки памяти и исправление, замер производительности
// ===== МЕМОИЗАЦИЯ =====
console.log('=== Мемоизация ===')
function memoize(fn, maxSize = 100) {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log('Кэш hit:', key)
return cache.get(key)
}
// LRU: если кэш полон — удаляем самый старый элемент
if (cache.size >= maxSize) {
const firstKey = cache.keys().next().value
cache.delete(firstKey)
}
const result = fn.apply(this, args)
cache.set(key, result)
console.log('Кэш miss, вычислено:', result)
return result
}
}
// Тяжёлая функция — Фибоначчи
function slowFib(n) {
if (n <= 1) return n
return slowFib(n - 1) + slowFib(n - 2)
}
// Мемоизированная версия
const fastFib = memoize(function fib(n) {
if (n <= 1) return n
return fastFib(n - 1) + fastFib(n - 2)
})
console.log('\nfib(10):')
console.log(fastFib(10)) // вычисляет
console.log('\nfib(10) снова:')
console.log(fastFib(10)) // из кэша
// Замер разницы в производительности
console.log('\n=== Замер производительности ===')
let t0, t1
// БЕЗ мемоизации
t0 = Date.now()
const resultSlow = slowFib(35)
t1 = Date.now()
console.log('slowFib(35):', resultSlow, '| Время:', t1 - t0, 'ms')
// С мемоизацией
const memoFib = memoize(function fibM(n) {
if (n <= 1) return n
return memoFib(n - 1) + memoFib(n - 2)
})
t0 = Date.now()
const resultFast = memoFib(35)
t1 = Date.now()
console.log('memoFib(35):', resultFast, '| Время:', t1 - t0, 'ms')
// ===== ПАТТЕРН УТЕЧКИ ПАМЯТИ И ИСПРАВЛЕНИЕ =====
console.log('\n=== Утечка памяти: пример и исправление ===')
// УТЕЧКА: unbounded cache
function createLeakyService() {
const requestCache = {} // никогда не очищается!
return {
getData(key) {
if (!requestCache[key]) {
requestCache[key] = { data: new Array(1000).fill(key), timestamp: Date.now() }
}
return requestCache[key]
},
// нет метода для очистки кэша!
getCacheSize() {
return Object.keys(requestCache).length
}
}
}
// ИСПРАВЛЕНИЕ: кэш с TTL и лимитом
function createSafeService(maxItems = 50, ttlMs = 60000) {
const cache = new Map()
function cleanup() {
const now = Date.now()
for (const [key, entry] of cache.entries()) {
if (now - entry.timestamp > ttlMs) {
cache.delete(key)
}
}
// Также соблюдаем лимит по размеру (LRU)
while (cache.size > maxItems) {
const oldestKey = cache.keys().next().value
cache.delete(oldestKey)
}
}
return {
getData(key) {
cleanup() // очищаем устаревшие записи
if (cache.has(key)) {
return cache.get(key).data
}
const data = { value: key, computed: Date.now() }
cache.set(key, { data, timestamp: Date.now() })
return data
},
getCacheSize() {
return cache.size
}
}
}
const leaky = createLeakyService()
const safe = createSafeService(3) // лимит 3 для демо
// Симулируем много запросов
for (let i = 0; i < 10; i++) {
leaky.getData(`key-${i}`)
safe.getData(`key-${i}`)
}
console.log('Leaky кэш размер:', leaky.getCacheSize()) // 10 (растёт)
console.log('Safe кэш размер:', safe.getCacheSize()) // 3 (ограничен)
// ===== МЕМОИЗАЦИЯ С РАЗНЫМИ ТИПАМИ АРГУМЕНТОВ =====
console.log('\n=== Мемоизация сложных аргументов ===')
const memoizeDeep = memoize((arr, multiplier) => {
console.log(' Вычисление...')
return arr.map(x => x * multiplier)
})
console.log(memoizeDeep([1, 2, 3], 2)) // вычисляет
console.log(memoizeDeep([1, 2, 3], 2)) // из кэша
console.log(memoizeDeep([1, 2, 3], 3)) // новые аргументы — вычисляетРеализуй функцию memoize(fn, maxSize), которая кэширует результаты вызовов fn. Параметр maxSize ограничивает размер кэша: при достижении лимита удаляется самая старая запись (LRU). Функция должна корректно работать с любыми сериализуемыми аргументами.
Используй new Map() для кэша. Ключ: JSON.stringify(args). Для LRU: cache.keys().next().value даёт первый (старейший) ключ Map. Проверяй cache.size >= maxSize перед записью.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке