首頁 >web前端 >css教學 >懶惰加載自定義元素的方法

懶惰加載自定義元素的方法

Joseph Gordon-Levitt
Joseph Gordon-Levitt原創
2025-03-09 11:39:10208瀏覽

An Approach to Lazy Loading Custom Elements

本文探討了一種自定義元素的懶加載方法,以提升網頁性能。這種方法的靈感源於同事的實驗,其核心思想是在自定義元素添加到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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn