异步加载的方式

1、同步加载与异步加载的形式

1. 同步加载

咱们平时最常使用的就是这种同步加载形式:<script src="http://yourdomain.com/script.js"></script> javascript

同步模式,又称阻塞模式,会阻止浏览器的后续处理,中止了后续的解析,所以中止了后续的文件加载(如图像)、渲染、代码执行。java

js 之因此要同步执行,是由于 js 中可能有输出 document 内容、修改dom、重定向等行为,因此默认同步执行才是安全的。jquery

之前的通常建议是把<script>放在页面末尾</body>以前,这样尽量减小这种阻塞行为,而先让页面展现出来。ajax

简单说:加载的网络 timeline 是瀑布模型,而异步加载的 timeline 是并发模型。

2. 常见异步加载(Script DOM Element)

(function() { var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'http://yourdomain.com/script.js'; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); })();

异步加载又叫非阻塞,浏览器在下载执行 js 同时,还会继续进行后续页面的处理。api

这种方法是在页面中<script>标签内,用 js 建立一个 script 元素并插入到 document 中。这样就作到了非阻塞的下载 js 代码。浏览器

async属性是HTML5中新增的异步支持,见后文解释,加上好(不加也不影响)。缓存

此方法被称为 Script DOM Element 法,不要求 js 同源。安全

将js代码包裹在匿名函数中并当即执行的方式是为了保护变量名泄露到外部可见,这是很常见的方式,尤为是在 js 库中被广泛使用。网络

例如 Google Analytics 和 Google+ Badge 都使用了这种异步加载代码:并发

(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); })(); (function(){
  
var po = document.createElement("script");    po.type = "text/javascript";

   po.async = true;
   po.src = "https://apis.google.com/js/plusone.js";    var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(po, s); })();
可是,这种加载方式在加载执行完以前会阻止 onload 事件的触发,而如今不少页面的代码都在 onload 时还要执行额外的渲染工做等,因此仍是会阻塞部分页面的初始化处理

3. onload 时的异步加载

(function() { function async_load(){ var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'http://yourdomain.com/script.js'; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); } if (window.attachEvent) window.attachEvent('onload', async_load); else window.addEventListener('load', async_load, false); })();

这和前面的方式差很少,但关键是它不是当即开始异步加载 js ,而是在 onload 时才开始异步加载。这样就解决了阻塞 onload 事件触发的问题。

补充:DOMContentLoaded 与 OnLoad 事件

DOMContentLoaded : 页面(document)已经解析完成,页面中的dom元素已经可用。可是页面中引用的图片、subframe可能尚未加载完。

OnLoad:页面的全部资源都加载完毕(包括图片)。浏览器的载入进度在这时才中止。

这两个时间点将页面加载的timeline分红了三个阶段。

4.$(document).ready()

  • 须要引入jquery

  • 兼容全部浏览器

$(document).ready(function() { alert("加载完成!"); });

5.<script>标签的async="async"属性

<script src="file.js" async></script> 

  • async属性是HTML5新增属性,须要Chrome、FireFox、IE9+浏览器支持;
  • async属性规定一旦脚本可用,则会异步执行;

  • async属性仅适用于外部脚本;

  • 此方法不能保证脚本按顺序执行;

  • 他们将在onload事件以前完成。

6.<script>标签的defer="defer"属性

 <script src="file.js" defer></script> 

  • defer属性规定是否对脚本执行进行延迟,直到页面加载为止;

  • 若是脚本不会改变文档的内容,可将defer属性加入到<script>标签中,以便加快处理文档的速度;

  • 兼容全部浏览器;

  • 此方法能够确保全部设置了defer属性的脚本按顺序执行。

7.异步加载的其它方法

因为Javascript的动态特性,还有不少异步加载方法:

  • XHR Eval 

  • XHR Injection

  • Script in Iframe

  • document.write Script Tag

  • 还有一种方法是用 setTimeout 延迟0秒与其它方法组合。

