当前位置: 首页 > news >正文

一种延迟加载自定义元素的方法

您可能实际上并不需要所有这些;通常有一个更简单的方法。如果有意使用,此处显示的技术可能仍然对您的工具集有用。

为了保持一致性,我们希望我们的自动加载器也成为一个自定义元素——这也意味着我们可以通过 HTML 轻松配置它。但首先,让我们逐步确定那些未解决的自定义元素:

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript"><span style="color:#83ba52">class</span> AutoLoader <span style="color:#83ba52">extends</span> HTMLElement <span style="color:#72e0d1">{</span>connectedCallback<span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> scope <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>parentNode<span style="color:#72e0d1">;</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>discover<span style="color:#72e0d1">(</span>scope<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span>
<span style="color:#72e0d1">}</span>
customElements<span style="color:#72e0d1">.</span>define<span style="color:#72e0d1">(</span><span style="color:#83ba52">"ce-autoloader"</span><span style="color:#72e0d1">,</span> AutoLoader<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span></code></span></span>

假设我们已经预先加载了这个模块(使用async是理想的),我们可以将一个<ce-autoloader>元素放入<body>我们的文档中。这将立即启动 的所有子元素的发现过程<body>,这些子元素现在构成了我们的根元素。<ce-autoloader>我们可以通过添加到相应的容器元素来将发现限制在文档的子树中——实际上,我们甚至可以为不同的子树设置多个实例。

当然,我们仍然必须实现该discover方法(作为上面类的一部分AutoLoader):

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript">discover<span style="color:#72e0d1">(</span>scope<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> candidates <span style="color:#aa7ee1">=</span> <span style="color:#72e0d1">[</span>scope<span style="color:#72e0d1">,</span> <span style="color:#aa7ee1">...</span>scope<span style="color:#72e0d1">.</span>querySelectorAll<span style="color:#72e0d1">(</span><span style="color:#83ba52">"*"</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">]</span><span style="color:#72e0d1">;</span><span style="color:#83ba52">for</span><span style="color:#72e0d1">(</span><span style="color:#83ba52">let</span> el <span style="color:#83ba52">of</span> candidates<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> tag <span style="color:#aa7ee1">=</span> el<span style="color:#72e0d1">.</span>localName<span style="color:#72e0d1">;</span><span style="color:#83ba52">if</span><span style="color:#72e0d1">(</span>tag<span style="color:#72e0d1">.</span>includes<span style="color:#72e0d1">(</span><span style="color:#83ba52">"-"</span><span style="color:#72e0d1">)</span> <span style="color:#aa7ee1">&&</span> <span style="color:#aa7ee1">!</span>customElements<span style="color:#72e0d1">.</span><span style="color:#83ba52">get</span><span style="color:#72e0d1">(</span>tag<span style="color:#72e0d1">)</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>load<span style="color:#72e0d1">(</span>tag<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">}</span>
<span style="color:#72e0d1">}</span></code></span></span>

在这里,我们检查我们的根元素以及每个后代 ( *)。如果它是一个自定义元素——如带连字符的标签所示——但尚未升级,我们将尝试加载相应的定义。以这种方式查询 DOM 可能代价高昂,所以我们应该小心一点。我们可以通过推迟这项工作来减轻主线程上的负载:

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript">connectedCallback<span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> scope <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>parentNode<span style="color:#72e0d1">;</span>requestIdleCallback<span style="color:#72e0d1">(</span><span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>discover<span style="color:#72e0d1">(</span>scope<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span>
<span style="color:#72e0d1">}</span></code></span></span>

requestIdleCallback还没有得到普遍支持,但我们可以使用requestAnimationFrame作为后备:

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript"><span style="color:#83ba52">let</span> defer <span style="color:#aa7ee1">=</span> window<span style="color:#72e0d1">.</span>requestIdleCallback <span style="color:#aa7ee1">||</span> requestAnimationFrame<span style="color:#72e0d1">;</span><span style="color:#83ba52">class</span> AutoLoader <span style="color:#83ba52">extends</span> HTMLElement <span style="color:#72e0d1">{</span>connectedCallback<span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> scope <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>parentNode<span style="color:#72e0d1">;</span>defer<span style="color:#72e0d1">(</span><span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>discover<span style="color:#72e0d1">(</span>scope<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#5e7671"><em>// ...</em></span>
<span style="color:#72e0d1">}</span></code></span></span>

