内容简介:在探讨 CSS、JS 对阻塞行为前,先建立如下的 html,后续的探讨都在这个 html 的基础上进行。html 文件如下:可以预见的是 html 加载完毕后页面会呈现一个蓝色的正方形。
在探讨 CSS、JS 对阻塞行为前,先建立如下的 html,后续的探讨都在这个 html 的基础上进行。
html 文件如下:
<!DOCTYPE html> <html lang="en"> <head> <style> div { width: 100px; height: 100px; background: blue; } </style> </head> <body> <div /> </body> </html> 复制代码
可以预见的是 html 加载完毕后页面会呈现一个蓝色的正方形。
JS 的阻塞行为
-
<script src="script.js"></script>
对于没有 async 和 defer 属性的 script,当浏览器解析到 script 标签时会立即加载并执行脚本,这会阻止 dom 的解析,也就说在 script 加载执行完成前 script 标签后的 dom 都不会解析。
加载脚本阻止 dom 解析
如下,head 内添加了一个内联脚本,一个外部脚本(sleep.js,为一个空文件),外部脚本将在服务端延迟 5 秒后返回。当 document.readyState 变为 interactive 可交互时,表明文档已解析完成,接近于 DOMContentLoaded 事件的触发。
<head> <script> console.log('start'); document.onreadystatechange = function () { if (document.readyState === "interactive") { console.log('DOMContentLoaded', document.body.children); } } </script> <script src="/sleep.js"></script> </head> 复制代码
效果如下:
可以看到,页面刷新,start 首先执行,5 秒后 DOMContentLoaded 才执行,也就是说 js 的加载会阻止 dom 的解析。事实上多数浏览器在 js 加载执行时都会停止解析文档,因为 js 可能操作 dom。
执行脚本阻止 dom 解析
如下,head 内添加了两个内联脚本,第二个内联脚本将执行至少 5 秒钟。
<head> <script> console.log('start'); document.onreadystatechange = function () { if (document.readyState === "interactive") { console.log('DOMContentLoaded', document.body.children); } } </script> <script> var now = Date.now(); var isRun = true; while(isRun) { var time = Date.now(); if (time - now > 5000) { isRun = false; } } console.log('body', document.body); console.log('end'); </script> </head> 复制代码
效果如下:
可以看到,页面刷新,start 首先执行,5 秒后 DOMContentLoaded 才执行,脚本执行完毕前 body 为 null,也就是说 js 的执行会阻止 dom 的解析。
-
<script async src="script.js"></script>
async 属性会使脚本后续文档的加载渲染和脚本的加载执行并行进行。async 脚本在下载完成后立即执行,所以不能保证脚本的执行顺序,以乱序执行为主。此外,async 不支持内联脚本。
加载 async 脚本不阻止 dom 解析
如下,将"加载脚本阻止 dom 解析"例子中的脚本改成 async。
<script async src="/sleep.js"></script> 复制代码
页面刷新,可以看到 DOMContentLoaded 立即打印了,也就说带有 async 属性的脚本加载时不会阻塞 dom 的解析。
执行 async 脚本不阻止 dom 解析
添加如下代码到 sleep.js
var now = Date.now(); var isRun = true; while(isRun) { var time = Date.now(); if (time - now > 5000) { isRun = false; } } console.log('body', document.body); console.log('end'); 复制代码
如下: 以 async 的方式加载 sleep.js,服务端立即返回 sleep.js。
<head> <script> console.log('start'); document.onreadystatechange = function () { if (document.readyState === "interactive") { console.log('DOMContentLoaded', document.body.children); } } </script> <script async src="/sleep.js"></script> </head> 复制代码
页面刷新,可以看到 DOMContentLoaded 立即打印了,也就说带有 async 属性的脚本执行时不会阻塞 dom 的解析。
-
<script defer src="script.js"></script>
defer 会使脚本后续文档的加载渲染和脚本的加载并行进行,但 defer 脚本的执行要在所有元素解析完成之后 DOMContentLoaded 事件触发前完成,它是按着脚本加载顺序进行执行。
加载 defer 脚本不阻止 dom 解析
将 "加载 async 脚本不阻止 dom 解析例子" 中 async 换成 defer
<script defer src="/sleep.js"></script> 复制代码
页面刷新,可以看到 DOMContentLoaded 立即打印了,也就说带有 defer 属性的脚本加载时不会阻塞 dom 的解析。
执行 defer 脚本不阻止 dom 解析
将 "执行 async 脚本不阻止 dom 解析例子" 中 async 换成 defer。
<script> console.log('start'); document.onreadystatechange = function () { if (document.readyState === "interactive") { console.log('DOMContentLoaded', document.body.children); } } </script> <script defer src="/sleep.js"></script> 复制代码
页面刷新,可以看到 DOMContentLoaded 立即打印了,也就说带有 defer 属性的脚本执行时不会阻塞 dom 的解析。
CSS 的阻塞行为
在 html 的 head 标签内加上 script 标签和 css 的 link,main.css 在服务器端延迟 5 秒后返回。
<head> <script> document.onreadystatechange = function () { if (document.readyState === "interactive") { console.log('DOMContentLoaded', document.body.children); } } </script> <link rel="stylesheet" href="/main.css" /> </head> 复制代码
main.css 文件如下:
div { background: red; } 复制代码
效果如下:
可以看到,页面刷新时,立即打印出了 DOMContentLoaded,尽管 main.css 是在延迟 5 秒后返回的,也就是说在 css 加载完成之前 dom 就已经解析完成了,css 的加载并不会阻止 dom 的解析。此外,我们并没有看到蓝色的正方形,而一直是一个红色的正方形,这意味着浏览器在 css 加载解析完成前没有渲染它后面的 dom(如果不是,则先看到蓝色的正方形,再看到红色的正方形),而是在 css 加载解析后再进行渲染,也就是说 css 会阻塞页面的渲染。这种策略是能够说得通的,试想如果先呈现出一个样子,一会又变一下,体验会比较差,而且多次渲染也浪费性能。
另一方面,在最初的测试时 script 是 link 后边的,如下:
<head> <link rel="stylesheet" href="/main.css" /> <script> document.onreadystatechange = function () { if (document.readyState === "interactive") { console.log('DOMContentLoaded', document.body.children); } } </script> </head> 复制代码
结果是,等到 main.css 加载完成后才打印了 DOMContentLoaded,这似乎和 css 不阻止 dom 解析相悖。事实上,由于 script 可能去获取 style 信息,如果 css 没有加载完成,显然不能够获取正确的信息,因此部分浏览器会直接阻止后续 script 的执行。
结论
- 没有 async 和 defer 属性的 script 加载或执行都会阻塞 dom 的解析。
- 带有 async 或 defer 属性的 script 加载或执行都不会阻塞 dom 的解析。
- async 的脚本加载完毕后立即执行,不保证执行顺序,而 defer 脚本在 dom 解析完毕后才执行,基本能保证按着脚本加载顺序执行。
- css 的加载解析会阻塞后续的 script 执行,但不会阻塞 dom 解析。
- css 的加载解析会阻塞 dom 的渲染。
需要说明的是以上所有结论在不同浏览器不同的版本,所采取的策略并不完全一致,比如脚本加载执行时,chrome(v:74)会继续下载 link 指定的文件,而 safari(v:12.0.2)link 文件 的下载会被阻塞。
以上所述就是小编给大家介绍的《CSS与JS对DOM的阻塞行为》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Node.js 指南(阻塞与非阻塞概述)
- Node.js 回调函数 阻塞与非阻塞
- 明明白白学 同步、异步、阻塞与非阻塞
- 从 Linux 源码看 socket 的阻塞和非阻塞
- 分布式系统关注点——阻塞与非阻塞有什么区别?
- Netty基础系列(2) --彻底理解阻塞非阻塞与同步异步的区别
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解 Flask
[美]Jack Stouffer / 苏丹 / 电子工业出版社 / 2016-7-1 / 79.00
Flask 是一种具有平缓学习曲线和庞大社区支持的微框架,利用它可以构建大规模的web应用。学习上手Flask非常轻松,但要深入理解却并不容易。 本书从一个简单的Flask应用开始,通过解决若干实战中的问题,对一系列进阶的话题进行了探讨。书中使用MVC(模型-视图-控制器)架构对示例应用进行了转化重构,以演示如何正确地组织应用代码结构。有了可扩展性强的应用结构之后,接下来的章节使用Flask......一起来看看 《深入理解 Flask》 这本书的介绍吧!