在開啓本篇的閱讀以前,先問一個問題,組件是什麼?html
組件,是數據和方法的一個封裝,其定義了一個可重用的軟件元素的功能,展現和使用,一般表現爲一個或一組可重用的元素。前端
組件的特性是什麼?一般能夠總結爲如下幾點:git
組件化,給前端開發帶來了極大的效率提高,是近幾年以來web開發發展的趨勢,各類組件化的用戶界面庫,框架也層出不窮,如,React,Vue,Ionic等,這些框架關於組件化都有各自的實現,推崇理念,與編程規範,各大框架的支持者之間的爭論也是向來不斷,而若想在不一樣框架間切換,成本仍是挺高的,由於畢竟誰都但願本身能佔主流,佔據絕對優點地位,就像當前IE與網景瀏覽器之爭,延續到如今,各種瀏覽器標準兼容差別萬千,近年來w3c不斷在爲web標準規範作努力,Web Components就是推出的關於組件化的一個標準,但願它能將組件化更好的帶進web開發,同時儘可能保證標準規範,開發者能夠更好的關注於開發,而不是框架選擇與爭論之上。github
Web Components將一系列特性加入HTML和DOM規範,使得開發者能夠自由建立在web應用或文檔可重用的元素或部件,其由四部分組成:web
<template>
元素內聲明。自定義元素支持開發者定義一類新HTML元素,聲明其行爲和樣式,自定義元素分兩類:ajax
支持建立自定義元素,Web Components比較好的實現了組件開發的可拓展性。編程
爲了建立一個自定義標籤元素,咱們須要繼承HTMLELement類, 如在不少頁面咱們常常會有一鍵回到頁面頂部功能,咱們建立一個返回頂部的組件:瀏覽器
class GoTop extends HTMLElement { constructor() { super(); } } customElements.define('go-top', GoTop);
在須要使用該組件的頁面只需像使用正常HTML元素同樣:app
<go-top>Top</go-top>
固然,該元素的一切樣式,行爲,事件監聽,默認行爲均須要開發者自行定義,沒法期待它有像<button>
同樣的默認行爲詳細參考建立自定義標籤元素。框架
不少時候咱們並不須要徹底建立一個新元素,而只是須要在某些內置元素基礎上進行拓展,建立自定義內置元素,須要繼承該類元素類,如HTMLButtonElement
或HTMLDivElement
:
class MenuButton extends HTMLButtonElement { ... } customElements.define('menu-button', MenuButton);
使用也很簡單,和內置元素同樣的語法;不一樣的是,在須要使用自定義內置元素時,爲內置元素添加is
特性,該特性值對應建立的自定義內置元素名稱:
<button is="menu-button">menu</button>
該元素默認行爲繼承自<button>
元素,可是咱們能夠爲其設置拓展功能或性質。
經過上面實例可知,自定義標籤元素與內置元素主要表如今兩點不一樣:
DOM,即文檔對象模型,是HTML文檔的一個結構表示,以樹形結構表示一個文檔,文檔中元素間關係按照父子,兄弟關係排列;DOM規範提供一系列API支持咱們操做文檔節點,即一般所說的DOM API。
前面提到Web Components指封裝DOM和樣式,以組件的形式在文檔中使用,而不一樣於JavaScript中函數會造成一個單獨做用域,文檔DOM樹的層次結構中是不存在局部做用域概念的,也就是說文檔內全部定義的樣式都對整個文檔產生影響,文檔中的樣式也會影響組件內的聲明樣式,而不限定於元素所處位置,這樣顯然極大阻礙了組件的獨立性和可重用性,是必需要解決的問題,不過不用擔憂,這都已經解決了,解決方案就是下文介紹的attachShadow()
方法。
影子DOM API提供了attachShadow()
方法,建立一個影子DOM,支持將封裝的內容或組件做爲一個獨立DOM子樹附加進一個HTML文檔,組件內與外部隔離,樣式互不影響,這也印證了組件開發的封裝性需求。
要建立一個影子DOM,很簡單,使用attachShadow()
方法便可,而須要注意的是全部影子DOM必須和一個文檔中存在的元素(HTML內置元素或自定義元素)綁定,才能使用:
var frag = document.createElement('div'); var shadowRoot = frag.attachShadow({mode: 'open'}); shadowRoot.innerHTML = '<p>Shadow DOM Content</p>';
上文使用attachShadow()
方法建立的元素就是一個影子DOM,而其子內容就構成一棵影子樹(shadow tree),而和影子DOM綁定,也就是包含該樹的文檔內元素一般稱爲影子主體(shadow host)。
如上,當一個元素(即影子主體)內存在影子DOM,瀏覽器默認只會渲染該影子DOM的影子樹,而不渲染影子主體的其餘子內容,如,現有某元素<div class="menus">
,在文檔中使用以下:
<div class="menus"> <h2>Menus</h2> </div>
給該元素綁定影子DOM:
var menus = document.querySelector('.menus'); var shadowRoot = menus.attachShadow({mode: 'open'}); shadowRoot.innerHTML = '<ul>\ <li>Home</li>\ <li>About</li>\ </ul>';
其影子樹內容爲:
<ul> <li>Home</li> <li>About</li> </ul>
最後渲染結果以下:
<div class="menus"> <ul> <li>Home</li> <li>About</li> </ul> </div>
你好發現影子主體本來的子元素內容沒有被渲染,那麼是否是沒辦法了?固然不是,若是要保存子內容,須要使用<slot>
槽位元素,至關於作一個佔位符,只須要把前文影子主體內容修改成以下:
<div class="menus"> <h2>Menus</h2> <slot></slot> </div>
渲染結果以下, 一切符合需求:
<div class="menus"> <h2>Menus</h2> <ul> <li>Home</li> <li>About</li> </ul> </div>
上文顯示的是隻有一個槽位的實例,假如須要有多個分組怎麼辦呢?Web Components也有解決方案,那就是使用命名槽,即給槽位添加name
屬性,依然使用如上實例,修改影子主體內容:
<div class="menus"> <slot></slot> <slot name="top"></slot> <slot name="right"></slot> </div>
假如影子樹內容以下:
<h2>Menus</h2> <ul slot="top"> <li>Home</li> <li>About</li> </ul> <ul slot="right"> <li>Home</li> <li>Top</li> </ul>
渲染結果以下:
<div class="menus"> <h2>Menus</h2> <ul> <li>Home</li> <li>About</li> </ul> <ul> <li>Home</li> <li>Top</li> </ul> </div>
如上,能夠發現擁有name
屬性的槽位由對應slot
屬性值相同的影子子樹替換,而剩下的內容默認替換空名槽位,若不存在空名槽位,則剩餘內容將被拋棄。
前文已經提到,Web Components定義的組件內的樣式與外部環境的樣式是互不影響的,那麼如何爲組件設置樣式呢,依然使用<style>
標籤:
<head> <style> .top {margin-top: 30px;} </style> </head> ... <div class="top"> ... </div> ... <div class="menus"> #shadow-root <style> .top {margin-top: 10px;} </style> <div class="top"> ... </div> </div>
如上實例,在組件內部top
類元素margin-top
值爲10px,而外部top
類元素margin-top
值爲30px,二者是獨立的。
關於影子DOM樹的渲染,其方式與web文檔DOM樹的渲染方式並沒有區別,均由瀏覽器渲染引擎進行渲染,須要注意的是,影子樹的DOM渲染過程和文檔DOM樹的渲染是獨立分別進行的。
如何在HTML文檔中引入另外一個web文檔或web組件呢?像JSP或PHP語言都對HTML語法進行了拓展,咱們可使用諸如<include>
標籤直接引入另外一個文檔,然而在這以前,原生HTML規範並不支持直接引入另外一文檔,一般都得經過ajax請求另外一文檔內容,而後經過JavaScript使用DOM API將內容插入,對於組件化開發和使用,這樣顯然不是咱們指望的結果,這與組件的易用性是背離的,因此,HTML imports定義瞭如何在文檔內引入和重用另外一文檔。
在文檔內直接引入外鏈資源的文檔或web組件,語法以下,使用<link>
標籤:
<link rel="import" href="components.html">
假如在components.html中定義了got-top
自定義元素,則在本文檔內能夠直接使用:
<go-top>GoTop</go-top>
如上,僅僅將<link>
標籤的rel
屬性設置成import便可,另外值得注意的是:爲了不重複執行引入文檔內的腳本,對於已加載文檔,import方式將跳過其加載和執行過程。
爲了更友好的處理組件模板,Web Components規範,支持<template>
模板標籤,HTML模板定義了使用<template>
標籤聲明能夠經過腳本操做插入文檔的HTML模板片斷:
<template id="menusTemplate"> <ul> <li>Home</li> <li>About</li> </ul> </template>
使用腳本操做,該元素content
屬性可訪問模板內容:
var menusTemplate = document.querySelector('#menusTemplate'); var frag = document.importNode(menusTemplate.content, true); document.querySelector('.menus').appendChild(frag);
<template>
標籤本質上與其餘HTML內置標籤同樣,可使用DOM API進行操做,可是須要明白,在將模板激活(生成DOM或插入文檔)前:
<template>
標籤內的內容不會被渲染;對於Web Components規範的兼容性,目前仍是須要使用webcomponentsjs polyfills的方式支持開發,總的來講,目前Safari 10, Google Chrome (53)兼容的更好;雖然兼容性並很差,還在推動過程當中,可是對其進行學習仍是頗有必要的。
推薦幾個常見的Web組件類庫: