libin1991 / libin_Blog

爬虫-博客大全
https://libin.netlify.com/
124 stars 17 forks source link

【面试】script标签的defer和async的异同 #510

Open libin1991 opened 6 years ago

libin1991 commented 6 years ago

背景

随着业务功能的增加,Web App的“身躯”变得越来越臃肿,性能优化迫在眉睫,而首当其冲的是首屏渲染速度的问题。

正文

我们常常提及首屏渲染速度,但是鲜有人去给它下一个确切的定义。在我的理解里面,这里面的速度并不是指我们狭义里的速度,而是广义上的。因为我们无法准确地统计网络传输所经过的物理里程,故不能用v=s/t去计算。那么如何广义法呢?其实,我们可以将首屏渲染速度等同为首屏渲染时间来理解。可以这么说,从用户在浏览器地址栏敲入一个url到用户看到一个完整的页面,这个过程所花费的时间就是首屏渲染时间。关系到这个首屏渲染时间的长短有很多因素,比如说,DNS解析时间网络传输时间页面渲染时间等等。今天,我们就挑其中的一个子环节页面渲染来说说。页面渲染是指从浏览器接收到从服务器返回的HTML文件那一刻算起,到你眼睛看到一个完整页面为止的过程。对于前端开发来说,做首屏渲染速度的优化往往会选择在页面渲染这个过程发力。通过优化页面渲染过程,我们可以减少页面处于白屏状态的时间,从而达到一定的首屏渲染速度优化效果。

上面只是提到了页面渲染的一个很表象的说法。其实页面渲染这个过程包含了一系列的环节,我可以简单地描述为(如下图):解析HTML构建DOM树;解析CSS,构建CSSOM tree;然后将DOM tree和CSSOM tree合并为render tree。再然后使用render tree所提供的数据进行布局(layout),最后是将整个页面绘制(paint)到屏幕上。而今天我们的话题跟这其中一个环节-HTML解析有关。

浏览器在解析HTML文档的时候,遇到常规的script标签会停下来,转而去加载所请求的脚本,紧跟着执行加载回来的脚本。换句话说,使用常规script标签去请求外部脚本的话,会阻塞当前的HTML解析。而阻塞当前的HTML解析,就是阻塞整个页面渲染的过程。所以,我们可以做的就是使得script标签引用外部脚本的时候不要阻塞HTML解析。

为了达到这个目的,我们通常会采用以下的方案:

  • 把常规的script标签放置到</ body>标签前。
  • 给script标签添加defer,async标志位,使它的加载与HTML解析并行进行。

到这里,我们就引出了我们今天的两个主角:defer和async。正如上面所说的,添加了这个标志位的script标签都会异步加载脚本。这是它们共同的特性,那么它们之间又有什么不同的地方呢?

它们不同的地方体现在两个方面:

  1. 脚本的执行是不是紧跟着加载发生的?
  2. 如果文档中有多个设置了相同标志位的script标签,它们的加载顺序会是如何?

经过查阅资料和亲身实践,我们可以有以下的结论:

对于defer标志位而言:

  1. 添加了defer标志位的script标签所加载回来的脚本会在HTML解析完之后,DOMContendLoaded事件发生之前执行。
  2. 如果文档中有多个设置了defer标志位的script标签的话,它们会按照在文档出现的顺序来加载和执行的。

对于async标志位而言:

  1. 添加了async标志位的script标签所加载回来的脚本会紧接着就执行了。
  2. 如果文档中有多个设置了async标志位的script标签的话,它们不会按照在文档出现的顺序来加载和执行的,它们都是乱序的主。

从上面两个结论来看,使用了defer和async标志为的script标签所引用的脚本虽然在加载阶段都不会阻塞HTML解析,但是设置了async标志位的script标签还是会在紧接着的执行阶段阻塞了HTML解析。并且由于它是乱序的主,所以它不能满足各个类库在依赖管理方面的需求。

正所谓一图胜万言。常规script标签,defer标签和async标签的加载和执行过程跟HTML解析过程的关系如下图:

总结

综上所述,通过调整外部脚本的加载和执行次序来优化首屏渲染速度诸多方案中,为script标签添加defer标志位无疑是最优的。