成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 是如何工作: Shadow DOM 的內(nèi)部結(jié)構(gòu)+如何編寫(xiě)?yīng)毩⒌慕M件!

godlong_X / 1746人閱讀

摘要:向影子樹(shù)添加的任何內(nèi)容都將成為宿主元素的本地元素,包括,這就是影子實(shí)現(xiàn)樣式作用域的方式。

這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 17 篇。

想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你!

如果你錯(cuò)過(guò)了前面的章節(jié),可以在這里找到它們:

JavaScript 是如何工作的:引擎,運(yùn)行時(shí)和調(diào)用堆棧的概述!

JavaScript 是如何工作的:深入V8引擎&編寫(xiě)優(yōu)化代碼的5個(gè)技巧!

JavaScript 是如何工作的:內(nèi)存管理+如何處理4個(gè)常見(jiàn)的內(nèi)存泄漏!

JavaScript 是如何工作的:事件循環(huán)和異步編程的崛起+ 5種使用 async/await 更好地編碼方式!

JavaScript 是如何工作的:深入探索 websocket 和HTTP/2與SSE +如何選擇正確的路徑!

JavaScript 是如何工作的:與 WebAssembly比較 及其使用場(chǎng)景!

JavaScript 是如何工作的:Web Workers的構(gòu)建塊+ 5個(gè)使用他們的場(chǎng)景!

JavaScript 是如何工作的:Service Worker 的生命周期及使用場(chǎng)景!

JavaScript 是如何工作的:Web 推送通知的機(jī)制!

JavaScript是如何工作的:使用 MutationObserver 跟蹤 DOM 的變化!

JavaScript是如何工作的:渲染引擎和優(yōu)化其性能的技巧!

JavaScript是如何工作的:深入網(wǎng)絡(luò)層 + 如何優(yōu)化性能和安全!

JavaScript是如何工作的:CSS 和 JS 動(dòng)畫(huà)底層原理及如何優(yōu)化它們的性能!

JavaScript的如何工作的:解析、抽象語(yǔ)法樹(shù)(AST)+ 提升編譯速度5個(gè)技巧!

JavaScript是如何工作的:深入類和繼承內(nèi)部原理+Babel和 TypeScript 之間轉(zhuǎn)換!

JavaScript是如何工作的:存儲(chǔ)引擎+如何選擇合適的存儲(chǔ)API!

概述

Web Components 是一套不同的技術(shù),允許你創(chuàng)建可重用的定制元素,它們的功能封裝在你的代碼之外,你可以在 Web 應(yīng)用中使用它們。

Web組件由四部分組成:

Shadow DOM(影子DOM)

HTML templates(HTML模板)

Custom elements(自定義元素)

HTML Imports(HTML導(dǎo)入)

在本文中主要講解 Shadow DOM(影子DOM)

Shadow DOM 這款工具旨在構(gòu)建基于組件的應(yīng)用。因此,可為網(wǎng)絡(luò)開(kāi)發(fā)中的常見(jiàn)問(wèn)題提供解決方案:

隔離 DOM:組件的 DOM 是獨(dú)立的(例如,document.querySelector() 不會(huì)返回組件 shadow DOM 中的節(jié)點(diǎn))。

作用域 CSS:shadow DOM 內(nèi)部定義的 CSS 在其作用域內(nèi)。樣式規(guī)則不會(huì)泄漏,頁(yè)面樣式也不會(huì)滲入。

組合:為組件設(shè)計(jì)一個(gè)聲明性、基于標(biāo)記的 API。

簡(jiǎn)化 CSS - 作用域 DOM 意味著您可以使用簡(jiǎn)單的 CSS 選擇器,更通用的 id/類名稱,而無(wú)需擔(dān)心命名沖突。

Shadow DOM

本文假設(shè)你已經(jīng)熟悉 DOM 及其它的 Api 的概念。如果不熟悉,可以在這里閱讀關(guān)于它的詳細(xì)文章—— https://developer.mozilla.org...。

