向微队列添加任务的四种方式
关于微任务,微队列,事件循环,可参考:深入:微任务与 Javascript 运行时环境 - Web API 接口参考 | MDN (mozilla.org)
先说答案, 四种方法:
Promise.resolve().then()
;MutationObserver
process.nextTick()
;queueMicrotask()
Promise.resolve().then()
查看ECMA-262关于then方法的描述:
可知then
方法会在promise
状态为fulfilled
或rejected
时将对应的任务(onFulfilled
和onRejected
)放到任务队列中,然后注意到MDN在一篇关于微任务的文章中提到:
JavaScript 中的 promise 和 Mutation 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");
})
如有错漏,恳请指出