异步加载的三个方法

在学习JavaScript的一些总结和经验,供大家参考和学习,同时也欢迎大家参与讨论。

异步加载

延迟脚本defer

  • 脚本会被延迟到整个页面都解析完毕(即DomTree构建完,并不代表整个页面加载完),通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。
  • defer属性只适用于外部脚本文件
  • IE9以下版本才能用
1
<script type="text/javascript" defer="defer" src="example.js"></script>

异步脚本async

  • 只能加载外部脚本文件
  • W3C标准方法,大多数浏览器支持
  • 标记有async属性的脚本并不保证按照指定他们的先后顺序执行。
1
<script type="text/javascript" async="async" src="example.js"></script>

动态加载脚本

1
2
3
4
5
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "test.js";//下载完test.js,并没有执行
//如果不写下面这段代码,那么test.js只是加载了,并没有执行
document.body.appendChild(script);

竟然说添加了这段document.body.appendChild(script);就是执行test.js了,那我们在test.js文件中添加这段代码:function test(){alert("hello")};并在下面原来的代码添加调用test()

1
2
3
4
5
6
7
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "test.js";//下载完test.js,并没有执行
//如果不写下面这段代码,那么test.js只是加载了,并没有执行
document.body.appendChild(script);

test();

按照我们正常的理解,这样页面应该会弹出hello,但是很遗憾,控制台弹出了这样的错误:Uncaught ReferenceError: test is not defined,test根本就没定义?这是怎么回事?

解释:我们知道,程序执行是微妙(ms)级别的;非常快的,当程序读到test()的时候,上面的script.src中的test.js文件还没加载完,所以执行不了test()函数;所以出现未定义的错误。

问题:那么,有没有一种办法,可以等到test.js文件加载完了,才执行之后的代码?

onload事件

整个HTML页面解析完(图像等加载完)才执行

1
2
3
4
5
6
7
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "test.js";
script.onload = function(){
test();
}
document.body.appendChild(script);

IE上有一个状态码,script.readyState,功能与script.onload类似;

script.readyState = “loading”;最开始的值。

script.readyState = “complete”;或者是“loaded”表示加载完成

我们加一个监听这个readyState属性的事件onreadystatechange,它依赖于readyState的变化,一旦readyState的值变化,就会触发onreadystatechange事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "test.js";
if (script.readyState) {//支持IE的方法
script.onreadystatechange = function(){
if (script.readyState == "complete" || script.readyState == "loaded") {
test();
}
}
}else{
script.onload = function(){
//Safari、chrome、firefox、opera
test();
}
}
document.body.appendChild(script);

我们要知道,一旦readyState变化,就会触发onreadystatechange事件

那么有没有出现一种情况:就是script.src 瞬间就下载完,也就是瞬间到达”complete”或”loaded”阶段,那么onreadystatechange事件就不会被触发,那么在IE上就不好使了,所以我们改善一下代码,把script.scr = url这段移到if循环下面,顺便封装成loadScript函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function loadScript(url, callback){
var script = document.createElement('script');
script.type = "text/javascript";
if (script.readyState) {//支持IE的方法
script.onreadystatechange = function(){
if (script.readyState == "complete" || script.readyState == "loaded") {
callback();
}
}
}else{
script.onload = function(){
//Safari、chrome、firefox、opera
callback();
}
}
script.src = url;
document.body.appendChild(script);
}
loadScript('test.js',test);

于是我们高高兴兴的执行一下代码,心想着能够成功,哦吼完蛋,报错了ReferenceError: test is not defined,为什么会出现未定义的错误呢?

解释:因为代码在执行的时候,它的执行顺序是解析一下function loadScript(){}函数,它根本不会去执行loadScript里面的代码,然后在执行loadScript('test.js',test);的时候才会触发loadScript函数里面代码的执行,也就是当loadScript('test.js',test);在传参的时候发生在test.js文件加载之前,所以它根本不知道test是什么?

我们就加一个匿名函数:它也是一个函数的引用,它也不会去执行它里面的代码只是先解析。

1
2
3
loadScript('test.js',function(){
test();
});

我们把test.js写成json对象的一个形式,我们这样写的优点就是如果我们在加载一个库时,我们只需要加载里面的某个方法,我们就可以采用这个形式进行按需加载,比较灵活。

test.js文件:

1
2
3
4
5
6
7
8
9
var test = {
tools: function(){
alert('hello');
},
demo: function(){
//........
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function loadScript(url, callback){
var script = document.createElement('script');
script.type = "text/javascript";
if (script.readyState) {//支持IE的方法
script.onreadystatechange = function(){
if (script.readyState == "complete" || script.readyState == "loaded") {
test[callback]();//callback是一个属性名,必须是一个字符串形式
}
}
}else{
script.onload = function(){
//Safari、chrome、firefox、opera
test[callback]();
}
}
script.src = url;
document.body.appendChild(script);
}
loadScript('test.js',"tools");


文章标题: 异步加载的三个方法
文章作者: 王奕聪,QQ:1301842163
许可协议: 知识共享许可协议
©署名-非商用-相同方式共享 4.0