某天, 小枫在网上浏览前端性能优化的相关文章, 文章中指出: 将JavaScript的引入置于
尾部小枫思索了一会, 整个人都自闭了...于是乎, 接着上网查阅资料:
为什么JavaScript的引入要置于
尾部?查了一圈之后发现, 网上普遍的观点如下:JavaScript执行会阻塞HTML的渲染, 因此将JavaScript的引入置于
尾部, 可以使页面先呈现出来, 避免JavaScript的执行导致页面白屏;出于钻研的精神, 小枫决定自己来验证这个结论, 请给小枫点个赞谢谢(是字体自己加粗的, 逃0.0)
大家都知道, chrome(文章使用的是版本为: 86.0.4240.80)是个神器, 自带性能调优工具, 可以记录分析网页的渲染流程: 按"F12"或是"鼠标右键+检查"打开控制台, 选择Performance栏, 如下图;
我们先新建一个简单的HTML文件, 使用Performance分析页面的渲染流程:
钻研精神你好, 小枫
从上图可以看出, 网页的渲染流程大致如下:Parse HTML 该阶段生成了DOM Tree和CSSOM Tree;
Layout 将DOM Tree结合CSSOM Tree, 生成Layout Tree(又称Render Tree), 计算每个元素的尺寸和位置;
Update Layout Tree 更新Layout Tree;
Paint 生成PaintLayout Tree记录元素绘制顺序;
Composite 合成视图输出到屏幕;
简单理解网页渲染流程之后, 我们来对问题进行验证, 列举以下两种情况:置于
中:钻研精神for (let i = 0; i < 100000; i++) {
for (let j = 0; j < 100000; j++) {}
}
你好, 小枫
在上面的html文件中, 我们将置于
中, 中是一段计算量很大的JavaScript, 值得注意的是, 这里的JavaScript是以嵌入的形式存在于HTML中, 并非使用src属性进行外部引入; 接着在浏览器打开该html文件, 并使用Performance进行分析:script标签置于head标签中
如上图所示, ParseHTML阶段一直呈阻塞状态, 直到Evalute Script阶段结束才重新开始ParseHTML, 接着进行Layout以及Paint等渲染页面流程; 在这个过程中页面一直呈现白屏状态, 直到JavaScript加载完成才有内容展示; 可以看出, JavaScript的执行阻塞了HTML的解析和渲染;置于
中:钻研精神你好, 小枫
for (let i = 0; i < 100000; i++) {
for (let j = 0; j < 100000; j++) {}
}
接着, 我们把放在
尾部, 此处的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的形式, 开启下一轮验证:置于
中:将计算量很大的代码移至js文件: index.jsfor (let i = 0; i < 100000; i++) {
for (let j = 0; j < 100000; j++) {}
}
并在html中引入 index.js:
钻研精神你好, 小枫
接着, 使用Performance对网页进行分析:
如上图, 浏览器先调用Network线程下载HTML文件, 之后对HTML进行自上往下解析工作, 直到遇到
中的script标签, 由于script标签引入的是外部js文件, 浏览器接着调用Network线程下载目标js文件, 可以看到, 这一过程中并没有进行ParseHTML;下载完js文件之后, 浏览器开始执行JavaScript, 等待代码执行完毕之后, 开始ParseHTML, Layout以及Paint等网页解析渲染操作; 整个流程, 网页一直呈现白屏状态, 直到JavaScript执行完毕, 也就是说, 当外部引入js文件时, script标签置于head标签中时, Network线程下载js文件将会阻塞HTML的解析, 导致页面在JavaScript下载并执行完之前一直呈现白屏状态;置于
中:将script标签移到body尾部:
钻研精神你好, 小枫
接着使用Performance进行网页分析:
如图所示, HTML自上而下解析, 进行ParseHTML, 遇到引入外部js的script标签, Network线程开始下载js文件, 网页的渲染同时进行, 直到js文件下载完成, 开始执行JavaScript; 整个过程中, 网页在JavaScript执行完毕前先呈现出内容, 因为在js文件下载之前, 浏览器已经完成HTML的解析工作, 而在js文件下载过程中, 网页的渲染并没有因此阻塞, 可以得出结论:
"当使用script标签引入外部js文件时, js文件下载并不阻塞网页的渲染, 而是阻塞HTML的解析!"
为了充分验证上述结论, 我们在script标签后加多一个元素:
钻研精神你好, 小枫
你好, 小枫
观察页面变化并使用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文件下载时间太长导致的页面持续白屏!