js-performance-patterns

安装量: 218
排名: #9487

安装

npx skills add https://github.com/patternsdev/skills --skill js-performance-patterns

JavaScript Performance Patterns Table of Contents When to Use Instructions Details Source Runtime performance micro-patterns for JavaScript hot paths. These patterns matter most in tight loops, frequent callbacks (scroll, resize, animation frames), and data-heavy operations. They apply to any JavaScript environment — React, Vue, vanilla, Node.js. When to Use Reference these patterns when: Profiling reveals a hot function or tight loop Processing large datasets (1,000+ items) Handling high-frequency events (scroll, mousemove, resize) Optimizing build-time or server-side scripts Reviewing code for performance in critical paths Instructions Apply these patterns only in measured hot paths — code that runs frequently or processes large datasets. Don't apply them to cold code paths where readability is more important than nanosecond gains. Details Overview Micro-optimizations are not a substitute for algorithmic improvements. Address the algorithm first (O(n^2) to O(n), removing waterfalls, reducing DOM mutations). Once the algorithm is right, these patterns squeeze additional performance from hot paths. 1. Use Set and Map for Lookups Impact: HIGH for large collections — O(1) vs O(n) per lookup. Array methods like .includes() , .find() , and .indexOf() scan linearly. For repeated lookups against the same collection, convert to Set or Map first. Avoid — O(n) per check: const 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) Prefer — O(1) per check: 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) For key-value lookups, use Map instead of scanning an array of objects: // 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) 2. Batch DOM Reads and Writes Impact: HIGH — Prevents layout thrashing. Interleaving DOM reads (e.g., offsetHeight , getBoundingClientRect ) with DOM writes (e.g., style.height = ... ) forces the browser to recalculate layout multiple times. Batch all reads first, then all writes. Avoid — layout thrashing (read/write/read/write): elements . forEach ( el => { const height = el . offsetHeight // read → forces layout el . style . height = ${ height * 2 } px // write } ) // Each iteration forces a layout recalculation Prefer — batched reads then writes: // Read phase const heights = elements . map ( el => el . offsetHeight ) // Write phase elements . forEach ( ( el , i ) => { el . style . height = ${ heights [ i ] * 2 } px } ) For complex cases, use requestAnimationFrame to defer writes to the next frame, or use a library like fastdom . CSS class approach — single reflow: // 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;' 3. Cache Property Access in Tight Loops Impact: MEDIUM — Reduces repeated property resolution. Accessing deeply nested properties or array .length in every iteration adds overhead in tight loops. Avoid: for ( let i = 0 ; i < data . items . length ; i ++ ) { process ( data . items [ i ] . value . nested . prop ) } Prefer: const { items } = data for ( let i = 0 , len = items . length ; i < len ; i ++ ) { const val = items [ i ] . value . nested . prop process ( val ) } This matters for arrays with 10,000+ items or when called at 60fps. For small arrays or infrequent calls, the readable version is fine. 4. Memoize Expensive Function Results Impact: MEDIUM-HIGH — Avoids recomputing the same result. When a pure function is called repeatedly with the same arguments, cache the result. Simple single-value cache: 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 ) } ) Multi-key cache with Map: 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 } For caches that can grow unbounded, use an LRU strategy or WeakMap for object keys. 5. Combine Iterations Over the Same Data Impact: MEDIUM — Single pass instead of multiple. Chaining .filter().map().reduce() creates intermediate arrays and iterates the data multiple times. For large arrays in hot paths, combine into a single loop. Avoid — 3 iterations, 2 intermediate arrays: const result = users . filter ( u => u . active ) . map ( u => u . name ) . reduce ( ( acc , name ) => acc + name + ', ' , '' ) Prefer — single pass: let result = '' for ( const u of users ) { if ( u . active ) { result += u . name + ', ' } } For small arrays (< 100 items), the chained version is fine and more readable. Optimize only when profiling shows it matters. 6. Short-Circuit with Length Checks First Impact: LOW-MEDIUM — Avoids expensive operations on empty inputs. Before running expensive comparisons or transformations, check if the input is empty. 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 ) ) } 7. Return Early to Skip Unnecessary Work Impact: LOW-MEDIUM — Reduces average-case execution. Structure functions to exit as soon as possible for common non-matching cases. Avoid — always does full work: function processEvent ( event : AppEvent ) { let result = null if ( event . type === 'click' ) { if ( event . target && event . target . matches ( '.actionable' ) ) { result = handleAction ( event ) } } return result } Prefer — exits early: function processEvent ( event : AppEvent ) { if ( event . type !== 'click' ) return null if ( ! event . target ?. matches ( '.actionable' ) ) return null return handleAction ( event ) } 8. Hoist RegExp and Constant Creation Outside Loops Impact: LOW-MEDIUM — Avoids repeated compilation. Creating RegExp objects or constant values inside loops or frequently-called functions wastes CPU. Avoid — compiles regex 10,000 times: function validate ( items : string [ ] ) { return items . filter ( item => { const pattern = / ^ [ a - z A - Z 0 - 9 .%+- ] + @ [ a - z A - Z 0 - 9 .- ] + . [ a - z A - Z ] {2,} $ / return pattern . test ( item ) } ) } Prefer — compile once: const EMAIL_PATTERN = / ^ [ a - z A - Z 0 - 9 .%+- ] + @ [ a - z A - Z 0 - 9 .- ] + . [ a - z A - Z ] {2,} $ / function validate ( items : string [ ] ) { return items . filter ( item => EMAIL_PATTERN . test ( item ) ) } 9. Use toSorted() , toReversed() , toSpliced() for Immutability Impact: LOW — Correct immutability without manual copying. The new non-mutating array methods avoid the [...arr].sort() pattern and communicate intent more clearly. Avoid — manual copy then mutate: const sorted = [ ... items ] . sort ( ( a , b ) => a . price - b . price ) const reversed = [ ... items ] . reverse ( ) const without = [ ... items ] ; without . splice ( index , 1 ) Prefer — non-mutating methods: const sorted = items . toSorted ( ( a , b ) => a . price - b . price ) const reversed = items . toReversed ( ) const without = items . toSpliced ( index , 1 ) These are available in all modern browsers and Node.js 20+. 10. Use requestAnimationFrame for Visual Updates Impact: MEDIUM — Syncs with the browser's render cycle. DOM updates triggered outside the rendering cycle (from timers, event handlers, etc.) can cause jank. Batch visual updates inside requestAnimationFrame . Avoid — updates outside render cycle: window . addEventListener ( 'scroll' , ( ) => { progressBar . style . width = ${ getScrollPercent ( ) } % counter . textContent = ${ getScrollPercent ( ) } % } , { passive : true } ) Prefer — synced to render: 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 } ) 11. Use structuredClone for Deep Copies Impact: LOW — Correct deep cloning without libraries. structuredClone() handles circular references, typed arrays, Dates, RegExps, Maps, and Sets — unlike 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 ) Note: structuredClone cannot clone functions or DOM nodes. For those cases, implement a custom clone. 12. Prefer Map Over Plain Objects for Dynamic Keys Impact: LOW-MEDIUM — Better performance for frequent additions/deletions. V8 optimizes plain objects for static shapes. When keys are added and removed dynamically (caches, counters, registries), Map provides consistently better performance. // 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 ) } ) Source Patterns from patterns.dev — JavaScript performance guidance for the broader web engineering community.

返回排行榜