今天在作一个小需的时候,突然看到前辈一句吊炸天的代码javascript
<script src="#link("xxxx/xx/home/home.js")" type="text/javascript" async defer></script>
卧槽,居然同时有async
和defer
属性,心想着确定是前辈老司机的什么黑科技,两个一起确定会发生什么神奇化学反应,因而赶忙怀着一颗崇敬的心去翻书翻文档,先复习一下各自的定义。css
先看看async
和defer
各自的定义吧,翻开红宝书望远镜,是这么介绍的html
这个属性的用途是代表脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。所以,在
<script>
元素中设置defer
属性,至关于告诉浏览器当即下载,但延迟执行。html5HTML5规范要求脚本按照它们出现的前后顺序执行,所以第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于
DOMContentLoaded
事件执行。在现实当中,延迟脚本并不必定会按照顺序执行,也不必定会在DOMContentLoad
时间触发前执行,所以最好只包含一个延迟脚本。java
这个属性与
defer
相似,都用于改变处理脚本的行为。一样与defer
相似,async
只适用于外部脚本文件,并告诉浏览器当即下载文件。但与defer
不一样的是,标记为async
的脚本并不保证按照它们的前后顺序执行。jquery第二个脚本文件可能会在第一个脚本文件以前执行。所以确保二者之间互不依赖很是重要。指定
async
属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其余内容。git
归纳来说,就是这两个属性都会使script标签异步加载,然而执行的时机是不同的。引用segmentfault上的一个回答中的一张图蓝色线表明网络读取,红色线表明执行时间,这俩都是针对脚本的;绿色线表明 HTML 解析。github
也就是说async
是乱序的,而defer
是顺序执行,这也就决定了async
比较适用于百度分析或者谷歌分析这类不依赖其余脚本的库。从图中能够看到一个普通的<script>
标签的加载和解析都是同步的,会阻塞DOM的渲染,这也就是咱们常常会把<script>
写在<body>
底部的缘由之一,为了防止加载资源而致使的长时间的白屏,另外一个缘由是js可能会进行DOM操做,因此要在DOM所有渲染完后再执行。web
然而,这张图(几乎是百度搜到的惟一答案)是不严谨的,这只是规范的状况,大多数浏览器在实现的时候会做出优化。chrome
来看看chrome是怎么作的
《WebKit技术内幕》:
当用户输入网页URL的时候,WebKit调用其资源加载器加载该URL对应的网页。
加载器依赖网络模块创建链接,发送请求并接受答复。
WebKit接收到各类网页或者资源的数据,其中某些资源多是同步或异步获取的。
网页被交给HTML解释器转变成一系列的词语(Token)。
解释器根据词语构建节点(Node),造成DOM树。
若是节点是JavaScript代码的话,调用JavaScript引擎解释并执行。
JavaScript代码可能会修改DOM树的结构。
若是节点须要依赖其余资源,例如图片、CSS、视频等,调用资源加载器来加载他们,可是他们是异步的,不会阻碍当前DOM树的继续建立;若是是JavaScript资源URL(没有标记异步方式),则须要中止当前DOM树的建立,直到JavaScript的资源加载并被JavaScript引擎执行后才继续DOM树的建立。
因此,通俗来说,chrome浏览器首先会请求HTML文档,而后对其中的各类资源调用相应的资源加载器进行异步网络请求,同时进行DOM渲染,直到遇到<script>
标签的时候,主进程才会中止渲染等待此资源加载完毕而后调用V8引擎对js解析,继而继续进行DOM解析。个人理解若是加了async
属性就至关于单独开了一个进程去独立加载和执行,而defer
是和将<script>
放到<body>
底部同样的效果。
为了验证上面的结论咱们来测试一下
<!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进行验证。
<head>
内
异步加载资源,但会阻塞<body>
的渲染会出现白屏,按照顺序当即执行脚本
<body>
底部
异步加载资源,等<body>
中的内容渲染完毕后且加载完按顺序执行JS
<head>
头部并使用async
异步加载资源,且加载完JS资源当即执行,并不会按顺序,谁快谁先上
<head>
头部并使用defer
异步加载资源,在DOM渲染后以后再按顺序执行JS
<head>
头部并同时使用async
和defer
表现和async
一致,开了个脑洞,把这两个属性交换一下位置,看会不会有覆盖效果,结果发现是一致的 = =、
综上,在webkit引擎下,建议的方式仍然是把<script>
写在<body>
底部,若是须要使用百度谷歌分析或者不蒜子等独立库时可使用async
属性,若你的<script>
标签必须写在<head>
头部内可使用defer
属性
那么,揣摩一下前辈的心理,同时写上的缘由是什么呢,兼容性?
上caniuse,async在IE<=9时不支持,其余浏览器OK;defer在IE<=9时支持但会有bug,其余浏览器OK;现象在这个issue里有描述,这也就是“望远镜”里建议只有一个defer
的缘由。因此两个属性都指定是为了在async
不支持的时候启用defer
,但defer
在某些状况下仍是有bug。
The defer attribute may be specified even if the async attribute is specified, to cause legacy Web browsers that only support defer (and not async) to fall back to the defer behavior instead of the synchronous blocking behavior that is the default.
其实这么讲来,最稳妥的办法仍是把<script>
写在<body>
底部,没有兼容性问题,没有白屏问题,没有执行顺序问题,高枕无忧,不要搞什么defer
和async
的花啦~
目前只研究了chrome的webkit的渲染机制,Firefox和IE的有待继续研究,图片和CSS以及其余外部资源的渲染有待研究。
更多信息在 这里