在HTML中执行脚本最重要的方法就是使用<script>
元素,但是执行<script>
元素时会阻塞后面文档的加载。
那么首先为什么会阻塞呢,是由于哪些原因呢?
其实是<script>标签中的src属性在作怪,因为一个src就相当于一次http请求,他的作用就是把src所对应的地址上的文档下载到本地,因此当浏览器碰到<script>标签时,严格的说也就是src时,他会立即停止HTML文档的解析,而去下载并且执行脚本,当脚本执行完毕才会继续解析HTML文档。这就是形成阻塞的原因,
这里不得不提的是,<a>标签中的href属性,它的意义只是把此文档和href后面跟的文档建立连接关系,而不用下载,这就是他与src的区别。
为了解决这个问题HTML5为<script>
元素添加了async
和defer
属性。
一、浏览器加载到<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的底部比较合适,对于比较旧的浏览器这是最好的优化方式,并且能够保证非脚本的文档以最快的速度解析。
下面来看一张图:
上图中,绿色代表HTML文档解析;
蓝色代表网路读取,红色代表执行时间,这两个是针对脚本的。
从上图中我们可以得出以下几点总结:
-
async
和defer
在网络读取这一部分是一样的,即下载脚本的时候都是异步的。 -
async
和defer
很明显的差别在于执行时间不同,async
在加载完之后就执行,而defer
是在页面解析之后执行,显然defer
更接近我们对于脚本执行的要求。 - 关于
defer
,图中没有表现出来的是defer
是按照顺序执行脚本的。 - 关于
defer
,并不能保证执行的顺序,它加载和执行是紧紧挨着的,加载完后就立即执行,不管你声明的顺序如何,都不知道它是什么时候执行的。 - 因为
async
不能保证脚本执行的顺序,所以要设置async的脚本一定要和其他脚本没有依赖关系,并且不操作DOM。