javascript七基础学习系列二千四百一十七:使用ES6 之前的模块加载器

在ES6 原生支持模块之前,使用模块的JavaScript 代码本质上是希望使用默认没有的语言特性。因
此,必须按照符合某种规范的模块语法来编写代码,另外还需要单独的模块工具把这些模块语法与
JavaScript 运行时连接起来。这里的模块语法和连接方式有不同的表现形式,通常需要在浏览器中额外
加载库或者在构建时完成预处理。
CommonJS
CommonJS 规范概述了同步声明依赖的模块定义。这个规范主要用于在服务器端实现模块化代码组
织,但也可用于定义在浏览器中使用的模块依赖。CommonJS 模块语法不能在浏览器中直接运行。
CommonJS 模块定义需要使用require()指定依赖,而使用exports 对象定义自己的公共API。
下面的代码展示了简单的模块定义:
var moduleB = require(‘./moduleB’);
module.exports = {
stuff: moduleB.doStuff();
};
moduleA 通过使用模块定义的相对路径来指定自己对moduleB 的依赖。什么是“模块定义”,以及
如何将字符串解析为模块,完全取决于模块系统的实现。比如在Node.js 中,模块标识符可能指向文件,
也可能指向包含index.js 文件的目录。
请求模块会加载相应模块,而把模块赋值给变量也非常常见,但赋值给变量不是必需的。调用
require()意味着模块会原封不动地加载进来:
console.log(‘moduleA’);
require(‘./moduleA’); // “moduleA”
无论一个模块在require()中被引用多少次,模块永远是单例。在下面的例子中,moduleA 只会
被打印一次。这是因为无论请求多少次,moduleA 只会被加载一次。
console.log(‘moduleA’);
var a1 = require(‘./moduleA’);
var a2 = require(‘./moduleA’);
console.log(a1 === a2); // true
模块第一次加载后会被缓存,后续加载会取得缓存的模块(如下代码所示)。模块加载顺序由依赖
图决定。
console.log(‘moduleA’);
require(‘./moduleA’);
require(‘./moduleB’); // “moduleA”
require(‘./moduleA’);
在CommonJS 中,模块加载是模块系统执行的同步操作。因此require()可以像下面这样以编程
方式嵌入在模块中:
console.log(‘moduleA’);
if (loadCondition) {
require(‘./moduleA’);
}
这里,moduleA 只会在loadCondition 求值为true 时才会加载。这个加载是同步的,因此if()
块之前的任何代码都会在加载moduleA 之前执行,而if()块之后的任何代码都会在加载moduleA 之
后执行。同样,加载顺序规则也会适用。因此,如果moduleA 已经在前面某个地方加载过了,这个条
件require()就意味着只暴露moduleA 这个命名空间而已。
在上面的例子中,模块系统是Node.js 实现的,因此./moduleB 是相对路径,指向与当前模块位于
同一目录中的模块目标。Node.js 会使用require()调用中的模块标识符字符串去解析模块引用。在
Node.js 中可以使用绝对或相对路径,也可以使用安装在node_modules 目录中依赖的模块标识符。我
们并不关心这些细节,重要的是知道在不同的CommonJS 实现中模块字符串引用的含义可能不同。不过,
所有CommonJS 风格的实现共同之处是模块不会指定自己的标识符,它们的标识符由其在模块文件层级
中的位置决定。
指向模块定义的路径可能引用一个目录,也可能是一个JavaScript 文件。无论是什么,这与本地模
块实现无关,而moduleB 被加载到本地变量中。moduleA 在module.exports 对象上定义自己的公
共接口,即foo 属性。
如果有模块想使用这个接口,可以像下面这样导入它:
var moduleA = require(‘./moduleA’);
console.log(moduleA.stuff);
注意,此模块不导出任何内容。即使它没有公共接口,如果应用程序请求了这个模块,那也会在加
载时执行这个模块体。
module.exports 对象非常灵活,有多种使用方式。如果只想导出一个实体,可以直接给module.
exports 赋值:
module.exports = ‘foo’;
这样,整个模块就导出一个字符串,可以像下面这样使用:
var moduleA = require(‘./moduleB’);
console.log(moduleB); // ‘foo’
导出多个值也很常见,可以使用对象字面量赋值或每个属性赋一次值来实现:
// 等价操作:
module.exports = {
a: ‘A’,
b: ‘B’
};
module.exports.a = ‘A’;
module.exports.b = ‘B’;
模块的一个主要用途是托管类定义(这里使用ES6 风格的类定义,不过ES5 风格也兼容):
class A {}
module.exports = A;
var A = require(‘./moduleA’);
var a = new A();
也可以将类实例作为导出值:
class A {}
module.exports = new A();
此外,CommonJS 也支持动态依赖:
if (condition) {
var A = require(‘./moduleA’);
}
CommonJS 依赖几个全局属性如require 和module.exports。如果想在浏览器中使用CommonJS
模块,就需要与其非原生的模块语法之间构筑“桥梁”。模块级代码与浏览器运行时之间也需要某种“屏
障”,因为没有封装的CommonJS 代码在浏览器中执行会创建全局变量。这显然与模块模式的初衷相悖。
常见的解决方案是提前把模块文件打包好,把全局属性转换为原生JavaScript 结构,将模块代码封
装在函数闭包中,最终只提供一个文件。为了以正确的顺序打包模块,需要事先生成全面的依赖图。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值