数字营销 · Web开发 · 基础设施

用 RequireJS 实现异步加载 jQuery 库

从网站加载速度优化中遇到的 jQuery 依赖问题出发,记录使用 RequireJS 实现 JavaScript 异步加载和模块依赖管理的实践。

这是2013年的文章,那时候使用RequireJS进行异步加载是优化网站性能的流行做法,到了现在(2026),异步加载还是需要,但是完全就没有必要使用RequireJS了,本站就是自己实现的异步加载功能。

在文章开始之前,先说一个当时遇到的实际问题。

手里有一个网站,前端的大部分 JavaScript 效果都是使用 jQuery 编写的。由于我本身比较关注 SEO 和网站加载速度,所以对网站性能一直比较在意。

以前其实并没有特别关注过这个问题,一方面是自己的网络速度还算快,另一方面可能也是当时对前端性能了解得还不够深入。

后来偶然使用 Google PageSpeed 对网站进行了测试,发现评分并不理想,其中比较明显的问题主要集中在:

  • JavaScript
  • 图片

图片优化这里暂时不讨论,这篇主要记录一下当时遇到的 JavaScript 加载问题,以及后来使用 RequireJS 的处理方法。

jQuery 加载带来的问题

当时网站的 <head> 中直接加载了 jQuery:

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

由于页面中后面的很多脚本都依赖 jQuery,因此必须等 jQuery 下载并执行完成之后,相关代码才能继续正常运行。

对于页面来说,这也可能影响后续内容的加载和渲染。

于是,我首先想到的是使用浏览器提供的异步加载方式,例如:

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

或者:

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

这样确实可以让 jQuery 不再按照传统方式阻塞脚本加载。

但很快又遇到了另外一个问题。

异步加载后,依赖关系怎么办?

网站中很多代码都是使用 jQuery 编写的,例如:

$(".menu").hide();

或者:

$("#submit").click(function(){
// ...
});

如果 jQuery 异步加载,而这些依赖 jQuery 的脚本已经开始执行,就可能出现:

$ is not defined

也就是说,虽然 jQuery 和其他 JavaScript 都进行了异步加载,但:

谁先加载完成,其实是不可控的。

例如:

开始加载 jQuery
开始加载业务脚本
业务脚本先加载完成
开始执行
jQuery 还没有加载完成
$ is not defined

因此,单纯把所有 JavaScript 都改成异步加载,并不能解决模块之间的依赖问题。

当时由于需要尽快解决这个问题,最后先采用了一个比较简单的办法:

把 jQuery 和依赖 jQuery 的脚本统一放到页面底部加载。

虽然这个办法比较直接,但至少能够保证页面主体内容先完成加载,再执行相关 JavaScript。

不过,后来了解到 RequireJS 之后,发现它正好可以解决 JavaScript 的模块依赖和异步加载问题。

什么是 RequireJS?

这里不准备讲太多概念。

对我来说,RequireJS 最主要的作用就是解决两个问题:

  1. JavaScript 模块的异步加载
  2. 模块之间的依赖关系管理

例如,一个脚本依赖 jQuery,那么就明确告诉 RequireJS:

这个脚本需要先加载 jQuery,等 jQuery 准备好之后再执行。

这样就不需要自己去判断 jQuery 到底什么时候加载完成。

基本加载方式

首先在页面中加载 RequireJS:

<script
src="require.js"
data-main="main"
async>
</script>

其中:

data-main="main"

表示 RequireJS 会继续加载对应的:main.js

main.js 主要负责后续模块的配置和加载。

页面结构可以简单理解为:

页面
require.js
main.js
加载所需模块
执行依赖模块的代码

配置 jQuery 模块

main.js 的内容可以这样写:

require.config({
paths: {
"jquery": "http://code.jquery.com/jquery-1.10.2.min"
}
});

这里的:

paths

用于配置模块路径。

上面的配置表示:

jquery

这个模块对应:jquery-1.10.2.min.js

当然,也可以把 jQuery 放在自己的服务器上,例如:

require.config({
paths: {
"jquery": "/js/jquery-1.10.2.min"
}
});

这里需要注意一点,配置模块路径时通常不需要填写:

.js

后缀。

也就是说:"jquery": "/js/jquery-1.10.2.min"

而不是:"jquery": "/js/jquery-1.10.2.min.js"

加载 jQuery 并执行代码

配置完成之后,可以通过:

require(['jquery'], function($) {
$("title").text("网页的标题被 jQuery 改变了");
});

来加载 jQuery。

这段代码可以理解为:

先加载 jquery 模块,等模块加载完成之后,再执行后面的函数。

函数中的:

$

就是加载完成后的 jQuery 对象。

因此,RequireJS 会处理加载顺序:

RequireJS
加载 jQuery
jQuery 加载完成
执行 function($)
安全使用 $

这样就避免了因为异步加载顺序不确定,而出现:

$ is not defined

的问题。

页面加载速度和模块依赖

使用 RequireJS 之后,JavaScript 可以按照模块的方式进行加载。

页面不需要一次性加载所有脚本,而是根据实际需要加载对应的模块。

例如:

页面
├── require.js
└── main.js
├── jquery
├── module A
└── module B

对于一些功能比较多的网站来说,这种方式可以让 JavaScript 的组织结构更加清晰。

同时,模块之间的依赖关系也不需要完全依靠手动控制。

例如:

require(['jquery', 'moduleA'], function($, moduleA) {
// jQuery 和 moduleA 都加载完成后执行
});

只有需要的模块准备完成之后,相关代码才会执行。

当时的一点理解

当时接触 RequireJS 之后,感觉它最大的价值并不仅仅是“异步加载”。

真正解决的问题其实是:

异步加载之后,如何正确处理 JavaScript 之间的依赖关系。

如果只是简单使用 async 加载多个脚本,虽然它们都可以异步下载,但执行顺序并不一定符合实际需求。

而模块化加载可以明确告诉程序:

A 依赖 B
B 必须先准备完成
然后才能执行 A

对于当时依赖大量 jQuery 脚本的网站来说,这种方式确实方便了很多。

当然,如果只是一个 JavaScript 文件不多的小型网站,直接把 jQuery 和相关代码放在页面底部加载,也是一种简单有效的处理方式。

不同的网站,还是应该根据实际情况选择合适的加载方案。

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。