Shadow DOM
Shadow DOM nima, Shadow Host/Root/Tree tushunchalari, open va closed rejimlar farqi, CSS izolyatsiyasi engine darajasida qanday ishlashi, :host va ::slotted, <slot> orqali content proyeksiyasi, composed event'lar va Web Components bilan bog'liqligi.
88-dars
Shadow DOM
Oddiy DOM'da muammo: agar siz komponent yozsangiz (masalan, custom "rating stars" widget), uning ichki HTML/CSS'i butun sahifa bilan bitta umumiy DOM daraxti va bitta umumiy CSS scopeda yashaydi.
<style>
.star { color: gold; }
</style>
<div class="my-widget">
<span class="star">★</span>
</div>
<!-- boshqa joyda, butunlay boshqa dasturchi yozgan -->
<div class="star">Bu — reklama yulduzchasi</div>
Bu yerda ikkita muammo bor:
- CSS to'qnashuvi —
.starklassi ikkala joyga ham tegadi, garchi ular butunlay bog'liq bo'lmasa ham - DOM ifloslanishi — sizning widget'ingiz ichidagi elementlarni tashqi JS
document.querySelector('.star')orqali kutilmagan tarzda topib, o'zgartirib yuborishi mumkin
Shadow DOM — bu muammoni til/browser darajasida, haqiqiy izolyatsiya orqali yechadi: u elementga o'z alohida, "yashirin" DOM daraxtini beradi — bu daraxt tashqi CSS'dan himoyalangan, va tashqi JS'dan odatdagi usullar bilan ko'rinmaydi.
1. Asosiy tushunchalar — uchta atama
| Atama | Ma'nosi |
|---|---|
| Shadow Host | Oddiy DOM elementi, unga Shadow DOM "biriktiriladi" |
| Shadow Root | Yangi, alohida DOM daraxtining ildizi |
| Shadow Tree | Shadow Root ichidagi barcha elementlar (bu — "yashirin" qism) |
const host = document.querySelector('.my-widget');
const shadowRoot = host.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
.star { color: gold; font-size: 24px; }
</style>
<span class="star">★</span>
`;
Natijada DOM tuzilmasi shunday bo'ladi:
<div class="my-widget"> ← Shadow Host (light DOM'da ko'rinadi)
#shadow-root (open) ← Shadow Root (chegaralovchi devor)
<style>...</style> ← Shadow Tree ichida, TASHQARIGA chiqmaydi
<span class="star">★</span> ← Shadow Tree ichida
2. mode: 'open' vs 'closed' — nima farqi
// open — tashqi JS shadowRoot'ga kira oladi
const host1 = document.querySelector('.widget1');
host1.attachShadow({ mode: 'open' });
console.log(host1.shadowRoot); // → #shadow-root obyekti, KIRISH MUMKIN
// closed — tashqi JS'dan butunlay bekitilgan
const host2 = document.querySelector('.widget2');
host2.attachShadow({ mode: 'closed' });
console.log(host2.shadowRoot); // → null, KIRISH MUMKIN EMAS
closed rejimida shadowRoot faqat attachShadow() chaqirgan funksiya ichidagi yopiq closureda saqlanadi (Module Pattern'ni eslang — aynan shu printsip!). Ko'pchilik real loyihalarda open ishlatiladi, chunki closed debugging'ni og'irlashtiradi (hatto brauzer DevTools ham ba'zi holatlarda qiynaladi), va haqiqiy xavfsizlik bermaydi (DevTools baribir ko'radi) — u faqat "tasodifiy" tashqi kirishni to'sadi, ataylab hujumdan himoya qilmaydi.
3. CSS izolyatsiyasi — qanday ishlaydi (engine darajasida)
Bu — Shadow DOM'ning eng muhim xususiyati. Browser'ning CSS style resolution (uslub hisoblash) algoritmi Shadow Tree'ni alohida "scope" sifatida ko'radi:
<style>
/* SAHIFA darajasidagi CSS */
p { color: red; }
</style>
<div id="host"></div>
<script>
const shadow = document.getElementById('host').attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>p { color: blue; }</style>
<p>Bu matn qanday rangda?</p>
`;
</script>
Natija: matn ko'k (blue) bo'ladi, garchi sahifada p { color: red } tashqarida yozilgan bo'lsa ham — chunki tashqi qoida Shadow Tree ichiga umuman kirmaydi. Va aksincha — Shadow Tree ichidagi <style> ham tashqariga chiqmaydi.
Ichki mexanizm darajasida: brauzer har bir DOM elementi uchun style resolution qilganda (CSSOM qurish va matching jarayonida — bu V8'da emas, balki rendering engineda, Chromium'da Blink qismida bajariladi), Shadow Root — bu yangi "tree scope" deb belgilanadi. Selektor moslashtirish (selector matching) algoritmi tree scope chegarasidan o'tmaydi, faqat maxsus pseudo-elementlar (::slotted, :host) orqali ataylab teshik ochilgan joylardagina o'tishga ruxsat beriladi.
4. :host va ::slotted — chegaradan boshqarilgan o'tish
shadow.innerHTML = `
<style>
:host {
display: block;
border: 1px solid gray;
}
:host(.active) {
border-color: green;
}
::slotted(span) {
font-weight: bold;
}
</style>
<slot></slot>
`;
:host— Shadow Tree ichidan turib, tashqi Host elementning o'ziga uslub berish imkonini beradi (aks holda Shadow Tree ichidan tashqariga hech qanday CSS ta'sir qilolmas edi):host(.active)— Host elementdaactiveklassi bo'lsa, shu uslub qo'llanadi::slotted(span)— pastda tushuntiriladigan<slot>orqali kiritilgan tashqi elementlarga uslub berish uchun maxsus teshik
5. <slot> — tashqi content'ni ichkariga "proyeksiya" qilish
Bu — Shadow DOM'ning eng amaliy qismi: siz Shadow Tree ichida "bo'sh joy" qoldirasiz, tashqi (light DOM) content shu joyga ko'rinadigan tarzda joylashadi, lekin jismonan ko'chmaydi:
<my-card>
<span>Bu — tashqi, LIGHT DOM content</span>
</my-card>
<script>
class MyCard extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>.wrapper { border: 2px solid black; padding: 10px; }</style>
<div class="wrapper">
<slot></slot>
</div>
`;
}
}
customElements.define('my-card', MyCard);
</script>
Natijada <span> elementi vizual jihatdan .wrapper ichida ko'rinadi, lekin document.querySelector('span') bilan qidirsangiz — u hali ham light DOM'da, Shadow Tree'ga jismonan ko'chmagan.
Bu — "flattened tree" (tekislangan daraxt) tushunchasi: brauzer render qilish uchun light DOM va shadow DOM'ni birlashtirib, "logical" ko'rinish yaratadi, lekin bu ikkalasi JS/DOM API darajasida — butunlay alohida daraxtlar bo'lib qoladi.
6. composed: true — Event'lar chegaradan qanday o'tadi
shadow.querySelector('.star').addEventListener('click', (e) => {
console.log(e.target); // Shadow Tree ichidagi element
console.log(e.composedPath()); // to'liq yo'l — Shadow Root'dan tashqarigacha
});
Odatiy DOM eventlar (click, input) composed: true bilan yaratilgan bo'lib, ular Shadow chegarasidan "sizib chiqadi" (bubble qiladi) — tashqi kod host.addEventListener('click', ...) orqali ichkaridagi bosishni ushlay oladi. Lekin e.target — tashqi kod uchun "retargeting" qilinadi: agar closed shadow bo'lsa, tashqi kod e.target sifatida Shadow Host'ning o'zini ko'radi, ichki elementni emas — bu yana bir izolyatsiya qatlami, ichki tuzilishni tashqariga oshkor qilmaslik uchun.
7. Nega bu real loyihalarda muhim — Web Components bilan bog'liqlik
Shadow DOM — bu Web Componentsning uchta ustunidan biri (keyingi mavzuda to'liq ko'ramiz):
Custom Elements → yangi HTML teg yaratish (<my-card>)
Shadow DOM → ichki tuzilmani izolyatsiya qilish
HTML Templates → qayta ishlatiladigan HTML shablon (<template>)
React/Vue kabi framework'lar CSS-in-JS yoki CSS Modules orqali (build vaqtida class nomlarini Button_root_a3f2x kabi noyob qilib) sun'iy izolyatsiya yaratadi. Shadow DOM esa — bu haqiqiy, browser darajasidagi izolyatsiya, hech qanday build tool kerak emas.
8. V8 va rendering pipeline darajasida — qisqa xulosa
| Bosqich | Nima sodir bo'ladi |
|---|---|
attachShadow() chaqirilganda |
Blink (rendering engine) yangi, alohida tree scope yaratadi, Host elementga bog'laydi |
| CSS hisoblanganda | Style resolution algoritmi tree scope chegarasidan o'tmaydi (faqat :host/::slotted orqali) |
| Render qilinganda | Light DOM + Shadow Tree flattened treega birlashtiriladi — bu faqat vizual, DOM API'da alohida qoladi |
| Event otilganda | composed: true bo'lsa, chegaradan o'tadi, lekin target retargeting qilinadi |
JS orqali qidirilganda (querySelector) |
Shadow Tree ko'rinmaydi — faqat host.shadowRoot.querySelector(...) orqali, aniq kirish kerak |
Bu yerda ham, IndexedDB/WebSocket kabi, V8'ning o'zi Shadow DOM haqida hech narsa bilmaydi — bu butunlay Blink'ning DOM/CSS qatlamida amalga oshirilgan. V8 faqat sizning attachShadow(), shadow.innerHTML kabi JS chaqiruvlaringizni C++ binding orqali Blink'ga uzatadi, xolos.
9. Qachon ishlatiladi
Shadow DOM asosan kodning tashqi muhitdan mustaqil bo'lishi talab qilinganda ishlatiladi.
1. Tashqi saytlarga joylanadigan vidjetlar (Third-party widgets)
- Boshqa saytlarga ulanadigan chat-bot vidjetlari (masalan, Intercom, JivoChat).
- Ob-havo, valyuta kursi yoki to'lov modullari (masalan, Stripe card input).
Sababi: Mijoz saytidagi CSS uslublari vidjet dizaynini buzib yubormasligi yoki vidjetdagi CSS mijoz saytiga ta'sir qilmasligi kerak.
2. Web Components va Microfrontend arxitekturasi
- Loyihada frameworklardan xoli, universal va qayta ishlatiluvchan UI komponentlar (masalan,
<my-button>,<user-avatar>) yaratishda. - Katta tizimlarda bir sahifada bir nechta jamoa mustaqil ishlayotganda, ularning stillari va DOM elementlari to'qnashib ketmasligi uchun.
3. Brauzer plaginlari (Chrome Extensions)
- Plagin sahifaga o'z menyusi, tugmasi yoki ma'lumotlar panelini qo'shganda.
Sababi: Har qanday saytda plagin interfeysi o'zgarmasdan, asl ko'rinishida qolishi shart.
4. Katta dizayn tizimlari (Design Systems)
Kompaniya ichida turli loyihalar va turli frameworklar (React, Vue, Angular yoki oddiy HTML) uchun bitta umumiy komponentlar kutubxonasi yaratilganda.