HTML5中<script>标签中的defer与async属性详解

   在HTML中执行脚本最重要的方法就是使用<script>元素,但是执行<script>元素时会阻塞后面文档的加载。

那么首先为什么会阻塞呢,是由于哪些原因呢?

其实是<script>标签中的src属性在作怪,因为一个src就相当于一次http请求,他的作用就是把src所对应的地址上的文档下载到本地,因此当浏览器碰到<script>标签时,严格的说也就是src时,他会立即停止HTML文档的解析,而去下载并且执行脚本,当脚本执行完毕才会继续解析HTML文档。这就是形成阻塞的原因,

这里不得不提的是,<a>标签中的href属性,它的意义只是把此文档和href后面跟的文档建立连接关系,而不用下载,这就是他与src的区别。

    为了解决这个问题HTML5<script>元素添加了asyncdefer属性。

一、浏览器加载到<script>元素,没有设置async或者defer

<code class="hljs xml has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">src</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"main.js"</span>></span><span class="javascript" style="box-sizing: border-box;"></span><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

       浏览器执行到这个元素的时候会立即下载src所指向的脚本并且执行,在执行完该脚本以后再加载这个<script>元素后面的文档。

二、当浏览器执行到<script>元素,元素设置了async属性

<code class="hljs xml has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">async</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">src</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"main.js"</span>></span><span class="javascript" style="box-sizing: border-box;"></span><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

       当浏览器解析到这个<script>元素的时候,会立即下载该脚本,但是不糊阻塞页面中的其他操作,比如其他脚本的加载和页面的渲染。加载和渲染后续文档的过程和加载main.js并行执行(异步)。

       async不能保证按照脚本出现的先后顺序执行,所以,确保该脚本和其他文档的相互依赖关系非常重要,指定async的目的是让页面不用等待这个脚本加载完毕以后再继续下一步操作,让指定async的脚本和后续文档异步执行,所以设置异步加载的脚本最好不要操作DOM,因为不知道他什么时候执行,容易产生错误。

       async脚本会在页面的load事件之前执行,但是不一定在DOMContentLoaded事件之前执行,有可能在这个事件之前或者之后执行。支持async的浏览器有:Firefox 3.6、Safari 5 和Chrome。

三、当浏览器解析到<script>,元素设置了defer属性

<code class="hljs xml has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">defer</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">src</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"main_1.js"</span>></span><span class="javascript" style="box-sizing: border-box;"></span><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span>></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">defer</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">src</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">"main_2.js"</span>></span><span class="javascript" style="box-sizing: border-box;"></span><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">script</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

       当元素加载到<script>元素,该元素设置了defer时,会立即下载该脚本,但是会延迟执行,加载后续文档的过程会和该脚本并行进行(异步),它会在所有文档解析之后,在DOMContentLoaded事件执行之前完成。和async不同的还有一点是设置了defer的脚本会按照它出现在HTML页面中的顺序执行。

四、总结

       从实际角度出发,还是把<script>放在body的底部比较合适,对于比较旧的浏览器这是最好的优化方式,并且能够保证非脚本的文档以最快的速度解析。

下面来看一张图: 
HTML5中&lt;script&gt;标签中的defer与async属性详解 
上图中,绿色代表HTML文档解析; 
蓝色代表网路读取,红色代表执行时间,这两个是针对脚本的。

从上图中我们可以得出以下几点总结:

  1. asyncdefer在网络读取这一部分是一样的,即下载脚本的时候都是异步的。
  2. asyncdefer很明显的差别在于执行时间不同,async在加载完之后就执行,而defer是在页面解析之后执行,显然defer更接近我们对于脚本执行的要求。
  3. 关于defer,图中没有表现出来的是defer是按照顺序执行脚本的。
  4. 关于defer ,并不能保证执行的顺序,它加载和执行是紧紧挨着的,加载完后就立即执行,不管你声明的顺序如何,都不知道它是什么时候执行的。
  5. 因为async不能保证脚本执行的顺序,所以要设置async的脚本一定要和其他脚本没有依赖关系,并且不操作DOM。
上一篇:我的atom插件


下一篇:angularjs2.0指南