說明
浏覽器工作原理與實踐專欄學習筆記
什麼是元件化?
特點:對内高内聚,對外低耦合。
對内各個元素彼此緊密結合、互相依賴,對外和其他元件的聯系最少且接口簡單。
阻礙前端元件化的因素
比如:在頁面中嵌入第三方内容時,需要確定第三方的内容樣式不會影響到目前内容,同樣也要確定目前的 DOM 不會影響到第三方的内容。
CSS 是如何阻礙前端元件化的
例子:比如不同人分别寫了不同的全局樣式,個人測試沒問題,但是合并時會出現 CSS 屬性影響到其他外部的标簽的樣式
<style>p {
background-color: brown;
color: cornsilk
}</style>
<p>time.geekbang.org</p>
<style>p {
background-color: red;
color: blue
}</style>
<p>time.geekbang</p>
顯然,CSS 的全局屬性會阻礙元件化。
DOM 也是阻礙元件化的一個因素,因為在頁面中任何地方都可以直接讀取和修改 DOM。
WebComponent 元件化開發
上面我們了解到了:CSS 和 DOM 是阻礙元件化的兩個因素,WebComponent 給出的解決思路
WebComponent 提供了對局部視圖封裝能力,可以讓 DOM、CSSOM 和 JavaScript 運作在局部環境中,這樣就使得局部的 CSS 和 DOM 不會影響到全局。
WebComponent 是怎麼實作元件化的
MDN:Web Components
Web Components 概念
Web Components旨在解決這些問題 — 它由三項主要技術組成,它們可以一起使用來建立封裝功能的定制元素,可以在你喜歡的任何地方重用,不必擔心代碼沖突。
- Custom elements(自定義元素):一組JavaScript API,允許您定義custom elements及其行為,然後可以在您的使用者界面中按照需要使用它們。
- Shadow DOM(影子DOM):一組JavaScript API,用于将封裝的“影子”DOM樹附加到元素(與主文檔DOM分開呈現)并控制其關聯的功能。通過這種方式,您可以保持元素的功能私有,這樣它們就可以被腳本化和樣式化,而不用擔心與文檔的其他部分發生沖突。
- HTML templates(HTML模闆):
和<template>
元素使您可以編寫不在呈現頁面中顯示的标記模闆。然後它們可以作為自定義元素結構的基礎被多次重用。<slot>
Web Components 使用
實作 web component 的基本方法通常如下所示:
- 建立一個類或函數來指定web元件的功能,如果使用類,請使用 ECMAScript 2015 的類文法。
- 使用
方法注冊您的新自定義元素 ,并向其傳遞要定義的元素名稱、指定元素功能的類、以及可選的其所繼承自的元素。CustomElementRegistry.define()
- 如果需要的話,使用
方法将一個 shadow DOM 附加到自定義元素上。使用通常的 DOM 方法向 shadow DOM 中添加子元素、事件監聽器等等。Element.attachShadow()
- 如果需要的話,使用
和<template>
定義一個HTML模闆。再次使用正常DOM方法克隆模闆并将其附加到您的shadow DOM中。<slot>
- 在頁面任何您喜歡的位置使用自定義元素,就像使用正常HTML元素那樣。
Web Components 例子
要使用 WebComponent 需要實作下面三步:
1.使用 template 屬性來建立模闆。
利用 DOM 可以查找到模闆的内容,但是模闆元素是不會被渲染到頁面上的,在模闆的内部定義樣式資訊。
<!-- 定義模闆樣式 -->
<template id="kxm-template">
<style>p {
background-color: green;
color: white;
}
div {
width: 200px;
background-color: blue;
border: 3px solid red;
border-radius: 10px;
}</style>
<div>
<p>kxm</p>
<p>kaimo313</p>
</div>
<script>function foo() {
console.log('template log');
}</script>
</template>
2.建立一個類
- 查找模闆内容;
- 建立影子 DOM;
- 再将模闆添加到影子 DOM 上。
可以把影子 DOM 看成是一個作用域,其内部的樣式和元素是不會影響到全局的樣式和元素的,而在全局環境下,要通路影子 DOM 内部的樣式或者元素也是需要通過約定好的接口的。
<!-- 建立一個類 -->
<script>class Kaimo313 extends HTMLElement {
constructor() {
super()
// 擷取元件模闆
const content = document.querySelector('#kxm-template').content;
// 建立影子DOM節點
const shadowDOM = this.attachShadow({ mode: 'open' });
// 将模闆添加到影子DOM上
shadowDOM.appendChild(content.cloneNode(true));
}
}
// 使用 customElements.define 來自定義元素
customElements.define('kaimo-313', Kaimo313);</script>
3.使用該元素
<!-- 使用該元素 -->
<kaimo-313></kaimo-313>
<div>
<p>kxm</p>
<p>kaimo313</p>
</div>
<kaimo-313></kaimo-313>
完整的代碼展示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>12.Web Components 例子</title>
</head>
<body>
<h1>kxm 測試 Web Components 例子</h1>
<!-- 定義模闆樣式 -->
<template id="kxm-template">
<style>p {
background-color: green;
color: white;
}
div {
width: 200px;
background-color: blue;
border: 3px solid red;
border-radius: 10px;
}</style>
<div>
<p>kxm</p>
<p>kaimo313</p>
</div>
<script>function foo() {
console.log('template log');
}</script>
</template>
<!-- 建立一個類 -->
<script>class Kaimo313 extends HTMLElement {
constructor() {
super()
// 擷取元件模闆
const content = document.querySelector('#kxm-template').content;
// 建立影子DOM節點
const shadowDOM = this.attachShadow({ mode: 'open' });
// 将模闆添加到影子DOM上
shadowDOM.appendChild(content.cloneNode(true));
}
}
// 使用 customElements.define 來自定義元素
customElements.define('kaimo-313', Kaimo313);</script>
<!-- 使用該元素 -->
<kaimo-313></kaimo-313>
<div>
<p>kxm</p>
<p>kaimo313</p>
</div>
<kaimo-313></kaimo-313>
</body>
</html>
使用影子 DOM 的輸出效果
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SOzkDO4QWMiVWOkJmY5MmZyYzX5QTM0UTM3EzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
浏覽器如何實作影子 DOM
影子 DOM 的作用
- 影子 DOM 中的元素對于整個網頁是不可見的;
- 影子 DOM 的 CSS 不會影響到整個網頁的 CSSOM,影子 DOM 内部的 CSS 隻對内部的元素起作用。
影子 DOM 示意圖
影子 DOM 都有一個 shadow root 的根節點
浏覽器怎麼實作 DOM API 無法直接查詢到影子 DOM 的内部元素?
當通過 DOM 接口去查找元素時,渲染引擎會去判斷 kaimo-313 屬性下面的 shadow-root 元素是否是影子 DOM,如果是,那麼就直接跳過 shadow-root 元素的查詢操作。