XHR Eval :经过 ajax 获取js的内容,而后 eval 执行。

 var xhrObj = getXHRObject(); xhrObj.onreadystatechange = function() { if ( xhrObj.readyState != 4 ) return; eval(xhrObj.responseText); }; xhrObj.open('GET', 'A.js', true); xhrObj.send(''); 

Script in Iframe:建立并插入一个iframe元素,让其异步执行 js 。

 var iframe = document.createElement('iframe'); document.body.appendChild(iframe); var doc = iframe.contentWindow.document; doc.open().write('<body onload="insertJS()">'); doc.close();

GMail Mobile:页内 js 的内容被注释,因此不会执行,而后在须要的时候,获取script元素中 text 内容,去掉注释后 eval 执行。

<script type="text/javascript"> 
 /* var ... */ 
 </script>

详见参考资料中2010年的Velocity 大会 Steve Souders 和淘宝的那两个讲义。

2、async 和 defer 属性

1. defer 属性

<script src="file.js" defer></script> 

defer属性声明这个脚本中将不会有 document.write 或 dom 修改。

浏览器将会并行下载 file.js 和其它有 defer 属性的script,而不会阻塞页面后续处理

defer属性在IE 4.0中就实现了,超过13年了!Firefox 从 3.5 开始支持defer属性 。

注:全部的defer脚本保证是按顺序依次执行的。

2. async 属性

<script src="file.js" async></script> 

async属性是HTML5新增的。做用和defer相似,可是它将在下载后尽快执行,不能保证脚本会按顺序执行。它们将在onload 事件以前完成。
Firefox 3.六、Opera 10.五、IE 9 和 最新的Chrome 和 Safari 都支持 async 属性。能够同时使用 async 和 defer,这样IE 4以后的全部 IE 都支持异步加载。

3. 详细解释

<script> 标签在 HTML 4.01 与 HTML5 的区别:

  • type 属性在HTML 4中是必须的,在HTML5中是可选的。

  • async 属性是HTML5中新增的。

  • 个别属性(xml:space)在HTML5中不支持。

说明:

(1)没有 async 属性,script 将当即获取(下载)并执行,而后才继续后面的处理,这期间阻塞了浏览器的后续处理。

(2)若是有 async 属性,那么 script 将被异步下载并执行,同时浏览器继续后续的处理。

(3)HTML4中就有了defer属性,它提示浏览器这个 script 不会产生任何文档元素(没有document.write),所以浏览器会继续后续处理和渲染。

(4)若是没有 async 属性可是有 defer 属性,那么script 将在页面parse以后执行。

(5)若是同时设置了两者,那么 defer 属性主要是为了让不支持 async 属性的老浏览器按照原来的 defer 方式处理,而不是同步方式。

PS:既然 HTML5 中已经支持异步加载,为何还要使用前面推荐的那种麻烦(动态建立 script 元素)的方式?

答:为了兼容尚不支持 async 老浏览器。若是未来全部浏览器都支持了,那么直接在script中加上async 属性是最简单的方式。

3、延迟加载(lazy loading)

前面解决了异步加载(async loading)问题,再谈谈什么是延迟加载。

延迟加载:有些 js 代码并非页面初始化的时候就马上须要的,而是稍后的某些状况才须要的。延迟加载就是一开始并不加载这些暂时不用的js,而是在须要的时候或稍后再经过js 的控制来异步加载

也就是将 js 切分红许多模块,页面初始化时只加载须要当即执行的 js ,而后其它 js 的加载延迟到第一次须要用到的时候再加载。

特别是页面有大量不一样的模块组成,不少可能暂时不用或根本就没用到。就像图片的延迟加载,在图片出如今可视区域内时(在滚动条下拉)才加载显示图片。

4、script 的两阶段加载 与 延迟执行(lazy execution)

JS的加载实际上是由两阶段组成:下载内容(download bytes)和执行(parse and execute)。

浏览器在下载完 js 的内容后就会当即对其解析和执行,不论是同步加载仍是异步加载。

