网页性能优化之异步加载js文件

一个网页的有不少地方能够进行性能优化,比较常见的一种方式就是异步加载js脚本文件。在谈异步加载以前,先来看看浏览器加载js文件的原理。javascript

浏览器加载 JavaScript 脚本,主要经过<script>元素完成。正常的网页加载流程是这样的。css

  1. 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
  2. 解析过程当中,浏览器发现<script>元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。
  3. 若是<script>元素引用了外部脚本,就下载该脚本再执行,不然就直接执行代码。
  4. JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复解析 HTML 网页。

加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。缘由是 JavaScript 代码能够修改 DOM,因此必须把控制权让给它,不然会致使复杂的线程竞赛的问题。java

上面所说的,就是咱们平时最多见到的,将`<script>`标签放到`<head>`中的作法,这样的加载方式叫作同步加载,或者叫阻塞加载,由于在加载js脚本文件时,会阻塞浏览器解析HTML文档,等到下载并执行完毕以后,才会接着解析HTML文档。若是加载时间过长(好比下载时间太长),就会形成浏览器“假死”,页面一片空白。并且,放在`<head>`中同步加载的js文件中不能对DOM进行操做,不然会产生错误,由于这个时候HTML尚未进行解析,DOM尚未生成。由此看来,同步加载带来的体验每每并很差。jquery

下面咱们来看几种异步加载的方式。浏览器

1. 将<script>标签放到<body>底部

严格来讲,这并不算是异步加载,可是这也是常见的经过改变js加载方式来提高页面性能的一种方式,因此也就放到这里来讲。
<script>放到<body>底部,解决上上面说到的几个问题,一是不会形成页面解析的阻塞,就算加载时间过长用户也能够看到页面而不是一片空白,并且这时候能够在脚本中操做DOM。性能优化

2. defer属性

经过给<script>标签设置defer属性,将脚本文件设置为延迟加载,当浏览器遇到带有defer属性的<script>标签时,会再开启一个线程去下载js文件,同时继续解析HTML文档,等等HTML所有解析完毕DOM加载完成以后,再去执行加载好的js文件。
这种方式只适用于引用外部js文件的<script>标签,能够保证多个js文件的执行顺序就是它们在页面中出现的顺序,可是要注意,添加defer属性的js文件不该该使用document.write方法。网络

3. async属性

async属性和defer属性相似,也是会开启一个线程去下载js文件,但和defer不一样的时,它会在下载完成后马上执行,而不是会等到DOM加载完成以后再执行,因此仍是有可能会形成阻塞。
一样的,async也是只适用于外部js文件,也不能在js中使用document.write方法,可是对多个带有async的js文件,它不能像defer那样保证按顺序执行,它是哪一个js文件先下载完就先执行哪一个。异步

4. 动态建立<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.DOMContentLoadedwindow.onload这两个事件的区别,前者是在DOM解析完毕以后触发,这时候DOM解析完毕,JavaScript能够获取到DOM引用,可是页面中的一些资源好比图片、视频等尚未加载完,做用同jQuery中的ready事件。后者则是页面彻底加载完毕,包括各类资源。

 

说完了这几种常见的异步加载js脚本的方式,再来看最后一个问题,何时用defer,何时用async呢?通常来讲,二者之间的选择则是看脚本之间是否有依赖关系,有依赖的话应当要保证执行顺序,应当使用defer没有依赖的话使用async,同时使用的话defer失效。要注意的是二者都不该该使用document.write,这个致使整个页面被清除。

下面一幅图代表了同步加载以及deferasync加载时的区别,其中绿色线表明 HTML 解析,蓝色线表明网络读取js脚本,红色线表明js脚本执行时间:

相关文章
相关标签/搜索