[{"data":1,"prerenderedAt":3148},["ShallowReactive",2],{"page-\u002Fjs\u002F19-the-dom":3},{"id":4,"title":5,"body":6,"description":3141,"extension":3142,"meta":3143,"navigation":46,"path":3144,"seo":3145,"stem":3146,"__hash__":3147},"content\u002Fjs\u002F19-the-dom.md","JavaScript 19 — DOM Internals: Selection, Mutation & Live Collections",{"type":7,"value":8,"toc":3131},"minimark",[9,14,19,442,446,771,775,1101,1105,1424,1428,1873,1877,2513,2517,2790,2794,2798,2883,3128],[10,11,13],"h1",{"id":12},"_19-dom-internals-selection-mutation-live-collections","19 — DOM Internals: Selection, Mutation & Live Collections",[15,16,18],"h2",{"id":17},"dom-selection-methods","DOM Selection Methods",[20,21,23],"code-wrapper",{"language":22},"javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F ── Selection methods (ranked by specificity and performance) ──\n\n\u002F\u002F getElementById: fastest, returns a single element (or null)\nconst el = document.getElementById(\"header\");  \u002F\u002F O(1) — hashed lookup by ID\n\u002F\u002F Only works for IDs. Returns the first match (IDs should be unique).\n\n\u002F\u002F querySelector: CSS selector, returns first match (or null)\nconst first = document.querySelector(\".item\");          \u002F\u002F first .item\nconst nested = document.querySelector(\".container > .item:first-child\");\nconst complex = document.querySelector('[data-role=\"admin\"]:not(.disabled)');\n\n\u002F\u002F querySelectorAll: returns a STATIC NodeList (not live — doesn't update)\nconst items = document.querySelectorAll(\".item\");  \u002F\u002F NodeList (array-like, has forEach)\nitems.forEach(item => console.log(item.textContent));\n\u002F\u002F ⚠️ This is a SNAPSHOT — if you add a new .item later, this NodeList doesn't update.\n\n\u002F\u002F getElementsByClassName \u002F getElementsByTagName: return LIVE HTMLCollection\nconst liveItems = document.getElementsByClassName(\"item\");  \u002F\u002F HTMLCollection (LIVE)\nconst allDivs = document.getElementsByTagName(\"div\");     \u002F\u002F HTMLCollection (LIVE)\n\u002F\u002F ⚠️ LIVE collection: if you add\u002Fremove elements, the collection updates automatically.\n\u002F\u002F This can cause bugs in loops (adding elements extends the loop → infinite loop).\n\n\u002F\u002F ── Live collection loop trap ──\nconst divs = document.getElementsByTagName(\"div\");\n\u002F\u002F ❌ NAIVE — modifying the DOM while iterating a live collection\n\u002F\u002F for (let i = 0; i \u003C divs.length; i++) {\n\u002F\u002F     document.body.appendChild(document.createElement(\"div\"));  \u002F\u002F adds to DOM → divs.length grows\n\u002F\u002F }  \u002F\u002F infinite loop! (divs.length keeps increasing)\n\u002F\u002F ✅ FIX: cache the length first, or use a static copy\nfor (let i = 0, len = divs.length; i \u003C len; i++) { ... }\n\u002F\u002F Or: const staticCopy = [...document.getElementsByTagName(\"div\")];\n","",[30,31,32,41,48,54,89,95,100,106,132,154,175,180,186,210,237,243,248,254,279,304,310,316,321,327,347,353,359,368,377,383,436],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","\u002F\u002F ── Selection methods (ranked by specificity and performance) ──\n",[33,42,44],{"class":35,"line":43},2,[33,45,47],{"emptyLinePlaceholder":46},true,"\n",[33,49,51],{"class":35,"line":50},3,[33,52,53],{"class":39},"\u002F\u002F getElementById: fastest, returns a single element (or null)\n",[33,55,57,61,65,68,72,76,79,83,86],{"class":35,"line":56},4,[33,58,60],{"class":59},"svdQ7","const",[33,62,64],{"class":63},"snvgF"," el",[33,66,67],{"class":59}," =",[33,69,71],{"class":70},"ssxIu"," document.",[33,73,75],{"class":74},"sIsaT","getElementById",[33,77,78],{"class":70},"(",[33,80,82],{"class":81},"sJ6F3","\"header\"",[33,84,85],{"class":70},");  ",[33,87,88],{"class":39},"\u002F\u002F O(1) — hashed lookup by ID\n",[33,90,92],{"class":35,"line":91},5,[33,93,94],{"class":39},"\u002F\u002F Only works for IDs. Returns the first match (IDs should be unique).\n",[33,96,98],{"class":35,"line":97},6,[33,99,47],{"emptyLinePlaceholder":46},[33,101,103],{"class":35,"line":102},7,[33,104,105],{"class":39},"\u002F\u002F querySelector: CSS selector, returns first match (or null)\n",[33,107,109,111,114,116,118,121,123,126,129],{"class":35,"line":108},8,[33,110,60],{"class":59},[33,112,113],{"class":63}," first",[33,115,67],{"class":59},[33,117,71],{"class":70},[33,119,120],{"class":74},"querySelector",[33,122,78],{"class":70},[33,124,125],{"class":81},"\".item\"",[33,127,128],{"class":70},");          ",[33,130,131],{"class":39},"\u002F\u002F first .item\n",[33,133,135,137,140,142,144,146,148,151],{"class":35,"line":134},9,[33,136,60],{"class":59},[33,138,139],{"class":63}," nested",[33,141,67],{"class":59},[33,143,71],{"class":70},[33,145,120],{"class":74},[33,147,78],{"class":70},[33,149,150],{"class":81},"\".container > .item:first-child\"",[33,152,153],{"class":70},");\n",[33,155,157,159,162,164,166,168,170,173],{"class":35,"line":156},10,[33,158,60],{"class":59},[33,160,161],{"class":63}," complex",[33,163,67],{"class":59},[33,165,71],{"class":70},[33,167,120],{"class":74},[33,169,78],{"class":70},[33,171,172],{"class":81},"'[data-role=\"admin\"]:not(.disabled)'",[33,174,153],{"class":70},[33,176,178],{"class":35,"line":177},11,[33,179,47],{"emptyLinePlaceholder":46},[33,181,183],{"class":35,"line":182},12,[33,184,185],{"class":39},"\u002F\u002F querySelectorAll: returns a STATIC NodeList (not live — doesn't update)\n",[33,187,189,191,194,196,198,201,203,205,207],{"class":35,"line":188},13,[33,190,60],{"class":59},[33,192,193],{"class":63}," items",[33,195,67],{"class":59},[33,197,71],{"class":70},[33,199,200],{"class":74},"querySelectorAll",[33,202,78],{"class":70},[33,204,125],{"class":81},[33,206,85],{"class":70},[33,208,209],{"class":39},"\u002F\u002F NodeList (array-like, has forEach)\n",[33,211,213,216,219,221,225,228,231,234],{"class":35,"line":212},14,[33,214,215],{"class":70},"items.",[33,217,218],{"class":74},"forEach",[33,220,78],{"class":70},[33,222,224],{"class":223},"sCrzJ","item",[33,226,227],{"class":59}," =>",[33,229,230],{"class":70}," console.",[33,232,233],{"class":74},"log",[33,235,236],{"class":70},"(item.textContent));\n",[33,238,240],{"class":35,"line":239},15,[33,241,242],{"class":39},"\u002F\u002F ⚠️ This is a SNAPSHOT — if you add a new .item later, this NodeList doesn't update.\n",[33,244,246],{"class":35,"line":245},16,[33,247,47],{"emptyLinePlaceholder":46},[33,249,251],{"class":35,"line":250},17,[33,252,253],{"class":39},"\u002F\u002F getElementsByClassName \u002F getElementsByTagName: return LIVE HTMLCollection\n",[33,255,257,259,262,264,266,269,271,274,276],{"class":35,"line":256},18,[33,258,60],{"class":59},[33,260,261],{"class":63}," liveItems",[33,263,67],{"class":59},[33,265,71],{"class":70},[33,267,268],{"class":74},"getElementsByClassName",[33,270,78],{"class":70},[33,272,273],{"class":81},"\"item\"",[33,275,85],{"class":70},[33,277,278],{"class":39},"\u002F\u002F HTMLCollection (LIVE)\n",[33,280,282,284,287,289,291,294,296,299,302],{"class":35,"line":281},19,[33,283,60],{"class":59},[33,285,286],{"class":63}," allDivs",[33,288,67],{"class":59},[33,290,71],{"class":70},[33,292,293],{"class":74},"getElementsByTagName",[33,295,78],{"class":70},[33,297,298],{"class":81},"\"div\"",[33,300,301],{"class":70},");     ",[33,303,278],{"class":39},[33,305,307],{"class":35,"line":306},20,[33,308,309],{"class":39},"\u002F\u002F ⚠️ LIVE collection: if you add\u002Fremove elements, the collection updates automatically.\n",[33,311,313],{"class":35,"line":312},21,[33,314,315],{"class":39},"\u002F\u002F This can cause bugs in loops (adding elements extends the loop → infinite loop).\n",[33,317,319],{"class":35,"line":318},22,[33,320,47],{"emptyLinePlaceholder":46},[33,322,324],{"class":35,"line":323},23,[33,325,326],{"class":39},"\u002F\u002F ── Live collection loop trap ──\n",[33,328,330,332,335,337,339,341,343,345],{"class":35,"line":329},24,[33,331,60],{"class":59},[33,333,334],{"class":63}," divs",[33,336,67],{"class":59},[33,338,71],{"class":70},[33,340,293],{"class":74},[33,342,78],{"class":70},[33,344,298],{"class":81},[33,346,153],{"class":70},[33,348,350],{"class":35,"line":349},25,[33,351,352],{"class":39},"\u002F\u002F ❌ NAIVE — modifying the DOM while iterating a live collection\n",[33,354,356],{"class":35,"line":355},26,[33,357,358],{"class":39},"\u002F\u002F for (let i = 0; i \u003C divs.length; i++) {\n",[33,360,362,365],{"class":35,"line":361},27,[33,363,364],{"class":39},"\u002F\u002F     document.body.appendChild(document.createElement(\"div\"));",[33,366,367],{"class":39},"  \u002F\u002F adds to DOM → divs.length grows\n",[33,369,371,374],{"class":35,"line":370},28,[33,372,373],{"class":39},"\u002F\u002F }",[33,375,376],{"class":39},"  \u002F\u002F infinite loop! (divs.length keeps increasing)\n",[33,378,380],{"class":35,"line":379},29,[33,381,382],{"class":39},"\u002F\u002F ✅ FIX: cache the length first, or use a static copy\n",[33,384,386,389,392,395,398,401,404,407,409,412,415,418,421,424,427,430,433],{"class":35,"line":385},30,[33,387,388],{"class":59},"for",[33,390,391],{"class":70}," (",[33,393,394],{"class":59},"let",[33,396,397],{"class":70}," i ",[33,399,400],{"class":59},"=",[33,402,403],{"class":63}," 0",[33,405,406],{"class":70},", len ",[33,408,400],{"class":59},[33,410,411],{"class":70}," divs.",[33,413,414],{"class":63},"length",[33,416,417],{"class":70},"; i ",[33,419,420],{"class":59},"\u003C",[33,422,423],{"class":70}," len; i",[33,425,426],{"class":59},"++",[33,428,429],{"class":70},") { ",[33,431,432],{"class":59},"...",[33,434,435],{"class":70}," }\n",[33,437,439],{"class":35,"line":438},31,[33,440,441],{"class":39},"\u002F\u002F Or: const staticCopy = [...document.getElementsByTagName(\"div\")];\n",[15,443,445],{"id":444},"anti-pattern-repeated-layout-thrashing-reflow","Anti-Pattern: Repeated Layout Thrashing (Reflow)",[20,447,448],{"language":22},[24,449,451],{"className":26,"code":450,"language":22,"meta":28,"style":28},"\u002F\u002F ❌ NAIVE — reading and writing layout alternately (forces reflow each time)\nconst items = document.querySelectorAll(\".item\");\nitems.forEach(item => {\n    const width = item.offsetWidth;   \u002F\u002F READ: forces layout calculation (reflow)\n    item.style.width = width + 10 + \"px\";  \u002F\u002F WRITE: invalidates layout\n    \u002F\u002F Next iteration: offsetWidth reads again → another reflow!\n});\n\u002F\u002F N elements → N reflows (very expensive — each reflow recalculates the whole page layout)\n\n\u002F\u002F ✅ CORRECT — batch reads first, then batch writes (minimize reflows)\nconst items = document.querySelectorAll(\".item\");\n\u002F\u002F Phase 1: read all layout values\nconst widths = [...items].map(item => item.offsetWidth);  \u002F\u002F all reads at once (1 reflow)\n\u002F\u002F Phase 2: write all styles\nitems.forEach((item, i) => {\n    item.style.width = widths[i] + 10 + \"px\";  \u002F\u002F all writes at once (1 reflow)\n});\n\u002F\u002F Total: 2 reflows (instead of 2*N) — much faster.\n\n\u002F\u002F ✅ BEST — use requestAnimationFrame for DOM writes\nfunction batchDOMUpdates(updates) {\n    \u002F\u002F Collect all updates, apply them in a single frame\n    requestAnimationFrame(() => {\n        for (const [el, style] of updates) {\n            Object.assign(el.style, style);\n        }\n    });\n}\n",[30,452,453,458,476,491,507,535,540,545,550,554,559,577,582,614,619,644,666,670,675,679,684,700,705,717,745,756,761,766],{"__ignoreMap":28},[33,454,455],{"class":35,"line":36},[33,456,457],{"class":39},"\u002F\u002F ❌ NAIVE — reading and writing layout alternately (forces reflow each time)\n",[33,459,460,462,464,466,468,470,472,474],{"class":35,"line":43},[33,461,60],{"class":59},[33,463,193],{"class":63},[33,465,67],{"class":59},[33,467,71],{"class":70},[33,469,200],{"class":74},[33,471,78],{"class":70},[33,473,125],{"class":81},[33,475,153],{"class":70},[33,477,478,480,482,484,486,488],{"class":35,"line":50},[33,479,215],{"class":70},[33,481,218],{"class":74},[33,483,78],{"class":70},[33,485,224],{"class":223},[33,487,227],{"class":59},[33,489,490],{"class":70}," {\n",[33,492,493,496,499,501,504],{"class":35,"line":56},[33,494,495],{"class":59},"    const",[33,497,498],{"class":63}," width",[33,500,67],{"class":59},[33,502,503],{"class":70}," item.offsetWidth;   ",[33,505,506],{"class":39},"\u002F\u002F READ: forces layout calculation (reflow)\n",[33,508,509,512,514,517,520,523,526,529,532],{"class":35,"line":91},[33,510,511],{"class":70},"    item.style.width ",[33,513,400],{"class":59},[33,515,516],{"class":70}," width ",[33,518,519],{"class":59},"+",[33,521,522],{"class":63}," 10",[33,524,525],{"class":59}," +",[33,527,528],{"class":81}," \"px\"",[33,530,531],{"class":70},";  ",[33,533,534],{"class":39},"\u002F\u002F WRITE: invalidates layout\n",[33,536,537],{"class":35,"line":97},[33,538,539],{"class":39},"    \u002F\u002F Next iteration: offsetWidth reads again → another reflow!\n",[33,541,542],{"class":35,"line":102},[33,543,544],{"class":70},"});\n",[33,546,547],{"class":35,"line":108},[33,548,549],{"class":39},"\u002F\u002F N elements → N reflows (very expensive — each reflow recalculates the whole page layout)\n",[33,551,552],{"class":35,"line":134},[33,553,47],{"emptyLinePlaceholder":46},[33,555,556],{"class":35,"line":156},[33,557,558],{"class":39},"\u002F\u002F ✅ CORRECT — batch reads first, then batch writes (minimize reflows)\n",[33,560,561,563,565,567,569,571,573,575],{"class":35,"line":177},[33,562,60],{"class":59},[33,564,193],{"class":63},[33,566,67],{"class":59},[33,568,71],{"class":70},[33,570,200],{"class":74},[33,572,78],{"class":70},[33,574,125],{"class":81},[33,576,153],{"class":70},[33,578,579],{"class":35,"line":182},[33,580,581],{"class":39},"\u002F\u002F Phase 1: read all layout values\n",[33,583,584,586,589,591,594,596,599,602,604,606,608,611],{"class":35,"line":188},[33,585,60],{"class":59},[33,587,588],{"class":63}," widths",[33,590,67],{"class":59},[33,592,593],{"class":70}," [",[33,595,432],{"class":59},[33,597,598],{"class":70},"items].",[33,600,601],{"class":74},"map",[33,603,78],{"class":70},[33,605,224],{"class":223},[33,607,227],{"class":59},[33,609,610],{"class":70}," item.offsetWidth);  ",[33,612,613],{"class":39},"\u002F\u002F all reads at once (1 reflow)\n",[33,615,616],{"class":35,"line":212},[33,617,618],{"class":39},"\u002F\u002F Phase 2: write all styles\n",[33,620,621,623,625,628,630,633,636,639,642],{"class":35,"line":239},[33,622,215],{"class":70},[33,624,218],{"class":74},[33,626,627],{"class":70},"((",[33,629,224],{"class":223},[33,631,632],{"class":70},", ",[33,634,635],{"class":223},"i",[33,637,638],{"class":70},") ",[33,640,641],{"class":59},"=>",[33,643,490],{"class":70},[33,645,646,648,650,653,655,657,659,661,663],{"class":35,"line":245},[33,647,511],{"class":70},[33,649,400],{"class":59},[33,651,652],{"class":70}," widths[i] ",[33,654,519],{"class":59},[33,656,522],{"class":63},[33,658,525],{"class":59},[33,660,528],{"class":81},[33,662,531],{"class":70},[33,664,665],{"class":39},"\u002F\u002F all writes at once (1 reflow)\n",[33,667,668],{"class":35,"line":250},[33,669,544],{"class":70},[33,671,672],{"class":35,"line":256},[33,673,674],{"class":39},"\u002F\u002F Total: 2 reflows (instead of 2*N) — much faster.\n",[33,676,677],{"class":35,"line":281},[33,678,47],{"emptyLinePlaceholder":46},[33,680,681],{"class":35,"line":306},[33,682,683],{"class":39},"\u002F\u002F ✅ BEST — use requestAnimationFrame for DOM writes\n",[33,685,686,689,692,694,697],{"class":35,"line":312},[33,687,688],{"class":59},"function",[33,690,691],{"class":74}," batchDOMUpdates",[33,693,78],{"class":70},[33,695,696],{"class":223},"updates",[33,698,699],{"class":70},") {\n",[33,701,702],{"class":35,"line":318},[33,703,704],{"class":39},"    \u002F\u002F Collect all updates, apply them in a single frame\n",[33,706,707,710,713,715],{"class":35,"line":323},[33,708,709],{"class":74},"    requestAnimationFrame",[33,711,712],{"class":70},"(() ",[33,714,641],{"class":59},[33,716,490],{"class":70},[33,718,719,722,724,726,728,731,733,736,739,742],{"class":35,"line":329},[33,720,721],{"class":59},"        for",[33,723,391],{"class":70},[33,725,60],{"class":59},[33,727,593],{"class":70},[33,729,730],{"class":63},"el",[33,732,632],{"class":70},[33,734,735],{"class":63},"style",[33,737,738],{"class":70},"] ",[33,740,741],{"class":59},"of",[33,743,744],{"class":70}," updates) {\n",[33,746,747,750,753],{"class":35,"line":349},[33,748,749],{"class":70},"            Object.",[33,751,752],{"class":74},"assign",[33,754,755],{"class":70},"(el.style, style);\n",[33,757,758],{"class":35,"line":355},[33,759,760],{"class":70},"        }\n",[33,762,763],{"class":35,"line":361},[33,764,765],{"class":70},"    });\n",[33,767,768],{"class":35,"line":370},[33,769,770],{"class":70},"}\n",[15,772,774],{"id":773},"documentfragment-batch-dom-insertion","DocumentFragment: Batch DOM Insertion",[20,776,777],{"language":22},[24,778,780],{"className":26,"code":779,"language":22,"meta":28,"style":28},"\u002F\u002F ❌ NAIVE — appending elements one at a time (N reflows for N elements)\nfor (let i = 0; i \u003C 1000; i++) {\n    const li = document.createElement(\"li\");\n    li.textContent = `Item ${i}`;\n    document.querySelector(\"ul\").appendChild(li);  \u002F\u002F each append triggers a reflow\n}\n\u002F\u002F 1000 appends → 1000 reflows (very slow)\n\n\u002F\u002F ✅ CORRECT — use DocumentFragment (batch all insertions, 1 reflow)\nconst fragment = document.createDocumentFragment();  \u002F\u002F lightweight container (no DOM node)\nfor (let i = 0; i \u003C 1000; i++) {\n    const li = document.createElement(\"li\");\n    li.textContent = `Item ${i}`;\n    fragment.appendChild(li);  \u002F\u002F append to fragment (no reflow — fragment isn't in the DOM)\n}\ndocument.querySelector(\"ul\").appendChild(fragment);  \u002F\u002F 1 append → 1 reflow (all 1000 items inserted at once)\n\n\u002F\u002F ✅ ALSO CORRECT — build a string and use innerHTML (1 reflow, but no event listeners on innerHTML)\nconst html = Array.from({ length: 1000 }, (_, i) => `\u003Cli>Item ${i}\u003C\u002Fli>`).join(\"\");\ndocument.querySelector(\"ul\").innerHTML = html;  \u002F\u002F 1 reflow (but XSS risk if data is untrusted!)\n\u002F\u002F ⚠️ innerHTML with user data = XSS vulnerability. Sanitize or use textContent \u002F DOM methods.\n",[30,781,782,787,815,836,854,878,882,887,891,896,916,942,960,974,986,990,1011,1015,1020,1075,1096],{"__ignoreMap":28},[33,783,784],{"class":35,"line":36},[33,785,786],{"class":39},"\u002F\u002F ❌ NAIVE — appending elements one at a time (N reflows for N elements)\n",[33,788,789,791,793,795,797,799,801,803,805,808,811,813],{"class":35,"line":43},[33,790,388],{"class":59},[33,792,391],{"class":70},[33,794,394],{"class":59},[33,796,397],{"class":70},[33,798,400],{"class":59},[33,800,403],{"class":63},[33,802,417],{"class":70},[33,804,420],{"class":59},[33,806,807],{"class":63}," 1000",[33,809,810],{"class":70},"; i",[33,812,426],{"class":59},[33,814,699],{"class":70},[33,816,817,819,822,824,826,829,831,834],{"class":35,"line":50},[33,818,495],{"class":59},[33,820,821],{"class":63}," li",[33,823,67],{"class":59},[33,825,71],{"class":70},[33,827,828],{"class":74},"createElement",[33,830,78],{"class":70},[33,832,833],{"class":81},"\"li\"",[33,835,153],{"class":70},[33,837,838,841,843,846,848,851],{"class":35,"line":56},[33,839,840],{"class":70},"    li.textContent ",[33,842,400],{"class":59},[33,844,845],{"class":81}," `Item ${",[33,847,635],{"class":70},[33,849,850],{"class":81},"}`",[33,852,853],{"class":70},";\n",[33,855,856,859,861,863,866,869,872,875],{"class":35,"line":91},[33,857,858],{"class":70},"    document.",[33,860,120],{"class":74},[33,862,78],{"class":70},[33,864,865],{"class":81},"\"ul\"",[33,867,868],{"class":70},").",[33,870,871],{"class":74},"appendChild",[33,873,874],{"class":70},"(li);  ",[33,876,877],{"class":39},"\u002F\u002F each append triggers a reflow\n",[33,879,880],{"class":35,"line":97},[33,881,770],{"class":70},[33,883,884],{"class":35,"line":102},[33,885,886],{"class":39},"\u002F\u002F 1000 appends → 1000 reflows (very slow)\n",[33,888,889],{"class":35,"line":108},[33,890,47],{"emptyLinePlaceholder":46},[33,892,893],{"class":35,"line":134},[33,894,895],{"class":39},"\u002F\u002F ✅ CORRECT — use DocumentFragment (batch all insertions, 1 reflow)\n",[33,897,898,900,903,905,907,910,913],{"class":35,"line":156},[33,899,60],{"class":59},[33,901,902],{"class":63}," fragment",[33,904,67],{"class":59},[33,906,71],{"class":70},[33,908,909],{"class":74},"createDocumentFragment",[33,911,912],{"class":70},"();  ",[33,914,915],{"class":39},"\u002F\u002F lightweight container (no DOM node)\n",[33,917,918,920,922,924,926,928,930,932,934,936,938,940],{"class":35,"line":177},[33,919,388],{"class":59},[33,921,391],{"class":70},[33,923,394],{"class":59},[33,925,397],{"class":70},[33,927,400],{"class":59},[33,929,403],{"class":63},[33,931,417],{"class":70},[33,933,420],{"class":59},[33,935,807],{"class":63},[33,937,810],{"class":70},[33,939,426],{"class":59},[33,941,699],{"class":70},[33,943,944,946,948,950,952,954,956,958],{"class":35,"line":182},[33,945,495],{"class":59},[33,947,821],{"class":63},[33,949,67],{"class":59},[33,951,71],{"class":70},[33,953,828],{"class":74},[33,955,78],{"class":70},[33,957,833],{"class":81},[33,959,153],{"class":70},[33,961,962,964,966,968,970,972],{"class":35,"line":188},[33,963,840],{"class":70},[33,965,400],{"class":59},[33,967,845],{"class":81},[33,969,635],{"class":70},[33,971,850],{"class":81},[33,973,853],{"class":70},[33,975,976,979,981,983],{"class":35,"line":212},[33,977,978],{"class":70},"    fragment.",[33,980,871],{"class":74},[33,982,874],{"class":70},[33,984,985],{"class":39},"\u002F\u002F append to fragment (no reflow — fragment isn't in the DOM)\n",[33,987,988],{"class":35,"line":239},[33,989,770],{"class":70},[33,991,992,995,997,999,1001,1003,1005,1008],{"class":35,"line":245},[33,993,994],{"class":70},"document.",[33,996,120],{"class":74},[33,998,78],{"class":70},[33,1000,865],{"class":81},[33,1002,868],{"class":70},[33,1004,871],{"class":74},[33,1006,1007],{"class":70},"(fragment);  ",[33,1009,1010],{"class":39},"\u002F\u002F 1 append → 1 reflow (all 1000 items inserted at once)\n",[33,1012,1013],{"class":35,"line":250},[33,1014,47],{"emptyLinePlaceholder":46},[33,1016,1017],{"class":35,"line":256},[33,1018,1019],{"class":39},"\u002F\u002F ✅ ALSO CORRECT — build a string and use innerHTML (1 reflow, but no event listeners on innerHTML)\n",[33,1021,1022,1024,1027,1029,1032,1035,1038,1041,1044,1047,1049,1051,1053,1055,1058,1060,1063,1065,1068,1070,1073],{"class":35,"line":281},[33,1023,60],{"class":59},[33,1025,1026],{"class":63}," html",[33,1028,67],{"class":59},[33,1030,1031],{"class":70}," Array.",[33,1033,1034],{"class":74},"from",[33,1036,1037],{"class":70},"({ length: ",[33,1039,1040],{"class":63},"1000",[33,1042,1043],{"class":70}," }, (",[33,1045,1046],{"class":223},"_",[33,1048,632],{"class":70},[33,1050,635],{"class":223},[33,1052,638],{"class":70},[33,1054,641],{"class":59},[33,1056,1057],{"class":81}," `\u003Cli>Item ${",[33,1059,635],{"class":70},[33,1061,1062],{"class":81},"}\u003C\u002Fli>`",[33,1064,868],{"class":70},[33,1066,1067],{"class":74},"join",[33,1069,78],{"class":70},[33,1071,1072],{"class":81},"\"\"",[33,1074,153],{"class":70},[33,1076,1077,1079,1081,1083,1085,1088,1090,1093],{"class":35,"line":306},[33,1078,994],{"class":70},[33,1080,120],{"class":74},[33,1082,78],{"class":70},[33,1084,865],{"class":81},[33,1086,1087],{"class":70},").innerHTML ",[33,1089,400],{"class":59},[33,1091,1092],{"class":70}," html;  ",[33,1094,1095],{"class":39},"\u002F\u002F 1 reflow (but XSS risk if data is untrusted!)\n",[33,1097,1098],{"class":35,"line":312},[33,1099,1100],{"class":39},"\u002F\u002F ⚠️ innerHTML with user data = XSS vulnerability. Sanitize or use textContent \u002F DOM methods.\n",[15,1102,1104],{"id":1103},"dom-traversal","DOM Traversal",[20,1106,1107],{"language":22},[24,1108,1110],{"className":26,"code":1109,"language":22,"meta":28,"style":28},"const el = document.querySelector(\".container\");\n\n\u002F\u002F ── Tree traversal ──\nel.parentNode;           \u002F\u002F direct parent (or null)\nel.parentElement;        \u002F\u002F parent if it's an element (or null if parent is not element)\nel.childNodes;           \u002F\u002F NodeList of ALL children (including text nodes, comments)\nel.children;            \u002F\u002F HTMLCollection of ELEMENT children only (no text\u002Fcomment nodes)\nel.firstChild;          \u002F\u002F first child (including text nodes — might be a whitespace text node)\nel.firstElementChild;   \u002F\u002F first ELEMENT child (skips text\u002Fcomment nodes)\nel.lastElementChild;    \u002F\u002F last element child\nel.nextElementSibling;  \u002F\u002F next sibling element\nel.previousElementSibling;  \u002F\u002F previous sibling element\n\n\u002F\u002F ── closest: traverse UP the tree (find nearest ancestor matching selector) ──\nconst button = document.querySelector(\"button\");\nconst row = button.closest(\"tr\");  \u002F\u002F nearest ancestor \u003Ctr> (or null)\nconst form = button.closest(\"form\");\n\n\u002F\u002F ── matches: check if element matches a CSS selector ──\nif (el.matches(\".active\")) { ... }\nif (el.matches(\"[data-action='delete']\")) { ... }\n\n\u002F\u002F ── contains: check if an element is a descendant ──\nif (parentEl.contains(childEl)) { ... }  \u002F\u002F true if childEl is inside parentEl (or is parentEl)\n\u002F\u002F Useful for click-outside detection:\ndocument.addEventListener(\"click\", (e) => {\n    if (!dropdown.contains(e.target)) dropdown.close();\n});\n",[30,1111,1112,1131,1135,1140,1148,1156,1164,1172,1180,1188,1196,1204,1212,1216,1221,1241,1266,1286,1290,1295,1318,1337,1341,1346,1367,1372,1396,1420],{"__ignoreMap":28},[33,1113,1114,1116,1118,1120,1122,1124,1126,1129],{"class":35,"line":36},[33,1115,60],{"class":59},[33,1117,64],{"class":63},[33,1119,67],{"class":59},[33,1121,71],{"class":70},[33,1123,120],{"class":74},[33,1125,78],{"class":70},[33,1127,1128],{"class":81},"\".container\"",[33,1130,153],{"class":70},[33,1132,1133],{"class":35,"line":43},[33,1134,47],{"emptyLinePlaceholder":46},[33,1136,1137],{"class":35,"line":50},[33,1138,1139],{"class":39},"\u002F\u002F ── Tree traversal ──\n",[33,1141,1142,1145],{"class":35,"line":56},[33,1143,1144],{"class":70},"el.parentNode;           ",[33,1146,1147],{"class":39},"\u002F\u002F direct parent (or null)\n",[33,1149,1150,1153],{"class":35,"line":91},[33,1151,1152],{"class":70},"el.parentElement;        ",[33,1154,1155],{"class":39},"\u002F\u002F parent if it's an element (or null if parent is not element)\n",[33,1157,1158,1161],{"class":35,"line":97},[33,1159,1160],{"class":70},"el.childNodes;           ",[33,1162,1163],{"class":39},"\u002F\u002F NodeList of ALL children (including text nodes, comments)\n",[33,1165,1166,1169],{"class":35,"line":102},[33,1167,1168],{"class":70},"el.children;            ",[33,1170,1171],{"class":39},"\u002F\u002F HTMLCollection of ELEMENT children only (no text\u002Fcomment nodes)\n",[33,1173,1174,1177],{"class":35,"line":108},[33,1175,1176],{"class":70},"el.firstChild;          ",[33,1178,1179],{"class":39},"\u002F\u002F first child (including text nodes — might be a whitespace text node)\n",[33,1181,1182,1185],{"class":35,"line":134},[33,1183,1184],{"class":70},"el.firstElementChild;   ",[33,1186,1187],{"class":39},"\u002F\u002F first ELEMENT child (skips text\u002Fcomment nodes)\n",[33,1189,1190,1193],{"class":35,"line":156},[33,1191,1192],{"class":70},"el.lastElementChild;    ",[33,1194,1195],{"class":39},"\u002F\u002F last element child\n",[33,1197,1198,1201],{"class":35,"line":177},[33,1199,1200],{"class":70},"el.nextElementSibling;  ",[33,1202,1203],{"class":39},"\u002F\u002F next sibling element\n",[33,1205,1206,1209],{"class":35,"line":182},[33,1207,1208],{"class":70},"el.previousElementSibling;  ",[33,1210,1211],{"class":39},"\u002F\u002F previous sibling element\n",[33,1213,1214],{"class":35,"line":188},[33,1215,47],{"emptyLinePlaceholder":46},[33,1217,1218],{"class":35,"line":212},[33,1219,1220],{"class":39},"\u002F\u002F ── closest: traverse UP the tree (find nearest ancestor matching selector) ──\n",[33,1222,1223,1225,1228,1230,1232,1234,1236,1239],{"class":35,"line":239},[33,1224,60],{"class":59},[33,1226,1227],{"class":63}," button",[33,1229,67],{"class":59},[33,1231,71],{"class":70},[33,1233,120],{"class":74},[33,1235,78],{"class":70},[33,1237,1238],{"class":81},"\"button\"",[33,1240,153],{"class":70},[33,1242,1243,1245,1248,1250,1253,1256,1258,1261,1263],{"class":35,"line":245},[33,1244,60],{"class":59},[33,1246,1247],{"class":63}," row",[33,1249,67],{"class":59},[33,1251,1252],{"class":70}," button.",[33,1254,1255],{"class":74},"closest",[33,1257,78],{"class":70},[33,1259,1260],{"class":81},"\"tr\"",[33,1262,85],{"class":70},[33,1264,1265],{"class":39},"\u002F\u002F nearest ancestor \u003Ctr> (or null)\n",[33,1267,1268,1270,1273,1275,1277,1279,1281,1284],{"class":35,"line":250},[33,1269,60],{"class":59},[33,1271,1272],{"class":63}," form",[33,1274,67],{"class":59},[33,1276,1252],{"class":70},[33,1278,1255],{"class":74},[33,1280,78],{"class":70},[33,1282,1283],{"class":81},"\"form\"",[33,1285,153],{"class":70},[33,1287,1288],{"class":35,"line":256},[33,1289,47],{"emptyLinePlaceholder":46},[33,1291,1292],{"class":35,"line":281},[33,1293,1294],{"class":39},"\u002F\u002F ── matches: check if element matches a CSS selector ──\n",[33,1296,1297,1300,1303,1306,1308,1311,1314,1316],{"class":35,"line":306},[33,1298,1299],{"class":59},"if",[33,1301,1302],{"class":70}," (el.",[33,1304,1305],{"class":74},"matches",[33,1307,78],{"class":70},[33,1309,1310],{"class":81},"\".active\"",[33,1312,1313],{"class":70},")) { ",[33,1315,432],{"class":59},[33,1317,435],{"class":70},[33,1319,1320,1322,1324,1326,1328,1331,1333,1335],{"class":35,"line":312},[33,1321,1299],{"class":59},[33,1323,1302],{"class":70},[33,1325,1305],{"class":74},[33,1327,78],{"class":70},[33,1329,1330],{"class":81},"\"[data-action='delete']\"",[33,1332,1313],{"class":70},[33,1334,432],{"class":59},[33,1336,435],{"class":70},[33,1338,1339],{"class":35,"line":318},[33,1340,47],{"emptyLinePlaceholder":46},[33,1342,1343],{"class":35,"line":323},[33,1344,1345],{"class":39},"\u002F\u002F ── contains: check if an element is a descendant ──\n",[33,1347,1348,1350,1353,1356,1359,1361,1364],{"class":35,"line":329},[33,1349,1299],{"class":59},[33,1351,1352],{"class":70}," (parentEl.",[33,1354,1355],{"class":74},"contains",[33,1357,1358],{"class":70},"(childEl)) { ",[33,1360,432],{"class":59},[33,1362,1363],{"class":70}," }  ",[33,1365,1366],{"class":39},"\u002F\u002F true if childEl is inside parentEl (or is parentEl)\n",[33,1368,1369],{"class":35,"line":349},[33,1370,1371],{"class":39},"\u002F\u002F Useful for click-outside detection:\n",[33,1373,1374,1376,1379,1381,1384,1387,1390,1392,1394],{"class":35,"line":355},[33,1375,994],{"class":70},[33,1377,1378],{"class":74},"addEventListener",[33,1380,78],{"class":70},[33,1382,1383],{"class":81},"\"click\"",[33,1385,1386],{"class":70},", (",[33,1388,1389],{"class":223},"e",[33,1391,638],{"class":70},[33,1393,641],{"class":59},[33,1395,490],{"class":70},[33,1397,1398,1401,1403,1406,1409,1411,1414,1417],{"class":35,"line":361},[33,1399,1400],{"class":59},"    if",[33,1402,391],{"class":70},[33,1404,1405],{"class":59},"!",[33,1407,1408],{"class":70},"dropdown.",[33,1410,1355],{"class":74},[33,1412,1413],{"class":70},"(e.target)) dropdown.",[33,1415,1416],{"class":74},"close",[33,1418,1419],{"class":70},"();\n",[33,1421,1422],{"class":35,"line":370},[33,1423,544],{"class":70},[15,1425,1427],{"id":1426},"mutationobserver-watching-dom-changes","MutationObserver: Watching DOM Changes",[20,1429,1430],{"language":22},[24,1431,1433],{"className":26,"code":1432,"language":22,"meta":28,"style":28},"\u002F\u002F ── MutationObserver: watch for DOM mutations (efficient, async) ──\nconst observer = new MutationObserver((mutations, obs) => {\n    \u002F\u002F mutations: array of MutationRecord (what changed)\n    for (const mutation of mutations) {\n        if (mutation.type === \"childList\") {\n            console.log(\"children changed\");\n            console.log(\"added:\", mutation.addedNodes);    \u002F\u002F NodeList of added nodes\n            console.log(\"removed:\", mutation.removedNodes); \u002F\u002F NodeList of removed nodes\n        }\n        if (mutation.type === \"attributes\") {\n            console.log(\"attribute changed:\", mutation.attributeName);\n            console.log(\"old value:\", mutation.oldValue);\n        }\n        if (mutation.type === \"characterData\") {\n            console.log(\"text content changed\");\n        }\n    }\n});\n\n\u002F\u002F Start observing (with options)\nobserver.observe(document.body, {\n    childList: true,      \u002F\u002F watch for added\u002Fremoved children\n    subtree: true,        \u002F\u002F watch all descendants (not just direct children)\n    attributes: true,     \u002F\u002F watch attribute changes\n    attributeOldValue: true,  \u002F\u002F record old attribute values\n    characterData: true,  \u002F\u002F watch text content changes\n    characterDataOldValue: true,\n});\n\n\u002F\u002F Stop observing (when done — prevents memory leaks)\n\u002F\u002F observer.disconnect();\n\n\u002F\u002F ── Use case: infinite scroll (observe last element visibility) ──\nconst sentinel = document.querySelector(\".scroll-sentinel\");\nconst scrollObserver = new IntersectionObserver((entries) => {\n    if (entries[0].isIntersecting) {\n        loadMoreContent();  \u002F\u002F load next page when sentinel is visible\n    }\n}, { rootMargin: \"100px\" });  \u002F\u002F trigger 100px before sentinel enters viewport\nscrollObserver.observe(sentinel);\n",[30,1434,1435,1440,1471,1476,1494,1510,1524,1541,1558,1562,1575,1589,1603,1607,1620,1633,1637,1642,1646,1650,1655,1666,1680,1693,1706,1719,1731,1741,1745,1749,1754,1759,1764,1770,1791,1817,1831,1842,1847,1862],{"__ignoreMap":28},[33,1436,1437],{"class":35,"line":36},[33,1438,1439],{"class":39},"\u002F\u002F ── MutationObserver: watch for DOM mutations (efficient, async) ──\n",[33,1441,1442,1444,1447,1449,1452,1455,1457,1460,1462,1465,1467,1469],{"class":35,"line":43},[33,1443,60],{"class":59},[33,1445,1446],{"class":63}," observer",[33,1448,67],{"class":59},[33,1450,1451],{"class":59}," new",[33,1453,1454],{"class":74}," MutationObserver",[33,1456,627],{"class":70},[33,1458,1459],{"class":223},"mutations",[33,1461,632],{"class":70},[33,1463,1464],{"class":223},"obs",[33,1466,638],{"class":70},[33,1468,641],{"class":59},[33,1470,490],{"class":70},[33,1472,1473],{"class":35,"line":50},[33,1474,1475],{"class":39},"    \u002F\u002F mutations: array of MutationRecord (what changed)\n",[33,1477,1478,1481,1483,1485,1488,1491],{"class":35,"line":56},[33,1479,1480],{"class":59},"    for",[33,1482,391],{"class":70},[33,1484,60],{"class":59},[33,1486,1487],{"class":63}," mutation",[33,1489,1490],{"class":59}," of",[33,1492,1493],{"class":70}," mutations) {\n",[33,1495,1496,1499,1502,1505,1508],{"class":35,"line":91},[33,1497,1498],{"class":59},"        if",[33,1500,1501],{"class":70}," (mutation.type ",[33,1503,1504],{"class":59},"===",[33,1506,1507],{"class":81}," \"childList\"",[33,1509,699],{"class":70},[33,1511,1512,1515,1517,1519,1522],{"class":35,"line":97},[33,1513,1514],{"class":70},"            console.",[33,1516,233],{"class":74},[33,1518,78],{"class":70},[33,1520,1521],{"class":81},"\"children changed\"",[33,1523,153],{"class":70},[33,1525,1526,1528,1530,1532,1535,1538],{"class":35,"line":102},[33,1527,1514],{"class":70},[33,1529,233],{"class":74},[33,1531,78],{"class":70},[33,1533,1534],{"class":81},"\"added:\"",[33,1536,1537],{"class":70},", mutation.addedNodes);    ",[33,1539,1540],{"class":39},"\u002F\u002F NodeList of added nodes\n",[33,1542,1543,1545,1547,1549,1552,1555],{"class":35,"line":108},[33,1544,1514],{"class":70},[33,1546,233],{"class":74},[33,1548,78],{"class":70},[33,1550,1551],{"class":81},"\"removed:\"",[33,1553,1554],{"class":70},", mutation.removedNodes); ",[33,1556,1557],{"class":39},"\u002F\u002F NodeList of removed nodes\n",[33,1559,1560],{"class":35,"line":134},[33,1561,760],{"class":70},[33,1563,1564,1566,1568,1570,1573],{"class":35,"line":156},[33,1565,1498],{"class":59},[33,1567,1501],{"class":70},[33,1569,1504],{"class":59},[33,1571,1572],{"class":81}," \"attributes\"",[33,1574,699],{"class":70},[33,1576,1577,1579,1581,1583,1586],{"class":35,"line":177},[33,1578,1514],{"class":70},[33,1580,233],{"class":74},[33,1582,78],{"class":70},[33,1584,1585],{"class":81},"\"attribute changed:\"",[33,1587,1588],{"class":70},", mutation.attributeName);\n",[33,1590,1591,1593,1595,1597,1600],{"class":35,"line":182},[33,1592,1514],{"class":70},[33,1594,233],{"class":74},[33,1596,78],{"class":70},[33,1598,1599],{"class":81},"\"old value:\"",[33,1601,1602],{"class":70},", mutation.oldValue);\n",[33,1604,1605],{"class":35,"line":188},[33,1606,760],{"class":70},[33,1608,1609,1611,1613,1615,1618],{"class":35,"line":212},[33,1610,1498],{"class":59},[33,1612,1501],{"class":70},[33,1614,1504],{"class":59},[33,1616,1617],{"class":81}," \"characterData\"",[33,1619,699],{"class":70},[33,1621,1622,1624,1626,1628,1631],{"class":35,"line":239},[33,1623,1514],{"class":70},[33,1625,233],{"class":74},[33,1627,78],{"class":70},[33,1629,1630],{"class":81},"\"text content changed\"",[33,1632,153],{"class":70},[33,1634,1635],{"class":35,"line":245},[33,1636,760],{"class":70},[33,1638,1639],{"class":35,"line":250},[33,1640,1641],{"class":70},"    }\n",[33,1643,1644],{"class":35,"line":256},[33,1645,544],{"class":70},[33,1647,1648],{"class":35,"line":281},[33,1649,47],{"emptyLinePlaceholder":46},[33,1651,1652],{"class":35,"line":306},[33,1653,1654],{"class":39},"\u002F\u002F Start observing (with options)\n",[33,1656,1657,1660,1663],{"class":35,"line":312},[33,1658,1659],{"class":70},"observer.",[33,1661,1662],{"class":74},"observe",[33,1664,1665],{"class":70},"(document.body, {\n",[33,1667,1668,1671,1674,1677],{"class":35,"line":318},[33,1669,1670],{"class":70},"    childList: ",[33,1672,1673],{"class":63},"true",[33,1675,1676],{"class":70},",      ",[33,1678,1679],{"class":39},"\u002F\u002F watch for added\u002Fremoved children\n",[33,1681,1682,1685,1687,1690],{"class":35,"line":323},[33,1683,1684],{"class":70},"    subtree: ",[33,1686,1673],{"class":63},[33,1688,1689],{"class":70},",        ",[33,1691,1692],{"class":39},"\u002F\u002F watch all descendants (not just direct children)\n",[33,1694,1695,1698,1700,1703],{"class":35,"line":329},[33,1696,1697],{"class":70},"    attributes: ",[33,1699,1673],{"class":63},[33,1701,1702],{"class":70},",     ",[33,1704,1705],{"class":39},"\u002F\u002F watch attribute changes\n",[33,1707,1708,1711,1713,1716],{"class":35,"line":349},[33,1709,1710],{"class":70},"    attributeOldValue: ",[33,1712,1673],{"class":63},[33,1714,1715],{"class":70},",  ",[33,1717,1718],{"class":39},"\u002F\u002F record old attribute values\n",[33,1720,1721,1724,1726,1728],{"class":35,"line":355},[33,1722,1723],{"class":70},"    characterData: ",[33,1725,1673],{"class":63},[33,1727,1715],{"class":70},[33,1729,1730],{"class":39},"\u002F\u002F watch text content changes\n",[33,1732,1733,1736,1738],{"class":35,"line":361},[33,1734,1735],{"class":70},"    characterDataOldValue: ",[33,1737,1673],{"class":63},[33,1739,1740],{"class":70},",\n",[33,1742,1743],{"class":35,"line":370},[33,1744,544],{"class":70},[33,1746,1747],{"class":35,"line":379},[33,1748,47],{"emptyLinePlaceholder":46},[33,1750,1751],{"class":35,"line":385},[33,1752,1753],{"class":39},"\u002F\u002F Stop observing (when done — prevents memory leaks)\n",[33,1755,1756],{"class":35,"line":438},[33,1757,1758],{"class":39},"\u002F\u002F observer.disconnect();\n",[33,1760,1762],{"class":35,"line":1761},32,[33,1763,47],{"emptyLinePlaceholder":46},[33,1765,1767],{"class":35,"line":1766},33,[33,1768,1769],{"class":39},"\u002F\u002F ── Use case: infinite scroll (observe last element visibility) ──\n",[33,1771,1773,1775,1778,1780,1782,1784,1786,1789],{"class":35,"line":1772},34,[33,1774,60],{"class":59},[33,1776,1777],{"class":63}," sentinel",[33,1779,67],{"class":59},[33,1781,71],{"class":70},[33,1783,120],{"class":74},[33,1785,78],{"class":70},[33,1787,1788],{"class":81},"\".scroll-sentinel\"",[33,1790,153],{"class":70},[33,1792,1794,1796,1799,1801,1803,1806,1808,1811,1813,1815],{"class":35,"line":1793},35,[33,1795,60],{"class":59},[33,1797,1798],{"class":63}," scrollObserver",[33,1800,67],{"class":59},[33,1802,1451],{"class":59},[33,1804,1805],{"class":74}," IntersectionObserver",[33,1807,627],{"class":70},[33,1809,1810],{"class":223},"entries",[33,1812,638],{"class":70},[33,1814,641],{"class":59},[33,1816,490],{"class":70},[33,1818,1820,1822,1825,1828],{"class":35,"line":1819},36,[33,1821,1400],{"class":59},[33,1823,1824],{"class":70}," (entries[",[33,1826,1827],{"class":63},"0",[33,1829,1830],{"class":70},"].isIntersecting) {\n",[33,1832,1834,1837,1839],{"class":35,"line":1833},37,[33,1835,1836],{"class":74},"        loadMoreContent",[33,1838,912],{"class":70},[33,1840,1841],{"class":39},"\u002F\u002F load next page when sentinel is visible\n",[33,1843,1845],{"class":35,"line":1844},38,[33,1846,1641],{"class":70},[33,1848,1850,1853,1856,1859],{"class":35,"line":1849},39,[33,1851,1852],{"class":70},"}, { rootMargin: ",[33,1854,1855],{"class":81},"\"100px\"",[33,1857,1858],{"class":70}," });  ",[33,1860,1861],{"class":39},"\u002F\u002F trigger 100px before sentinel enters viewport\n",[33,1863,1865,1868,1870],{"class":35,"line":1864},40,[33,1866,1867],{"class":70},"scrollObserver.",[33,1869,1662],{"class":74},[33,1871,1872],{"class":70},"(sentinel);\n",[15,1874,1876],{"id":1875},"tips-tricks","💡 Tips & Tricks",[20,1878,1879],{"language":22},[24,1880,1882],{"className":26,"code":1881,"language":22,"meta":28,"style":28},"\u002F\u002F ── data-* attributes for metadata ──\nconst el = document.querySelector(\"[data-user-id]\");\nel.dataset.userId;   \u002F\u002F \"123\" (camelCase: data-user-id → dataset.userId)\nel.dataset.role;     \u002F\u002F \"admin\"\nel.dataset.customKey = \"value\";  \u002F\u002F sets data-custom-key\n\n\u002F\u002F ── classList API (no manual string manipulation) ──\nel.classList.add(\"active\", \"visible\");      \u002F\u002F add multiple classes\nel.classList.remove(\"hidden\");              \u002F\u002F remove\nel.classList.toggle(\"active\");              \u002F\u002F toggle (add if absent, remove if present)\nel.classList.toggle(\"active\", condition);  \u002F\u002F toggle based on condition\nel.classList.replace(\"old\", \"new\");         \u002F\u002F replace\nel.classList.contains(\"active\");            \u002F\u002F check\n\n\u002F\u002F ── Create and configure elements in one step ──\nfunction createElement(tag, props = {}, ...children) {\n    const el = document.createElement(tag);\n    for (const [key, value] of Object.entries(props)) {\n        if (key === \"class\") el.className = value;\n        else if (key === \"style\") Object.assign(el.style, value);\n        else if (key.startsWith(\"on\") && typeof value === \"function\") {\n            el.addEventListener(key.slice(2).toLowerCase(), value);\n        } else if (key in el) el[key] = value;\n        else el.setAttribute(key, value);\n    }\n    for (const child of children) {\n        el.appendChild(typeof child === \"string\" ? document.createTextNode(child) : child);\n    }\n    return el;\n}\nconst div = createElement(\"div\", { class: \"card\", onClick: handler }, \"Hello\");\n\n\u002F\u002F ── Avoid layout thrashing with getBoundingClientRect ──\nconst rect = el.getBoundingClientRect();  \u002F\u002F READ (1 reflow)\n\u002F\u002F Now you have: rect.top, rect.left, rect.width, rect.height, rect.bottom, rect.right\n\u002F\u002F Use these cached values — don't read them again after DOM modifications.\n\n\u002F\u002F ── Shadow DOM for style encapsulation ──\nconst host = document.querySelector(\"#host\");\nconst shadow = host.attachShadow({ mode: \"open\" });\nshadow.innerHTML = `\n    \u003Cstyle>:host { display: block; padding: 10px; } p { color: red; }\u003C\u002Fstyle>\n    \u003Cp>Shadow content\u003C\u002Fp>\n`;\n\u002F\u002F Styles in shadow DOM don't leak out, page styles don't leak in (encapsulation)\n",[30,1883,1884,1889,1908,1916,1924,1939,1943,1948,1972,1990,2006,2022,2045,2061,2065,2070,2099,2114,2144,2164,2187,2222,2248,2270,2283,2287,2303,2340,2344,2352,2356,2385,2389,2394,2413,2418,2423,2427,2432,2452,2476,2487,2493,2499,2507],{"__ignoreMap":28},[33,1885,1886],{"class":35,"line":36},[33,1887,1888],{"class":39},"\u002F\u002F ── data-* attributes for metadata ──\n",[33,1890,1891,1893,1895,1897,1899,1901,1903,1906],{"class":35,"line":43},[33,1892,60],{"class":59},[33,1894,64],{"class":63},[33,1896,67],{"class":59},[33,1898,71],{"class":70},[33,1900,120],{"class":74},[33,1902,78],{"class":70},[33,1904,1905],{"class":81},"\"[data-user-id]\"",[33,1907,153],{"class":70},[33,1909,1910,1913],{"class":35,"line":50},[33,1911,1912],{"class":70},"el.dataset.userId;   ",[33,1914,1915],{"class":39},"\u002F\u002F \"123\" (camelCase: data-user-id → dataset.userId)\n",[33,1917,1918,1921],{"class":35,"line":56},[33,1919,1920],{"class":70},"el.dataset.role;     ",[33,1922,1923],{"class":39},"\u002F\u002F \"admin\"\n",[33,1925,1926,1929,1931,1934,1936],{"class":35,"line":91},[33,1927,1928],{"class":70},"el.dataset.customKey ",[33,1930,400],{"class":59},[33,1932,1933],{"class":81}," \"value\"",[33,1935,531],{"class":70},[33,1937,1938],{"class":39},"\u002F\u002F sets data-custom-key\n",[33,1940,1941],{"class":35,"line":97},[33,1942,47],{"emptyLinePlaceholder":46},[33,1944,1945],{"class":35,"line":102},[33,1946,1947],{"class":39},"\u002F\u002F ── classList API (no manual string manipulation) ──\n",[33,1949,1950,1953,1956,1958,1961,1963,1966,1969],{"class":35,"line":108},[33,1951,1952],{"class":70},"el.classList.",[33,1954,1955],{"class":74},"add",[33,1957,78],{"class":70},[33,1959,1960],{"class":81},"\"active\"",[33,1962,632],{"class":70},[33,1964,1965],{"class":81},"\"visible\"",[33,1967,1968],{"class":70},");      ",[33,1970,1971],{"class":39},"\u002F\u002F add multiple classes\n",[33,1973,1974,1976,1979,1981,1984,1987],{"class":35,"line":134},[33,1975,1952],{"class":70},[33,1977,1978],{"class":74},"remove",[33,1980,78],{"class":70},[33,1982,1983],{"class":81},"\"hidden\"",[33,1985,1986],{"class":70},");              ",[33,1988,1989],{"class":39},"\u002F\u002F remove\n",[33,1991,1992,1994,1997,1999,2001,2003],{"class":35,"line":156},[33,1993,1952],{"class":70},[33,1995,1996],{"class":74},"toggle",[33,1998,78],{"class":70},[33,2000,1960],{"class":81},[33,2002,1986],{"class":70},[33,2004,2005],{"class":39},"\u002F\u002F toggle (add if absent, remove if present)\n",[33,2007,2008,2010,2012,2014,2016,2019],{"class":35,"line":177},[33,2009,1952],{"class":70},[33,2011,1996],{"class":74},[33,2013,78],{"class":70},[33,2015,1960],{"class":81},[33,2017,2018],{"class":70},", condition);  ",[33,2020,2021],{"class":39},"\u002F\u002F toggle based on condition\n",[33,2023,2024,2026,2029,2031,2034,2036,2039,2042],{"class":35,"line":182},[33,2025,1952],{"class":70},[33,2027,2028],{"class":74},"replace",[33,2030,78],{"class":70},[33,2032,2033],{"class":81},"\"old\"",[33,2035,632],{"class":70},[33,2037,2038],{"class":81},"\"new\"",[33,2040,2041],{"class":70},");         ",[33,2043,2044],{"class":39},"\u002F\u002F replace\n",[33,2046,2047,2049,2051,2053,2055,2058],{"class":35,"line":188},[33,2048,1952],{"class":70},[33,2050,1355],{"class":74},[33,2052,78],{"class":70},[33,2054,1960],{"class":81},[33,2056,2057],{"class":70},");            ",[33,2059,2060],{"class":39},"\u002F\u002F check\n",[33,2062,2063],{"class":35,"line":212},[33,2064,47],{"emptyLinePlaceholder":46},[33,2066,2067],{"class":35,"line":239},[33,2068,2069],{"class":39},"\u002F\u002F ── Create and configure elements in one step ──\n",[33,2071,2072,2074,2077,2079,2082,2084,2087,2089,2092,2094,2097],{"class":35,"line":245},[33,2073,688],{"class":59},[33,2075,2076],{"class":74}," createElement",[33,2078,78],{"class":70},[33,2080,2081],{"class":223},"tag",[33,2083,632],{"class":70},[33,2085,2086],{"class":223},"props",[33,2088,67],{"class":59},[33,2090,2091],{"class":70}," {}, ",[33,2093,432],{"class":59},[33,2095,2096],{"class":223},"children",[33,2098,699],{"class":70},[33,2100,2101,2103,2105,2107,2109,2111],{"class":35,"line":250},[33,2102,495],{"class":59},[33,2104,64],{"class":63},[33,2106,67],{"class":59},[33,2108,71],{"class":70},[33,2110,828],{"class":74},[33,2112,2113],{"class":70},"(tag);\n",[33,2115,2116,2118,2120,2122,2124,2127,2129,2132,2134,2136,2139,2141],{"class":35,"line":256},[33,2117,1480],{"class":59},[33,2119,391],{"class":70},[33,2121,60],{"class":59},[33,2123,593],{"class":70},[33,2125,2126],{"class":63},"key",[33,2128,632],{"class":70},[33,2130,2131],{"class":63},"value",[33,2133,738],{"class":70},[33,2135,741],{"class":59},[33,2137,2138],{"class":70}," Object.",[33,2140,1810],{"class":74},[33,2142,2143],{"class":70},"(props)) {\n",[33,2145,2146,2148,2151,2153,2156,2159,2161],{"class":35,"line":281},[33,2147,1498],{"class":59},[33,2149,2150],{"class":70}," (key ",[33,2152,1504],{"class":59},[33,2154,2155],{"class":81}," \"class\"",[33,2157,2158],{"class":70},") el.className ",[33,2160,400],{"class":59},[33,2162,2163],{"class":70}," value;\n",[33,2165,2166,2169,2172,2174,2176,2179,2182,2184],{"class":35,"line":306},[33,2167,2168],{"class":59},"        else",[33,2170,2171],{"class":59}," if",[33,2173,2150],{"class":70},[33,2175,1504],{"class":59},[33,2177,2178],{"class":81}," \"style\"",[33,2180,2181],{"class":70},") Object.",[33,2183,752],{"class":74},[33,2185,2186],{"class":70},"(el.style, value);\n",[33,2188,2189,2191,2193,2196,2199,2201,2204,2206,2209,2212,2215,2217,2220],{"class":35,"line":312},[33,2190,2168],{"class":59},[33,2192,2171],{"class":59},[33,2194,2195],{"class":70}," (key.",[33,2197,2198],{"class":74},"startsWith",[33,2200,78],{"class":70},[33,2202,2203],{"class":81},"\"on\"",[33,2205,638],{"class":70},[33,2207,2208],{"class":59},"&&",[33,2210,2211],{"class":59}," typeof",[33,2213,2214],{"class":70}," value ",[33,2216,1504],{"class":59},[33,2218,2219],{"class":81}," \"function\"",[33,2221,699],{"class":70},[33,2223,2224,2227,2229,2232,2235,2237,2240,2242,2245],{"class":35,"line":318},[33,2225,2226],{"class":70},"            el.",[33,2228,1378],{"class":74},[33,2230,2231],{"class":70},"(key.",[33,2233,2234],{"class":74},"slice",[33,2236,78],{"class":70},[33,2238,2239],{"class":63},"2",[33,2241,868],{"class":70},[33,2243,2244],{"class":74},"toLowerCase",[33,2246,2247],{"class":70},"(), value);\n",[33,2249,2250,2253,2256,2258,2260,2263,2266,2268],{"class":35,"line":323},[33,2251,2252],{"class":70},"        } ",[33,2254,2255],{"class":59},"else",[33,2257,2171],{"class":59},[33,2259,2150],{"class":70},[33,2261,2262],{"class":59},"in",[33,2264,2265],{"class":70}," el) el[key] ",[33,2267,400],{"class":59},[33,2269,2163],{"class":70},[33,2271,2272,2274,2277,2280],{"class":35,"line":329},[33,2273,2168],{"class":59},[33,2275,2276],{"class":70}," el.",[33,2278,2279],{"class":74},"setAttribute",[33,2281,2282],{"class":70},"(key, value);\n",[33,2284,2285],{"class":35,"line":349},[33,2286,1641],{"class":70},[33,2288,2289,2291,2293,2295,2298,2300],{"class":35,"line":355},[33,2290,1480],{"class":59},[33,2292,391],{"class":70},[33,2294,60],{"class":59},[33,2296,2297],{"class":63}," child",[33,2299,1490],{"class":59},[33,2301,2302],{"class":70}," children) {\n",[33,2304,2305,2308,2310,2312,2315,2318,2320,2323,2326,2328,2331,2334,2337],{"class":35,"line":361},[33,2306,2307],{"class":70},"        el.",[33,2309,871],{"class":74},[33,2311,78],{"class":70},[33,2313,2314],{"class":59},"typeof",[33,2316,2317],{"class":70}," child ",[33,2319,1504],{"class":59},[33,2321,2322],{"class":81}," \"string\"",[33,2324,2325],{"class":59}," ?",[33,2327,71],{"class":70},[33,2329,2330],{"class":74},"createTextNode",[33,2332,2333],{"class":70},"(child) ",[33,2335,2336],{"class":59},":",[33,2338,2339],{"class":70}," child);\n",[33,2341,2342],{"class":35,"line":370},[33,2343,1641],{"class":70},[33,2345,2346,2349],{"class":35,"line":379},[33,2347,2348],{"class":59},"    return",[33,2350,2351],{"class":70}," el;\n",[33,2353,2354],{"class":35,"line":385},[33,2355,770],{"class":70},[33,2357,2358,2360,2363,2365,2367,2369,2371,2374,2377,2380,2383],{"class":35,"line":438},[33,2359,60],{"class":59},[33,2361,2362],{"class":63}," div",[33,2364,67],{"class":59},[33,2366,2076],{"class":74},[33,2368,78],{"class":70},[33,2370,298],{"class":81},[33,2372,2373],{"class":70},", { class: ",[33,2375,2376],{"class":81},"\"card\"",[33,2378,2379],{"class":70},", onClick: handler }, ",[33,2381,2382],{"class":81},"\"Hello\"",[33,2384,153],{"class":70},[33,2386,2387],{"class":35,"line":1761},[33,2388,47],{"emptyLinePlaceholder":46},[33,2390,2391],{"class":35,"line":1766},[33,2392,2393],{"class":39},"\u002F\u002F ── Avoid layout thrashing with getBoundingClientRect ──\n",[33,2395,2396,2398,2401,2403,2405,2408,2410],{"class":35,"line":1772},[33,2397,60],{"class":59},[33,2399,2400],{"class":63}," rect",[33,2402,67],{"class":59},[33,2404,2276],{"class":70},[33,2406,2407],{"class":74},"getBoundingClientRect",[33,2409,912],{"class":70},[33,2411,2412],{"class":39},"\u002F\u002F READ (1 reflow)\n",[33,2414,2415],{"class":35,"line":1793},[33,2416,2417],{"class":39},"\u002F\u002F Now you have: rect.top, rect.left, rect.width, rect.height, rect.bottom, rect.right\n",[33,2419,2420],{"class":35,"line":1819},[33,2421,2422],{"class":39},"\u002F\u002F Use these cached values — don't read them again after DOM modifications.\n",[33,2424,2425],{"class":35,"line":1833},[33,2426,47],{"emptyLinePlaceholder":46},[33,2428,2429],{"class":35,"line":1844},[33,2430,2431],{"class":39},"\u002F\u002F ── Shadow DOM for style encapsulation ──\n",[33,2433,2434,2436,2439,2441,2443,2445,2447,2450],{"class":35,"line":1849},[33,2435,60],{"class":59},[33,2437,2438],{"class":63}," host",[33,2440,67],{"class":59},[33,2442,71],{"class":70},[33,2444,120],{"class":74},[33,2446,78],{"class":70},[33,2448,2449],{"class":81},"\"#host\"",[33,2451,153],{"class":70},[33,2453,2454,2456,2459,2461,2464,2467,2470,2473],{"class":35,"line":1864},[33,2455,60],{"class":59},[33,2457,2458],{"class":63}," shadow",[33,2460,67],{"class":59},[33,2462,2463],{"class":70}," host.",[33,2465,2466],{"class":74},"attachShadow",[33,2468,2469],{"class":70},"({ mode: ",[33,2471,2472],{"class":81},"\"open\"",[33,2474,2475],{"class":70}," });\n",[33,2477,2479,2482,2484],{"class":35,"line":2478},41,[33,2480,2481],{"class":70},"shadow.innerHTML ",[33,2483,400],{"class":59},[33,2485,2486],{"class":81}," `\n",[33,2488,2490],{"class":35,"line":2489},42,[33,2491,2492],{"class":81},"    \u003Cstyle>:host { display: block; padding: 10px; } p { color: red; }\u003C\u002Fstyle>\n",[33,2494,2496],{"class":35,"line":2495},43,[33,2497,2498],{"class":81},"    \u003Cp>Shadow content\u003C\u002Fp>\n",[33,2500,2502,2505],{"class":35,"line":2501},44,[33,2503,2504],{"class":81},"`",[33,2506,853],{"class":70},[33,2508,2510],{"class":35,"line":2509},45,[33,2511,2512],{"class":39},"\u002F\u002F Styles in shadow DOM don't leak out, page styles don't leak in (encapsulation)\n",[15,2514,2516],{"id":2515},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[20,2518,2519],{"language":22},[24,2520,2522],{"className":26,"code":2521,"language":22,"meta":28,"style":28},"\u002F\u002F ── Live collections update automatically (can cause loop bugs) ──\nconst divs = document.getElementsByTagName(\"div\");\nfor (let i = 0; i \u003C divs.length; i++) {\n    document.body.appendChild(document.createElement(\"div\"));  \u002F\u002F divs.length grows → infinite loop\n}\n\u002F\u002F Fix: cache length, or use a static copy ([...divs] or querySelectorAll).\n\n\u002F\u002F ── querySelectorAll returns a STATIC NodeList (not live) ──\nconst items = document.querySelectorAll(\".item\");\ndocument.body.appendChild(document.createElement(\"div\")).className = \"item\";\nitems.length;  \u002F\u002F unchanged (static snapshot — doesn't reflect new .item)\n\n\u002F\u002F ── firstChild includes text nodes (whitespace) ──\n\u002F\u002F \u003Cdiv>\\n  \u003Cspan>text\u003C\u002Fspan>\\n\u003C\u002Fdiv>\n\u002F\u002F div.firstChild is a text node (the \"\\n  \" whitespace), NOT the span\n\u002F\u002F Use firstElementChild for elements only.\n\n\u002F\u002F ── innerHTML is XSS-prone with user data ──\n\u002F\u002F ❌ el.innerHTML = `\u003Cdiv>${userInput}\u003C\u002Fdiv>`;  \u002F\u002F XSS if userInput has \u003Cscript>\n\u002F\u002F ✅ el.textContent = userInput;  \u002F\u002F safe (escaped automatically)\n\u002F\u002F ✅ el.innerHTML = `\u003Cdiv>${escapeHtml(userInput)}\u003C\u002Fdiv>`;  \u002F\u002F escape manually\n\n\u002F\u002F ── offsetWidth\u002FoffsetHeight force reflow (layout calculation) ──\n\u002F\u002F Reading offsetWidth after a style change forces the browser to recalculate layout.\n\u002F\u002F Batch reads: const w = el.offsetWidth (read once, use multiple times).\n\n\u002F\u002F ── textContent vs innerHTML ──\nel.textContent = \"\u003Cb>not bold\u003C\u002Fb>\";  \u002F\u002F displays literally (escaped)\nel.innerHTML = \"\u003Cb>bold\u003C\u002Fb>\";        \u002F\u002F parses HTML (renders as bold)\n\u002F\u002F Use textContent for plain text (safe). Use innerHTML only for trusted HTML.\n",[30,2523,2524,2529,2547,2575,2597,2601,2606,2610,2615,2633,2658,2669,2673,2678,2683,2688,2693,2697,2702,2710,2718,2726,2730,2735,2740,2745,2749,2754,2769,2785],{"__ignoreMap":28},[33,2525,2526],{"class":35,"line":36},[33,2527,2528],{"class":39},"\u002F\u002F ── Live collections update automatically (can cause loop bugs) ──\n",[33,2530,2531,2533,2535,2537,2539,2541,2543,2545],{"class":35,"line":43},[33,2532,60],{"class":59},[33,2534,334],{"class":63},[33,2536,67],{"class":59},[33,2538,71],{"class":70},[33,2540,293],{"class":74},[33,2542,78],{"class":70},[33,2544,298],{"class":81},[33,2546,153],{"class":70},[33,2548,2549,2551,2553,2555,2557,2559,2561,2563,2565,2567,2569,2571,2573],{"class":35,"line":50},[33,2550,388],{"class":59},[33,2552,391],{"class":70},[33,2554,394],{"class":59},[33,2556,397],{"class":70},[33,2558,400],{"class":59},[33,2560,403],{"class":63},[33,2562,417],{"class":70},[33,2564,420],{"class":59},[33,2566,411],{"class":70},[33,2568,414],{"class":63},[33,2570,810],{"class":70},[33,2572,426],{"class":59},[33,2574,699],{"class":70},[33,2576,2577,2580,2582,2585,2587,2589,2591,2594],{"class":35,"line":56},[33,2578,2579],{"class":70},"    document.body.",[33,2581,871],{"class":74},[33,2583,2584],{"class":70},"(document.",[33,2586,828],{"class":74},[33,2588,78],{"class":70},[33,2590,298],{"class":81},[33,2592,2593],{"class":70},"));  ",[33,2595,2596],{"class":39},"\u002F\u002F divs.length grows → infinite loop\n",[33,2598,2599],{"class":35,"line":91},[33,2600,770],{"class":70},[33,2602,2603],{"class":35,"line":97},[33,2604,2605],{"class":39},"\u002F\u002F Fix: cache length, or use a static copy ([...divs] or querySelectorAll).\n",[33,2607,2608],{"class":35,"line":102},[33,2609,47],{"emptyLinePlaceholder":46},[33,2611,2612],{"class":35,"line":108},[33,2613,2614],{"class":39},"\u002F\u002F ── querySelectorAll returns a STATIC NodeList (not live) ──\n",[33,2616,2617,2619,2621,2623,2625,2627,2629,2631],{"class":35,"line":134},[33,2618,60],{"class":59},[33,2620,193],{"class":63},[33,2622,67],{"class":59},[33,2624,71],{"class":70},[33,2626,200],{"class":74},[33,2628,78],{"class":70},[33,2630,125],{"class":81},[33,2632,153],{"class":70},[33,2634,2635,2638,2640,2642,2644,2646,2648,2651,2653,2656],{"class":35,"line":156},[33,2636,2637],{"class":70},"document.body.",[33,2639,871],{"class":74},[33,2641,2584],{"class":70},[33,2643,828],{"class":74},[33,2645,78],{"class":70},[33,2647,298],{"class":81},[33,2649,2650],{"class":70},")).className ",[33,2652,400],{"class":59},[33,2654,2655],{"class":81}," \"item\"",[33,2657,853],{"class":70},[33,2659,2660,2662,2664,2666],{"class":35,"line":177},[33,2661,215],{"class":70},[33,2663,414],{"class":63},[33,2665,531],{"class":70},[33,2667,2668],{"class":39},"\u002F\u002F unchanged (static snapshot — doesn't reflect new .item)\n",[33,2670,2671],{"class":35,"line":182},[33,2672,47],{"emptyLinePlaceholder":46},[33,2674,2675],{"class":35,"line":188},[33,2676,2677],{"class":39},"\u002F\u002F ── firstChild includes text nodes (whitespace) ──\n",[33,2679,2680],{"class":35,"line":212},[33,2681,2682],{"class":39},"\u002F\u002F \u003Cdiv>\\n  \u003Cspan>text\u003C\u002Fspan>\\n\u003C\u002Fdiv>\n",[33,2684,2685],{"class":35,"line":239},[33,2686,2687],{"class":39},"\u002F\u002F div.firstChild is a text node (the \"\\n  \" whitespace), NOT the span\n",[33,2689,2690],{"class":35,"line":245},[33,2691,2692],{"class":39},"\u002F\u002F Use firstElementChild for elements only.\n",[33,2694,2695],{"class":35,"line":250},[33,2696,47],{"emptyLinePlaceholder":46},[33,2698,2699],{"class":35,"line":256},[33,2700,2701],{"class":39},"\u002F\u002F ── innerHTML is XSS-prone with user data ──\n",[33,2703,2704,2707],{"class":35,"line":281},[33,2705,2706],{"class":39},"\u002F\u002F ❌ el.innerHTML = `\u003Cdiv>${userInput}\u003C\u002Fdiv>`;",[33,2708,2709],{"class":39},"  \u002F\u002F XSS if userInput has \u003Cscript>\n",[33,2711,2712,2715],{"class":35,"line":306},[33,2713,2714],{"class":39},"\u002F\u002F ✅ el.textContent = userInput;",[33,2716,2717],{"class":39},"  \u002F\u002F safe (escaped automatically)\n",[33,2719,2720,2723],{"class":35,"line":312},[33,2721,2722],{"class":39},"\u002F\u002F ✅ el.innerHTML = `\u003Cdiv>${escapeHtml(userInput)}\u003C\u002Fdiv>`;",[33,2724,2725],{"class":39},"  \u002F\u002F escape manually\n",[33,2727,2728],{"class":35,"line":318},[33,2729,47],{"emptyLinePlaceholder":46},[33,2731,2732],{"class":35,"line":323},[33,2733,2734],{"class":39},"\u002F\u002F ── offsetWidth\u002FoffsetHeight force reflow (layout calculation) ──\n",[33,2736,2737],{"class":35,"line":329},[33,2738,2739],{"class":39},"\u002F\u002F Reading offsetWidth after a style change forces the browser to recalculate layout.\n",[33,2741,2742],{"class":35,"line":349},[33,2743,2744],{"class":39},"\u002F\u002F Batch reads: const w = el.offsetWidth (read once, use multiple times).\n",[33,2746,2747],{"class":35,"line":355},[33,2748,47],{"emptyLinePlaceholder":46},[33,2750,2751],{"class":35,"line":361},[33,2752,2753],{"class":39},"\u002F\u002F ── textContent vs innerHTML ──\n",[33,2755,2756,2759,2761,2764,2766],{"class":35,"line":370},[33,2757,2758],{"class":70},"el.textContent ",[33,2760,400],{"class":59},[33,2762,2763],{"class":81}," \"\u003Cb>not bold\u003C\u002Fb>\"",[33,2765,531],{"class":70},[33,2767,2768],{"class":39},"\u002F\u002F displays literally (escaped)\n",[33,2770,2771,2774,2776,2779,2782],{"class":35,"line":379},[33,2772,2773],{"class":70},"el.innerHTML ",[33,2775,400],{"class":59},[33,2777,2778],{"class":81}," \"\u003Cb>bold\u003C\u002Fb>\"",[33,2780,2781],{"class":70},";        ",[33,2783,2784],{"class":39},"\u002F\u002F parses HTML (renders as bold)\n",[33,2786,2787],{"class":35,"line":385},[33,2788,2789],{"class":39},"\u002F\u002F Use textContent for plain text (safe). Use innerHTML only for trusted HTML.\n",[15,2791,2793],{"id":2792},"quick-quiz","🧠 Quick Quiz",[2795,2796,2797],"p",{},"What's wrong with this code?",[20,2799,2800],{"language":22},[24,2801,2803],{"className":26,"code":2802,"language":22,"meta":28,"style":28},"const items = document.getElementsByClassName(\"item\");\nfor (let i = 0; i \u003C items.length; i++) {\n    if (items[i].dataset.active === \"true\") {\n        items[i].remove();\n    }\n}\n",[30,2804,2805,2823,2852,2866,2875,2879],{"__ignoreMap":28},[33,2806,2807,2809,2811,2813,2815,2817,2819,2821],{"class":35,"line":36},[33,2808,60],{"class":59},[33,2810,193],{"class":63},[33,2812,67],{"class":59},[33,2814,71],{"class":70},[33,2816,268],{"class":74},[33,2818,78],{"class":70},[33,2820,273],{"class":81},[33,2822,153],{"class":70},[33,2824,2825,2827,2829,2831,2833,2835,2837,2839,2841,2844,2846,2848,2850],{"class":35,"line":43},[33,2826,388],{"class":59},[33,2828,391],{"class":70},[33,2830,394],{"class":59},[33,2832,397],{"class":70},[33,2834,400],{"class":59},[33,2836,403],{"class":63},[33,2838,417],{"class":70},[33,2840,420],{"class":59},[33,2842,2843],{"class":70}," items.",[33,2845,414],{"class":63},[33,2847,810],{"class":70},[33,2849,426],{"class":59},[33,2851,699],{"class":70},[33,2853,2854,2856,2859,2861,2864],{"class":35,"line":50},[33,2855,1400],{"class":59},[33,2857,2858],{"class":70}," (items[i].dataset.active ",[33,2860,1504],{"class":59},[33,2862,2863],{"class":81}," \"true\"",[33,2865,699],{"class":70},[33,2867,2868,2871,2873],{"class":35,"line":56},[33,2869,2870],{"class":70},"        items[i].",[33,2872,1978],{"class":74},[33,2874,1419],{"class":70},[33,2876,2877],{"class":35,"line":91},[33,2878,1641],{"class":70},[33,2880,2881],{"class":35,"line":97},[33,2882,770],{"class":70},[2884,2885,2886,2890,2912,2959,2965,3052,3055,3118],"details",{},[2887,2888,2889],"summary",{},"Answer",[2795,2891,2892,2894,2895,2899,2900,2903,2904,2907,2908,2911],{},[30,2893,268],{}," returns a ",[2896,2897,2898],"strong",{},"live"," ",[30,2901,2902],{},"HTMLCollection",". When you ",[30,2905,2906],{},"remove()"," an element, the collection ",[2896,2909,2910],{},"shrinks immediately",". This causes the loop to skip elements:",[2913,2914,2915,2933,2949],"ul",{},[2916,2917,2918,2919,2922,2923,2926,2927,2929,2930],"li",{},"Iteration 0: ",[30,2920,2921],{},"i=0",", remove ",[30,2924,2925],{},"items[0]"," → collection shrinks, ",[30,2928,2925],{}," is now the former ",[30,2931,2932],{},"items[1]",[2916,2934,2935,2936,2939,2940,2929,2942,2945,2946,2948],{},"Iteration 1: ",[30,2937,2938],{},"i=1",", but ",[30,2941,2932],{},[30,2943,2944],{},"items[2]"," (former ",[30,2947,2932],{}," moved to index 0)",[2916,2950,2951,2952,2954,2955,2958],{},"The former ",[30,2953,2932],{}," is ",[2896,2956,2957],{},"never checked"," — skipped!",[2795,2960,2961,2964],{},[2896,2962,2963],{},"Fix",": iterate backwards (removing from the end doesn't shift indices):",[24,2966,2968],{"className":26,"code":2967,"language":22,"meta":28,"style":28},"const items = document.getElementsByClassName(\"item\");\nfor (let i = items.length - 1; i >= 0; i--) {\n    if (items[i].dataset.active === \"true\") {\n        items[i].remove();\n    }\n}\n",[30,2969,2970,2988,3024,3036,3044,3048],{"__ignoreMap":28},[33,2971,2972,2974,2976,2978,2980,2982,2984,2986],{"class":35,"line":36},[33,2973,60],{"class":59},[33,2975,193],{"class":63},[33,2977,67],{"class":59},[33,2979,71],{"class":70},[33,2981,268],{"class":74},[33,2983,78],{"class":70},[33,2985,273],{"class":81},[33,2987,153],{"class":70},[33,2989,2990,2992,2994,2996,2998,3000,3002,3004,3007,3010,3012,3015,3017,3019,3022],{"class":35,"line":43},[33,2991,388],{"class":59},[33,2993,391],{"class":70},[33,2995,394],{"class":59},[33,2997,397],{"class":70},[33,2999,400],{"class":59},[33,3001,2843],{"class":70},[33,3003,414],{"class":63},[33,3005,3006],{"class":59}," -",[33,3008,3009],{"class":63}," 1",[33,3011,417],{"class":70},[33,3013,3014],{"class":59},">=",[33,3016,403],{"class":63},[33,3018,810],{"class":70},[33,3020,3021],{"class":59},"--",[33,3023,699],{"class":70},[33,3025,3026,3028,3030,3032,3034],{"class":35,"line":50},[33,3027,1400],{"class":59},[33,3029,2858],{"class":70},[33,3031,1504],{"class":59},[33,3033,2863],{"class":81},[33,3035,699],{"class":70},[33,3037,3038,3040,3042],{"class":35,"line":56},[33,3039,2870],{"class":70},[33,3041,1978],{"class":74},[33,3043,1419],{"class":70},[33,3045,3046],{"class":35,"line":91},[33,3047,1641],{"class":70},[33,3049,3050],{"class":35,"line":97},[33,3051,770],{"class":70},[2795,3053,3054],{},"Or use a static copy:",[24,3056,3058],{"className":26,"code":3057,"language":22,"meta":28,"style":28},"const items = [...document.getElementsByClassName(\"item\")];  \u002F\u002F static array\nitems.forEach(item => { if (item.dataset.active === \"true\") item.remove(); });\n",[30,3059,3060,3086],{"__ignoreMap":28},[33,3061,3062,3064,3066,3068,3070,3072,3074,3076,3078,3080,3083],{"class":35,"line":36},[33,3063,60],{"class":59},[33,3065,193],{"class":63},[33,3067,67],{"class":59},[33,3069,593],{"class":70},[33,3071,432],{"class":59},[33,3073,994],{"class":70},[33,3075,268],{"class":74},[33,3077,78],{"class":70},[33,3079,273],{"class":81},[33,3081,3082],{"class":70},")];  ",[33,3084,3085],{"class":39},"\u002F\u002F static array\n",[33,3087,3088,3090,3092,3094,3096,3098,3101,3103,3106,3108,3110,3113,3115],{"class":35,"line":43},[33,3089,215],{"class":70},[33,3091,218],{"class":74},[33,3093,78],{"class":70},[33,3095,224],{"class":223},[33,3097,227],{"class":59},[33,3099,3100],{"class":70}," { ",[33,3102,1299],{"class":59},[33,3104,3105],{"class":70}," (item.dataset.active ",[33,3107,1504],{"class":59},[33,3109,2863],{"class":81},[33,3111,3112],{"class":70},") item.",[33,3114,1978],{"class":74},[33,3116,3117],{"class":70},"(); });\n",[2795,3119,3120,3123,3124,3127],{},[2896,3121,3122],{},"The lesson",": live collections (",[30,3125,3126],{},"getElementsBy*",") update in real-time. Removing elements during forward iteration causes index shifting and skipped elements. Iterate backwards or use a static copy.",[735,3129,3130],{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":28,"searchDepth":43,"depth":43,"links":3132},[3133,3134,3135,3136,3137,3138,3139,3140],{"id":17,"depth":43,"text":18},{"id":444,"depth":43,"text":445},{"id":773,"depth":43,"text":774},{"id":1103,"depth":43,"text":1104},{"id":1426,"depth":43,"text":1427},{"id":1875,"depth":43,"text":1876},{"id":2515,"depth":43,"text":2516},{"id":2792,"depth":43,"text":2793},"Deep-dive into the DOM API: selection methods (querySelector vs getElementById), node types, live vs static collections, DocumentFragment for batch insertion, mutation observation, and Shadow DOM encapsulation. Code-first reference for senior engineers.","md",{},"\u002Fjs\u002F19-the-dom",{"title":5,"description":3141},"js\u002F19-the-dom","UWcBZr3YnjmDrCuEANe59eD2PqfQ9v-avstnld4hCOk",1789924649405]