前言
普通script标签加载
都知道,前端性能优化的一条原则是将script标签放在body底部,为什么呢?因为script标签的加载和执行时会阻塞DOM结构渲染的,若是script标签放在头部,加载时间或者执行时间过长,会影响后续DOM的渲染,造成很长时间的页面白屏,前端体验会变得很差。
那么我们不妨亲自试一试?
为了使例子更加直观,直接用nodejs写一个服务。
先看一下文件目录:
// a.js
console.log(new Date(Date.now()), "我是外部a.js文件");
// b.js
console.log(new Date(Date.now()), "我是外部b.js文件");
//server.js
const fs = require("fs");
const http = require("http");
const a = fs.readFileSync("./a.js");
const b = fs.readFileSync("./b.js");
const serverBack = (req, res) => {
const url = req.url;
if (url === "/a.js") {
res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
setTimeout(() => {
res.write(a);
res.end();
}, 9000);
return;
}
if (url === "/b.js") {
res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
setTimeout(() => {
res.write(b);
res.end();
}, 5000);
return;
}
};
http.createServer(serverBack).listen(3003);
a.js
和b.js
代码中都获取一下当前执行时间的时间戳,在server.js
中读取两个文件的代码,若是访问相应的文件,返回即可,a.js
延迟9s返回,b.js
延迟5s返回,这样时间体验上会更清晰。
在前端的html文件中直接通过script标签引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
console.log(new Date(Date.now()), "内部script文件", "上");
</script>
<script src="http://localhost:3003/a.js"></script>
<script src="http://localhost:3003/b.js"></script>
<script>
console.log(new Date(Date.now()), "内部script文件", "下");
</script>
</body>
</html>
那么,直接运行这个html文件,控制台打印的最终结果为:
可以看出来,内部上边的script标签的最先被执行,然后大约9s后基本上是同时执行了外部引入的
a.js
、b.js
和内部下边的script标签。那么它们三个的执行顺序是随机的吗?
当然不是!不管刷新多少次,它们的执行顺序是永远不会改变的,永远是a.js
->b.js
->内部下边的script标签
。我们在server端设置的b.js
文件是5s返回结果,那么b.js
文件肯定是先下载完的,可是会永远先执行a.js
文件。
这是因为<script>
标签是并发下载,同步执行的,也就是说,不管引入多少个外部script标签,它们都会异步去下载,但是下载完成后,会按照标签的顺序同步执行,这也是为什么<script>
阻塞DOM渲染的原因。
那么外部js文件中是异步代码呢?
// a.js
console.log(new Date(Date.now()), "我是外部a.js文件");
setTimeout(() => {
console.log(new Date(Date.now()), "我是外部a.js文件", "setTimeout");
}, 6000);
// b.js
console.log(new Date(Date.now()), "我是外部b.js文件");
setTimeout(() => {
console.log(new Date(Date.now()), "我是外部b.js文件", "setTimeout");
}, 3000);
我们来看一下运行结果:
可以得出结论,
a.js
和b.js
文件依然会按照同步顺序执行,但是对于异步任务,也是遵循eventloop
机制,将异步任务放入异步队列中去执行,内部script标签并不会等待异步任务执行完。
⚠️defer和async属性只适用于外部引入的js文件
defer延迟脚本
现在我们来给外部的两个script标签加上defer属性:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
console.log(new Date(Date.now()), "内部script文件", "上");
</script>
<script src="http://localhost:3003/a.js" defer></script>
<script src="http://localhost:3003/b.js" defer></script>
<script>
console.log(new Date(Date.now()), "内部script文件", "下");
</script>
</body>
</html>
打印结果如下:
可以看出来,内部的script标签中的代码并没有等
a.js
和b.js
文件全部执行下载完就执行了,这也就是defer
属性的延迟执行功能。虽然是延迟执行,但两个文件执行的先后顺序并不能改变。所以defer
属性是并发下载,延迟同步执行,不会阻塞DOM渲染。
在《javascript高级程序设计》中,原话是这样的:
HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个执行。在现实中,延迟脚本并不一定会按照顺序执行,因此最好只包含一个延迟脚本
。
本文中的浏览器是Chrome,在FirFox和Safari中也尝试了一下,结果并没有发生改变。猜测作者是考虑到某些低版本浏览器没有做到严格的规范,才出此言。
async异步脚本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
console.log(new Date(Date.now()), "内部script文件", "上");
</script>
<script src="http://localhost:3003/a.js" async></script>
<script src="http://localhost:3003/b.js" async></script>
<script>
console.log(new Date(Date.now()), "内部script文件", "下");
</script>
</body>
</html>
将所有defer
属性全部换成async
,我们来看一下结果:
可以看出,内部下边的script标签中代码也没有等到
a.js
和b.js
执行完就已经执行了。通过b.js
的执行时间可以看出,b.js
文件是下载完立即执行的,并没有等待a.js
执行完。所以anync
属性是并发下载,异步执行
,也不会阻塞DOM渲染。
总结:
- script标签加载都是并发加载,这是浏览器提供的功能。但是执行是按照标签写入顺序同步执行,异步代码遵循eventloop。
- defer属性是延迟同步执行,也就是等到html文档解析到
</html>
时才会回过头看script脚本是否全部下载完成?若是下载完成则按照顺序同步执行,否则继续等待所有标签下载完成在执行。 - async属性是异步执行,也就是script脚本什么时候下载完,什么时候执行。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!