:rocket:
script标签中 有两个属性:async和defer,意思就是异步加载和延迟加载。今天来详细研究下这两个属性。先看一下基本的参数介绍:
1
2
3
4
5
6
7
|
async (HTML5属性)
该布尔属性指示浏览器是否在允许的情况下异步执行该脚本。该属性对于内联脚本无作用 (即没有src属性的脚本)。
defer
这个布尔属性定义该脚本是否会延迟到文档解析完毕后才执行。但因为这个功能还未被所有主流浏览器实施,开发人员不应假设脚本实际上都会被延迟执行。defer属性不应在没有src属性的脚本标签上使用。从Gecko 1.9.2开始, 没有src属性的脚本标签的defer属性会被忽略。但是在Gecko 1.9.1中,如果定义了defer属性,即使内嵌的脚本也会被延迟执行。
|
以上属性解释来自:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script
大概的意思是这两个属性是可以延迟js的加载,改变js在页面中的加载和执行顺序。
<script>常用用法:
1
2
3
4
|
<script type="text/javascript" src="test.js"></script> //外联形式
<script type="text/javascript"> //内联形式
//js代码
</script>
|
首先要知道,浏览器在加载页面时会重上往下解析html代码,如果js在前就会先加载js,举个简单的例子:
1
2
3
4
5
|
<script type="text/javascript">
document.write(1111);
</script>
<br/>
2222
|
上面这样一个html,最后输出结果肯定是 1111在前,2222在后,没有问题。如果调换一下js顺序放在后面:
1
2
3
4
5
|
2222
<br/>
<script type="text/javascript">
document.write(1111);
</script>
|
结果就是输出2222再1111。因为这里是很简单的js,体积很小并且是内联,如果是外联引用的js文件,数量多或者体积大,当加载速度慢的时候就会出现 “白页
"。
这种”白页
“就是js速度慢(或者css速度加载慢),浏览器需要等待js加载完才会去渲染下面的内容,实际开发中这样的情况是很不利用户体验的,举个例子:
去找了一个国外的jquery的cdn,加载速度很慢,用来模拟js加载速度慢。可以复制下面的代码保存为html自己试一下看是否有白页出现。tips:第一次加载完成后想要再模拟应该先清缓存或强制刷新,因为浏览器已经把js缓存了.
1
2
3
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.1/dist/jquery.min.js?123"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
2222
|
js加载的越慢,白页出现的时间越长,所以网页基本优化思路,尽量把js文件放在body之后,这样就算加载慢但不会影响浏览器渲染基本内容,页面上文字会先出来.如上面改成:
1
2
3
|
2222
<script src="https://cdn.jsdelivr.net/npm/jquery@3.1/dist/jquery.min.js?123"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
说了这么多是时候引出本文的主角:async和defer
async
如果给script加上async和defer也可以实现js后加载,让文字内容先渲染出来.
1
2
3
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.1/dist/jquery.min.js" async></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" async></script>
2222
|
加上了async属性,结果是先显示出2222,js文件还在加载,不会有"白页"出现。
async属性是当js异步加载完就执行,浏览器可能在渲染的中途,js文件加载好了,js就开始执行,如果你有多个js之间有关联的就不要这么用,比如引入了jquery和一个依赖jquery的js,如果后者再jquery之前执行了就肯定报错了,如果就想用那么建议将多个js文件内容合并,再用async属性就可以。
defer
1
2
3
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.1/dist/jquery.min.js" defer></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" defer></script>
2222
|
基本表现效果是和async一致的。但是这个是等文档解析完毕后才执行,什么叫文档解析完成?
就是DOMContentLoaded事件触发之前才会加载执行。
用了async或defer就不能在js文件里面使用document.write()和document.writeln();
chorme中有错误提示:
1
2
3
|
Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
无法在“文档”上执行“写入”:除非显式打开,否则无法从异步加载的外部脚本写入文档。
|
通过创建script元素的方式来引入
除了使用标签的属性达到延迟,用js创建元素的方式来引入js也是可以的。
这个可以参见:https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts
1
2
3
4
5
|
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true; //默认就是使用异步,所以这一句可以忽略不写
script.src = "file.js";
document.body.appendChild(script);
|
但 script.defer = true;
是不可以的,没用,还是用的async的方式加载。
综合测试
写了一个测试的html,加载4个js文件,2个标签形式的,2个创建script元素形式的,并绑定了DOMContentLoaded和load事件。每个js文件里面都有document.write()。
HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript" src="async.js" async></script>
<script type="text/javascript" src="defer.js" defer></script>
<script type="text/javascript">
window.addEventListener("load", function() {
var dom = document.createElement('p');
dom.innerHTML="load";
document.getElementById('div').appendChild(dom);
}, false);
document.addEventListener("DOMContentLoaded", function() {
var dom = document.createElement('p');
dom.innerHTML="DOMContentLoaded";
document.getElementById('div').appendChild(dom);
}, false);
(function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'create_element_async.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
(function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.defer = true;
s.src = 'create_element_defer.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
</script>
</head>
<body>
<div id="div">
body content
</div>
</body>
</html>
|
async.js 用async属性加载
1
2
3
4
5
6
7
8
9
10
11
12
13
|
document.writeln("document_write_async");
var dom = document.createElement('p');
dom.innerHTML="async";
document.getElementById('div').appendChild(dom);
setTimeout(cc,2000); //加入了2秒延时,用来测试async是否阻塞load事件.
function cc () {
var dom = document.createElement('p');
dom.innerHTML="async 2s delay";
document.getElementById('div').appendChild(dom);
}
|
defer.js 用defer属性加载
1
2
3
4
5
|
document.writeln("document_write_defer");
var dom = document.createElement('p');
dom.innerHTML="defer";
document.getElementById('div').appendChild(dom);
|
create_element_async.js 用创建script方式加载
1
2
3
4
5
|
document.writeln("document_write_create_element_async");
var dom = document.createElement('p');
dom.innerHTML="create_element_async";
document.getElementById('div').appendChild(dom);
|
create_element_defer.js 用创建script方式加载
1
2
3
4
5
|
document.writeln("document_write_create_element_defer");
var dom = document.createElement('p');
dom.innerHTML="create_element_defer";
document.getElementById('div').appendChild(dom);
|
可以在这里下载DEMO:
https://pan.baidu.com/s/1o770oJo
测试结果:chrome,firefox和IE都进行了测试,360也测了。
截图如下: (截取其中两次不一样的结果)
chrome:
firefox:
360:
电脑中IE是8,根本就不支持async属性。。。。
总结
从上面的测试结论:
1.不要再async或defer引入的文件中调用document.write()。
2.defer加载的js始终在DOMContentLoaded事件之前执行。
3.async加载的js是什么时候加载完什么时候执行,执行顺序可能会飘忽不定。
4.IE10以下不要使用async,不起作用。
5.async和defer对内联的js没有作用,就是没有src的那种(上面测试中没有体现)。
网页加载优化建议
从上面我们也可以总结出一些网页优化的建议,
如果js和页面渲染样式没有关系就放在最后面加载.
合并js文件,减少HTTP请求,加快加载速度.
合理使用async和defer
最后给出async和defer的浏览器兼容性列表:注意IE是10以上。
参见:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script#浏览器兼容性