如何更好的处理还未创立的 DOM 节点?

首页>>网站学院>>前端知识

999+

在平常的研制需求中,咱们可能会遇到这样的场景:某些 dom 节点还没创立出来,咱们也不知道它何时创立出来。可是咱们期望在这个节点创立出来的时分做点啥事情,比如创立一个事情监听器。

 image.png

这种需求在处理某些三方的 JavaScript 脚本的时分特别常见,因为这些元素的创立是咱们不可控的,下面举一个实际的比如:

 

咱们想调用 Google 提供的 reCAPTCHA 才能来验证用户是否为机器人,然后咱们想在用户点击了 reCAPTCHA 之后来更新 UI,可是 reCAPTCHA 本身也没有提供像 blur 这样的事情,咱们可能需求自己来为它添加一个事情监听器,但咱们又不知道这个元素啥时分会被创立出来。

 

下面咱们来模拟一个简单的比如,咱们用一个定时器来模拟一个 DOM 元素的随机创立,设定一个 0-17 秒的规模。

setTimeout(() => {

 const input = document.createElement('input');
  input.id = 'robot-check';
  input.placeholder = '验证你不是个机器人!';  document.body.append(input);
  
}, Math.random() * 17 * 1000);

如果我们一开始就直接为这个元素创建一个事件监听器,那肯定会报错:

document.querySelector('#robot-check').addEventListener('blur', () => {
 alert('有好事即将发生!')
});

为了解决这个问题,我们通常会使用轮询的方式:通过 setTimeout 或者 setInterval 每隔一段时间查询一下 DOM 节点有没有被创建出来:

function attachListener() {  let input = document.getElementById('robot-check');  if (input) {
    input.addEventListener('blur', () => alert('有好事即将发生!'));    return;
  } // 递归调用实现轮询  setTimeout(attachListener, 100);}attachListener();

或者基于 Promise,你可以这样优化一下你的代码

async function attachListener() {  let input = document.getElementById('robot-check');  while (!input) {
    input = document.getElementById('robot-check');    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  input.addEventListener('blur', () => alert('有好事即将发生!'));
}

attachListener();

不论以上哪种实现方式,都会带来不小的性能成本,比如如果我们删除了 setTimeout ,会直接导致脚本完全同步运行,从而阻塞主线程以及需要在主线程上执行的任何其他任务。setTimeout 会不断把事件追加到下一次事件循环中,这会让我们的调用堆栈不断膨胀。所以我们可能会调大轮询的时间间隔,但是这又会损失一部分用户体验,也不是一个很好的选择。

下面就有请我们的 MutationObserver api 闪亮登场了。

MutationObserver API 就是为观测 DOM 而生,它的目的非常明确:当 DOM 树发生变化的时候(包括插入、删除、更新节点)做一些事情。比如下面的代码可以让你观测到 Body 内部的任何变化(当 DOM 发生变化时自动执行回调函数):

const domObserver = new MutationObserver((mutationList) => { // body 结构发生了变化,可以做点事情~
});

domObserver.observe(document.body, { childList: true, subtree: true });

作为原生浏览器 API,我们不用担心会有像使用轮询那样的性能问题。再回到我们前面的示例,可以这样实现:

const domObserver = new MutationObserver(() => {

  const input = document.getElementById('robot-check');  if (input) {
    input.addEventListener('click', () => alert('有好事即将发生!'));
  }
});

domObserver.observe(document.body, { childList: true, subtree: true });

observe() 方法接受两个参数,第一个参数是我们要观测的 DOM 节点,第二个参数还有一些可选配置:

  • childList:一个布尔值,表示是否观察目标节点的子节点的添加或删除。
  • attributes:一个布尔值,表示是否观察目标节点属性的变化。
  • characterData:一个布尔值,表示是否观察目标节点文本内容的变化。
  • subtree:一个布尔值,表示是否观察目标节点的所有后代节点的变化。
  • attributeOldValue:一个布尔值,表示观察属性变化时是否在 MutationRecord 对象中包含变化前的属性值。
  • characterDataOldValue:一个布尔值,表示观察文本内容变化时是否在 MutationRecord 对象中包含变化前的文本内容。
  • attributeFilter:一个数组,包含要观察的属性名,只有在属性变化时才会触发回调函数。

另外还有比较重要的一点,当我们做完需要处理的事情之后,做一下清理,不然就可能重复执行,我们可以在观测结束后调用 observer.disconnect(); 方法:

const domObserver = new MutationObserver(() => {

  const input = document.getElementById('robot-check');  if (input) {
    input.addEventListener('click', () => alert('有好事即将发生!'));
    
    observer.disconnect();
  }
});

domObserver.observe(document.body, { childList: true, subtree: true });

MutationObserver API 现在现已得到所有主流浏览器的广泛支持,所以能够放心运用~

 

咱们前面说到,setTimeout、setInterval 性能取决于咱们怎样设定轮询的时间间隔,它们都会在主使命队列上运转它们的回调函数。但是,MutationObserver 会在微使命队列上触发它的回调,这意味着它无需等待事情循环的完整进程即可触发回调,所以它的响应速度要快得多。

 

下面是运用 setTimeout(间隔设定为 0ms) 和 MutationObserver 的性能测试比照:

当然,本文只是给了一个简单的示例,MutationObserver 还可以在更多的场景发挥更大的作用,比如 DOM 的防窜改等等


发布留言:


留言信息(最新3条):

昵称:A****nda 时间:2023-04-13 10:23:34

留言:麻烦帮我改下模板代码咧 什么时候比较方便

昵称:WSS素材网 时间:2023-04-13 10:23:34

回复:请添加微信:Wisdom-boylin 或 添加QQ:584244711

昵称:炸****趴菜。 时间:2023-04-12 15:14:27

留言:兄弟 我需要个蜘蛛池啊 有没介绍看到加我

昵称:WSS素材网 时间:2023-04-12 15:14:27

回复:请添加微信:Wisdom-boylin 或 添加QQ:584244711

昵称:彩****的雨云 时间:2023-04-12 14:47:10

留言:我想咨询下你们做不做小旋风超级模板的 需要10套

昵称:WSS素材网 时间:2023-04-12 14:47:10

回复:请添加微信:Wisdom-boylin 或 添加QQ:584244711


版权声明:本文为「WSS素材网」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。  
原文链接:https://wsstp.com/fore/66.html