现在我们可以继续实现缺少的load方法来动态注入<script>元素:

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript">load<span style="color:#72e0d1">(</span>tag<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> el <span style="color:#aa7ee1">=</span> document<span style="color:#72e0d1">.</span>createElement<span style="color:#72e0d1">(</span><span style="color:#83ba52">"script"</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#83ba52">let</span> res <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">new</span> Promise<span style="color:#72e0d1">(</span><span style="color:#72e0d1">(</span>resolve<span style="color:#72e0d1">,</span> reject<span style="color:#72e0d1">)</span> <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span>el<span style="color:#72e0d1">.</span>addEventListener<span style="color:#72e0d1">(</span><span style="color:#83ba52">"load"</span><span style="color:#72e0d1">,</span> ev <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span>resolve<span style="color:#72e0d1">(</span><span style="color:#83ba52">null</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span>el<span style="color:#72e0d1">.</span>addEventListener<span style="color:#72e0d1">(</span><span style="color:#83ba52">"error"</span><span style="color:#72e0d1">,</span> ev <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span>reject<span style="color:#72e0d1">(</span><span style="color:#83ba52">new</span> Error<span style="color:#72e0d1">(</span><span style="color:#83ba52">"failed to locate custom-element definition"</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span>el<span style="color:#72e0d1">.</span>src <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>elementURL<span style="color:#72e0d1">(</span>tag<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span>document<span style="color:#72e0d1">.</span>head<span style="color:#72e0d1">.</span>appendChild<span style="color:#72e0d1">(</span>el<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#83ba52">return</span> res<span style="color:#72e0d1">;</span>
<span style="color:#72e0d1">}</span>elementURL<span style="color:#72e0d1">(</span>tag<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">return</span> <span style="color:#83ba52">`</span><span style="color:#72e0d1">${</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>rootDir<span style="color:#72e0d1">}</span><span style="color:#83ba52">/</span><span style="color:#72e0d1">${</span>tag<span style="color:#72e0d1">}</span><span style="color:#83ba52">.js`</span><span style="color:#72e0d1">;</span>
<span style="color:#72e0d1">}</span></code></span></span>

请注意elementURL. 该src属性的 URL 假设有一个目录,所有自定义元素定义都位于该目录中(例如<my-widget>→ /components/my-widget.js)。我们可以提出更详尽的策略,但这足以满足我们的目的。将此 URL 委托给一个单独的方法允许在需要时进行特定于项目的子类化:

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript"><span style="color:#83ba52">class</span> FancyLoader <span style="color:#83ba52">extends</span> AutoLoader <span style="color:#72e0d1">{</span>elementURL<span style="color:#72e0d1">(</span>tag<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#5e7671"><em>// fancy logic</em></span><span style="color:#72e0d1">}</span>
<span style="color:#72e0d1">}</span></code></span></span>

无论哪种方式,请注意我们依赖于this.rootDir. 这就是前面提到的可配置性的用武之地。让我们添加一个相应的 getter:

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript"><span style="color:#83ba52">get</span> rootDir<span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> uri <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>getAttribute<span style="color:#72e0d1">(</span><span style="color:#83ba52">"root-dir"</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#83ba52">if</span><span style="color:#72e0d1">(</span><span style="color:#aa7ee1">!</span>uri<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">throw</span> <span style="color:#83ba52">new</span> Error<span style="color:#72e0d1">(</span><span style="color:#83ba52">"cannot auto-load custom elements: missing `root-dir`"</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#83ba52">if</span><span style="color:#72e0d1">(</span>uri<span style="color:#72e0d1">.</span>endsWith<span style="color:#72e0d1">(</span><span style="color:#83ba52">"/"</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span> <span style="color:#5e7671"><em>// remove trailing slash</em></span><span style="color:#83ba52">return</span> uri<span style="color:#72e0d1">.</span>substring<span style="color:#72e0d1">(</span><span style="color:#fc9463">0</span><span style="color:#72e0d1">,</span> uri<span style="color:#72e0d1">.</span>length <span style="color:#aa7ee1">-</span> <span style="color:#fc9463">1</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#83ba52">return</span> uri<span style="color:#72e0d1">;</span>
<span style="color:#72e0d1">}</span></code></span></span>

您现在可能正在考虑observedAttributes,但这并没有真正让事情变得更容易。另外,root-dir在运行时进行更新似乎是我们永远不需要的。

现在我们可以——而且必须——配置我们的元素目录:<ce-autoloader root-dir="/components">.

