目录

网页加载优化-script标签的async和defer属性

: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:

https://cooldev-1251672755.cos.ap-shanghai.myqcloud.com/youdao/1554740710313.jpg

https://cooldev-1251672755.cos.ap-shanghai.myqcloud.com/youdao/1554740776756.jpg

firefox:

https://cooldev-1251672755.cos.ap-shanghai.myqcloud.com/youdao/1554740789557.jpg

https://cooldev-1251672755.cos.ap-shanghai.myqcloud.com/youdao/1554740795176.jpg

360:

https://cooldev-1251672755.cos.ap-shanghai.myqcloud.com/youdao/1554740804893.jpg

https://cooldev-1251672755.cos.ap-shanghai.myqcloud.com/youdao/1554740813411.jpg

电脑中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#浏览器兼容性