本文探讨了一种自定义元素的懒加载方法,以提升网页性能。这种方法的灵感源于同事的实验,其核心思想是在自定义元素添加到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中文网其他相关文章!