某天, 小枫在网上浏览 前端性能优化 的相干文章, 文章中指出: 将 JavaScript 的引入置于 <body></body> 尾部
小枫考虑了一会, 整个人都自闭了 … 于是乎, 接着上网查阅材料:
为什么 JavaScript 的引入要置于 <body></body> 尾部?
查了一圈之后发现, 网上广泛的观点如下:
JavaScript 执行会阻塞 HTML 的渲染, 因而将 JavaScript 的引入置于 <body></body> 尾部, 能够使页面先出现进去, 防止 JavaScript 的执行导致页面白屏;
出于钻研的精力, 小枫决定本人来验证这个论断, 请给小枫点个赞谢谢(是字体本人加粗的, 逃 0.0)
大家都晓得, chrome(文章应用的是版本为: 86.0.4240.80)是个神器, 自带性能调优工具, 能够记录剖析网页的渲染流程: 按 ”F12″ 或是 ” 鼠标右键 + 查看 ” 关上控制台, 抉择 Performance 栏, 如下图;
咱们先新建一个简略的 HTML 文件, 应用 Performance 剖析页面的渲染流程:
<!doctype html>
<html>
<head>
<title> 钻研精力 </title>
</head>
<body>
<p> 你好, 小枫 </p>
</body>
</html>
从上图能够看出, 网页的渲染流程大抵如下:
- Parse HTML 该阶段生成了 DOM Tree 和 CSSOM Tree;
- Layout 将 DOM Tree 联合 CSSOM Tree, 生成 Layout Tree(又称 Render Tree), 计算每个元素的尺寸和地位;
- Update Layout Tree 更新 Layout Tree;
- Paint 生成 PaintLayout Tree 记录元素绘制程序;
- Composite 合成视图输入到屏幕;
简略了解网页渲染流程之后, 咱们来对问题 进行验证, 列举以下两种状况:
- <script></script>置于 <head></head> 中:
<!doctype html>
<html>
<head>
<title> 钻研精力 </title>
<script>
for (let i = 0; i < 100000; i++) {for (let j = 0; j < 100000; j++) {}}
</script>
</head>
<body>
<p> 你好, 小枫 </p>
</body>
</html>
在下面的 html 文件中, 咱们将 <script></script> 置于 <head></head> 中, <script></script>中是一段 计算量很大 的 JavaScript, 值得注意的是 , 这里的 JavaScript 是以嵌入的模式存在于 HTML 中, 并非应用src 属性进行内部引入; 接着在浏览器关上该 html 文件, 并应用 Performance 进行剖析:
script 标签置于 head 标签中
如上图所示, ParseHTML阶段始终呈阻塞状态, 直到 Evalute Script 阶段完结才从新开始 ParseHTML, 接着进行Layout 以及 Paint 等渲染页面流程; 在这个过程中页面始终出现白屏状态, 直到 JavaScript 加载实现才有内容展现; 能够看出, JavaScript 的执行阻塞了 HTML 的解析和渲染;
- <script></script>置于 <body></body> 中:
<!doctype html>
<html>
<head>
<title> 钻研精力 </title>
</head>
<body>
<p> 你好, 小枫 </p>
<script>
for (let i = 0; i < 100000; i++) {for (let j = 0; j < 100000; j++) {}}
</script>
</body>
</html>
接着, 咱们把 <script></script> 放在 <body></body> 尾部, 此处的 JavaScript 同样为一段 计算量很大 的代码, 并且也以嵌入的模式引入; 接着应用 Performance 对网页进行剖析:
script 标签置于 body 标签尾部
如上图所示, 因为 JavaScript 的执行, ParseHTML阶段同样始终呈阻塞状态, 但不一样的是, 当 Evalute Script 阶段完结时, 间接进行 Layout 和Paint等渲染网页流程; 这个过程页面同样始终白屏, 直到 JavaScript 执行实现; 咱们能够这么了解: HTML 自上往下执行, 因而在执行 JavaScript 之前 ParseHTML 流程失常执行: 当 script 标签 放在 body 标签 尾部时, 浏览器会先解析 HTML, 之后遇到script 标签, 开始执行 JavaScript, 这个过程将会阻塞 HTML 的解析渲染, 所以页面才会始终出现白屏状态直到 JavaScript 执行结束;
从下面两种状况能够看出, 当 JavaScript 以 script 标签嵌入的形式执行时, 不论 script 标签置于 head 标签中或是置于 body 标签尾部, 都会因为 JavaScript 阻塞了 HTML 解析渲染的起因, 导致页面白屏, 直到 JavaScript 加载结束;
所以这能够证实网上搜寻到的观点是谬误的吗?
“ 不行!”
因为 JavaScript 的引入形式并非这一种, 还有另外一种更为常见的模式: 应用 script 标签的 src 属性引入内部 JavaScript; 于是乎, 以 script 标签引入内部 JavaScript 的模式, 开启下一轮验证:
- <script></script>置于 <head></head> 中:
将计算量很大的代码移至 js 文件: index.js
for (let i = 0; i < 100000; i++) {for (let j = 0; j < 100000; j++) {}}
并在 html 中引入 index.js:
<!doctype html>
<html>
<head>
<title> 钻研精力 </title>
<script src="./index.js"></script>
</head>
<body>
<p> 你好, 小枫 </p>
</body>
</html>
接着, 应用 Performance 对网页进行剖析:
如上图, 浏览器先调用 Network 线程 下载 HTML 文件, 之后对 HTML 进行自上往下解析工作, 直到遇到 <head></head> 中的 script 标签, 因为 script 标签引入的是内部 js 文件, 浏览器接着调用Network 线程 下载指标 js 文件, 能够看到, 这一过程中并没有进行ParseHTML;
下载完 js 文件之后, 浏览器开始执行 JavaScript, 期待代码执行结束之后, 开始 ParseHTML, Layout 以及 Paint 等网页解析渲染操作; 整个流程, 网页始终出现白屏状态, 直到 JavaScript 执行结束, 也就是说, 当内部引入 js 文件时, script 标签 置于 head 标签 中时, Network 线程 下载 js 文件将会阻塞 HTML 的解析, 导致页面在 JavaScript 下载并执行完之前始终出现白屏状态;
- <script></script>置于 <body></body> 中:
将 script 标签 移到body 尾部:
<!doctype html>
<html>
<head>
<title> 钻研精力 </title>
</head>
<body>
<p> 你好, 小枫 </p>
<script src="./index.js"></script>
</body>
</html>
接着应用 Performance 进行网页剖析:
如图所示, HTML 自上而下解析, 进行 ParseHTML, 遇到引入内部 js 的script 标签, Network 线程 开始下载 js 文件, 网页的渲染同时进行, 直到 js 文件下载实现, 开始执行 JavaScript; 整个过程中, 网页在 JavaScript 执行结束前先呈现出内容, 因为在 js 文件下载之前, 浏览器曾经实现 HTML 的解析工作, 而在 js 文件下载过程中, 网页的渲染并没有因而阻塞, 能够得出结论:
“ 当应用 script 标签引入内部 js 文件时, js 文件下载并不阻塞网页的渲染, 而是阻塞 HTML 的解析!”
为了充沛验证上述论断, 咱们在 script 标签后加多一个元素:
<!doctype html>
<html>
<head>
<title> 钻研精力 </title>
</head>
<body>
<p> 你好, 小枫 </p>
<script src="./index.js"></script>
<p> 你好, 小枫 </p>
</body>
</html>
察看页面变动并应用 Performance 剖析网页:
如上图, 网页先展现出第一行文本, 第二行文本并未呈现, 此时 script 标签仍出于下载内部 js 文件阶段, 说 Network 线程 下载 js 文件的过程阻塞第二行文本的解析, 而第一行文本能失常出现进去, 阐明这一过程并没有阻塞网页的渲染;
一段时间过后, JavaScript 下载并执行结束, 此时第二行文字也显示进去;
总结
联合下面的验证, 能够得出结论:
- JavaScript 的执行 会阻塞 HTML 的解析渲染;
- 当应用 script 标签引入内部 js 文件时, Network 线程 会阻塞 HTML 的解析, 但不会阻塞 HTML 的渲染;
因而网上大部分的观点是谬误的, JavaScript 执行的确会阻塞 HTML 的解析渲染, 若是以 嵌入的形式 引入 JavaScript, 不论 script 标签 是放在 head 标签中 或是 body 标签尾部, 页面都会因为 JavaScript 的执行而继续白屏; 而在 引入内部 js 文件 的状况, 因为 Network 线程 下载内部 js 文件仅阻塞 HTML 的解析而不会阻塞 HTML 的渲染, script 标签 置于 body 标签尾部 能够防止因为 js 文件下载工夫太长导致的页面继续白屏!