JavaScript中defer与async属性的区别详解

作者:半夏时间:2026-10-30 14:16:23浏览:203

本文深入解析JavaScript中defer和async属性的核心区别。defer实现异步加载但延迟至DOM解析完成后按序执行,适合依赖DOM的应用脚本;async则加载与执行完全异步,不保证顺序,适用于独立脚本。文章通过代码示例和时序图解,对比两者在网络读取与执行阶段的差异,并提供将脚本置于body末尾的最佳实践建议,帮助开发者优化页面加载性能。

JavaScript脚本加载基础与defer/async概述

首先来看以下三种脚本加载方式的表现:


<script src="script.js"></script>

若未设置defer或async属性,浏览器会立即加载并执行指定的脚本。这里的“立即”是指在渲染该script标签下方的文档元素之前发生,即不等待后续文档元素载入,浏览器读取到脚本标签时便会立即加载并执行。


<script async src="script.js"></script>

若设置了async属性,后续文档元素的加载与渲染过程将与script.js的加载和执行并行进行(即异步处理)。


<script defer src="myscript.js"></script>

若设置了defer属性,后续文档元素的加载过程将与script.js的加载并行进行(异步),但script.js的实际执行必须等到所有元素解析完成后、事件触发之前才能完成。

接下来详细解析这两者的具体区别。

常规脚本加载方式及其局限性

常规情况下的脚本处理

按照惯例,所有script元素通常放置在页面的head元素中。这样做的目的是将所有外部文件(包括CSS和JavaScript文件)的引用集中在同一位置。然而,在head元素中包含所有JavaScript文件意味着必须等待所有JavaScript代码下载、解析并执行完毕后,才能开始呈现页面内容(浏览器在遇到body标签时才开始呈现内容)。

对于依赖大量JavaScript代码的页面,这种做法会导致浏览器在呈现页面时出现明显的延迟,且在延迟期间浏览器窗口显示为空白。为避免此问题,现代Web应用程序通常将所有JavaScript引用放置在body元素中页面内容的后面。这样,在解析包含的JavaScript代码之前,页面内容已完全呈现在浏览器中。用户因浏览器窗口显示空白页面的时间缩短,会感觉页面打开速度有所提升。


<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <title></title>
</head>
<body>
<script type="text/javascript" src="script.js"></script>
</body>
</html>

defer属性的工作机制与注意事项

defer(延迟脚本)

defer属性仅适用于外部脚本文件。

如果给script标签定义了defer属性,其作用是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,如果script元素中设置了defer属性,相当于告诉浏览器立即下载脚本,但延迟执行。

示例如下:


<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <title>延迟加载</title>
 <script defer type="text/javascript" src="test.js"></script>
</head>
<body>
</body>
</html>

在这个例子中,尽管我们将script元素放在了文档的head元素中,但其中包含的脚本将延迟到浏览器遇到</html>标签后再执行。HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件(在DOM树构建完成后触发,不需要等到所有的资源都加载完毕)执行。

特别注意:在现实环境中,延迟脚本并不一定会严格按照顺序执行,也不一定会在事件触发前执行,因此建议页面中最好只包含一个延迟脚本。

具备 defer 属性时,后续文档元素的加载可与 script.js 的下载并行(异步),但脚本执行需待所有元素解析完毕,且在 DOMContentLoaded 事件触发前完成。

脚本放置的最佳实践与defer的浏览器差异

最佳实践建议

从实用角度而言,将所有脚本放置在</body>标签之前是最佳实践。对于旧版浏览器来说,这是唯一的优化手段,该方法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。

注意:defer属性在不同浏览器之间的表现存在不一致性。为了避免跨浏览器的差异,可以使用“lazy loading”(懒加载)的方法,即直到真正用到该脚本时才加载。


function lazyload() {
 var elem = document.createElement("script");
 elem.type = "text/javascript";
 elem.async = true;
 elem.src = "script.js"; 
 document.body.appendChild(elem);
}

if (window.addEventListener) {
 window.addEventListener("load", lazyload, false);
} else if (window.attachEvent) {
 window.attachEvent("onload", lazyload);
} else {
 window.onload = lazyload;
}

async属性的特性与适用场景

async(异步脚本)

async属性同样仅适用于外部脚本文件,并告诉浏览器立即下载该文件。

但与defer不同:标记为async的脚本并不保证按照指定它们的先后顺序执行。


<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <title>异步加载</title>
 <script async type="text/javascript" src="test1.js"></script>
 <script async type="text/javascript" src="test2.js"></script>
</head>
<body>
</body>
</html>

在这个例子中,test2.js可能会在test1.js之前执行。因此,确保这两个脚本之间互不依赖非常重要。指定async属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。因此,建议异步脚本不要在加载期间修改DOM。

查看一张更加清晰的对比图:

JavaScript脚本加载基础与defer/async概述

图解说明:蓝色线代表网络读取过程,红色线代表执行时间,这两者均针对脚本;绿色线代表HTML解析过程。

defer与async的核心区别对比总结

通过上图和之前的分析,我们可以得出以下结论:

1、defer和async在网络读取(即脚本下载)方面是一致的,都是异步的(相对于HTML解析而言)。

2、两者的主要差别在于脚本下载完之后何时执行。显然,defer最接近我们对应用脚本加载和执行的要求。defer是立即下载但延迟执行,加载后续文档元素的过程将和脚本的加载并行进行(异步),但脚本的执行要在所有元素解析完成之后、DOMContentLoaded 事件触发之前完成。而async是立即下载并执行,加载和渲染后续文档元素的过程将和js脚本的加载与执行并行进行(异步)。

3、关于defer,还需记住它是按照加载顺序执行脚本的。

4、标记为async的脚本并不保证按照指定它们的先后顺序执行。对于async而言,脚本的加载和执行是紧密相连的,因此无论声明顺序如何,只要脚本加载完毕就会立即执行。

5、async对于应用脚本的用处不大,因为它完全不考虑依赖关系(哪怕是最低级的顺序执行要求)。不过,对于那些可以不依赖任何脚本或不被任何脚本依赖的独立脚本来说,async是非常合适的。

总结

以上就是关于JavaScript中defer和async区别的全部内容。文章介绍较为详细,希望能对大家的学习或工作带来帮助。如有任何疑问,欢迎留言交流。

作者最新文章

相关标签: