Loading...
Loading...
JavaScript runtime performance patterns for hot paths, loops, DOM operations, caching, and data structures. Framework-agnostic.
npx skill4agent add patternsdev/skills js-performance-patternsSetMap.includes().find().indexOf()SetMapconst allowedIds = ['a', 'b', 'c', /* ...hundreds more */]
function isAllowed(id: string) {
return allowedIds.includes(id) // scans entire array
}
items.filter(item => allowedIds.includes(item.id)) // O(n * m)const allowedIds = new Set(['a', 'b', 'c', /* ...hundreds more */])
function isAllowed(id: string) {
return allowedIds.has(id)
}
items.filter(item => allowedIds.has(item.id)) // O(n)Map// Avoid
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
const user = users.find(u => u.id === targetId) // O(n)
// Prefer
const userMap = new Map(users.map(u => [u.id, u]))
const user = userMap.get(targetId) // O(1)offsetHeightgetBoundingClientRectstyle.height = ...elements.forEach(el => {
const height = el.offsetHeight // read → forces layout
el.style.height = `${height * 2}px` // write
})
// Each iteration forces a layout recalculation// Read phase
const heights = elements.map(el => el.offsetHeight)
// Write phase
elements.forEach((el, i) => {
el.style.height = `${heights[i] * 2}px`
})requestAnimationFrame// Avoid multiple style mutations
el.style.width = '100px'
el.style.height = '200px'
el.style.margin = '10px'
// Prefer — one reflow
el.classList.add('expanded')
// or
el.style.cssText = 'width:100px;height:200px;margin:10px;'.lengthfor (let i = 0; i < data.items.length; i++) {
process(data.items[i].value.nested.prop)
}const { items } = data
for (let i = 0, len = items.length; i < len; i++) {
const val = items[i].value.nested.prop
process(val)
}function memoize<T extends (...args: any[]) => any>(fn: T): T {
let lastArgs: any[] | undefined
let lastResult: any
return ((...args: any[]) => {
if (lastArgs && args.every((arg, i) => Object.is(arg, lastArgs![i]))) {
return lastResult
}
lastArgs = args
lastResult = fn(...args)
return lastResult
}) as T
}
const expensiveCalc = memoize((data: number[]) => {
return data.reduce((sum, n) => sum + heavyTransform(n), 0)
})const cache = new Map<string, Result>()
function getResult(key: string): Result {
if (cache.has(key)) return cache.get(key)!
const result = computeExpensiveResult(key)
cache.set(key, result)
return result
}WeakMap.filter().map().reduce()const result = users
.filter(u => u.active)
.map(u => u.name)
.reduce((acc, name) => acc + name + ', ', '')let result = ''
for (const u of users) {
if (u.active) {
result += u.name + ', '
}
}function findMatchingItems(items: Item[], query: string): Item[] {
if (items.length === 0 || query.length === 0) return []
const normalized = query.toLowerCase()
return items.filter(item =>
item.name.toLowerCase().includes(normalized)
)
}function processEvent(event: AppEvent) {
let result = null
if (event.type === 'click') {
if (event.target && event.target.matches('.actionable')) {
result = handleAction(event)
}
}
return result
}function processEvent(event: AppEvent) {
if (event.type !== 'click') return null
if (!event.target?.matches('.actionable')) return null
return handleAction(event)
}function validate(items: string[]) {
return items.filter(item => {
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
return pattern.test(item)
})
}const EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
function validate(items: string[]) {
return items.filter(item => EMAIL_PATTERN.test(item))
}toSorted()toReversed()toSpliced()[...arr].sort()const sorted = [...items].sort((a, b) => a.price - b.price)
const reversed = [...items].reverse()
const without = [...items]; without.splice(index, 1)const sorted = items.toSorted((a, b) => a.price - b.price)
const reversed = items.toReversed()
const without = items.toSpliced(index, 1)requestAnimationFramerequestAnimationFramewindow.addEventListener('scroll', () => {
progressBar.style.width = `${getScrollPercent()}%`
counter.textContent = `${getScrollPercent()}%`
}, { passive: true })let ticking = false
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
const pct = getScrollPercent()
progressBar.style.width = `${pct}%`
counter.textContent = `${pct}%`
ticking = false
})
ticking = true
}
}, { passive: true })structuredClonestructuredClone()JSON.parse(JSON.stringify())// Avoid — loses Dates, Maps, Sets, undefined values
const copy = JSON.parse(JSON.stringify(original))
// Prefer — handles all standard types
const copy = structuredClone(original)structuredCloneMapMap// Avoid for dynamic keys
const counts: Record<string, number> = {}
items.forEach(item => {
counts[item.category] = (counts[item.category] || 0) + 1
})
// Prefer for dynamic keys
const counts = new Map<string, number>()
items.forEach(item => {
counts.set(item.category, (counts.get(item.category) ?? 0) + 1)
})