本文详解JavaScript文件加载对页面性能的影响,分析script标签放置位置、async与defer属性的执行机制及多脚本依赖场景。通过对比代码示例,阐明如何避免浏览器阻塞,提升用户体验,并提供防堵塞的最佳实践建议。
在编写HTML页面时,引入外部JavaScript文件时,script标签应放置于何处?不同的放置位置是否会影响页面加载效果?默认情况下,浏览器采用同步方式加载JavaScript脚本:当渲染引擎遇到script标签时,会暂停当前操作,等待脚本执行完毕后再继续向下渲染。若为外部脚本,还需加上下载所需的时间。当脚本体积较大时,下载和执行耗时较长,会导致浏览器阻塞,用户会感到页面“卡死”,出现短暂空白且无响应。这会严重影响用户体验,解决此问题主要有两种方案:
① 调整script标签的放置位置。建议将其置于body标签末尾,即</body>标签之前。这种方式不会干扰浏览器的DOM渲染过程,确保页面内容先执行完毕后再加载脚本。
② 将同步加载转为异步加载。浏览器支持脚本异步加载,此时script标签可保留在head头部。以下是两种异步加载的语法实现:
<script src="./1.js" async></script>
<script src="./1.js" defer></script>
async与defer
通过为script标签添加defer或async属性,可实现脚本异步加载。当浏览器渲染引擎遇到该命令时,会立即开始下载外部脚本,同时在下载过程中继续执行后续命令。
async和defer属性均确保在外部脚本下载期间,渲染引擎不会停止工作。
async属性与defer属性的主要区别在于:

图中蓝色线条代表网络读取(即脚本下载),红色线条代表脚本执行,这两者均针对脚本本身;绿色线条代表HTML解析过程。
defer属性:浏览器会立即下载相应脚本,下载过程中页面处理不会停止,待文档解析完成后,脚本才会执行。
async属性:浏览器会立即下载相应脚本,下载过程中页面处理不会停止,但脚本下载完成后会立即执行,此时页面处理会暂停直至脚本执行完毕。
若不设置任何属性,当遇到script脚本时,浏览器会等待脚本下载和执行全部完成后,才继续执行后续的页面处理。
【注】若同一标签同时使用async和defer属性,系统将遵循async属性。
多个脚本
async和defer的区别不仅体现在外部script文件的下载和执行上,更体现在多个脚本共存时的不同行为:以下提供代码示例:
外部script文件 1.js 文件:
// ... 非常多的js代码
console.log('1');
2.js 文件:
console.log('2');
主html文件
使用defer:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>js阻塞</title>
<!-- defer会让dom先执行 -->
<script src="./1.js" defer></script>
<script src="./2.js" defer></script>
</head>
<body>
<h1>js的阻塞是如何进行的?</h1>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded');
})
</script>
</body>
</html>
控制台执行结果:

使用async:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>js阻塞</title>
<!-- defer会让dom先执行 -->
<script src="./1.js" async></script>
<script src="./2.js" async></script>
</head>
<body>
<h1>js的阻塞是如何进行的?</h1>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded');
})
</script>
</body>
</html>
控制台执行结果:

从控制台运行结果可以看出: defer:第一个延迟脚本会先于第二个延迟脚本执行(尽管部分浏览器若未完全遵循HTML5标准,可能出现执行顺序不一致的情况),且这两个脚本均会在DOMContentLoaded事件之前执行。
async:哪个脚本先下载完成,哪个就立即执行!这两个脚本不一定在DOMContentLoaded事件触发前执行,但一定会在window.onload事件之前执行完毕。另外需注意,先下载完成的脚本在执行过程中,其他脚本不会停止下载,而是继续下载。
【注】DOMContentLoaded事件在DOM加载完成后触发,即文档完全加载和解析之后触发。 ○ 更多关于DOMContentLoaded,请参考 https://www.jb51.net/article/222345.htm
小结
- 防止页面堵塞的最佳实践是将script标签放置在body标签的最底部;
- defer和async在网络读取(下载)方面表现一致,均为异步(相对于HTML解析)。 两者的差异在于脚本下载完成后何时执行。显然,defer最接近我们对应用脚本加载和执行的要求,特别是当script文件之间存在依赖关系(即某个JS文件引用了前一个JS文件的内容)时,它会严格按照加载顺序执行脚本。
- async则是乱序执行的典型,对它而言脚本的加载和执行紧密相连。因此,无论声明顺序如何,只要脚本加载完成便会立即执行。 仔细分析,async对于应用脚本的用处不大,因为它完全忽略依赖关系(甚至不考虑最基本的顺序执行)。不过,对于那些不依赖任何脚本或不被其他脚本依赖的脚本来说,async非常合适,最典型的例子是Google Analytics。
- defer和async仅适用于操作外部脚本。此外,在操作DOM的脚本中最好避免使用async/defer,因为例如使用async时,页面可能尚未加载完毕就执行了JS代码,这极易引发异常。如果必须使用,可将需要操作DOM的JS部分置于DOMContentLoaded事件回调中执行☺!
参考
[1] https://blog.csdn.net/mx18519142864/article/details/82021754 [2] https://blog.csdn.net/weixin_42561383/article/details/86564715 [3] https://segmentfault.com/q/1010000000640869
到此这篇关于JavaScript 文件加载与阻塞问题之性能优化案例详解的文章就介绍到这了,更多相关JavaScript 文件加载与阻塞问题内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!