问题描述
我有以下关于在 Google Chrome 中运行的微观和宏观任务的问题。请参阅下一个示例(以下代码由 Jake Archibald 提出):
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
// Let's listen for attribute changes on the
// outer element
new MutationObserver(function () {
console.log('mutate');
}).observe(outer,{
attributes: true,});
// Here's a click listener…
function onClick() {
console.log('click');
setTimeout(function () {
console.log('timeout');
},0);
Promise.resolve().then(function () {
console.log('promise');
});
outer.setAttribute('data-random',Math.random());
}
// …which we'll attach to both elements
inner.addEventListener('click',onClick);
outer.addEventListener('click',onClick);
inner.click();
如果运行下面的代码,日志的顺序是:
click
click
promise
mutate
promise
timeout
timeout
这里有两个问题我不明白,因为我都有一个模糊的想法。
- 如果调用inner.click() 一次,为什么click 会打印两次?我认为这是因为点击内部也会使点击外部,但我不确定。
- 为什么只 mutate 更改一次?这对我来说很有意义,因为在第二次迭代中它已经发生了变异,因此它不会触发回调。
我不确定它们中的任何一个。
有什么想法吗?谢谢,曼努埃尔
解决方法
如果 inner.click()
被调用一次,为什么点击会打印两次?我认为这是因为点击内部也会使点击外部,但我不确定。
是的 - 这是因为点击事件向上传播到包含元素。除非在事件上调用 stopPropagation
,否则它将触发事件被分派到的元素(此处为 inner
)和根之间的所有侦听器。 outer
是 inner
的容器,因此在传播过程中点击内部会到达外部。
为什么只 mutate 更改一次?这对我来说很有意义,因为在第二次迭代中它已经发生了变异,因此它不会触发回调。
因为 DOM 的两个变化是同步完成的,所以当 setAttribute
在两个点击侦听器中被调用时 - 观察者仅在所有侦听器任务完成后运行。
两个突变都将在突变列表中可见(传递给 MutationObserver
回调的数组):
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
// Let's listen for attribute changes on the
// outer element
new MutationObserver(function (mutations) {
console.log('mutate');
for (const mutation of mutations) {
console.log('change detected on attribute',mutation.attributeName);
}
}).observe(outer,{
attributes: true,});
// Here's a click listener…
function onClick() {
console.log('click');
setTimeout(function () {
console.log('timeout');
},0);
Promise.resolve().then(function () {
console.log('promise');
});
outer.setAttribute('data-random',Math.random());
}
// …which we'll attach to both elements
inner.addEventListener('click',onClick);
outer.addEventListener('click',onClick);
inner.click();
<div class="outer">
<div class="inner">
click
</div>
</div>
出于类似原因:
elm.className = 'foo';
elm.className = 'bar';
将导致 elm
上的观察者仅触发一次。
对比
elm.className = 'foo';
setTimeout(() => {
elm.className = 'bar';
});
(或在您的代码中实现的同类事物)将导致观察者触发两次,因为超时是一个宏任务,而观察者回调作为微任务运行。