问题描述
在看事件循环相关视频的时候发现其中有一个例子不理解,查了资料才明白其中的缘由,遂以志之。问题是这样的:
<button id="button">button</button>
button.addEventListener("click", () => {
Promise.resolve().then(() => console.log("Microtask 1"));
console.log("Listener 1");
});
button.addEventListener("click", () => {
Promise.resolve().then(() => console.log("Microtask 2"));
console.log("Listener 2");
});
要问的是:
- 你手动点击button,打印的log顺序是啥?
如果改成用js调用lick的方式,那么输出顺序又是啥?
// 在上面的js代码最后一行新增 button.click();
问题解析
首先,公布一下答案。
第一种情况输出是:
Listener 1
Microtask 1
Listener 2
Microtask 2
第二种情况的输出是:
Listener 1
Listener 2
Microtask 1
Microtask 2
先回顾一下时间循环中的部分知识点:
在事件循环中,点击事件的回调属于Macrotask,Promise.then的回调属于Microtask,先执行一个Macrotask,然后等调用栈为空之后就会执行所有Microtask。
结合上述知识,情况1的答案是完全符合这个说法的。
但是情况2就不能理解了,为何连着执行了两个Macrotask,然后才执行的Microtask,难道这个这个逻辑有特殊情况?非也!
问题的核心在于手动点击的方式回调是异步的,而button.click()
这种调用方式它是同步的,没错,它是同步调用的。
和经由浏览器触发,并通过事件循环异步调用事件处理程序的“原生”事件不同,dispatchEvent() 会同步调用事件处理函数。在 dispatchEvent() 返回之前,所有监听该事件的事件处理程序将在代码继续前执行并返回。
来源文档MDN:EventTarget.dispatchEvent()
明白这一点之后再看情况2就很好理解了,就相当于:
function fn1() {
Promise.resolve().then(() => console.log("Microtask 1"));
console.log("Listener 1");
});
function fn2 {
Promise.resolve().then(() => console.log("Microtask 2"));
console.log("Listener 2");
});
// button.click();
fn1();
fn2();
最后
这个题目出现在一个与事件循环相关的视频中,于是有不少文章在解释情况2的时候都会解释说此时调用栈不为空,所以不能执行Microtask,但却没有解释为啥不为空,以及调用栈里有啥。我看了解释完全摸不着头脑,最后在了解了dispatchEvent
同步调用的知识点后才恍然大悟,这根本就是两码事嘛!吃了没文化的亏。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。