1. 什么鬼
今天在做一个小需的时候,忽然看到前辈一句吊炸天的代码
<script src="#link("xxxx/xx/home/home.js")" type="text/javascript" async defer></script>
卧槽,竟然同时有async
和defer
属性,心想着肯定是前辈老司机的什么黑科技,两个一块儿肯定会发生什么神奇化学反应,于是赶紧怀着一颗崇敬的心去翻书翻文档,先复习一下各自的定义。
2. 调查一番
先看看async
和defer
各自的定义吧,翻开红宝书望远镜,是这么介绍的
2.1 defer
2.2 async
概括来讲,就是这两个属性都会使script标签异步加载,然而执行的时机是不一样的。引用segmentfault上的一个回答中的一张图蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。
也就是说async
是乱序的,而defer
是顺序执行,这也就决定了async
比较适用于百度分析或者谷歌分析这类不依赖其他脚本的库。从图中可以看到一个普通的<script>
标签的加载和解析都是同步的,会阻塞DOM的渲染,这也就是我们经常会把<script>
写在<body>
底部的原因之一,为了防止加载资源而导致的长时间的白屏,另一个原因是js可能会进行DOM操作,所以要在DOM全部渲染完后再执行。
2.3 really?
然而,这张图(几乎是百度搜到的唯一答案)是不严谨的,这只是规范的情况,大多数浏览器在实现的时候会作出优化。
来看看chrome是怎么做的
所以,通俗来讲,chrome浏览器首先会请求HTML文档,然后对其中的各种资源调用相应的资源加载器进行异步网络请求,同时进行DOM渲染,直到遇到<script>
标签的时候,主进程才会停止渲染等待此资源加载完毕然后调用V8引擎对js解析,继而继续进行DOM解析。我的理解如果加了async
属性就相当于单独开了一个进程去独立加载和执行,而defer
是和将<script>
放到<body>
底部一样的效果。
3. 实验一发
3.1 demo
为了验证上面的结论我们来测试一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.css" rel="stylesheet">
<link href="http://cdn.staticfile.org/foundation/6.0.1/css/foundation.css" rel="stylesheet">
<script src="http://lib.sinaapp.com/js/angular.js/angular-1.2.19/angular.js"></script>
<script src="http://libs.baidu.com/backbone/0.9.2/backbone.js"></script>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
</head>
<body>
ul>li{这是第$个节点}*1000
</body>
</html>
一个简单的demo,从各个CDN上引用了2个CSS3个JS,在body里面创建了1000个li。通过调整外部引用资源的位置和加入相关的属性利用chrome的Timeline进行验证。
3.2 放置在<head>
内
异步加载资源,但会阻塞<body>
的渲染会出现白屏,按照顺序立即执行脚本
3.3 放置在<body>
底部
异步加载资源,等<body>
中的内容渲染完毕后且加载完按顺序执行JS
3.3 放置在<head>
头部并使用async
异步加载资源,且加载完JS资源立即执行,并不会按顺序,谁快谁先上
3.4 放置在<head>
头部并使用defer
异步加载资源,在DOM渲染后之后再按顺序执行JS
3.5 放置在<head>
头部并同时使用async
和defer
表现和async
一致,开了个脑洞,把这两个属性交换一下位置,看会不会有覆盖效果,结果发现是一致的 = =、
综上,在webkit引擎下,建议的方式仍然是把<script>
写在<body>
底部,如果需要使用百度谷歌分析或者不蒜子等独立库时可以使用async
属性,若你的<script>
标签必须写在<head>
头部内可以使用defer
属性
4. 兼容性
那么,揣摩一下前辈的心理,同时写上的原因是什么呢,兼容性?
上caniuse,async在IE<=9时不支持,其他浏览器OK;defer在IE<=9时支持但会有bug,其他浏览器OK;现象在这个issue里有描述,这也就是“望远镜”里建议只有一个defer
的原因。所以两个属性都指定是为了在async
不支持的时候启用defer
,但defer
在某些情况下还是有bug。
5. 结论
其实这么讲来,最稳妥的办法还是把<script>
写在<body>
底部,没有兼容性问题,没有白屏问题,没有执行顺序问题,高枕无忧,不要搞什么defer
和async
的花啦~
目前只研究了chrome的webkit的渲染机制,Firefox和IE的有待继续研究,图片和CSS以及其他外部资源的渲染有待研究。
更多信息在 这里