向微队列添加任务的四种方式

向微队列添加任务的四种方式

关于微任务,微队列,事件循环,可参考:深入:微任务与 Javascript 运行时环境 - Web API 接口参考 | MDN (mozilla.org)

先说答案, 四种方法:

  1. Promise.resolve().then();
  2. MutationObserver
  3. process.nextTick();
  4. queueMicrotask()

Promise.resolve().then()

查看ECMA-262关于then方法的描述:

可知then方法会在promise状态为fulfilledrejected时将对应的任务(onFulfilledonRejected)放到任务队列中,然后注意到MDN在一篇关于微任务的文章中提到:

JavaScript 中的 promiseMutation Observer API 都使用微任务队列去运行它们的回调函数

于是我们可以知道then方法的回调将会被添加到微队列中执行, 若查看V8引擎源码可进一步佐证这一点。

这样一来,我们就可以用以下代码向微队列中添加一个任务:

const task = () => {
    console.log("do somthing");
}
Promise.resolve().then(task);

进一步, 根据ECMA-262的描述(上图红框部分), 当promise状态为rejected时,onRejected也会被添加到微队列, 于是以下代码也可以达到相同的效果:

//以下两种方法等价
Promise.reject().then(null, task);

Promise.reject().catch(task);

验证一下, 执行以下代码, 发现打印顺序符合预期:

setTimeout(() => {
  console.log("timeout");
}, 0);
Promise.resolve().then(() => {
  console.log("promise resolve");
})

Promise.reject().then(null, () => {
  console.log("promise reject");
});

Promise.reject().catch(() => {
  console.log("promise catch");
});
console.log("同步");

//打印结果:
// 同步
// promise resolve
// promise reject
// promise catch
// timeout

MutationObserver API

同样根据上文提到的MDN的描述, 使用MutationObserver也可以达到向微队列中添加任务的目的, 结合MutationObserver的用法可以封装一个通用的函数:

    function enqueueMicroTaskByMutationObserver(task) {
      if (typeof task != "function") {
        throw "task必须为函数";
      }
      let m = new MutationObserver(task);
      let div = document.createElement("div");
      m.observe(div, {
        childList: true
      });
      div.innerText = "1";
    }

注意: nodejs环境不支持MutationObserver API, 因此这个方法只能在浏览器环境中使用

在html中运行以下代码验证, 打印顺序符合预期:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    function enqueueMicroTaskByMutationObserver(task) {
      if (typeof task != "function") {
        throw "task必须为函数";
      }
      let m = new MutationObserver(task);
      let div = document.createElement("div");
      m.observe(div, {
        childList: true
      });
      div.innerText = "1";
    }
    setTimeout(() => {
      console.log("time out");
    }, 0);
    enqueueMicroTaskByMutationObserver(() => {
      console.log("MutationObserver");
    })
  </script>
</body>

</html>

process.nextTick()

MutationObserver API相反,这个方法只能用于Node环境,根据Node文档的描述:

process.nextTick()callback 添加到 “下一个滴答队列”。在 JavaScript 堆栈上的当前操作运行完成之后,且在允许事件循环继续之前,此队列将被完全排空。

暂时可将"滴答队列"简单理解为微队列,实际上,"滴答队列"的优先级似乎比微队列更高, 观察以下代码:

setTimeout(() => {
  console.log("timeout");
}, 0);

Promise.resolve().then(() => {
  console.log("promise resolve");
})

process.nextTick(() => {
  console.log("tick");
})

console.log("同步");

发现无论执行多少次, "tick"总是先于"promise resolve"打印,于是推测"滴答队列"的优先级似乎比微队列更高.

queueMicrotask

在不考虑兼容性的情况下, 应当首选这个方法, 因为这个方法就是专门用来做将微任务加入队列这件事的, 参考MDN:queueMicrotask() - Web API 接口参考 | MDN (mozilla.org)

同时, queueMicrotask在浏览器和node环境中都可以使用且用起来也是最简单的, 用法如下:

queueMicrotask(() => {
  console.log("do somthing");
})

如有错漏,恳请指出

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JS的循环机制是指如何处理事件循环中的任务。在JS中,事件循环是一种机制,用于处理异步操作和事件处理。循环机制确保任务按照正确的顺序执行,以保证代码的正确性和性能。 在循环机制中,有两种类型的任务:宏任务(macro-task)和微任务(micro-task)。宏任务包括整体代码块、setTimeout、setInterval等,而微任务包括Promise、MutationObserver、process.nextTick等。 当代码执行时,会首先执行当前代码块中的同步任务,然后执行微任务队列中的所有微任务。接下来,会从宏任务队列中选择一个任务执行,直到宏任务队列为空。然后,再次执行所有微任务,以此类推。 事件队列是用来存储待执行的任务的数据结构。每个宏任务或微任务都会被添加到事件队列中。当事件循环开始时,会从事件队列中依次取出任务并执行。 需要注意的是,微任务的优先级高于宏任务。也就是说,在执行完当前宏任务后,会立即执行所有微任务,然后再执行下一个宏任务。这样可以确保微任务能够尽早地被执行,避免出现不必要的延迟。 总结一下: - 循环机制确保异步任务按照正确的顺序执行。 - 宏任务包括整体代码块、setTimeout、setInterval等。 - 微任务包括Promise、MutationObserver、process.nextTick等。 - 事件队列用来存储待执行的任务。 - 微任务优先级高于宏任务

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值