InnerHTML 是异步的吗?

我希望我不会让自己出丑,但我试图理解在这两行代码中发生了什么:

document.body.innerHTML = 'something';
alert('something else');

我观察到的是,在 HTML 更新之前会显示警报(或者可能已经更新了,但是页面没有被刷新/重新绘制/随便什么)

看看这个 Codepen看看我的意思。

请注意,即使把 alert放在 setTimeout(..., 0)中也没有帮助。看起来 innerHTML实际更新页面需要更多的事件循环。

编辑:

我忘了说我正在使用 Chrome 浏览器,也没有检查其他浏览器。看起来只能在 Chrome 中看到。尽管如此,我仍然对为什么会发生这种情况感兴趣。

11598 次浏览

Yes, it is synchronous, because this works (go ahead, type it in your console):

document.body.innerHTML = 'text';
alert(document.body.innerHTML);// you will see a 'text' alert

The reason you see the alert before you see the page changing is that the browser rendering takes more time and isn't as fast as your javascript executing line by line.

The innerHTML property actual does get updated synchronously, but the visual redraw that this change causes happens asynchronously.

The visual rendering the DOM is asynchronous in Chrome and will not happen until after the current JavaScript function stack has cleared and the browser is free to accept a new event. Other browsers might use separate threads to handle JavaScript code and browser rendering, or they might let some events get priority while an alert is halting the execution of another event.

You can see this in two ways:

  1. If you add for(var i=0; i<1000000; i++) { } before your alert, you've given the browser plenty of time to do a redraw, but it hasn't, because the function stack has not cleared (add is still running).

  2. If you delay your alert via an asynchronous setTimeout(function() { alert('random'); }, 1), the redraw process will get to go ahead of the function delayed by setTimeout.

    • This does not work if you use a timeout of 0, possibly because Chrome gives event-queue priority to 0 timeouts ahead of any other events (or at least ahead of redraw events).

Setting innerHTML is synchronous, as are most changes you can make to the DOM. However, rendering the webpage is a different story.

(Remember, DOM stands for "Document Object Model". It's just a "model", a representation of data. What the user sees on their screen is a picture of how that model should look. So, changing the model doesn't instantaneously change the picture - it take some time to update.)

Running JavaScript and rendering the webpage actually happen separately. To put it simplistically, first all of the JavaScript on the page runs (from the event loop - check out this excellent video for more detail) and then after that the browser renders any changes to the webpage for the user to see. This is why "blocking" is such a big deal - running computationally intensive code prevents the browser from getting past the "run JS" step and into the "render the page" step, causing the page to freeze or stutter.

Chrome's pipeline looks like this:

enter image description here

As you can see, all of the JavaScript happens first. Then the page gets styled, laid out, painted, and composited - the "render". Not all of this pipeline will execute every frame. It depends on what page elements changed, if any, and how they need to be rerendered.

Note: alert() is also synchronous and executes during the JavaScript step, which is why the alert dialog appears before you see changes to the webpage.

You might now ask "Hold on, what exactly gets run in that 'JavaScript' step in the pipeline? Does all my code run 60 times per second?" The answer is "no", and it goes back to how the JS event loop works. JS code only runs if it's in the stack - from things like event listeners, timeouts, whatever. See previous video (really).

https://developers.google.com/web/fundamentals/performance/rendering/