Script 标签的 defer 和 async 属性的异同
背景
随着业务功能的增加,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 标签都会异步加载脚本。这是它们共同的特性,那么它们之间又有什么不同的地方呢?
它们不同的地方体现在两个方面:
- 脚本的执行是不是紧跟着加载发生的?
- 如果文档中有多个设置了相同标志位的 script 标签,它们的加载顺序会是如何?
经过查阅资料和亲身实践,我们可以有以下的结论:
对于 defer 标志位而言:
- 添加了 defer 标志位的 script 标签所加载回来的脚本会在HTML解析完之后,DOMContendLoaded 事件发生之前执行。
- 如果文档中有多个设置了defer 标志位的 script 标签的话,它们会按照在文档出现的顺序来加载和执行的。
对于 async 标志位而言:
- 添加了async标志位的script标签所加载回来的脚本会紧接着就执行了。
- 如果文档中有多个设置了async标志位的script标签的话,它们不会按照在文档出现的顺序来加载和执行的,它们都是乱序的主。
从上面两个结论来看,使用了defer和async标志为的script标签所引用的脚本虽然在加载阶段都不会阻塞HTML解析,但是设置了async标志位的script标签还是会在紧接着的执行阶段阻塞了HTML解析。并且由于它是乱序的主,所以它不能满足各个类库在依赖管理方面的需求。
总结
综上所述,通过调整外部脚本的加载和执行次序来优化首屏渲染速度诸多方案中,为 script 标签添加 defer 标志位无疑是最优的。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论