肉烂在锅里

个人站

我是软件研发中心培训主管,我喜欢看动漫,学习web前端开发。


js和css造成的阻塞情况研究总结

前言

关于js和css阻塞问题之前一直一知半解,今天特花时间查阅资料进行总结性研究。

虽然看上却网上对js和css阻塞问题的文章有很多,看上去写的也非常好,但是总有一些细节还是领悟不到,可能不同人的盲点都是不同的。今天的研究成果让我感觉浏览器也是一个很实在的工具。 什么时候该做什么,什么时候不该做什么,什么时候能做什么都分的很清,很注重效率。

网上的很多关于这方面的结论都有不同说法,这也让我一脸懵逼,比如有人说js的下载会阻塞后面资源的加载,也有说不会的。但是后来发现他们说的其实都是对的,只不过是浏览器理论上操作的和实际上的操作不同罢了。

本篇文章虽然是纯文字描述,但是特意按一种直白,且详细的白话描述,作为我的研究总结。

一、CSS造成的阻塞和不阻塞关系

  1. 浏览器在接收到html并开始解析后,会从上到下的逐行解析token形成DOM树。

理论上,只有DOM树和CSSOM结合生成render树后才会进行渲染。但是为了提高性能,现代浏览器在没有遇见css文件之前就会认为没有css样式的参与,就直接的将DOM结果渲染到页面上。

  1. 在遇到CSS内容时,如果是link标签引入的,那么就立即下载link标签引入的外部样式表,在拿到样式表后,浏览器会另起一个进程开始解析CSS代码去生成CSSOM。解析CSS的进程和解析DOM的过程是同时进行的,也就是说,在请求过程以及形成CSSOM的过程,浏览器还是可以继续解析DOM的。但是值得注意的是,浏览器在发现有CSS代码的乱入时(不管是Link标签还是style标签),就知道了这里要有样式的引入,所以我不能继续将DOM直接渲染到页面上,我应该等这里的css解析完毕,并生成render树后在继续渲染。

也就是说CSS的加载是指脚本的下载和解析成CSSOM的过程。CSS的加载(下载和解析)是不会阻塞浏览器解析DOM的,但是会阻塞DOM的渲染

当style标签或者link标签混入到DOM结构中时,在速度足够慢的时候,我们凭借肉眼就会发现一个问题,DOM被渲染在了页面上没有CSS样式,不久之后DOM的样式突然出现。

之前提到过,当现代浏览器没有遇见CSS相关的代码时,就会认为不需要CSS的参与,为了尽快将内容呈现,他会直接把DOM渲染在页面上。可是当发现link标签和style标签的时候,浏览器才意识到,原来这里还有样式需要参与,于是乎就赶紧暂停DOM的渲染,等拿到render树之后在渲染,可是在这之前就已经有一部分DOM被渲染,此时等拿到CSS样式解析成CSSOM,形成render树之后,浏览器也只能无奈的重新渲染之前的部分,同时还要把下面没渲染的DOM渲染上去,事倍功半。

但是如果我们把所有样式部分都写入head标签内,就不会出新这种问题,因为link标签和style标签在浏览器真正渲染body部分的DOM之前(对于link标签,style,script浏览器不会把他们作为DOM的一部分进行渲染),浏览器及时的意识到了样式表的存在,因此他不会贸然的继续渲染DOM,他只把手头能干的干了(继续解析DOM,等待和CSSOM结合形成render树)。这样在css加载好之前是不会出现首屏样式闪出的问题。提升了用户体验,也不用让浏览器返功,事半功倍。因此将style,link写入head中是可以提高性能的。

二、Javascript造成的阻塞和不阻塞关系

  1. JS的V8引擎和CSS的解析引擎以及DOM的解析和渲染的引擎都是互斥的。理论上,当浏览器发现script标签后,就会暂停并整理之前做过的工作(因为js强依赖当前的DOM状态),换句话说,如果在script前面的样式信息或者DOM渲染没有处理完毕(没请求回来,或者没解析完成),浏览器就会停下来等之前的所有工作都处理之后在去请求js文件,之后会启动V8引擎去解析javascript代码,如果没有使用src引入外部文件,就直接用V8引擎去解析javascript。解析javascript的过程中浏览器不会在做任何其他事情(V8和其他引擎互斥)。

所以理论上,javascript的加载(下载和执行)是会阻塞DOM的解析和渲染,也会阻塞下面任何javascript和CSS的下载和执行。因此反过来说也对,CSS的加载也会阻塞javascript的加载。

但是事实上,现代浏览器为了提高性能,他们内部存在一个预扫描类,可以在阻塞发生前,先扫描以后只有的外部资源,把阻塞的外部资源提前下载。这也是很理所当然的事情,因为阻塞解析和执行可能很必要,但是阻塞资源的下载就没有必要了,我们完全而已在阻塞期间把之后需要下载的内容先下载好,省的阻塞过去后还是要请求,从而浪费时间。因此就会造成,js的加载不阻塞后部任何资源的下载(仅限下载没有解析),同样css的加载也不会阻塞后面任何资源的下载(包括js)。

需要注意的是,当请求的资源太多的时候,有时可能会发现部分js、css还是阻塞了下面资源的加载,这并不是浏览器引擎造成的,是因为浏览器限制我们对同一个url同时请求的个数。剩下的请求只能等下轮。(谷歌浏览器限制对同时对同一个url请求数为6)。因为无论js还是css还是img,如果资源所在cdn相同,他们就会受到同时请求的数量限制。

三、defer & async

  1. 为script脚本设置defer和async可以理所当然的改变js的执行时机。但是得益于现代浏览器的预扫描能力,defer和async带来的异步下载能力虽并没有体现出来,但他们控制js的执行时机确实十分强大的,我们也可以通过设置defer来让dom的解析先行。

defer会在DOMContentLoad事件触发前执行,写有defer的外部js,会立刻下载,并不阻塞后面任何资源的加载,等他们下载完也不能立刻得到V8引擎的关照,必须等到DOM树被构建完成(DOM解析完成)之后,浏览器才会启用V8引擎按书写顺序去执行相关js脚本。因此在defer中访问dom是可以访问到的。(但是并不保证图片和音频什么的也加载完成)。

不同于defer,async是H5新增的特性。他同样可以让js资源下载,并不阻塞后面任何资源的加载,但是他和defer不同的地方是,只要某个写了async的js资源下载完成,浏览器就立刻停下手中的工作去执行这个js(当然也要等待手头正在进行的工作处理完,不要忘记js是强依赖当前的DOM状态的)。这同样也无法保证async的脚本会和defer一样按书写顺序执行,而是谁先加载完就执行谁,async脚本中的dom也不能保证会获取的到。

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