JavaScript无阻塞加载与defer、async属性详解
本文深入解析JavaScript无阻塞加载技术,探讨传统同步加载对UI线程的阻塞问题。重点介绍动态创建script元素实现异步下载的方法,并详细对比HTML4的defer属性与HTML5新增的async属性在脚本执行顺序、并行下载及兼容性上的差异,帮助开发者优化页面加载性能。
JavaScript无阻塞加载概述
无阻塞加载
传统加载与并行下载的机制
将JavaScript代码放置在head标签中时,浏览器如何执行这些代码?是顺序加载还是并行加载?在旧版浏览器中,脚本严格按照先后顺序加载,以确保依赖关系正确。然而,部分新版浏览器已支持并行下载JavaScript文件,尽管下载过程可以同时进行,但脚本的执行仍遵循文档中的出现顺序。
虽然下载过程可以是异步的,但JavaScript的执行始终是同步的。除非脚本标记了defer属性,否则先出现的script标签必然先执行,即使它是最后下载完成的。任何JavaScript代码在执行时都会中断当前的HTML文档解析,从而阻止页面渲染。
JavaScript的加载本身不会影响已渲染的页面内容,但它会中断HTML文档的解析过程。浏览器会在JavaScript执行完毕后,决定当前文档是否需要重新渲染或重排。因此,即使将JavaScript代码置于文档末尾,也会导致浏览器暂停解析,但这不影响此前已解析出的DOM文档,用户此时仍可操作页面。
JavaScript下载完毕后通常会立即执行,且所有JavaScript的执行都会阻塞浏览器的其他行为,包括其他JavaScript代码的执行、其他HTTP请求的执行以及页面的解析和渲染。需要注意的是,HTML文档中外部JavaScript文件的下载也会阻塞浏览器行为,但通过创建script元素动态加载的JavaScript文件除外,这可能是因为浏览器认为动态加载的脚本不会改变页面初始效果,从而允许资源并行下载。

图示动态脚本的下载
UI线程与动态脚本加载原理
UI线程会根据页面中资源(如CSS文件、图片等)书写的先后顺序来加载资源。加载资源即通过HTTP请求获取资源,当CSS外部文件、HTML文件及图片等资源的HTTP请求处理完毕时,意味着资源加载结束。然而,外部JavaScript文件的加载过程不同:第一步与加载CSS和图片类似,即执行HTTP请求下载外部JS文件;但JavaScript完成HTTP操作后并不意味着操作结束,UI线程接着会执行该脚本。JavaScript脚本的下载和执行必须是一个完整的操作,不能被割裂。动态JavaScript的下载不会阻塞,但其执行一定会阻塞。
为了提升用户体验,加快UI线程的执行是一个无法回避的问题。由于拆分JavaScript的下载和执行在技术上不可行,浏览器转而采用另一种方式,即在相同时间内下载多个资源。
将常用且稳定的静态资源统一放置在静态资源服务器上,由统一的域名对外提供服务。该域名应与主体请求的域名不同,原理在于浏览器仅通过域名来限制连接数。如果一个页面包含两个不同域名的资源,那么并行的HTTP请求数量将变为原来的两倍。考虑到DNS解析的开销,使用两个域名是最佳选择。
将所有外部JavaScript代码分为UI初始化代码和其他代码。UI初始化代码是指在页面加载时执行的代码。对于那些不用于页面初始展示的JavaScript代码,可以通过onload事件在浏览器忙指示结束后触发加载和执行,即让与页面加载无关的JavaScript脚本在onload方法中执行。
动态创建Script元素实现无阻塞
无阻塞加载脚本的核心技术在于动态创建script DOM节点,且该方式支持跨域访问。
var script=document.createElement("script");
script.type="text/javascript";
script.src="file.js";
document.getElementsByTagName("head")[0].appendChild(script);
动态脚本元素是指<script>标签并非硬编码在HTML中,而是由现有脚本通过DOM API动态生成的。由于<script>标签也是DOM元素的一种,JavaScript可以通过操作DOM来创建它。动态脚本仅在新建的script元素被添加到HTML文档时开始下载,下载完成后立即执行。
无阻塞脚本的好处在于不会阻塞UI的执行,也不影响其他同步JavaScript代码的执行。由于无阻塞脚本改变了脚本的加载顺序,因此在使用时必须更加注意脚本之间的依赖关系,以确保整个页面的脚本都能正常执行。
使用无阻塞脚本后,代码置于head标签还是HTML文档底部已无关紧要。
页面加载的总时间并非衡量页面加载速度的标准,页面同步阻塞加载的时间才是衡量页面加载效率的准确标准。非阻塞脚本加载可能会增加整个页面的加载时间,但它可以减少页面阻塞加载的时间。
脚本的异步执行可能会产生前后依赖问题。在非IE浏览器中,脚本加载执行完毕后,会触发该<script>元素的onload事件;在IE浏览器中,则触发onreadystatechange事件。我们可以将回调函数放置在这些事件中处理。
每当浏览器解析到<script>标签(无论内嵌还是外链)时,浏览器会优先下载、解析并执行该标签中的JavaScript代码,同时阻塞其后所有页面内容的下载和渲染(即外部JavaScript的下载也会阻塞其他线程,目前仅有少部分浏览器支持并行下载JavaScript)。
无阻塞加载脚本技术的核心在于:动态下载JavaScript脚本时,不会阻塞UI线程的执行。动态脚本之所以不阻塞UI线程,可能是因为浏览器认为动态加载的资源不会影响页面渲染。
defer与async属性详解
使script延迟和异步的两个属性:defer和async。
JavaScript脚本会改变文档输入流的内容,因此执行JavaScript时会暂停页面渲染。对于内联脚本而言,这没有问题,因为脚本和HTML文档是同时加载的。但对于外部引入的脚本,脚本的下载(取决于网速)也会阻塞浏览器文档的解析和渲染,甚至阻塞某些浏览器下载其他资源(目前部分浏览器已实现并行下载)。因此,出现了defer和async属性,以优化页面显示效果。
defer(延迟)是HTML4.0中定义的属性,该属性使得浏览器能够延迟脚本的下载,等待document文档载入和解析完成后,按照它们在文档中出现的顺序再去下载和解析。也就是说,具有defer属性的<script>标签类似于将<script>放在body底部的效果,它会在document的DOMContentLoaded事件之前执行。
将脚本放在body底部比给脚本增加defer属性让脚本延迟加载效果更好。
async(异步)是HTML5新增的属性,其作用是让浏览器能够并行下载脚本且不阻塞浏览器的文档解析和渲染。下载完成后脚本立即执行,但执行顺序可能无序,具体取决于下载完成的时间。
若浏览器同时支持上述两种属性且script标签同时具有这两种属性,则async属性会优先于defer生效。
兼容旧浏览器的异步加载方案
在不支持async属性的浏览器中,可以通过动态创建script元素并插入文档中,实现脚本的异步载入和执行:

requirejs就是使用这个方法实现的。
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
相关标签:
