我們站在開發(fā)者和項目角度來聊聊 WebComponent,它是一套技術的組合,能提供給開發(fā)者組件化開發(fā)的能力。 那什么是組件化呢?其實組件化并沒有一個明確的定義,不過這里我們可以使用 10 個字來形容什么是組件化,那就是:對內高內聚,對外低耦合。對內各個元素彼此緊密結合、相互依賴,對外和其他組件的聯系最少且接口簡單。 阻礙前端組件化的因素在前端雖然 HTML、CSS 和 JavaScript 是強大的開發(fā)語言,但是在大型項目中維護起來會比較困難,如果在頁面中嵌入第三方內容時,還需要確保第三方的內容樣式不會影響到當前內容,同樣也要確保當前的 DOM 不會影響到第三方的內容。 CSS 的全局屬性會阻礙組件化,DOM 也是阻礙組件化的一個因素,因為在頁面中只有一個 DOM,任何地方都可以直接讀取和修改 DOM。所以使用 JavaScript 來實現組件化是沒有問題的,但是 JavaScript 一旦遇上 CSS 和 DOM,那么就相當難辦了。 WebComponent 組件化開發(fā)現在我們了解了CSS 和 DOM 是阻礙組件化的兩個因素,那要怎么解決呢? WebComponent 給出了解決思路,它提供了對局部視圖封裝能力,可以讓 DOM、CSSOM 和 JavaScript 運行在局部環(huán)境中,這樣就使得局部的 CSS 和 DOM 不會影響到全局。 WebComponent 是一套技術的組合,具體涉及到了Custom elements(自定義元素)、Shadow DOM(影子 DOM)和HTML templates(HTML 模板)。 下面我們就來演示下這 3 個技術是怎么實現數據封裝的,如下面代碼所示: 詳細觀察上面這段代碼,我們可以得出:要使用 WebComponent,通常要實現下面三個步驟。 首先,使用 template 屬性來創(chuàng)建模板。利用 DOM 可以查找到模板的內容,但是模板元素是不會被渲染到頁面上的,也就是說 DOM 樹中的 template 節(jié)點不會出現在布局樹中,所以我們可以使用 template 來自定義一些基礎的元素結構,這些基礎的元素結構是可以被重復使用的。一般模板定義好之后,我們還需要在模板的內部定義樣式信息。 其次,我們需要創(chuàng)建一個 GeekBang 的類。在該類的構造函數中要完成三件事:
上面最難理解的是影子 DOM,其實影子 DOM 的作用是將模板中的內容與全局 DOM 和 CSS 進行隔離,這樣我們就可以實現元素和樣式的私有化了。你可以把影子 DOM 看成是一個作用域,其內部的樣式和元素是不會影響到全局的樣式和元素的,而在全局環(huán)境下,要訪問影子 DOM 內部的樣式或者元素也是需要通過約定好的接口的。 總之,通過影子 DOM,我們就實現了 CSS 和元素的封裝,在創(chuàng)建好封裝影子 DOM 的類之后,我們就可以使用 customElements.define 來自定義元素了(可參考上述代碼定義元素的方式)。 最后,就很簡單了,可以像正常使用 HTML 元素一樣使用該元素,如上述代碼中的 <geek-bang></geek-bang> ,上述代碼最終渲染出來的頁面,如下圖所示: 從圖中我們可以看出,影子 DOM 內部的樣式是不會影響到全局 CSSOM 的。另外,使用 DOM 接口也是無法直接查詢到影子 DOM 內部元素的,比如你可以使用 document.getElementsByTagName('div') 來查找所有 div 元素,這時候你會發(fā)現影子 DOM 內部的元素都是無法查找的,因為要想查找影子 DOM 內部的元素需要專門的接口,所以通過這種方式又將影子內部的 DOM 和外部的 DOM 進行了隔離。 通過影子 DOM 可以隔離 CSS 和 DOM,不過需要注意一點,影子 DOM 的 JavaScript 腳本是不會被隔離的,比如在影子 DOM 定義的 JavaScript 函數依然可以被外部訪問,這是因為 JavaScript 語言本身已經可以很好地實現組件化了。 瀏覽器如何實現影子 DOM關于 WebComponent 的使用方式我們就介紹到這里。WebComponent 整體知識點不多,內容也不復雜,我認為核心就是影子 DOM。上面我們介紹影子 DOM 的作用主要有以下兩點:
那么瀏覽器是如何實現影子 DOM 的呢?下面我們就來分析下,如下圖: 該圖是上面那段示例代碼對應的 DOM 結構圖,從圖中可以看出,我們使用了兩次 geek-bang 屬性,那么就會生成兩個影子 DOM,并且每個影子 DOM 都有一個 shadow root 的根節(jié)點,我們可以將要展示的樣式或者元素添加到影子 DOM 的根節(jié)點上,每個影子 DOM 你都可以看成是一個獨立的 DOM,它有自己的樣式、自己的屬性,內部樣式不會影響到外部樣式,外部樣式也不會影響到內部樣式。 瀏覽器為了實現影子 DOM 的特性,在代碼內部做了大量的條件判斷,比如當通過 DOM 接口去查找元素時,渲染引擎會去判斷 geek-bang 屬性下面的 shadow-root 元素是否是影子 DOM,如果是影子 DOM,那么就直接跳過 shadow-root 元素的查詢操作。所以這樣通過 DOM API 就無法直接查詢到影子 DOM 的內部元素了。 另外,當生成布局樹的時候,渲染引擎也會判斷 geek-bang 屬性下面的 shadow-root 元素是否是影子 DOM,如果是,那么在影子 DOM 內部元素的節(jié)點選擇 CSS 樣式的時候,會直接使用影子 DOM 內部的 CSS 屬性。所以這樣最終渲染出來的效果就是影子 DOM 內部定義的樣式。 全文完。 如果對你有一點點幫助,可以點個關注。
|
|