浏览器运行原理—script元素和页面解析的关系
-
浏览器在解析HTML的过程中,遇到了script元素是不能继续构建DOM树的;
-
它会停止继续构建,首先下载JavaScript代码,并且执行JavaScript的脚本;
-
只有等到JavaScript脚本执行结束后,才会继续解析HTML,构建DOM树;
index.html 如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>测试代码</title>
</head>
<body>
<div>我是div元素</div>
<p>我是段落</p>
<script>
debugger;
console.log("test");
</script>
<div>我是js加载完才显示的元素</div>
</body>
</html>
index.js 如下:
debugger
console.log("test")
上面的代码中,我们使用了debugger使js的执行停住了,所以
为什么要这样做呢?
这是因为JavaScript的作用之一就是操作DOM,并且可以修改DOM;
如果我们等到DOM树构建完成并且渲染再执行JavaScript,会造成严重的回流和重绘,影响页面的性能;
所以会在遇到script元素时,优先下载和执行JavaScript代码,再继续构建DOM树;
但是这个也往往会带来新的问题,特别是现代页面开发中:
在目前的开发模式中(比如Vue、React),脚本往往比HTML页面更“重”,处理时间需要更长;
所以会造成页面的解析阻塞,在脚本下载、执行完成之前,用户在界面上什么都看不到;
为了解决这个问题,script元素给我们提供了两个属性(attribute):defer和async
defer
defer 属性告诉浏览器不要等待脚本下载(异步下载),而继续解析HTML,构建DOM Tree。即:
-
脚本会由浏览器来进行下载,但是不会阻塞DOM Tree的构建过程;
-
如果脚本提前下载好了,它会等待DOM Tree构建完成,在DOMContentLoaded事件之前先执行defer中的代码;(也就是说当执行defer文件中的js代码时,DOM Tree肯定已经构建完成了(因此我们可以在defer文件中进行DOM操作),且defer文件中的js代码执行完后才会触发DOMContentLoaded事件。)
<!-- index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>defer</title>
</head>
<body>
<div>我是div元素</div>
<p>我是段落</p>
<script>
window.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded")
})
</script>
<script src="./index.js" defer></script>
<div>我是js加载完才显示的元素</div>
</body>
</html>
// index.js
debugger
console.log("test")
运行index.html,控制台输出结果为:
-
**另外多个带defer的脚本是可以保持正确的顺序执行的。**即按出现的顺序执行每个带defer的脚本。
-
从某种角度来说,defer可以提高页面的性能,并且推荐放到head元素中;
-
注意:defer仅适用于外部脚本,对于script默认内容会被忽略。
async
async 特性与 defer 有些类似,它也能够让脚本不阻塞页面。
async是让一个脚本完全独立的:
-
浏览器不会因 async 脚本而阻塞(与 defer 类似);
-
async脚本不能保证顺序,它是独立下载、独立运行,不会等待其他脚本;
-
async不会能保证在DOMContentLoaded之前或者之后执行;
使用场景:
defer通常用于需要在文档解析后操作DOM的JavaScript代码,并且对多个script文件有顺序要求的;
async通常用于独立的脚本,对其他脚本,甚至DOM没有依赖的;
总结defer和async的区别:
使用defer属性的JavaScript会按照文件顺序执行,而使用async属性的JavaScript不会
使得defer属性的JavaScript会在监听到DOMContentLoaded事件时执行代码,而async不能保证在DOMContentLoaded之前或之后执行
本文使用 文章同步助手 同步