简介

        本文介绍浏览器的页面的渲染阻塞的场景及其优化方案。

        CSS和JavaScript都会阻塞浏览器的渲染,本文分这两个方面介绍。

CSS阻塞渲染


  • CSS不会阻塞DOM的解析。
  • 原因:HTML的DOM的解析与CSS解析是独立进行的,所以CSS解析不会阻塞DOM的解析。
  • CSS可能会阻塞渲染。
  • 不同的CSS的引入方式结果不同。

阻塞的场景




style标签中的样式



link引入的样式



解析器



由HTML解析器解析



由CSS解析器解析



是否阻塞DOM解析







是否阻塞渲染



不阻塞浏览器渲染(可能会产生“闪屏现象”)



阻塞浏览器渲染(可以利用这种阻塞避免“闪屏现象”)



是否阻塞JS






为什么不阻塞渲染会引起闪屏呢?

        因为不阻塞渲染就导致CSS很可能在渲染结束后生效,若渲染结束时CSS和后边新生效的CSS不同,那很显然会改变显示效果。

优化方案

CSS阻塞优化方法:尽可能快的提高外部css加载速度


  1. 使用CDN节点进行外部资源加速。
  2. 对css进行压缩(利用打包工具,比如webpack,gulp等,也可以通过开启gzip压缩)。
  3. 减少http请求数,将多个css文件合并或者是干脆直接写成内联样式(内联样式的一个缺点就是不能缓存)。
  4. 优化样式表的代码

JavaScript阻塞渲染

阻塞的场景

JavaScript会阻塞DOM解析、阻塞页面渲染、阻塞后续JS的执行。

1. JS阻塞DOM解析

        浏览器不知道后续脚本的内容,如果先去解析了下面的DOM,而随后的JS删除了后面所有的DOM,那么浏览器就做了无用功,浏览器无法预估脚本里面具体做了什么操作,例如像document.write 这种操作,索性全部停住,等脚本执行完了,浏览器再继续向下解析DOM。    

2. JS阻塞页面渲染

        JS中也可以给DOM设置样式,浏览器同样等该脚本执行完毕,再继续干活,避免做无用功。

3. JS阻塞后续js的执行

        为了维护依赖关系,例如:必须先引入jQuery再引入bootstrap

优化方案

        因为JS会阻塞后续的HTML解析、渲染,所以对JS的阻塞的优化方案大致就是:将JS放到最后、异步执行。

方案1:将JS放到最后

很简单,将script标签放到HTML文件的body标签的最后即可。例如:

<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>This is title</title>
</head>

<body>

<div class="container">
这是个Demo
</div>

<script>
let name = 'hello'
</script>
</body>
</html>

方案2:defer延迟加载

<script src="app1.js" defer> </script>

        defer 属性表示延迟执行引入 JavaScript,即 JavaScript 加载时 HTML 不会停止解析,这两个过程是并行的。 整个HTML解析完毕且defer-script也加载完成之后,才会执行所有由defer-script加载的JavaScript代码,再触发 DOMContentLoaded事件。

        defer 不会改变 script 中代码的执行顺序。

        所以,defer 与相比普通 script,有两点区别:


  1. 载入 JavaScript 文件时不阻塞 HTML 的解析。
  2. 执行阶段被放到 HTML 的解析完成之后。

方案3:async异步加载

async异步加载:告诉浏览器不必等到加载完外部文件,可以边渲染边下载,什么时候下载完成什么时候执行。

写法1:标签指定

<script src="app1.js" async> </script>

写法2:用js动态创建script元素

<script type="text/javascript">
(function () {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true; //这句可以删除,默认就是true。
s.src = 'js/a.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
</script>

这种方法会阻塞onload事件 

写法3: onload事件触发时异步加载

        等html的文件,图片之类的、页面所有的资源全部加载完成后再下载执行js,这样的方法可以避免阻塞onload事件的触发。

(function() {
function async_load(){
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'js/yibujiaz.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}
if (window.attachEvent){
window.attachEvent('onload', async_load);
} else {
window.addEventListener('load', async_load, false);
}
})();

补充内容

CSS与JS

        CSS的解析和JS的执行是互斥的(互相排斥),CSS解析的时候JS停止执行,JS执行的时候CSS停止解析。

不会阻塞外部资源

        CSS阻塞和JS阻塞都不会阻塞浏览器加载外部资源(图片、视频、样式、脚本等)。

        原因:浏览器始终处于一种:“先把请求发出去” 的工作模式,只要是涉及到网络请求的内容,无论是:图片、样式、脚本,都会先发送请求去获取资源,至于资源到本地之后什么时候用,由浏览器自己协调。这种做法效率很高。

预解析

        WebKit 和 Firefox 都进行了预解析这项优化。在执行js脚本时,浏览器的其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树。