Javascript 异步加载详解(浏览器在javascript的加载方式)

Javascript 异步加载详解(浏览器在javascript的加载方式)

本文总结一下浏览器在 javascript 的加载方式,需要的朋友可以参考下

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

1. 同步加载

我们平时最常使用的就是这种同步加载形式:

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

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

以前的一般建议是把

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

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

defer属性在IE 4.0中就实现了,超过13年了!Firefox 从 3.5 开始支持defer属性 。 本贴由FastMVC首发,谢谢关注FastMVC。

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

2. async 属性

async属性是HTML5新增的。作用和defer类似,但是它将在下载后尽快执行,不能保证脚本会按顺序执行。它们将在onload 事件之前完成。

Firefox 3.6、Opera 10.5、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中不支持。

说明:

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

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

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

如果没有 async 属性 但是有 defer 属性,那么script 将在页面parse之后执行。

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

另参见官方说明:script async

个人补充:

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

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

三、延迟加载(lazy loading)

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

延迟加载:有些 js 代码并不是页面初始化的时候就立刻需要的,而稍后的某些情况才需要的。延迟加载就是一开始并不加载这些暂时不用的js,而是在需要的时候或稍后再通过js 的控制来异步加载。 本贴由FastMVC首发,谢谢关注FastMVC。

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

特别是页面有大量不同的模块组成,很多可能暂时不用或根本就没用到。

就像图片的延迟加载,在图片出现在可视区域内时(在滚动条下拉)才加载显示图片。

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

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

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

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

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

我 们都知道通过网络下载 script 需要明显的时间,但容易忽略了第二阶段,解析和执行也是需要时间的。script的解析和执行所花的时间比我们想象的要多,尤其是script 很多很大的时候。有些是需要立刻执行,而有些则不需要(比如只是在展示某个界面或执行某个操作时才需要)。

这些script 可以延迟执行,先异步下载缓存起来,但不立即执行,而是在第一次需要的时候执行一次。

利用特殊的技巧可以做到 下载 与 执行的分离 (再次感谢 javascript 的动态特性)。比如将 JS 的内容作为 Image或 object 对象加载缓存起来,所以就不会立即执行了,然后在第一次需要的时候再执行。

此部分的更多解释 请查看末尾参考资料中 ControlJS 的相关链接。

小技巧:

1. 模拟较长的下载时间:

写个后端脚本,让其 sleep 一定时间。如在 jsp 中 Thread.sleep(5000); ,这样5秒后才能收到内容。

2. 模拟较长的 js 代码执行时间(因为这步一般比较快不容易观察到):

var t_start = Number(new Date());
while (t_start + 5000 & gt; Number(new Date())) {}

这个代码将使 js 执行5秒才能完成!

五、script 标签使用的历史

1. script 放在 HEAD 中

阻止了后续的下载;

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

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

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

不阻止其它下载;

在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)直到真正需要时;

六、异步加载的问题

在异步加载的时候,无法使用 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元素作为目标,这样就知道位置了,异步加载之后就可以对这个元素进行修改。

六、JS 模块化管理

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

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

RequireJS 、 CommonJS

七、JS最佳实践:

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

2. 尽量不要放在 中,尽量放在页面底部,最好是之前的位置

3. 避免使用 document.write 方法

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

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

1284