在最近的项目中,经常使用 RequireJS 或者建议添加 RequireJS。在这篇文章中,将描述 RequireJS 是什么以及它的一些基本应用场景。
在讨论 RequireJS 之前,先要了解什么是JavaScript模块以及AMD。
JavaScript 模块是遵循单一职责原则(SRP)的代码片段,它们暴露了一个公共 API。在当今的 JavaScript 开发中,通常将许多功能封装在模块中,大多数项目中每个模块都存在于自己的文件中。这使得 JavaScript 开发者的生活变得更加困难,因为他们需要不断关注模块之间的依赖关系,并以特定的顺序加载模块,否则在运行时会出现错误。
当想要加载JavaScript模块时,会使用 script 标签。为了加载模块依赖,需要先加载依赖,然后加载依赖项。使用 script 标签时,需要按特定顺序安排它们的加载,脚本将同步加载。可以使用 async 和 defer 关键字使加载异步,但在这个过程中可能会失去加载顺序。另一种选择是将所有脚本捆绑在一起,但仍然需要在捆绑过程中按正确的顺序对它们进行排序。
AMD 的核心思想是以一种方式定义模块,使得模块及其依赖可以异步加载,并且顺序正确。
CommonJS 是一种尝试标准化常见JavaScript模式的尝试,它包括了一个 AMD 定义,建议在继续阅读本文之前先阅读它。在 ECMAScript 6,即 JavaScript vNext 规范中,有关于导出、导入和模块的规范,这些规范将成为 JavaScript 语言的一部分,但只是在未来。这就是 RequireJS 进入故事的地方。
RequireJS 是一个 JavaScript 文件和模块框架,可以从 下载,或者如果在 Visual Studio 环境中工作,可以使用 Nuget。RequireJS 支持浏览器和服务器环境,如 node.js。使用 RequireJS,将只加载相关的模块依赖项,并按正确的顺序加载。
RequireJS 在使用时所做的是为每个定义的依赖创建 script 标签,并使用 head.appendChild() 函数即时加载这些依赖。然后,在依赖项加载完成后,RequireJS 将确定正确的顺序来定义模块,并将按该顺序调用每个模块定义。这意味着只需要一个根来加载需要的所有功能,RequireJS 将完成其余的工作。为了正确使用这个功能,将需要使用 RequireJS API 定义每个模块,否则将无法按预期工作。
RequireJS API 存在于 requirejs 命名空间内,当加载 RequireJS 脚本时,该命名空间将被加载。RequireJS 包括三个主要的 API 函数:
稍后将检查如何使用这些函数,但首先让了解如何开始 RequireJS 加载过程。
一旦下载了 RequireJS,将脚本放入解决方案后,第一件事就是理解 RequireJS 如何开始工作。一旦 RequireJS 加载完成,它会搜索具有 data-main 属性的脚本(它应该是设置 src 属性加载 RequireJS 的同一个脚本)。data-main 应该设置为所有脚本的基础 URL。从基础 URL,RequireJS 将开始加载所有相关的模块。
以下是一个带有 data-main 属性的 script 标签示例:
<script src="scripts/require.js" data-main="scripts/app.js"></script>
另一种定义基础 URL 的方法是使用 config 函数,将在后面看到。RequireJS 假设所有依赖项都是脚本,所以当声明一个依赖项时,不需要使用 .js 后缀。
如果想用自己的配置更改 RequireJS 的默认配置值,可以使用 requirejs.config 函数。config 函数接收一个选项对象,可以包括许多配置选项。以下是一些可以使用的配置:
以下是使用 config 函数的示例:
require.config({
baseUrl: 'scripts/app',
paths: {
lib: '../lib'
},
shim: {
'backbone': {
deps: ['underscore'],
exports: 'Backbone'
}
}
});
示例中的 baseUrl 设置为 scripts/app,每个以 lib 开头的模块都配置为从 scripts/lib 文件夹中使用,backbone 作为 shim 加载,具有依赖项。
模块只是良好作用域的对象,它们暴露了一个 API 并封装了它们的内部。为了定义一个模块,RequireJS 暴露了 define 函数。按照约定,每个 JavaScript 文件中应该只有一个 define 调用。define 函数接收一个依赖项数组和一个函数,该函数将包含所有模块定义。按照约定,模块定义函数接收所有先前的依赖项作为参数,并且按照它们在数组中提供的顺序。例如,这是一个简单的模块定义:
define(['logger'], function(logger) {
return {
firstName: 'John',
lastName: 'Black',
sayHello: function () {
logger.log('hello');
}
};
});
如所见,一个数组传递给 define 函数,其中包含一个 logger 依赖项,该依赖项稍后在模块中使用。此外,可以看到在模块定义函数中有一个名为 logger 的参数,它将被设置为加载的 logger 模块。每个模块都应该返回其 API,在这个例子中是两个属性(firstName 和 lastName)和一个函数(sayHello)。稍后,如果将这个模块作为另一个模块依赖项加载,将能够使用公开的 API。
require(['jquery'], function($) {
// jQuery 已加载,现在可以使用
});