陰影 DOM 只是一個(gè)普通的 DOM,除了兩個(gè)區(qū)別:

創(chuàng)建/使用的方式

與頁(yè)面其他部分有關(guān)的行為方式

通常,你創(chuàng)建 DOM 節(jié)點(diǎn)并將其附加至其他元素作為子項(xiàng)。 借助于 shadow DOM,您可以創(chuàng)建作用域 DOM 樹(shù),該 DOM 樹(shù)附加至該元素上,但與其自身真正的子項(xiàng)分離開(kāi)來(lái)。這一作用域子樹(shù)稱為影子樹(shù)。被附著的元素稱為影子宿主。 您在影子中添加的任何項(xiàng)均將成為宿主元素的本地項(xiàng),包括 Launch 模板 (Templates)

如果需要 Web 頁(yè)面上重復(fù)使用相同的標(biāo)簽結(jié)構(gòu)時(shí),最好使用某種類型的模板,而不是一遍又一遍地重復(fù)相同的結(jié)構(gòu)。這在以前也是可以實(shí)現(xiàn),但是 HTML

現(xiàn)在自定義組件可以這樣使用:



元素

模板有一些缺點(diǎn),主要是靜態(tài)內(nèi)容,它不允許我們渲染變量/數(shù)據(jù),好可以讓我們按照一般使用的標(biāo)準(zhǔn) HTML 模板的習(xí)慣來(lái)編寫(xiě)代碼。Slot 是組件內(nèi)部的占位符,用戶可以使用自己的標(biāo)記來(lái)填充。讓我們看看上面的模板怎么使用 slot


如果在標(biāo)記中包含元素時(shí)沒(méi)有定義插槽的內(nèi)容,或者瀏覽器不支持插槽, 就只展示文本 “Default text”。

為了定義插槽的內(nèi)容,應(yīng)該在 元素中包含一個(gè) HTML 結(jié)構(gòu),其中的 slot 屬性的值為我們定義插槽的名稱:


 Let"s have some different text!

可以插入插槽的元素稱為 Slotable; 當(dāng)一個(gè)元素插入一個(gè)插槽時(shí),它被稱為開(kāi)槽 (slotted)。

注意,在上面的例子中,插入了一個(gè) 元素,它是一個(gè)開(kāi)槽元素,它有一個(gè)屬性 slot,它等于 my-text,與模板中的 slot 定義中的 name 屬性的值相同。

在瀏覽器中渲染后,上面的代碼將構(gòu)建以下扁平 DOM 樹(shù):


  #shadow-root
  

Let"s have some different text!

設(shè)定樣式

使用 shadow DOM 的組件可通過(guò)主頁(yè)來(lái)設(shè)定樣式,定義其自己的樣式或提供鉤子(以 CSS 自定義屬性的形式)讓用戶替換默認(rèn)值。

組件定義的樣式

作用域 CSS 是 Shadow DOM 最大的特性之一:

外部頁(yè)面的 CSS 選擇器不應(yīng)用于組件內(nèi)部

組件內(nèi)定義的樣式不會(huì)影響頁(yè)面的其他元素,它們的作用域是宿主元素

shadow DOM 內(nèi)部使用的 CSS 選擇器在本地應(yīng)用于組件實(shí)際上,這意味著我們可以再次使用公共vid/類名,而不用擔(dān)心頁(yè)面上其他地方的沖突,最佳做法是在 Shadow DOM 內(nèi)使用更簡(jiǎn)單的 CSS 選擇器,它們?cè)谛阅苌弦膊诲e(cuò)。

看看在 #shadow-root 定義了一些樣式的:

#shadow-root


上面例子中的所有樣式都是#shadow-root的本地樣式。使用元素在#shadow-root中引入樣式表,這些樣式表也都屬于本地的。

:host 偽類選擇器

使用 :host 偽類選擇器,用來(lái)選擇組件宿主元素中的元素 (相對(duì)于組件模板內(nèi)部的元素)。



