innerHTML是异步的吗?

107

我希望自己不会出丑,但我正在努力理解这两行代码在发生什么:

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

我所观察到的是,警报在HTML被更新之前就已经出现了(或者可能已经更新但页面还没有刷新/重绘/等等)。

请查看此代码片段以了解我的意思。

请注意,即使将alert放入setTimeout(..., 0)中也没有帮助。似乎需要更多的事件循环才能实际更新页面的innerHTML

编辑:

我忘记说我正在使用Chrome并且没有检查其他浏览器。看起来这只在Chrome中可见。尽管如此,我仍然想知道为什么会发生这种情况。


4
这是Chrome的典型特征。 - trincot
2
@trincot 谢谢!我忘了提到我正在使用 Chrome,而在询问之前没有尝试其他浏览器。你有任何参考资料吗? - apieceofbart
18
改变DOM是同步的。实际上,在JavaScript堆栈清除后,渲染 DOM才会发生。 https://developers.google.com/web/fundamentals/performance/rendering/ JavaScript>样式>布局>绘画>复合。(至少对于Chrome是这样。其他浏览器也类似。) - jered
2
只是猜测:警告阻止浏览器达到重绘步骤,因此虽然DOM已更改,但尚未被允许重新绘制;再次强调,这只是猜测。 - zzzzBov
2
@qbolec,请查看这个视频:https://youtu.be/r8caVE_a5KQ - apieceofbart
显示剩余4条评论
3个回答

138

设置innerHTML是同步的,大多数对DOM的更改也是如此。然而,渲染网页则不同。

(记住,DOM代表“文档对象模型”。它只是一个“模型”,一种数据表示。用户在屏幕上看到的是这个模型应该呈现的图像。因此,更改模型并不会立即更改图片 - 它需要一些时间来更新。)

运行JavaScript和渲染网页实际上是分开的。简单地说,首先运行页面上的所有JavaScript代码(来自事件循环 - 查看这个优秀视频以获取更多详细信息),然后在此之后,浏览器会呈现任何对网页的更改供用户查看。这就是为什么“阻塞”是个大问题 - 运行计算密集型代码会阻止浏览器越过“运行JS”步骤进入“渲染页面”步骤,从而导致页面冻结或卡顿。

Chrome的流水线如下:

enter image description here

如您所见,所有JavaScript代码都会先运行。然后页面会被样式化、布局、绘制和合成 - 进行“渲染”。并不是所有这个流水线都会在每一帧执行。这取决于哪些页面元素发生了变化,如果有的话,以及它们需要如何重新呈现。

注意:alert()也是同步的,并在JavaScript步骤中执行,这就是为什么弹出对话框会在您看到网页更改之前出现的原因。

您可能会问:“等等,'JavaScript' 步骤中确切运行了什么?我的所有代码每秒都运行 60 次吗?”答案是“不是”,这与 JS 事件循环的工作方式有关。只有位于堆栈中的 JS 代码才会运行,例如来自事件监听器、超时或其他相似的事物。请参见以前的视频(真的)。

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


1
谢谢您的回答。我对事件循环等细节有一些了解。这个管道非常合理,但是正如示例所显示的那样,在每个浏览器中都不是相同的。如果其他浏览器在显示警报之前等待页面重新绘制,那么在单击按钮和显示警报本身之间可能会有巨大的延迟。 我稍后会尝试操作它。 - apieceofbart
@apieceofbart 其他浏览器也可能会在异步重新绘制页面的同时暂停 JavaScript 操作,直到用户处理警告窗口。这并不意味着它们必须等待重新绘制完成。 - Jonas Schäfer
如果在主流浏览器中绘制是异步的,那么我能否在绘制完成时获得回调或Promise? - David Spector
有没有可能等待适当的时机或处理重绘事件完成? - S. W. G.
@S.W.G. 最好的方法可能是使用 requestAnimationFrame(),但它在不同的浏览器中的行为会有所不同。https://youtu.be/8aGhZQkoFbQ - jered

28

是的,它是同步的,因为这个可以正常工作(试试在控制台中输入):

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

你在看到警告之前就看到页面发生变化的原因是浏览器渲染需要更多时间,而不像JavaScript一行一行地执行那么快。


3
这个警示框显示的文本是否正确?(例如我的例子中的“text”)这将回答你是否同步的问题。浏览器渲染和JavaScript执行是两码事 :) - d-_-b
1
你的问题字面上是“innerHTML是异步的吗?”。如果值可以在它被使用后立即使用,那么它是同步的,不是吗?我认为你的意思更多地是关于页面渲染,而不是innerHTML的同步性质。 - d-_-b
8
是的,这个问题显然是错误的,但我不知道该问什么。如果我知道正确的问题,我可能已经找到答案了。我并不是想刻意刁难你,无论如何还是谢谢你的回答! - apieceofbart
这并不是关于执行时间的“增加”,而更多地涉及到执行计划的“延后”。 - Lightness Races in Orbit
谢谢,这个可行。我会把它作为答案发布在这里。 - David Spector
显示剩余5条评论

6

innerHTML属性确实会同步更新,但这种更改引起的可见重绘是异步的。

在Chrome中,DOM的可视渲染是异步的,直到当前JavaScript函数栈清除并且浏览器可以接受新事件之后才会发生。其他浏览器可能使用单独的线程来处理JavaScript代码和浏览器渲染,或者他们可能让一些事件获得优先权,而警报正在停止执行另一个事件。

您可以用两种方式来看待这个问题:

  1. 如果在警报之前添加for(var i=0; i<1000000; i++) { },那么您已经给浏览器足够的时间进行重绘,但它没有完成,因为函数堆栈没有清除(add仍在运行)。

  2. 如果通过异步setTimeout(function() { alert('random'); }, 1)延迟您的alert,则重绘过程将在由setTimeout延迟的函数之前进行。

    • 如果您使用0的超时,这样做不起作用,可能是因为Chrome在任何其他事件(或者至少在重绘事件之前)都将事件队列优先于0超时。

谢谢您的回答!请注意,即使是setTimeout(func, 1)也不是每次都有效,请查看此视频:https://youtu.be/r8caVE_a5KQ - apieceofbart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接