摘要:若自定義元素標(biāo)簽名稱不可用則摒棄??傊?,自定義元素讓開(kāi)發(fā)者的代碼更易理解和維護(hù),并分割為小型,可復(fù)用及可封裝的模塊。被稱為自定義元素接口,雖然現(xiàn)在仍然可用,但是已經(jīng)被棄用并被認(rèn)為是糟糕的實(shí)現(xiàn)。
原文請(qǐng)查閱這里,略有刪減,本文采用知識(shí)共享署名 4.0 國(guó)際許可協(xié)議共享,BY Troland。
這是 JavaScript 工作原理第十九章。
概述在 前述文章中,我們介紹了 Shadow DOM 接口和一些其它概念,而這些都是網(wǎng)頁(yè)組件的組成部分。網(wǎng)頁(yè)組件背后的思想即通過(guò)創(chuàng)建顆粒化,模塊化和可復(fù)用的元素來(lái)擴(kuò)展 HTML 內(nèi)置功能。這是一個(gè)已經(jīng)被所有主流瀏覽器兼容的相對(duì)嶄新的 W3C 標(biāo)準(zhǔn)且可以被用在生產(chǎn)環(huán)境之中,雖然不兼容的瀏覽器需要使用墊片庫(kù)(將在隨后的章節(jié)中進(jìn)行討論)。
正如開(kāi)發(fā)者所知,瀏覽器為構(gòu)建網(wǎng)站和網(wǎng)頁(yè)程序提供了一些重要的開(kāi)發(fā)工具。我們所說(shuō)的 HTML,CSS 和 JavaScript 即開(kāi)發(fā)者使用 HTML 來(lái)構(gòu)建結(jié)構(gòu),CSS 進(jìn)行樣式化然后使用 JavaScript 來(lái)讓頁(yè)面動(dòng)起來(lái)。然而,在網(wǎng)頁(yè)組件出現(xiàn)之前,把 JavaScript 腳本和 HTML 結(jié)構(gòu)組合起來(lái)并非易事。
本文將闡述網(wǎng)頁(yè)組件的基石-自定義元素??傊?,開(kāi)發(fā)者可以使用自定義元素接口來(lái)創(chuàng)建包含 JavaScript 邏輯和樣式的自定義元素(正如名稱的字面意思)。許多開(kāi)發(fā)者會(huì)把自定義元素和 shadow DOM 混為一談。但是,他們是完全不同的概念且它們互補(bǔ)而不是可以相互替代的。
一些框架(比如 Angular,React) 試圖通過(guò)引進(jìn)其自有概念來(lái)解決同樣的問(wèn)題。開(kāi)發(fā)者可以把自定義元素和 Angular 的指令或者 React 組件進(jìn)行對(duì)比。然而,自定義元素是瀏覽器原生的且只需要原生 JavaScript,HTML 和 CSS。當(dāng)然了,這并不意味著它可以取代一個(gè)典型的 JavaScript 框架。現(xiàn)代框架不僅僅為開(kāi)發(fā)者提供模仿自定義元素行為的能力。因此,可以同時(shí)使用框架和自定義元素。
接口在深入了解之前,讓我們先大概快速瀏覽一下接口的內(nèi)容。全局 customElements 對(duì)象為開(kāi)發(fā)者提供了一些方法:
define(tagName, constructor, options) -創(chuàng)建一個(gè)新的自定義元素。
包含三個(gè)參數(shù):自定義元素的可用標(biāo)簽名稱,自定義元素類(lèi)定義及選項(xiàng)參數(shù)對(duì)象。目前僅支持一個(gè)選項(xiàng)參數(shù):extends 指定想要擴(kuò)展的 HTML 內(nèi)置元素名稱的字符串。用來(lái)創(chuàng)建定制化內(nèi)置元素。
get(tagName) -若元素已經(jīng)定義則返回自定義元素的構(gòu)造函數(shù)否則返回 undefined。只有一個(gè)參數(shù):自定義元素的可用標(biāo)簽名稱。
whenDefined(tagName)-返回一個(gè) promise 對(duì)象,當(dāng)定義自定義元素即解析。若元素已定義則立即進(jìn)行解析。若自定義元素標(biāo)簽名稱不可用則摒棄 promise。只有一個(gè)參數(shù):自定義元素的可用標(biāo)簽名稱。
如何創(chuàng)建自定義元素創(chuàng)建自定義元素實(shí)際上就是小菜一碟。開(kāi)發(fā)者只需要做兩件事:創(chuàng)建擴(kuò)展 HTMLElement 類(lèi)元素的類(lèi)定義,然后以合適的名稱注冊(cè)元素。
class MyCustomElement extends HTMLElement { constructor() { super(); // … } // … } customElements.define("my-custom-element", MyCustomElement);
或者如你所愿,可以使用匿名類(lèi)以防止弄亂當(dāng)前作用域
customElements.define("my-custom-element", class extends HTMLElement { constructor() { super(); // … } // … });
從以上例子可見(jiàn),使用 customElements.define(...) 方法注冊(cè)自定義元素。
自定義元素所解決的問(wèn)題實(shí)際上,問(wèn)題是啥?嵌套 DIV 是問(wèn)題之一。嵌套 Div 是啥?在現(xiàn)代網(wǎng)頁(yè)程序中這是一個(gè)非常常見(jiàn)的現(xiàn)象,開(kāi)發(fā)者會(huì)使用多個(gè)嵌套塊狀元素(div 互相嵌套之類(lèi))。
…
因?yàn)闉g覽器可以在頁(yè)面上正常進(jìn)行渲染,所以使用了這樣的嵌套結(jié)構(gòu)。但是,這會(huì)使得 HTML 不具可讀性且難以維護(hù)。
因此,例如假設(shè)有如下組件:
那么傳統(tǒng) HTML 結(jié)構(gòu)類(lèi)似如下:
但想象下如果可以使用類(lèi)似如下代碼:
要我說(shuō),第二個(gè)示例清爽多了。第二個(gè)示例更具可維護(hù)性,可讀性且對(duì)于瀏覽器和開(kāi)發(fā)者更加合理。更加簡(jiǎn)潔。
另一個(gè)問(wèn)題即可復(fù)用性。作為開(kāi)發(fā)者,不僅僅要書(shū)寫(xiě)可運(yùn)行的代碼還得寫(xiě)出可維護(hù)代碼。書(shū)寫(xiě)可維護(hù)代碼即能夠輕易地復(fù)用代碼片段而不是重復(fù)地復(fù)制粘貼。
我將會(huì)給出一個(gè)簡(jiǎn)單的示例而你就會(huì)明白。假設(shè)有如下元素:
若需要在其它地方使用這段代碼,開(kāi)發(fā)者需要再次書(shū)寫(xiě)相同的 HTML 結(jié)構(gòu)?,F(xiàn)在,想象 一下需要稍微修改一下這些元素。開(kāi)發(fā)者需要找出每個(gè)代碼需要修改的地方,然后一遍遍地做出同樣的修改。太惡心了。。。
若使用如下碼豈不會(huì)更好?
現(xiàn)代網(wǎng)頁(yè)程序不僅僅只有靜態(tài) HTML。開(kāi)發(fā)者需要做交互。這就需要 JavaScript。一般來(lái)說(shuō),開(kāi)發(fā)者需要做的即創(chuàng)建一些元素然后在上面監(jiān)聽(tīng)事件以響應(yīng)用戶輸入。點(diǎn)擊,拖拽或者懸浮事件等等。
var myDiv = document.querySelector(".my-custom-element"); myDiv.addEventListener("click", () => { myDiv.innerHTML = " I have been clicked "; });
I have not been clicked yet.
使用自定義元素接口可以把所有的邏輯封裝進(jìn)元素自身。以下代碼可以實(shí)現(xiàn)和上面代碼一樣的功能:
class MyCustomElement extends HTMLElement { constructor() { super(); var self = this; self.addEventListener("click", () => { self.innerHTML = " I have been clicked "; }); } } customElements.define("my-custom-element", MyCustomElement);
I have not been clicked yet
咋一看上去,自定義元素技術(shù)需要書(shū)寫(xiě)更多的 JavaScript 代碼。但是在實(shí)際程序中,創(chuàng)建不需復(fù)用的單一組件的情況是很少見(jiàn)的。一個(gè)典型的現(xiàn)代網(wǎng)頁(yè)程序的重要特征即大多數(shù)元素都是動(dòng)態(tài)創(chuàng)建的。那么,開(kāi)發(fā)者就需要分別處理使用 JavaScript 動(dòng)態(tài)添加元素或者使用 HTML 結(jié)構(gòu)中預(yù)定義內(nèi)容。那么可以使用自定義元素來(lái)實(shí)現(xiàn)這些功能。
總之,自定義元素讓開(kāi)發(fā)者的代碼更易理解和維護(hù),并分割為小型,可復(fù)用及可封裝的模塊。
要求在創(chuàng)建自定義元素之前,開(kāi)發(fā)者需要遵守如下特殊規(guī)則:
名稱必須包含一個(gè)破折號(hào) - 。這樣 HTML 解析器就可以把自定義元素和內(nèi)置元素區(qū)分開(kāi)來(lái)。這樣可以保證不會(huì)和內(nèi)置元素出現(xiàn)命名沖突的問(wèn)題(不管是現(xiàn)在或者將來(lái)當(dāng)添加其它元素的時(shí)候)。比如,
不允許重復(fù)注冊(cè)標(biāo)簽名稱。重復(fù)注冊(cè)標(biāo)簽名稱會(huì)導(dǎo)致瀏覽器拋出 DOMException 錯(cuò)誤。不可以覆蓋已注冊(cè)自定義元素。
自定義元素不可以自關(guān)閉。HTML 解析器只允許一小撮內(nèi)置元素可以自關(guān)閉(比如
let myCustomElementTemplate = document.querySelector("#my-custom-element-template"); class MyCustomElement extends HTMLElement { // ... constructor() { super(); let shadowRoot = this.attachShadow({mode: "open"}); shadowRoot.appendChild(myCustomElementTemplate.content.cloneNode(true)); } // ... });
那么現(xiàn)在,我們?cè)谧远x元素里面使用了 shadow DOM 和 模板,創(chuàng)建了一個(gè)元素,該元素作用域和其它元素隔絕且把 HTML 結(jié)構(gòu)和 JavaScript 邏輯完美地隔離開(kāi)來(lái)。
樣式化那么,我們講解了 HTML 和 JavaScript,現(xiàn)在還剩下 CSS。顯然,需要樣式化元素。開(kāi)發(fā)者可以在 shadow DOM 中添加樣式但是用戶如何從外部樣式化元素呢?答案很簡(jiǎn)單-只需要和一般的內(nèi)置元素一樣寫(xiě)樣式即可。
my-custom-element { border-radius: 5px; width: 30%; height: 50%; // ... }
請(qǐng)注意外部定義的樣式比元素內(nèi)部定義的樣式優(yōu)先級(jí)高,外部樣式會(huì)覆蓋掉元素內(nèi)定義的樣式。
開(kāi)發(fā)者需要明白有時(shí)候頁(yè)面渲染,然后會(huì)在某些時(shí)刻會(huì)發(fā)現(xiàn)無(wú)樣式內(nèi)容閃爍(FOUC)。開(kāi)發(fā)者可以通過(guò)為未定義組件定義樣式及當(dāng)元素已定義的時(shí)候使用一些動(dòng)畫(huà)過(guò)渡效果。使用 :defined 選擇器來(lái)達(dá)成這一效果。
my-button:not(:defined) { height: 20px; width: 50px; opacity: 0; }未知元素對(duì)比未定義自定義元素
HTML 規(guī)范非常靈活且允許開(kāi)發(fā)者任意聲明標(biāo)簽。若不被瀏覽器解析則會(huì)解析為 HTMLUnknownElement。
var element = document.createElement("thisElementIsUnknown"); if (element instanceof HTMLUnknownElement) { console.log("The selected element is unknown"); }
但是這并不適用于自定義元素。還記得討論定義自定義元素時(shí)候的特殊命名規(guī)則嗎?原因是因?yàn)楫?dāng)瀏覽器發(fā)現(xiàn)一個(gè)自定義元素的名稱有效的時(shí)候,瀏覽器會(huì)把它解析為 HTMLElement ,然后瀏覽器會(huì)把它看作一個(gè)未定義的自定義元素。
var element = document.createElement("this-element-is-undefined"); if (element instanceof HTMLElement) { console.log("The selected element is undefined but not unknown"); }
在視覺(jué)上, HTMLElement 和 HTMLUnknownElement 可能沒(méi)啥不同,但是需要注意其它地方。解析器會(huì)區(qū)別對(duì)待這兩種元素。具有有效自定義名稱的元素會(huì)被看作擁有自定義元素實(shí)現(xiàn)。在定義實(shí)現(xiàn)細(xì)節(jié)之前該自定義元素會(huì)被看成一個(gè)空 div 元素。而一個(gè)未定義元素沒(méi)有實(shí)現(xiàn)任何內(nèi)置元素的任何方法或?qū)傩浴?/p> 瀏覽器兼容
custom elements 第一版是在 Chrome 36+ 中引入的。被稱為自定義元素接口 v0,雖然現(xiàn)在仍然可用,但是已經(jīng)被棄用并被認(rèn)為是糟糕的實(shí)現(xiàn)。若想要學(xué)習(xí) v0 版,可以閱讀這篇文章。從 Chrome 54 和 Safari 10.1(雖然只有部分支持) 開(kāi)始支持自定義元素接口 v1,微軟 Edge 還處于其原型設(shè)計(jì)階段而 Mozilla 從 v50 開(kāi)始支持,但默認(rèn)不支持需要顯式啟用。目前只有 webkit 瀏覽器完全支持。然而,如上所述,可以使用墊片庫(kù)兼容到包括 IE11 在內(nèi)的所有瀏覽器。
檢測(cè)可用性通過(guò)檢查 window 對(duì)象中的 customElements 屬性是否可用來(lái)檢查瀏覽器是否支持自定義元素。
const supportsCustomElements = "customElements" in window; if (supportsCustomElements) { // 可以使用自定義元素接口 }
否則需要使用墊片庫(kù):
function loadScript(src) { return new Promise(function(resolve, reject) { const script = document.createElement("script"); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } // Lazy load the polyfill if necessary. if (supportsCustomElements) { // 瀏覽器原生支持自定義元素 } else { loadScript("path/to/custom-elements.min.js").then(_ => { // 加載自定義元素墊片 }); }
總之,網(wǎng)頁(yè)組件標(biāo)準(zhǔn)中的自定義元素為開(kāi)發(fā)者提供了如下功能:
把 JavaScript 和 CSS 樣式整合入 HTML 元素
允許開(kāi)發(fā)者擴(kuò)展已有的 HTML 元素(內(nèi)置和其它自定義元素)
不需要其它庫(kù)或者框架的支持。只需要原生 JavaScript,HTML 和 CSS 還有可選的墊片庫(kù)來(lái)支持舊瀏覽器。
可以和其它網(wǎng)頁(yè)組件功能無(wú)縫銜接(shadow DOM,模板,插槽等)。
和瀏覽器開(kāi)發(fā)者工具緊密集成在一起。
使用已知的可訪問(wèn)功能
總之,自定義元素和開(kāi)發(fā)者已經(jīng)使用過(guò)的組件技術(shù)并沒(méi)有什么大的不同。它只讓開(kāi)發(fā)網(wǎng)頁(yè)程序過(guò)程更加便攜的另一種方式。那么,它讓更快地構(gòu)建非常復(fù)雜的程序成為可能。
參考資料:
https://developers.google.com...
https://www.html5rocks.com/en...
https://github.com/w3c/webcom...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/102256.html
摘要:前端每周清單半年盤(pán)點(diǎn)之與篇前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開(kāi)發(fā)教程工程實(shí)踐深度閱讀開(kāi)源項(xiàng)目巔峰人生等欄目。與求同存異近日,宣布將的構(gòu)建工具由遷移到,引發(fā)了很多開(kāi)發(fā)者的討論。 前端每周清單半年盤(pán)點(diǎn)之 React 與 ReactNative 篇 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn);分為...
摘要:本文轉(zhuǎn)載自眾成翻譯譯者文藺鏈接原文今年的頂級(jí)舉措之一是為我們的用戶提供一個(gè)更好的瀏覽體驗(yàn)。這意味著保持最少的。這些組件有全局的,網(wǎng)站速度信標(biāo)現(xiàn)場(chǎng)速度信標(biāo)套件,試驗(yàn)的庫(kù)文件,以及統(tǒng)計(jì)模塊等。它們?cè)诎l(fā)布前要經(jīng)歷嚴(yán)格的回歸測(cè)試,這就會(huì)增加延時(shí)。 本文轉(zhuǎn)載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/912原文:http://www.ebaytechblog...
摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥(niǎo)雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門(mén),久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...
閱讀 3556·2021-11-23 10:09
閱讀 2120·2021-10-26 09:51
閱讀 1031·2021-10-09 09:44
閱讀 3962·2021-10-08 10:04
閱讀 2806·2021-09-22 15:14
閱讀 3722·2021-09-22 15:02
閱讀 1149·2021-08-24 10:03
閱讀 1799·2019-12-27 12:14