前面说的异步加载,解决的只是下载阶段的问题,但代码在下载后会当即执行。

而浏览器在解析执行 JS 阶段是阻塞任何操做的,这时的浏览器处于无响应状态。

咱们都知道经过网络下载 script 须要明显的时间,但容易忽略了第二阶段,解析和执行也是须要时间的。script的解析和执行所花的时间比咱们想象的要多,尤为是script 不少很大的时候。有些是须要马上执行,而有些则不须要(好比只是在展现某个界面或执行某个操做时才须要)。

这些script 能够延迟执行,先异步下载缓存起来,但不当即执行,而是在第一次须要的时候执行一次。 

利用特殊的技巧能够作到下载与 执行的分离 (再次感谢 javascript 的动态特性)。好比将 JS 的内容做为 Image或 object 对象加载缓存起来,因此就不会当即执行了,而后在第一次须要的时候再执行。 

5、script 标签使用历史

1. script 放在 HEAD 中

<head>
  <script src=“…”></script>
</head>
  • 阻止了后续的下载;

  • 在IE 6-7 中 script 是顺序下载的,而不是如今的 “并行下载、顺序执行” 的方式;

  • 在下载解析执行阶段阻止渲染(rendering)。

2. script 放在页面底部(2007)

... <script src=“…”></script> 
 </body>
  • 不阻止其它下载;

  • 在IE 6-7 中 script 是顺序下载的;

  • 在下载解析执行阶段阻止渲染(rendering)。

3. 异步加载script(2009)

var se = document.createElement ('script'); se.src = 'http://anydomain.com/A.js'; document.getElementsByTagName('head') [0].appendChild(se);

这就是本文主要说的方式。

  • 不阻止其它下载;

  • 在全部浏览器中,script都是并行下载;

  • 只在解析执行阶段阻止渲染(rendering)。

4. 异步下载 + 按需执行 (2010)

var se = new Image(); se.onload = registerScript(); se.src = 'http://anydomain.com/A.js';

 把下载 js 与 解析执行 js 分离出来

  • 不阻止其它下载;

  • 在全部浏览器中,script都是并行下载;

  • 不阻止渲染(rendering)直到真正须要时。

6、异步加载的问题

在异步加载的时候,没法使用 document.write 输出文档内容。

在同步模式下,document.write 是在当前 script 所在的位置输出文档的。而在异步模式下,浏览器继续处理后续页面内容,根本没法肯定 document.write 应该输出到什么位置,因此异步模式下 document.write 不可行。而到了页面已经 onload 以后,再执行 document.write 将致使当前页面的内容被清空,由于它会自动触发 document.open 方法。

实际上document.write的名声并很差,最好少用。

替代方法:

1. 虽然异步加载不能用 document.write,但仍是能够onload以后执行操做dom(建立dom或修改dom)的,这样能够实现一些本身的动态输出。好比要在页面异步建立一个浮动元素,这和它在页面中的位置就不要紧了,只要建立出该dom元素添加到 document 中便可。

2. 若是须要在固定位置异步生成元素的内容,那么能够在该固定位置设置一个dom元素做为目标,这样就知道位置了,异步加载以后就能够对这个元素进行修改。

6、JS 模块化管理

异步加载,须要将全部 js 内容按模块化的方式来切分组织,其中就存在依赖关系,而异步加载不保证执行顺序。

另外,namespace 如何管理等相关问题。这部分已超出本文内容,可参考:

RequireJS 、 CommonJS 以及 王保平(淘宝)的 SeaJS 及其博客 。

7、JS最佳实践

1. 最小化 js 文件,利用压缩工具将其最小化,同时开启http gzip压缩工具;

2. 尽可能不要放在 <head> 中,尽可能放在页面底部,最好是</body>以前的位置;

3. 避免使用 document.write 方法;

4. 异步加载 js ,使用非阻塞方式,就是此文内容;

5. 尽可能不直接在页面元素上使用 Inline Javascript,如onClick 。有利于统一维护和缓存处理。

相关文章
相关标签/搜索