दिन 25: DOM Performance और Patterns
✅ आज आप सीखेंगे
- Browser कैसे render करता है: JS → style → layout → paint
- Layout thrashing और reads/writes batch करना
- requestAnimationFrame, passive listeners, code lazy-load
- performance.now, DevTools Performance और Lighthouse से मापना
- Patterns: module, pub/sub (observer) और छोटा store
🏢 असली दुनिया में
मालिक के ₹8,000 वाले phone पर product page उपयोग लायक होने में 4 सेकंड लेता है और scrolling अटकती है। Google इसे मापता है (INP, LCP) और धीमी sites को नीचे रखता है; customers चले जाते हैं। ज़्यादातर JavaScript धीमापन कुछ ग़लतियों से आता है – आज आप मापकर उन्हें ढूँढना और आज़माए patterns से ठीक करना सीखेंगे।
Browser एक frame कैसे बनाता है
50 ms से लंबा कोई भी JavaScript task taps और scrolling रोकता है (“long task”)। Event handlers छोटे रखें, भारी काम मुख्य रास्ते से हटाएँ (chunks, Web Workers) और DOM batches में update करें।
Layout thrashing: पहले पढ़ो, फिर लिखो
<button id="slow">Slow: read/write mixed</button> <button id="fast">Fast: read all, then write all</button>
<p id="t"></p>
<div id="boxes"></div>
<script>
const boxes = document.querySelector('#boxes');
for (let i = 0; i < 400; i++) {
const d = document.createElement('div');
d.style.cssText = 'height:4px;margin:1px 0;background:#fde68a;width:50%';
boxes.append(d);
}
const items = [...boxes.children];
document.querySelector('#slow').addEventListener('click', () => {
const t0 = performance.now();
for (const el of items) {
const w = el.offsetWidth; // READ (forces layout)
el.style.width = (w % 300) + 20 + 'px'; // WRITE (invalidates layout) → next read recalculates
}
document.querySelector('#t').textContent = `Mixed: ${(performance.now() - t0).toFixed(1)} ms`;
});
document.querySelector('#fast').addEventListener('click', () => {
const t0 = performance.now();
const widths = items.map(el => el.offsetWidth); // all READS first
items.forEach((el, i) => { el.style.width = (widths[i] % 300) + 20 + 'px'; }); // then all WRITES
document.querySelector('#t').textContent = `Batched: ${(performance.now() - t0).toFixed(1)} ms`;
});
</script>| Write के बाद पढ़ने पर layout trigger | बेहतर |
|---|---|
| offsetWidth/Height, clientWidth, scrollTop | एक बार पढ़कर value cache करें |
| getBoundingClientRect() | IntersectionObserver / ResizeObserver |
| getComputedStyle() | Styles पढ़ने की जगह classes toggle |
| Loop में width/top बदलना | transform और opacity (CSS Day 13) |
requestAnimationFrame और passive listeners
<div id="bar" style="height:6px;background:#ca8a04;width:0;position:sticky;top:0;transition:none"></div>
<div id="page" style="height:240px;overflow:auto;border:1px solid #e5e7eb;border-radius:8px;padding:10px">
<p>Scroll this article – the reading-progress bar above updates once per frame.</p>
<div style="height:900px;background:linear-gradient(#fff,#fef3c7)"></div>
</div>
<script>
const page = document.querySelector('#page');
const bar = document.querySelector('#bar');
let ticking = false;
page.addEventListener('scroll', () => {
if (ticking) return; // at most one update per frame
ticking = true;
requestAnimationFrame(() => {
const max = page.scrollHeight - page.clientHeight;
bar.style.width = (page.scrollTop / max * 100) + '%';
ticking = false;
});
}, { passive: true }); // promise not to call preventDefault → smoother scrolling
</script>Long tasks: काम को टुकड़ों में बाँटें
// Processing 50,000 order rows in one go blocks the page; yield between chunks
const orders = Array.from({ length: 50000 }, (_, i) => ({ id: i, total: (i * 37) % 2500 }));
const yieldToBrowser = () => new Promise(r => setTimeout(r, 0)); // browsers also have scheduler.yield()
async function totalInChunks(rows, size = 5000) {
let sum = 0, chunks = 0;
for (let i = 0; i < rows.length; i += size) {
for (const row of rows.slice(i, i + size)) sum += row.total;
chunks++;
await yieldToBrowser(); // let clicks and paints happen between chunks
}
return { sum, chunks };
}
const t0 = performance.now();
totalInChunks(orders).then(r => console.log(`₹${r.sum.toLocaleString('en-IN')} in ${r.chunks} chunks, ${Math.round(performance.now() - t0)} ms total`));सिर्फ़ admin report page पर लगने वाली chart library home page धीमा न करे: btn.onclick = async () => { const { drawChart } = await import("./chart.js"); drawChart(); }। Vite इसे अपने-आप अलग file में बाँटता है।
Optimise करने से पहले मापें
| Tool | दिखाता है |
|---|---|
| performance.now() / console.time | एक function कितना समय लेता है |
| DevTools → Performance → Record | Long tasks (लाल कोने), हर frame में layout, paint |
| Lighthouse | LCP, INP, CLS, total blocking time + सुझाव |
| DevTools → Coverage | बेकार JS और CSS bytes |
| Network tab (Slow 4G throttling) | असली Indian mobile user का अनुभव |
बड़े apps को व्यवस्थित रखने वाले patterns
// Pub/Sub (observer): parts of the app talk without knowing each other
function createEventBus() {
const listeners = {};
return {
on(event, fn) { (listeners[event] ||= []).push(fn); return () => this.off(event, fn); },
off(event, fn) { listeners[event] = (listeners[event] || []).filter(f => f !== fn); },
emit(event, data) { (listeners[event] || []).forEach(fn => fn(data)); },
};
}
// A tiny store: one state object, updates through actions, subscribers re-render
function createStore(initial, bus) {
let state = initial;
return {
get: () => state,
update(change) { state = { ...state, ...change(state) }; bus.emit('change', state); },
};
}
const bus = createEventBus();
const store = createStore({ cart: [], total: 0 }, bus);
const stopBadge = bus.on('change', s => console.log(`🛒 badge: ${s.cart.length} item(s)`));
bus.on('change', s => console.log(`💰 summary: ₹${s.total}`));
store.update(s => ({ cart: [...s.cart, 'Kaju'], total: s.total + 900 }));
stopBadge(); // unsubscribe one listener
store.update(s => ({ cart: [...s.cart, 'Ladoo'], total: s.total + 500 }));| Pattern | क्या हल करता है | कहाँ दिखता है |
|---|---|---|
| Module | Private details + छोटा public API | ES modules, हर library |
| Observer / pub-sub | एक बदलाव पर कई हिस्से प्रतिक्रिया दें | addEventListener, Redux subscribe, Vue reactivity |
| Store (single state) | एक सत्य स्रोत | Redux, Zustand, हमारा to-do app |
| Delegation | कई elements के लिए एक listener | Lists, tables, menus |
| Factory | Configured objects बनाना | createStore, createCounter |
🏅 आज के Job Skills
Practice tasks
🏋️ Practice – ख़ुद करें
Quick quiz
🧠 Quick Quiz – ख़ुद को जाँचें
Interview corner
💼 Interview Corner
धीमा page कैसे तेज़ करेंगे?
पहले मापें (Lighthouse, Performance panel)। फिर JavaScript घटाएँ और defer करें (code-split, lazy-load), long tasks से बचें (काम बाँटें, Web Workers), DOM reads/writes batch करें, rAF और passive listeners, images/fonts optimise, API data cache, और Core Web Vitals दोबारा मापें।
Reflow (layout) क्या है और अनावश्यक reflows से कैसे बचें?
Reflow browser का element geometry दोबारा गिनना है। यह DOM/style बदलाव के बाद होता है और write के बाद layout properties पढ़ने पर जबरन synchronous होता है। Writes से पहले reads batch करके, कई inline styles की जगह classes बदलकर, transform/opacity animate करके और positions polling की जगह observers लेकर बचें।
सारांश
- Frames के पास ~16 ms; 50 ms से लंबे tasks interaction रोकते हैं।
- Writes से पहले DOM reads batch; rAF और passive listeners।
- लंबा काम बाँटें, भारी modules lazy-load, DevTools से मापें।
- Pub/sub और एक store बढ़ते apps व्यवस्थित रखते हैं।
Day 26: security और clean code – XSS, सुरक्षित rendering, secrets, CSP और ऐसा code जिसके लिए team आपको धन्यवाद दे।