當(dāng)涉及到 :host 選擇器時(shí),應(yīng)該小心一件事:父頁(yè)面中的規(guī)則具有比元素中定義的 :host 規(guī)則具有更高的優(yōu)先級(jí),這允許用戶從外部覆蓋頂級(jí)樣式。而且 :host 只在影子根目錄下工作,所以你不能在Shadow DOM 之外使用它。

如果 :host() 的函數(shù)形式與 匹配,你可以指定宿主,對(duì)于你的組件而言,這是一個(gè)很好的方法,它可讓你基于宿主將對(duì)用戶互動(dòng)或狀態(tài)的反應(yīng)行為進(jìn)行封裝,或?qū)?nèi)部節(jié)點(diǎn)進(jìn)行樣式設(shè)定:


:host-context()

:host-context() 或其任意父級(jí)與 匹配,它將與組件匹配。 例如,在文檔的元素上可能有一個(gè)用于表示樣式主題 (theme) 的 CSS 類,而我們應(yīng)當(dāng)基于它來(lái)決定組件的樣式。
比如,很多人都通過(guò)將類應(yīng)用到 或 進(jìn)行主題化:


  

在下面的例子中,只有當(dāng)某個(gè)祖先元素有 CSS 類theme-light時(shí),我們才會(huì)把background-color樣式應(yīng)用到組件內(nèi)部的所有元素中:

:host-context(.theme-light) h2 {
  background-color: #eef;
}
/deep/

組件樣式通常只會(huì)作用于組件自身的 HTML 上,我們可以使用 /deep/ 選擇器,來(lái)強(qiáng)制一個(gè)樣式對(duì)各級(jí)子組件的視圖也生效,它不但作用于組件的子視圖,也會(huì)作用于組件的內(nèi)容。

在下面例子中,我們以所有的元素為目標(biāo),從宿主元素到當(dāng)前元素再到 DOM 中的所有子元素:

:host /deep/ h3 {
  font-style: italic;
}

/deep/ 選擇器還有一個(gè)別名 >>>,可以任意交替使用它們。

/deep/>>> 選擇器只能被用在仿真 (emulated)模式下。 這種方式是默認(rèn)值,也是用得最多的方式。
從外部為組件設(shè)定樣式

有幾種方法可從外部為組件設(shè)定樣式:最簡(jiǎn)單的方法是使用標(biāo)記名稱作為選擇器,如下

custom-container {
  color: red;
}

外部樣式比在 Shadow DOM 中定義的樣式具有更高的優(yōu)先級(jí)。

例如,如果用戶編寫(xiě)選擇器:

custom-container {
  width: 500px;
}

它將覆蓋組件的樣式:

:host {
  width: 300px;
}

對(duì)組件本身進(jìn)行樣式化只能到此為止。但是如果人想要對(duì)組件的內(nèi)部進(jìn)行樣式化,會(huì)發(fā)生什么情況呢?為此,我們需要 CSS 自定義屬性。

使用 CSS 自定義屬性創(chuàng)建樣式鉤子

如果組件的開(kāi)發(fā)者通過(guò) CSS 自定義屬性提供樣式鉤子,則用戶可調(diào)整內(nèi)部樣式。其思想類似于,但適用于樣式。

看看下面的例子:





在其 shadow DOM 內(nèi)部:

:host([background]) {
  background: var(?-?custom-container-bg, #CECECE);
  border-radius: 10px;
  padding: 10px;
}

在本例中,該組件將使用 black 作為背景值,因?yàn)橛脩糁付嗽撝?,否則,背景顏色將采用默認(rèn)值 #CECECE

作為組件的作者,是有責(zé)任讓開(kāi)發(fā)人員了解他們可以使用的 CSS 定制屬性,將其視為組件的公共接口的一部分。
在 JS 中使用 slot

Shadow DOM API 提供了使用 slot 和分布式節(jié)點(diǎn)的實(shí)用程序,這些實(shí)用程序在編寫(xiě)自定義元素時(shí)遲早派得上用場(chǎng)。

