文章目录
默认script标签是同步执行,因此会发生阻塞
浏览器解析html文件时,从上向下解析,解析到DOM中的script时会暂停DOM构建,在脚本加载并执行完毕后才会继续向下解析
因此可以看到,JS脚本存在会阻塞DOM解析的问题进而影响页面渲染速度
实验
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<script src="index.js"></script>
<h1>222</h1>
</body>
</html>
index.js
const startTime = new Date().getTime()
let endTime = ''
do {
endTime = new Date().getTime()
} while (endTime - startTime <= 2000)
在浏览器中打开可以看到,script
之上的dom渲染完成,在加载和运行 script
时耗费了 2s后,script
后面的的 dom 才加载
解决方案
1. script 放在 body 最下方
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<h1>222</h1>
<!-- script 放在 body 最下方 -->
<script src="index.js"></script>
</body>
</html>
script 放在 body 最下方,所有的dom加载完成后才加载、执行script,因此不会阻塞页面的渲染,如下图
2. 使用 async 异步加载 script
dom
解析时,遇到设置了async
的脚本,就会在后台进行下载,但是并不会阻止dom
的渲染。
当页面解析并且渲染完毕后,在Load 事件触发前执行,async脚本的加载不计入DOMContentLoaded事件统计
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<!-- 使用 async 异步加载 script -->
<script async src="index.js"></script>
<h1>222</h1>
</body>
</html>
index.js
const startTime = new Date().getTime()
let endTime = ''
do {
endTime = new Date().getTime()
} while (endTime - startTime <= 2000)
debugger
下图可以看到,在执行断点前,dom已经渲染完毕,不会阻塞页面
3. 使用 defer 异步加载 script
dom
解析时,遇到设置了defer
的脚本,就会在后台进行下载,但是并不会阻止dom
的渲染,当页面解析并且渲染完毕后。
会等到所有的defer
脚本加载完毕并按照顺序执行,执行完毕后会触发DOMContentLoaded事件。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<!-- 使用 defer 异步加载 script -->
<script defer src="index.js"></script>
<h1>222</h1>
</body>
</html>
下图可以看到,在执行断点前,dom已经渲染完毕,也不会阻塞页面
4. WebWorker
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>js单线程阻塞</title>
</head>
<body>
<h1>111</h1>
<!-- WebWorker -->
<script>
const worker = new Worker('./index.js')
worker.postMessage("兄弟,帮我运行下这个脚本")
worker.onmessage = (e) => {
console.log(e);
}
</script>
<h1>222</h1>
</body>
</html>
index.js
const startTime = new Date().getTime()
let endTime = ''
do {
endTime = new Date().getTime()
} while (endTime - startTime <= 2000)
onmessage = (e) => {
console.log(e);
postMessage("好的大兄弟")
}
下图可以看到,在执行断点前,dom已经渲染完毕,不会阻塞页面
总结
问题
浏览器解析html文件时,从上向下解析,解析到DOM中的script时会暂停DOM渲染,在脚本加载并执行完毕后才会继续向下解析。因此JS脚本存在会阻塞DOM解析的问题进而影响页面渲染速度
解决方案
- 将script 放在 body 最下方
- 使用 async 异步加载 script
- 使用 defer 异步加载 script
- WebWorker
async、defer 推荐应用场景
-
如果你的JS代码依赖于页面中的DOM元素,或者被其他脚本文件依赖,应当使用
defer
; -
如果你的脚本并不关心页面中的DOM元素,并且也不会产生其他脚本需要的数据,可以使用
async
; -
如果不太确定的话,选择
defer
会比async
更靠谱