宏任务和微任务练习

问题描述

我有以下关于在 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

这里有两个问题我不明白,因为我都有一个模糊的想法。

  1. 如果调用inner.click() 一次,为什么click 会打印两次?我认为这是因为点击内部也会使点击外部,但我不确定。
  2. 为什么只 mutate 更改一次?这对我来说很有意义,因为在第二次迭代中它已经发生了变异,因此它不会触发回调。

我不确定它们中的任何一个

有什么想法吗?谢谢,曼努埃尔

解决方法

如果 inner.click() 被调用一次,为什么点击会打印两次?我认为这是因为点击内部也会使点击外部,但我不确定。

是的 - 这是因为点击事件向上传播到包含元素。除非在事件上调用 stopPropagation,否则它将触发事件被分派到的元素(此处为 inner)和根之间的所有侦听器。 outerinner 的容器,因此在传播过程中点击内部会到达外部。

为什么只 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';
});

(或在您的代码中实现的同类事物)将导致观察者触发两次,因为超时是一个宏任务,而观察者回调作为微任务运行。