事件执行顺序和渲染

Event execution sequence and rendering

var con = document.getElementById('con');
con.onclick = function () {
  Promise.resolve().then(function Promise1() {
    con.textContent = 0;
    // requestAnimationFrame(() => con.textContent = 0)
  });
};
<div id="con">this is con</div>

为什么这段代码执行微任务后不触发渲染?

setTimeout(function setTimeout1() {
  console.log('setTimeout1')
}, 0)
var channel = new MessageChannel();
channel.port1.onmessage = function onmessage1() {
  console.log('postMessage');
  Promise.resolve().then(function promise1() {
    console.log('promise1');
  })
};
channel.port2.postMessage(0);
setTimeout(function setTimeout2() {
  console.log('setTimeout2')
}, 0);
console.log('sync');

为什么postmessage先于timer执行?

Why this code does not trigger rendering after performing microtasks?

确实如此,否则您将看不到正在更新的文本...

也许您无法从您的开发工具中分辨出来?

可能 因为鼠标事件现在通常被限制为屏幕刷新率,这意味着当调度鼠标事件的任务将 运行,你' d 已经在画框中,这可能是出于其他原因(因为据我所知,mousemove 事件以这种方式被限制,而不是 click ...).
因此,您的 Promise callback will get executed synchronously (with only the sixth step "set currentTask to null" in between), before the update the rendering 步骤开始,所有开发工具将看到一个正常的绘画框架,就像它预期的那样。
所以可能,开发工具不会在这里显示任何特别的东西,但考虑到你的主张的广泛性,很难确定一个具体原因,这只是我的理论。

您可以尝试通过从此类事件内部调用 requestAnimationFrame 来验证该理论,并检查它是否确实在同一事件循环迭代中执行:

onclick = (evt) => {
  console.clear();
  setTimeout( () => console.log( 'timeout' ), 0 );
  requestAnimationFrame( () => console.log( 'rAF' ) );
};
Click anywhere<br>
If "rAF" gets logged before "timeout", the click event got handled in a painting frame.

对我来说,它在 Chrome 中经常出现,而在 Firefox 中只是偶尔出现一次,但同时我知道 Chrome's rAF is broken... 所以这个理论非常薄弱。


Why postmessage is executed before timer?

这将取决于用户代理(浏览器)以及何时执行此代码以使此语句成立,当然也取决于它成立的原因。

In Chrome,他们将最小 1ms 设置为传递给 setTimeout:

的超时值
  base::TimeDelta interval_milliseconds =
    std::max(base::TimeDelta::FromMilliseconds(1), interval);

消息 任务没有超时,因此会立即排队。因此,如果没有其他任务要处理,它将是下一个执行的任务,远在 1 毫秒超时解决之前。

In Firefox,它们将 setTimeout 安排的任务视为低优先级,从页面加载 安排的任务(这意味着在 Firefox 中,消息任务实际上会在 setTimeout 之后触发,如果两者都安排在页面加载之后:

function test() {
  setTimeout(function setTimeout1() {
    console.log('setTimeout1')
  }, 0)
  var channel = new MessageChannel();
  channel.port1.onmessage = function onmessage1() {
    console.log('postMessage');
    Promise.resolve().then(function promise1() {
      console.log('promise1');
    })
  };
  channel.port2.postMessage(0);
  setTimeout(function setTimeout2() {
    console.log('setTimeout2')
  }, 0);
  console.log('sync');
}
console.log( 'testing @ page load' );
test();
setTimeout(() => {
  console.log( 'testing after page load' );
  test();
}, 1000 );

/* results in Firefox:

testing @ page load
sync
postMessage
promise1
setTimeout1
setTimeout2
testing after page load
sync
setTimeout1
setTimeout2
postMessage
promise1
*/
).
因此,在页面加载的这种特殊情况下,他们会将消息任务视为比超时任务更重要,并且任务执行者必须选择下一个要执行的任务(作为 first step of the Event Loop processing model 的一部分) ), 它将在超时后选择消息。

但这些都是实现上的怪癖,规范中没有任何内容将这种行为正式化。