有了这个,我们的自动加载器就可以完成它的工作了。除了它只工作一次,对于初始化自动加载器时已经存在的元素。我们可能还想考虑动态添加的元素。这就是MutationObserver发挥作用的地方:

<span style="background-color:#1b1b1b"><span style="color:#f5d67b"><code class="language-javascript">connectedCallback<span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">let</span> scope <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>parentNode<span style="color:#72e0d1">;</span>defer<span style="color:#72e0d1">(</span><span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>discover<span style="color:#72e0d1">(</span>scope<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#83ba52">let</span> observer <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>_observer <span style="color:#aa7ee1">=</span> <span style="color:#83ba52">new</span> MutationObserver<span style="color:#72e0d1">(</span>mutations <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">for</span><span style="color:#72e0d1">(</span><span style="color:#83ba52">let</span> <span style="color:#72e0d1">{</span> addedNodes <span style="color:#72e0d1">}</span> <span style="color:#83ba52">of</span> mutations<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">for</span><span style="color:#72e0d1">(</span><span style="color:#83ba52">let</span> node <span style="color:#83ba52">of</span> addedNodes<span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span>defer<span style="color:#72e0d1">(</span><span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#aa7ee1">=></span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>discover<span style="color:#72e0d1">(</span>node<span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span>observer<span style="color:#72e0d1">.</span>observe<span style="color:#72e0d1">(</span>scope<span style="color:#72e0d1">,</span> <span style="color:#72e0d1">{</span> subtree<span style="color:#72e0d1">:</span> <span style="color:#fc9463">true</span><span style="color:#72e0d1">,</span> childList<span style="color:#72e0d1">:</span> <span style="color:#fc9463">true</span> <span style="color:#72e0d1">}</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span>
<span style="color:#72e0d1">}</span>disconnectedCallback<span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span> <span style="color:#72e0d1">{</span><span style="color:#83ba52">this</span><span style="color:#72e0d1">.</span>_observer<span style="color:#72e0d1">.</span>disconnect<span style="color:#72e0d1">(</span><span style="color:#72e0d1">)</span><span style="color:#72e0d1">;</span>
<span style="color:#72e0d1">}</span></code></span></span>

这样,每当 DOM 中出现新元素时,浏览器就会通知我们——或者更确切地说,我们各自的子树——然后我们使用它来重新启动发现过程。(你可能会争辩说我们在这里重新发明了自定义元素,你是对的。)

我们的自动装载机现在功能齐全。未来的增强可能会研究潜在的竞争条件并研究优化。但对于大多数情况来说,这可能已经足够好了。

http://www.lryc.cn/news/38621.html

相关文章:

  • Pytho经典面试题荟萃:第一期
  • 01背包问题(大彻大悟版)
  • 【麒麟服务器操作系统忘记开机密码怎么办?---银河麒麟服务器操作系统更改用户密码】
  • 华为OD机试(20222023)考点分类
  • 初级篇 3 - HTML 或 CSS 文件中不懂的标签属性详解
  • 【C语言】每日刷题 —— 牛客语法篇(4)
  • HashMap ConcurrentHashMap介绍
  • C++语法规则3(C++面向对象)
  • Python tkinter 如何实现网站下载工具?将所有数据一键获取
  • 第六章:C语言数据结构与算法初阶之栈
  • Android学习之WebView
  • 3/11 考试总结
  • Leetcode 141.环形链表 142环形链表II
  • hibernate学习(五)
  • STM32CubeIDE 快速开发入门指南
  • 华为OD机试 - 火星文计算(C 语言解题)【独家】
  • 超超超超保姆式详解——字符函数和字符串函数(学不会打我)上
  • Data mesh 笔记
  • (八十三)大白话透彻研究通过explain命令得到的SQL执行计划(2)
  • 案例18-面向对象之开门小例子
  • 【碎片化知识总结】三月第一周
  • 从零开始的JSON库(1):启程
  • 【Java】数组
  • 【C++】非类型的模板参数,特化
  • 核方法(kernel Method)
  • 消息队列MQ用来做什么的,市场上主流的四大MQ如何选择?RabbitMQ带你HelloWorld!
  • 2023年中国高校计算机大赛-团队程序设计天梯赛(GPLT)上海理工大学校内选拔赛(同步赛) A — E
  • 一文分析Linux v4l2框架
  • MFC常用控件使用(文本框、编辑框、下拉框、列表控件、树控件)
  • 13 node 程序后台执行加上 tail 命令, 中断 tail 命令, 同时也中断了 node 程序