宏任务和微任务练习

Macrotasks and microtasks exercise

我在 Google Chrome 中有以下关于微观和宏观任务 运行ning 的问题。看下一个例子(以下代码由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 只改变一次?这对我来说很有意义,因为在第二次迭代中它已经发生了变异,所以它不会触发回调。

其中 none 个我不确定。

有什么想法吗?谢谢,曼纽尔

Why click is print twice if inner.click() is called once? I think that it is because click inner makes click outer too, but I am not sure.

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

Why mutate changes only once? It makes sense to me because in the second iteration it is already mutated so it doesn't trigger the callback.

因为 DOM 的两个突变是同步完成的,当 setAttribute 在两个点击侦听器中被调用时 - 观察者仅在 after 所有侦听器运行任务完成。

两个突变都将在突变列表(传递给 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';
});

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