[英]When does parsing HTML DOM tree happen?
TL;DR:收到文件后立即开始解析。
为了更详细的解释,我们需要深入研究渲染引擎的工作方式。
渲染引擎解析 HTML 文档并创建两棵树: content tree
和render tree
。 内容树包含所有 DOM 节点。 呈现树包含了所有的造型信息( CSSOM
),只有所需要的渲染TE页面的DOM节点。
创建渲染树后,浏览器会经历两个过程:应用layout
和painting
每个 DOM 节点。 应用布局意味着计算 DOM 节点应该出现在屏幕上的确切坐标。 绘画意味着实际渲染像素并应用样式属性。
这是一个渐进的过程:浏览器不会等到所有 HTML 都被解析。 部分内容将被解析和显示,而该过程将继续处理来自网络的其余内容。
您可以在浏览器中看到这个过程。 例如,打开 Chrome 开发人员工具并加载您选择的站点。
在“ Network
选项卡中记录活动后,您会注意到在下载文档时开始解析。 它识别资源并开始下载它们。 蓝色竖线表示DOMContentLoaded
事件,红色竖线表示load
事件。
记录时间线可以让您更深入地了解幕后发生的事情。 我已将上面的屏幕截图作为示例,以表明在解析文档时发生绘制。 请注意,初始绘制发生在它继续解析文档的另一部分之前。 这个过程一直持续到它到达文档的末尾。
渲染引擎是单线程的。 除了网络操作,几乎所有事情都发生在这个线程中。
将其与网络的同步特性相结合。 开发人员期望<script>
被立即解析和执行(即:一旦解析器到达脚本标记)。 这意味着:
解析文档会停止,直到此过程完成。 您不会通过在文档末尾包含<script>
改善总解析时间。 它确实增强了用户体验,因为解析和绘制的过程不会被需要执行的<script>
中断。
可以通过使用defer
和/或async
标记资源来解决此问题。 async
在 HTML 解析期间下载文件,并在完成下载后暂停 HTML 解析器以执行它。 defer
在 HTML 解析过程中下载文件,并且只有在解析器完成后才会执行它。
一些浏览器旨在通过使用所谓的推测解析来解决<script>
的阻塞方面。 在下载和执行脚本时,引擎会提前解析(并运行 HTML 树构造!)。 Firefox 和 Chrome 使用这种技术。
如果推测成功,您可以想象性能提升(例如,DOM 没有被包含在文档中的脚本改变)。 无需等待脚本执行,页面已成功绘制。 缺点是当投机失败时会损失更多的工作。
对我们来说幸运的是,非常聪明的人使用这些技术,所以即使正确使用document.write
也不会破坏这个过程。 另一个经验法则是不要使用document.write
。 例如,它可以打破投机树:
// Results in an unbalanced tree
<script>document.write("<div>");</script>
// Results in an unfinished token
<script>document.write("<div></div");</script>
以下资源值得您花时间阅读:
另一种说法是,将
<script>
放在<body>
的末尾是最佳做法,以便页面在下载脚本之前呈现某些内容。
将 script 标签放在 body 标签末尾的主要原因是:下载并执行JavaScript 会阻止 HTML 解析(或者,您可以说它们只是解析的一部分)。 如果将它们放在<head>
,用户可能会等待很长时间才能在网页上看到任何内容。 图像你有一个像这样的 html 页面:
<html>
<head>
<!-- this huge.js takes 10 seconds to download -->
<script src="huge.js"></script>
</head>
<body>
<div>
My most fancy div!
</div>
</body>
</html>
// huge.js
(function () {
// Some CPU intensive JS operations which take 10 second to complete
})();
浏览器将在到达<script>
标签后立即开始执行那些 CPU 密集型 JS。 它会阻止解析其余的 HTML 内容。 因此,在这种情况下,用户将无法在 JavaScript 下载和执行之前看到他喜欢的 div(总共需要 20 秒)。
您可以使用DOMContentLoaded
来检测初始 DOM 是否已加载和解析。 并且您在最后一段中的陈述非常正确:每次 HTML 解析器看到<script>
,它都会同步下载并执行它(请参阅注意事项 2 )。 执行DOMContentLoaded
所有<script>
并解析所有 HTML 后,将触发DOMContentLoaded
。
注意 1 : DOMContentLoaded
不会等待 CSS 和图像
注意 2 :大多数浏览器都具有“推测解析”功能。 如果有多个 JavaScript 文件,它们将被同时下载。 但是,它们仍然会由主线程顺序执行。
对于您的最后一个问题:
或者,页面是否在解析DOM树的同时绘制页面?
根据我自己的理解,答案是YES ,浏览器会尝试尽快绘制。 也就是说,绘制引擎不会等待渲染树完全准备好。 所以应该有一个单独的线程来处理油漆。
如果我的理解有误,请随时纠正我:)
参考:
这实际上取决于浏览器加载所有内容的特定顺序,但对于 DOM 解析,它从上到下工作。 解析器逐个分支地移动,因此当它遇到头部时,它会遍历每个子节点。 如果一个元素有一个子元素,它将在移回树之前移动到子元素/子元素上。 把它放在非常基本的伪代码中:
while DOM != parsed:
if current_node.has_child():
current_node = child_node
execute_node()
elif current_node.has_sibling():
current_node = sibling_node
execute_node()
elif current_node.has_parent_sibling():
current_node = parent_sibling
execute_node()
else:
current_node = parent_node
它本质上将脚本/链接标签作为父节点处理,如果它是外部文件,则启动 HTTP/S GET 请求,并在移动到下一个节点之前解析代码。 所以我们在最后使用 put script 标签的原因是因为它们通常不在页面加载时使用,而是在加载后处理。 所以大家的共识是,最好在页面上获取一些内容,然后稍后加载您的 JS,以便它可以处理您在菜单项上拥有的如此重要的动画。
当然也有例外,您可以指定 DOM 解析器以异步方式执行脚本 - 解析器创建一个额外的线程来解析 JS - 或延迟 - 发出 GET 请求但该文件直到 HTML 文档被解析解析完毕。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.