看到的前辈写的代码如下
<script src="#link("xxxx/xx/home/home.js")" type="text/javascript" async defer></script>
竟然同时有async和defer属性,心想着肯定是前辈老司机的什么黑科技,两个一块儿肯定会发生什么神奇化学反应,于是赶紧怀着一颗崇敬的心去翻书翻文档,先复习一下各自的定义。
二、调查一番
先看看async和defer各自的定义吧,翻开红宝书望远镜,是这么介绍的
2.1 defer
这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素中设置defer属性,相当于告诉浏览器立即下载,但延迟执行。
HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件执行。在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoad时间触发前执行,因此最好只包含一个延迟脚本。
2.2 async
这个属性与defer类似,都用于改变处理脚本的行为。同样与defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer不同的是,标记为async的脚本并不保证按照它们的先后顺序执行。
第二个脚本文件可能会在第一个脚本文件之前执行。因此确保两者之间互不依赖非常重要。指定async属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。
概括来讲,就是这两个属性都会使script标签异步加载,然而执行的时机是不一样的。引用segmentfault上的一个回答中的一张图
蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。
也就是说async是乱序的,而defer是顺序执行,这也就决定了async比较适用于百度分析或者谷歌分析这类不依赖其他脚本的库。从图中可以看到一个普通的<script>标签的加载和解析都是同步的,会阻塞DOM的渲染,这也就是我们经常会把<script>写在<body>底部的原因之一,为了防止加载资源而导致的长时间的白屏,另一个原因是js可能会进行DOM操作,所以要在DOM全部渲染完后再执行。
2.3 really?
然而,这张图(几乎是百度搜到的唯一答案)是不严谨的,这只是规范的情况,大多数浏览器在实现的时候会作出优化。
来看看chrome是怎么做的
《WebKit技术内幕》:
1、当用户输入网页URL的时候,WebKit调用其资源加载器加载该URL对应的网页。
2、加载器依赖网络模块建立连接,发送请求并接受答复。
3、WebKit接收到各种网页或者资源的数据,其中某些资源可能是同步或异步获取的。
4、网页被交给HTML解释器转变成一系列的词语(Token)。
5、解释器根据词语构建节点(Node),形成DOM树。
6、如果节点是JavaScript代码的话,调用JavaScript引擎解释并执行。
7、JavaScript代码可能会修改DOM树的结构。
8、如果节点需要依赖其他资源,例如图片、CSS、视频等,调用资源加载器来加载他们,但是他们是异步的,不会阻碍当前DOM树的继续创建;如果是JavaScript资源URL(没有标记异步方式),则需要停止当前DOM树的创建,直到JavaScript的资源加载并被JavaScript引擎执行后才继续DOM树的创建。
所以,通俗来讲,chrome浏览器首先会请求HTML文档,然后对其中的各种资源调用相应的资源加载器进行异步网络请求,同时进行DOM渲染,直到遇到<script>标签的时候,主进程才会停止渲染等待此资源加载完毕然后调用V8引擎对js解析,继而继续进行DOM解析。我的理解如果加了async属性就相当于单独开了一个进程去独立加载和执行,而defer是和将<script>放到<body>底部一样的效果。
三、实验一发
3.1 demo
为了验证上面的结论我们来测试一下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link href="https://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.css"> <link href="https://cdn.staticfile.org/foundation/6.0.1/css/foundation.css"> <script src="https://lib.sinaapp.com/js/angular.js/angular-1.2.19/angular.js"></script> <script src="https://libs.baidu.com/backbone/0.9.2/backbone.js"></script> <script src="https://libs.baidu.com/jquery/2.0.0/jquery.js"></script> </head> <body> ul>li{这是第$个节点}*1000 </body> </html>