有没有例子证明微任务是在渲染之前执行的?

js1*_*0cc 4 javascript event-loop html-rendering

我看到几篇文章说渲染步骤是在微任务之后。

我用这段代码测试它:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <p>test</p>
  <script>
const p = document.querySelector('p');
p.textContent = 'text';
Promise.resolve().then(function microtask() {
  debugger;
  p.textContent = 'Promise';
});
  </script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

这段代码似乎表明 UI在微任务运行之前重新渲染

难道是我想错了?有没有什么好的例子表明渲染是在微任务运行之后进行的?

Kai*_*ido 9

您可以通过查看事件循环处理模型来简单地证明这一点。解释一下它的当前状态,同时省略我们不感兴趣的几个步骤:

    1. 选择要执行的任务
    1. 执行该任务
    1. 执行微任务端点
    1. 更新渲染(如果需要的话)
    1. 重复

因此,很明显,微任务是在渲染发生之前执行的。

还没有被说服吗?

这是一个仅使用微任务就会阻塞 UI 5 秒的代码片段。在释放此锁之前,页面内容不会被渲染:

// We wrap our code in a 0 timeout and two rAF levels
// to be sure the initial rendering of the page did occur
setTimeout( () => {
requestAnimationFrame( () => {
  requestAnimationFrame( () => {

    // Now we modify the DOM
    document.querySelector( 'p' ).textContent = "modified";
    // and we start locking the UI using a Promise chain
    const start = performance.now();
    (function lockUI() {     // IIFE
      if( performance.now() - start  < 5000 ) {
        // recursive Promise chaining
        // all these promises will get executed before the next rendering
        // and block the UI rendering
        // even though we were in an rAF callback!
        return Promise.resolve()
          .then( lockUI );
      }
    } )();
    
  } );
} );
}, 0 );
Run Code Online (Sandbox Code Playgroud)
<p>Initial (please wait 5s)</p>
Run Code Online (Sandbox Code Playgroud)

细心的读者会注意到,这个脚本甚至没有将微任务排队到事件循环的第7步,而是排队到11.12 交错微任务检查点

这只会更好地巩固实际渲染仅在步骤11.15完成的观点,并且之前的任何操作实际上都会延迟它。


因此,在您的测试用例中,永远不应该渲染“文本” ,因为通过调用Promise.resolve().then()您实际上会对微任务进行排队,从事件循环的角度来看,这实际上与这里的同步操作相同,因为排队之后没有任何事情发生。
在某些情况下,您仍然可以看到渲染的文本,即浏览器进入旋转事件循环算法。如果浏览器面临一个长任务并决定可以执行此算法,则可能会发生这种情况,这将允许即使仍然运行一个长任务也可以执行渲染步骤。
例如,每当您启动诸如alert()prompt()等模式时,Firefox 都会执行此操作。

所以在 Firefox 中,这个片段实际上会渲染文本text,Chrome 在这里不会调用这个算法,所以它不会渲染任何东西,甚至不会渲染初始的test

const p = document.querySelector('p');
p.textContent = 'text';
Promise.resolve().then(function microtask() {
  alert();
  p.textContent = 'Promise';
});
Run Code Online (Sandbox Code Playgroud)
<p>test</p>
Run Code Online (Sandbox Code Playgroud)

然而 Chrome确实debugger. 所以这个关键字不会阻止渲染,这就是为什么你会看到text正在渲染。