slotchange 事件

當(dāng) slot 的分布式節(jié)點(diǎn)發(fā)生變化時(shí),slotchange 事件將觸發(fā)。例如,如果用戶從 light DOM 中添加/刪除子元素。

var slot = this.shadowRoot.querySelector("#some_slot");
slot.addEventListener("slotchange", function(e) {
  console.log("Light DOM change");
});

要監(jiān)視對(duì) light DOM 的其他類型的更改,可以在元素的構(gòu)造函數(shù)中使用 MutationObserver。以前討論過(guò) MutationObserver 的內(nèi)部結(jié)構(gòu)以及如何使用它。

assignedNodes() 方法

有時(shí)候,了解哪些元素與 slot 相關(guān)聯(lián)非常有用。調(diào)用 slot.assignedNodes() 可查看 slot 正在渲染哪些元素。 {flatten: true} 選項(xiàng)將返回 slot 的備用內(nèi)容(前提是沒(méi)有分布任何節(jié)點(diǎn))。

讓我們看看下面的例子:

Default content

假設(shè)這是在一個(gè)名為 的組件中。

看看這個(gè)組件的不同用法,以及調(diào)用 assignedNodes() 的結(jié)果是什么:

在第一種情況下,我們將向 slot 中添加我們自己的內(nèi)容:


   container text 

調(diào)用 assignedNodes() 會(huì)得到 [ container text ],注意,結(jié)果是一個(gè)節(jié)點(diǎn)數(shù)組。

在第二種情況下,將內(nèi)容置空:

 

調(diào)用 assignedNodes() 的結(jié)果將返回一個(gè)空數(shù)組 []。

在第三種情況下,調(diào)用 slot.assignedNodes({flatten: true}),得到結(jié)果是: [

默認(rèn)內(nèi)容

]。

此外,要訪問(wèn) slot 中的元素,可以調(diào)用 assignedNodes() 來(lái)查看元素分配給哪個(gè)組件 slot

事件模型

值得注意的是,當(dāng)發(fā)生在 Shadow DOM 中的事件冒泡時(shí),會(huì)發(fā)生什么。

當(dāng)事件從 Shadow DOM 中觸發(fā)時(shí),其目標(biāo)將會(huì)調(diào)整為維持 Shadow DOM 提供的封裝。也就是說(shuō),事件的目標(biāo)重新進(jìn)行了設(shè)定,因此這些事件看起來(lái)像是來(lái)自組件,而不是來(lái)自 Shadow DOM 中的內(nèi)部元素。

下面是從 Shadow DOM 傳播出去的事件列表(有些沒(méi)有):

聚焦事件:blur、focus、focusin、focusout

鼠標(biāo)事件:click、dblclick、mousedown、mouseenter、mousemove,等等

滾輪事件:wheel

輸入事件:beforeinput、input

鍵盤(pán)事件:keydown、keyup

組合事件:compositionstart、compositionupdate、compositionend

拖放事件:dragstart、drag、dragend、drop,等等

自定義事件

默認(rèn)情況下,自定義事件不會(huì)傳播到 Shadow DOM 之外。如果希望分派自定義事件并使其傳播,則需要添加 bubbles: truecomposed: true 選項(xiàng)。

讓我們看看派發(fā)這樣的事件是什么樣的:

var container = this.shadowRoot.querySelector("#container");
container.dispatchEvent(new Event("containerchanged", {bubbles: true, composed: true}));
瀏覽器支持

如希望獲得 shadow DOM 檢測(cè)功能,請(qǐng)查看是否存在 attachShadow:

const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;

有史以來(lái)第一次,我們擁有了實(shí)施適當(dāng) CSS 作用域、DOM 作用域的 API 原語(yǔ),并且有真正意義上的組合。 與自定義元素等其他網(wǎng)絡(luò)組件 API 組合后,shadow DOM 提供了一種編寫(xiě)真正封裝組件的方法,無(wú)需花多大的功夫或使用如