一个网页的有不少地方能够进行性能优化,比较常见的一种方式就是异步加载js脚本文件。在谈异步加载以前,先来看看浏览器加载js文件的原理。javascript
浏览器加载 JavaScript 脚本,主要经过
<script>
元素完成。正常的网页加载流程是这样的。css
- 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
- 解析过程当中,浏览器发现
<script>
元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。- 若是
<script>
元素引用了外部脚本,就下载该脚本再执行,不然就直接执行代码。- JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复解析 HTML 网页。
加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。缘由是 JavaScript 代码能够修改 DOM,因此必须把控制权让给它,不然会致使复杂的线程竞赛的问题。java
上面所说的,就是咱们平时最多见到的,将`<script>`
标签放到`<head>`
中的作法,这样的加载方式叫作同步加载,或者叫阻塞加载,由于在加载js脚本文件时,会阻塞浏览器解析HTML文档,等到下载并执行完毕以后,才会接着解析HTML文档。若是加载时间过长(好比下载时间太长),就会形成浏览器“假死”,页面一片空白。并且,放在`<head>`
中同步加载的js文件中不能对DOM进行操做,不然会产生错误,由于这个时候HTML尚未进行解析,DOM尚未生成。由此看来,同步加载带来的体验每每并很差。jquery
下面咱们来看几种异步加载的方式。浏览器
<script>
标签放到<body>
底部严格来讲,这并不算是异步加载,可是这也是常见的经过改变js加载方式来提高页面性能的一种方式,因此也就放到这里来讲。
将<script>
放到<body>
底部,解决上上面说到的几个问题,一是不会形成页面解析的阻塞,就算加载时间过长用户也能够看到页面而不是一片空白,并且这时候能够在脚本中操做DOM。性能优化
defer
属性经过给<script>
标签设置defer
属性,将脚本文件设置为延迟加载,当浏览器遇到带有defer
属性的<script>
标签时,会再开启一个线程去下载js文件,同时继续解析HTML文档,等等HTML所有解析完毕DOM加载完成以后,再去执行加载好的js文件。
这种方式只适用于引用外部js文件的<script>
标签,能够保证多个js文件的执行顺序就是它们在页面中出现的顺序,可是要注意,添加defer
属性的js文件不该该使用document.write方法。网络
async
属性async
属性和defer
属性相似,也是会开启一个线程去下载js文件,但和defer
不一样的时,它会在下载完成后马上执行,而不是会等到DOM加载完成以后再执行,因此仍是有可能会形成阻塞。
一样的,async
也是只适用于外部js文件,也不能在js中使用document.write方法,可是对多个带有async
的js文件,它不能像defer那样保证按顺序执行,它是哪一个js文件先下载完就先执行哪一个。异步
<script>
标签能够经过动态地建立<script>
标签来实现异步加载js文件,例以下面代码:async
(function(){ var scriptEle = document.createElement("script"); scriptEle.type = "text/javasctipt"; scriptEle.async = true; scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js"; var x = document.getElementsByTagName("head")[0]; x.insertBefore(scriptEle, x.firstChild); })();
或者性能
(function(){ if(window.attachEvent){ window.attachEvent("load", asyncLoad); }else{ window.addEventListener("load", asyncLoad); } var asyncLoad = function(){ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); } })();
上面两种方法中,第一种方式执行完以前会阻止onload事件的触发,而如今不少页面的代码都在onload时还执行额外的渲染工做,因此仍是会阻塞部分页面的初始化处理。第二种则不会阻止onload事件的触发。
这里要简要说明一下window.DOMContentLoaded
和window.onload
这两个事件的区别,前者是在DOM解析完毕以后触发,这时候DOM解析完毕,JavaScript能够获取到DOM引用,可是页面中的一些资源好比图片、视频等尚未加载完,做用同jQuery中的ready事件。后者则是页面彻底加载完毕,包括各类资源。
说完了这几种常见的异步加载js脚本的方式,再来看最后一个问题,何时用defer
,何时用async
呢?通常来讲,二者之间的选择则是看脚本之间是否有依赖关系,有依赖的话应当要保证执行顺序,应当使用defer
没有依赖的话使用async
,同时使用的话defer
失效。要注意的是二者都不该该使用document.write,这个致使整个页面被清除。
下面一幅图代表了同步加载以及defer
、async
加载时的区别,其中绿色线表明 HTML 解析,蓝色线表明网络读取js脚本,红色线表明js脚本执行时间: