参考下这个:
https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model说下自己的理解,里面提到第 11 步是更新渲染,11 之后还有两步,一个是执行浏览器空闲回调(requestsIdleCallback)一个是 worker 的回调。
11 步之前是执行 JS,JS 主线程就一个线程,UI 渲染会等待 JS 执行,所以你这个阻塞也阻塞了渲染,
```
const div = document.querySelector('#root')
div.innerHTML = '1'
const now = performance.now()
console.log(div.innerHTML);
```
执行到这里如果没有后续代码,浏览器会尝试执行微任务栈,然后执行 UI 渲染,此时就会渲染 1 。
但要注意 innerHTML 实时改变了 DOM,但不是触发渲染的条件,改变 DOM != 渲染,走完这个循环才是执行渲染的条件。
```
while (performance.now() - now < 100) {
console.log(now)
}
console.log(div.innerHTML);
div.innerHTML = '2'
console.log(div.innerHTML);
```
加上下面这些,只是多阻塞了主线程一会,此时走完前面的 1-10,去执行 UI 渲染,而是不断执行 console.log(now),之后执行到了:
```
div.innerHTML = '2'
console.log(div.innerHTML);
```
执行渲染时的 innerHTML 已经是 2,所以渲染出来 2 了。
一般可以用 setTimeout+Promise,这样不会阻塞 UI 渲染,只会阻塞后续代码执行:
```
function sleep(times) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, times)
})
}
```
```
async function draw() {
const div = document.querySelector('#root')
div.innerHTML = '1'
//const now = performance.now()
//console.log(div.innerHTML);
//while (performance.now() - now < 100) {
// console.log(now)
//}
await sleep(100)
console.log(div.innerHTML);
div.innerHTML = '2'
console.log(div.innerHTML);
}
draw()
```
相关扩展可以了解一下 Vue 的$nextTick,曾经某一个版本把它从微任务改到了宏任务,此时用$nextTick 改变 DOM 的 Style 会出现抖动(因为 宏-> 微 -> UI 渲染 -> 宏 2),本来任务应该在 UI 渲染前结果成了 UI 渲染后。