所以这幅画只在解析DOM树并创建CSSOM后才开始,对吧?另一种说法是,将<script>
放在<body>
的末尾是最佳做法,以便在下载脚本之前页面呈现内容。
我的问题是,解析DOM树的时间何时发生?我们怎么说它已经完成了?根据我的理解,<script>
最终也是 DOM树的一部分,只有加载了脚本才能调用DOM树。浏览器从上到下读取html文件,创建DOM树,当它看到<script>
时,它停止下载并执行它,直到解析遍历整个页面。或者,页面是否在解析DOM树的同时绘制页面?
答案 0 :(得分:11)
TL; DR:收到文件后,解析开始瞬间。
有关更详细的解释,我们需要深入了解渲染引擎的工作方式。
渲染引擎解析HTML文档并创建两个树:content tree
和render tree
。内容树包含所有DOM节点。渲染树包含所有样式信息(CSSOM
)和仅渲染页面所需的DOM节点。
一旦创建了渲染树,浏览器就会经历两个过程:每个DOM节点应用layout
和painting
。应用布局意味着计算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>
以下资源值得您花时间阅读:
答案 1 :(得分:2)
另一种说法是,将
<script>
放在<body>
的末尾是最佳做法,以便在下载脚本之前页面呈现内容。
将脚本标记放在body标记末尾的主要原因是:下载和执行 JavaScripts将阻止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 )。在执行所有<script>
并解析所有HTML后,将触发DOMContentLoaded
。
通知1 :DOMContentLoaded
不会等待CSS和图片
通知2 :大多数浏览器都有&#34;推测解析&#34;特征。如果有多个JavaScript文件,则会同时下载它们。但是,它们仍将由主线程顺序执行。
关于你的上一个问题:
或者,页面是否在解析DOM树的同时绘制页面?
根据我自己的理解,答案是是,浏览器会尝试画出 ASAP 。也就是说,绘制引擎不会等待渲染树完全准备好。所以应该有一个分离的线程来处理油漆。
如果我的理解有误,请随时纠正我:)
参考文献:
答案 2 :(得分:0)