首页 >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