JavaScript 异步加载详解:实现方式与性能优化实践

一颗松果
一颗松果
发布时间:2026-10-15 | 4人浏览过
原创

了解浏览器中 JavaScript 的执行对页面性能至关重要。本文将剖析 JS 从同步阻塞到异步非阻塞的演变,对比 script DOM element 动态插入、HTML5 原生属性 async/defer 的技术细节,并讲解如何拆分依赖以消除渲染阻塞。

一、同步与异步加载的核心差异

传统开发中最常见的是同步加载:<script src="..."></script>。这种模式被称为“阻塞模式”,浏览器必须等待 JS 文件下载完毕并执行结束后,才能继续解析 HTML、加载图片资源或执行后续脚本。这是因为 JS 可能包含 document.write 或操作 DOM 的行为,同步执行能保证安全。

为了减少阻塞,早期的最佳实践是将 <script> 标签移至 </body> 之前,以确保页面主要结构和内容优先展示。从时间线角度看,同步加载是瀑布模型,而异步加载则是并发模型,允许浏览器在后台并行下载资源而不中断渲染。

二、常见的异步加载实现方案

第1步:使用 Script DOM Element 动态创建

这是最经典的异步加载方法,通过 JS 创建一个 script 元素并插入文档中,不阻塞页面后续处理。

(function() {
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.src = 'http://yourdomain.com/script.js';
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
})();

该方法的优点是不要求 JS 同源,且 Google Analytics 等常用工具广泛采用此模式。但需要注意的是,虽然它实现了非阻塞下载,但在代码下载完成并执行前,仍会阻止 onload 事件的触发。如果页面逻辑严重依赖 window.onload 进行初始化渲染,这种方法仍存在局部阻塞。

第2步:在 onload 事件后触发加载

为了解决上述阻塞问题,可以在浏览器 onload 事件触发后再开始加载外部 JS。这样既保证了核心页面的快速显示,又实现了资源的加载。

(function() {
    function async_load(){
        var s = document.createElement('script');
        s.type = 'text/javascript';
        s.async = true;
        s.src = 'http://yourdomain.com/script.js';
        var x = document.getElementsByTagName('script')[0];
        x.parentNode.insertBefore(s, x);
    }
    if (window.attachEvent)
        window.attachEvent('onload', async_load);
    else
        window.addEventListener('load', async_load, false);
})();

第3步:理解 DOMContentLoaded 与 OnLoad 时机

掌握这两个事件节点有助于精准控制加载时机:

  • DOMContentLoaded:页面 HTML 解析完成,DOM 树已构建,但图片和子框架可能尚未加载。适合用于不需要依赖外部资源的 DOM 操作。
  • OnLoad:页面所有资源(包括图片)加载完毕。浏览器的进度条在此时才真正停止。

三、HTML5 async 与 defer 属性详解

随着 HTML5 的普及,<script> 标签增加了两个强大的属性来管理加载行为。

1. defer 属性:顺序执行的异步

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

拥有 defer 属性的脚本,浏览器会并行下载但不阻塞渲染。关键特性是保证按顺序依次执行,且在 DOMContentLoaded 事件之后执行。这非常适合那些需要操作完整 DOM 结构的库文件。IE 4+ 均支持,Firefox 3.5+、Chrome 等均支持。

2. async 属性:尽快的无序异步

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

这是 HTML5 新增的特性。拥有 async 的脚本会在下载完成后尽快执行,不保证与其他脚本的顺序,也无法保证在 DOMContentLoaded 之前还是之后执行,通常发生在 onload 之前。由于执行时机会打断解析,因此脚本中不应包含 document.write 或依赖 DOM 结构的逻辑。Firefox 3.6+、Opera 10.5+、IE9+ 支持。

3. 两者混用的策略

若同时设置 async 和 defer,在不支持 async 的老版本 IE 中,浏览器会退回到兼容的 defer 行为,从而实现最大范围的兼容性。不过,现代开发中更倾向于根据业务需求单一选择。

四、延迟加载与两阶段执行分离

除了标准的异步,还有一种“延迟加载”(Lazy Loading)策略,专门针对非核心功能的 JS 模块。

延迟执行原理

JS 的加载分为“下载内容”和“解析执行”两阶段。普通异步加载仅解决了第一阶段的不阻塞,但一旦下载完代码就会立即阻塞线程去执行。对于某些仅在用户交互时才需要的功能(如展开高级菜单),可以采用“按需执行”技巧:

  1. 通过特殊手段(如将 JS 作为 Image 或 Object 加载)先将代码下载并缓存,此时不会立即执行。
  2. 当特定事件触发时,再从内存中提取代码片段进行执行。

这种方式彻底消除了大型第三方脚本对首屏渲染的任何干扰。

五、模块化管理与最佳实践

关于 document.write 的限制

在异步模式下,无法使用 document.write,因为无法确定写入的具体位置。若在异步回调中强行调用,甚至会导致清空当前页面内容。替代方案是直接操作 DOM API(如 createElement)将元素添加到预留给它的占位符容器中。

JS 模块化工具

异步加载往往伴随着复杂的依赖关系。为了解决命名空间冲突和执行顺序问题,建议引入模块化标准或库:

  • CommonJS / RequireJS:适用于服务端或浏览器端模块化依赖管理。
  • SeaJS:淘宝团队提出的轻量级模块加载器,国内应用广泛。

综合最佳实践总结

  1. 最小化体积:使用压缩工具减小文件大小,并开启 HTTP Gzip 压缩。
  2. 合理放置:若无异步要求,脚本尽量置于 </body> 闭合标签前。
  3. 避免内联:尽量不要在 HTML 标签(如 onclick)中直接写 JS,以利于缓存维护。
  4. 利用原生属性:优先使用 async 或 defer,它们是现代浏览器原生支持且最高效的方式。

以上就是 JavaScript 异步加载、async/defer 属性及应用优化的详细内容,更多关于 Web 前端性能调优的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表