一个简单的例子了解async跟defer

script标签

当我们要在页面当中引入脚本的时候,一般用的是script标签(i.e. <script>)。很多人对script标签的第一印象就是- 阻塞。在High Performance Web Sites 雅虎军规第6条中也提到尽量把script脚本放在body尾部。通过一个小例子我们看看script放在不同位置所产生的不同效果最后得出一丢丢优化结论。

首先明确一点,当在html页面里引入script时,浏览器做了2件事情:

  • 获取/加载脚本内容,这部分不会阻塞!
  • 运行获取的脚本内容,会阻塞!

假设我们有2个脚本:

//script1.js
let t1 = +new Date;
console.log('script1 is loading at', t1);

console.log('script1 element', document.getElementById('load-experiment'));
while((+new Date) - t1 < 1000) {
    // delay 1 seconds
}
console.log('script1 finishes loading at', +new Date);复制代码
//script2.js
let t2 = +new Date;
console.log('script2 is loading at', t2);

console.log('script2 element', document.getElementById('load-experiment'));
while((+new Date) - t2 < 2000) {
    // delay 2 seconds
}
console.log('script2 finishes loading at', +new Date);复制代码

把script标签都放head里

<!--all-in-head.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
        <script src='./script1.js'></script>
        <script src='./script2.js'></script>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
    </body>
</html>复制代码

console里的输出为:

script1 is loading at 1496747869008
script1.js:4 script1 element null
script1.js:8 script1 finishes loading at 1496747870008
script2.js:2 script2 is loading at 1496747870009
script2.js:4 script2 element null
script2.js:8 script2 finishes loading at 1496747872009复制代码

发现:

  • 用浏览器打开这个html会发现有明显的空白时间。这是因为script1跟script2的 执行 阻塞了DOM的加载。
  • script脚本执行的时候并不能获取DOM元素(getElementById返回null)。验证了上一条
  • script之间也是相互阻塞的。script2是等script1执行好后才执行。

把script标签都放body尾部

这也是雅虎军规High Performance Web Sites第6条里的建议。

<!--all-in-body.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
        <script src='./script1.js'></script>
        <script src='./script2.js'></script>
    </body>
</html>复制代码

console里的输出为:

script1 is loading at 1496751597679
script1.js:4 script1 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script1.js:8 script1 finishes loading at 1496751598679
script2.js:2 script2 is loading at 1496751598680
script2.js:4 script2 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script2.js:8 script2 finishes loading at 1496751600680复制代码

发现:

  • 用浏览器打开这个html不会有明显的空白时间。这是因为script1跟script2的发生在DOM加载后。
  • script脚本执行的时候能获取DOM元素(getElementById返回节点)。验证了上一条
  • script之间是相互阻塞的。script2是等script1执行好后才执行。

把所有的script脚本放body尾部的情况过于理想化了。例如body里有些内联JS需要引用到的功能代码就需要放head里提前运行好。最常见的例子就是计算首屏加载时间(Above The Fold),首屏加载时间肯定是不能等DOM加载完。这时候一般是在head里加载功能库并触发首屏加载开始计时,然后在首屏的末尾处用内联JS触发首屏加载结束计时。这时候就需要有些代码是在head里的。

script标签相应地放head跟body里

根据需要把script脚本相应地放在head跟body里。这是最常见的情况。

<html> 
    <head> 
        <script src="headScripts.js"></scripts> 
    </head> 
    <body>
        <h1 id='load-experiment'> hello world </h1>
        <script src="bodyScripts.js"></script> 
    </body>
</html>复制代码

defer!

把script放body尾的话有个缺点就是要等DOM加载好了才会去加载并执行script脚本。前面开头的时候说过获取/加载脚本内容不会阻塞,只有运行脚本内容的时候才会阻塞!如果能在加载DOM的同时加载script脚本,等到DOM解析好了再运行脚本,这样就可以节省部分加载脚本的时间。当脚本比较大时提高的效率会很可观。于是就有了script标签里的defer属性: 并行加载该脚本,直到DOM解析完了并且它前面含有defer的脚本都运行完后,才可以运行该脚本。

<!--defer.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
        <script defer src='./script1.js'></script>
        <script defer src='./script2.js'></script>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
    </body>
</html>复制代码

console里的输出为:

script1 is loading at 1496760312686
script1.js:4 script1 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script1.js:8 script1 finishes loading at 1496760313686
script2.js:2 script2 is loading at 1496760313686
script2.js:4 script2 element <h1 id=​"load-experiment">​ hello world ​</h1>​
script2.js:8 script2 finishes loading at 1496760315686复制代码

发现:

  • 跟把script放body的情况输出一样。从可以获取到DOM元素可以看出它会等到DOM解析好再运行。
  • 添加了defer属性的脚本是会按顺序运行的。script1先于script2运行。

async!

通常页面上会有些附加功能组件。这种组件相互独立并且不是该页面必需组件。也就是说该种脚本无运行顺序要求(因为独立)并且就算加载或者运行失败也不会对页面造成致命影响的。例如页面上的评论功能跟聊天功能。

<!--async.html-->
<html>
    <head>
        <title> test js tag async and defer attributes</title>
    </head>
    <body>
        <h1 id='load-experiment'> hello world </h1>
        <script async src='./script1.js'></script>
        <script async src='./script2.js'></script>
    </body>
</html>复制代码

多次刷新页面可以看到每次输出结果不一样。体现在:

  • scrip1跟script2的执行顺序在变
  • 获取DOM元素的结果在变。有时能获取到有时获取不到

页面上加了async的脚本无法保证其运行顺序,这通常也是隐藏bug的来源。除非你确定该脚本跟页面其他脚本无依赖关系,不然慎用async吧。

总结

综上所述,最后引入脚本的形式大概是这样滴:

<html> 
    <head> 
        <!--headScripts.js为必须在DOM解析前加载并执行的脚本-->
        <script src="headScripts.js"></scripts> 
        <!--bodyScripts.js加个defer,先加载。等DOM解析好了再执行-->
        <script defer src="bodyScripts.js"></script> 
    </head> 
    <body>
        <!--body内容-->
        <h1 id='load-experiment'> hello world </h1>
        <!--独立的组件,不依赖DOM, 锦上添花型的,可用async-->
        <script async src="forumWidget.js"></script>
        <script async src="chatWidget.js"></script>
    </body>
</html>复制代码

代码示例

Reference

Notice

  • 如果您觉得该Repo让您有所收获,请「Star 」支持楼主。
  • 如果您想持续关注楼主的最新系列文章,请「Watch」订阅
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值