本文探討了一種自定義元素的懶加載方法,以提升網頁性能。這種方法的靈感源於同事的實驗,其核心思想是在自定義元素添加到DOM後,自動加載相應的實現代碼。
通常情況下,無需如此復雜的懶加載機制,但本文介紹的技術對於特定場景仍然很有價值。
為了保持一致性,懶加載器本身也設計為一個自定義元素,方便通過HTML進行配置。首先,我們需要逐步識別未解析的自定義元素:
class AutoLoader extends HTMLElement { connectedCallback() { const scope = this.parentNode; this.discover(scope); } } customElements.define("ce-autoloader", AutoLoader);
假設我們已預先加載了這個模塊(使用async方式理想),我們可以在文檔中添加<ce-autoloader></ce-autoloader>
元素。這將立即啟動對所有子元素的查找過程,這些子元素構成了根元素。通過將<ce-autoloader></ce-autoloader>
添加到相應的容器元素,可以將查找範圍限製到文檔的子樹,甚至可以在不同的子樹中使用多個實例。
接下來,我們需要實現discover
方法(作為上面AutoLoader
類的一部分):
discover(scope) { const candidates = [scope, ...scope.querySelectorAll("*")]; for (const el of candidates) { const tag = el.localName; if (tag.includes("-") && !customElements.get(tag)) { this.load(tag); } } }
這段代碼檢查根元素及其所有後代元素(*)。如果元素是自定義元素(帶連字符的標籤),但尚未升級,則嘗試加載相應的定義。這種方式可能會佔用大量DOM查詢資源,因此需要謹慎處理。我們可以通過延遲執行來減輕主線程的負載:
connectedCallback() { const scope = this.parentNode; requestIdleCallback(() => { this.discover(scope); }); }
requestIdleCallback
並非所有瀏覽器都支持,可以使用requestAnimationFrame
作為後備方案:
const defer = window.requestIdleCallback || requestAnimationFrame; class AutoLoader extends HTMLElement { connectedCallback() { const scope = this.parentNode; defer(() => { this.discover(scope); }); } // ... }
現在,我們可以實現load
方法,動態注入<script></script>
元素:
load(tag) { const el = document.createElement("script"); const res = new Promise((resolve, reject) => { el.addEventListener("load", () => resolve(null)); el.addEventListener("error", () => reject(new Error("未能找到自定义元素定义"))); }); el.src = this.elementURL(tag); document.head.appendChild(el); return res; } elementURL(tag) { return `${this.rootDir}/${tag}.js`; }
請注意elementURL
中硬編碼的約定。 src
屬性的URL假設存在一個目錄包含所有自定義元素定義(例如,<my-widget></my-widget>
→ /components/my-widget.js
)。我們可以採用更複雜的策略,但這對於我們的目的已經足夠了。將此URL委託給單獨的方法允許在需要時進行項目特定的子類化:
class FancyLoader extends AutoLoader { elementURL(tag) { // 自定义逻辑 } }
無論哪種方式,我們都依賴於this.rootDir
。這就是前面提到的可配置性。讓我們添加一個對應的getter:
get rootDir() { const uri = this.getAttribute("root-dir"); if (!uri) { throw new Error("无法自动加载自定义元素:缺少`root-dir`属性"); } return uri.endsWith("/") ? uri.substring(0, uri.length - 1) : uri; }
我們不需要使用observedAttributes
,因為更新root-dir
屬性在運行時似乎沒有必要。
現在,我們可以(也必須)配置元素目錄:<ce-autoloader root-dir="/components"></ce-autoloader>
。
有了這個,我們的自動加載器就可以工作了。但它只對自動加載器初始化時已存在的元素有效。我們可能還需要考慮動態添加的元素。這就是MutationObserver
發揮作用的地方:
class AutoLoader extends HTMLElement { connectedCallback() { const scope = this.parentNode; this.discover(scope); } } customElements.define("ce-autoloader", AutoLoader);
通過這種方式,瀏覽器會在DOM中出現新元素時通知我們(更確切地說,是我們相應的子樹),然後我們使用它重新啟動查找過程。
我們的自動加載器現在完全可以使用了。未來的增強可能包括研究潛在的競爭條件和優化。但對於大多數場景來說,這已經足夠了。如果您有不同的方法,請在評論中告訴我,我們可以互相交流!
以上是懶惰加載自定義元素的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!