随着我们的应用越来越大,我们想要将其拆分成多个文件,即所谓的“模块”(module)。一个模块可以包含用于特定目的的类或函数库。
模块化的定义
一个模块(module)就是一个文件。一个脚本就是一个模块。
模块可以相互加载,并可以使用特殊的指令 export
和 import
来交换功能,从另一个模块调用一个模块的函数:
export
关键字标记了可以从当前模块外部访问的变量和函数。import
关键字允许从其他模块导入功能。模块化的历程
大致可以分为三个阶段:
早期模块化方案
命名空间
对象可以有属性,对象的属性通过对象名字来访问,相当于设定了一个命名空间。
var myModule = {
name: "Joe",
getName: function () {
console.log(this.name);
}
};
myModule.getName(); // "Joe"
然而缺点是,对象内部属性全部会暴露出来,内部状态可以被外部更改,如下:
var myModule = {
name: "Joe",
getName: function () {
console.log(this.name);
}
};
myModule.name = "Lucy";
myModule.getName(); // "Lucy"
立即执行函数 IIFE
立即执行函数(简称 IIFE)是利用函数闭包的特性来实现私有数据和共享方法,如下:
var myModule = (function () {
var name = "Joe";
function getName() {
console.log(name);
}
return { getName };
})();
myModule.name = "哈哈哈";
myModule.getName(); // "Joe"
我们可以看到,这种方法很好实现了数据的封装和私有化。如果依赖另外一个模块呢?我们只需要将其他模块作为参数传入:
// otherModule.js 模块文件
var otherModule = (function () {
return {
a: 1,
b: 2
};
})();
// myModule.js模块文件 - 依赖 otherModule 模块
var myModule = (function (other) {
var name = "Joe";
function getName() {
console.log(name);
console.log(other.a, other.b);
}
return { getName };
})(otherModule);
myModule.name = "哈哈哈";
myModule.getName(); // 依次输出:"Joe" 1 2
规范标准时代
主要有四种规范:
CommonJS
—— 为 Node.js
创建的模块系统。AMD
—— 最古老的模块系统之一,最初由 require.js
库实现。CMD
—— 它汲取了 CommonJS
和 AMD
规范的优点,也是专门用于浏览器的异步模块加载。UMD
—— 另外一个模块系统,建议作为通用的模块系统,它与 AMD
和 CommonJS
都兼容。CommonJS
在 Node.js
中,每一个文件就是一个模块,具有单独的作用域,对其他文件是不可见的。关于 CommonJS
的规范有几个需要注意的特点:
module.exports
属性。module.exports
属性输出的是值的拷贝,一旦这个值被输出,模块内再发生变化不会影响到输出的值。module.exports
和 exports
的区别CommonJS
规范用代码如何在浏览器端实现呢?其实就是实现 module.exports
和 require
方法。
实现思路:根据 require
的文件路径,加载文件内容并执行,同时将对外接口进行缓存。因此我们需要定义module.exports
后,借助立即执行函数,将 module
和 module.exports
对象进行赋值:
let module = {}
module.exports = {}
(function(module, exports) {
// ...
}(module, module.exports))
AMD
Asynchronous module definition (AMD) 是专门为浏览器环境设计的,它定义了一套异步加载标准来解决同步的问题。它规定了如何定义模块,如何对外输出,如何引入依赖。这一切都需要代码去实现,因此一个著名的库 —— require.js 应运而生,require.js 实现很简单:通过 define 方法,将代码定义为模块;通过 require 方法,实现代码的模块加载。
CMD
CMD
规范整合了 CommonJS
和 AMD
规范的特点。它的全称为:Common Module Definition,类似 require.js,
CMD
规范的实现为 sea.js。
AMD
和 CMD
的两个主要区别如下。
AMD
需要异步加载模块,而 CMD
在 require
依赖的时候,可以通过同步的形式(require),也可以通过异步的形式(require.async
)。CMD
遵循依赖就近原则,AMD 遵循依赖前置原则。也就是说,在 AMD
中,我们需要把模块所需要的依赖都提前在依赖数组中声明。而在 CMD
中,我们只需要在具体代码逻辑内,使用依赖前,把依赖的模块 require 进来。具体到代码实现,sea.js 与 require.js 并没有本质差别,这里不再另做分析。
UMD
UMD(Universal Module Definition),即通用模块定义。它随着大前端的趋势所诞生,可以通过运行时或者编译时让同一个代码模块在使用 CommonJS
、CMD
甚至是 AMD
的项目中运行,也就是说同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法就行了,那它是怎样实现的呢?
我们来看看这样一段代码:
((root, factory) => {
if (typeof define === "function" && define.amd) {
// AMD
define(factory);
} else if (typeof exports === "object") {
// CommonJS
module.exports = factory();
} else if (typeof define === "function" && define.cmd) {
// CMD
define(function (require, exports, module) {
module.exports = factory();
});
} else {
// 都不是
root.umdModule = factory();
}
})(this, () => {
console.log("我是UMD"); // "我是UMD"
// todo...
});
可以看到,define
是 AMD/CMD
语法,而 exports
只在 CommonJS
中存在,你会发现它在定义模块的时候会检测当前使用环境和模块的定义方式,如果匹配就使用其规范语法,全部不匹配则挂载在全局对象上,我们看到传入的是一个 this
,它在浏览器中指的就是 window
,在服务端环境中指的就是 global
,使用这样的方式将各种模块化定义都兼容。
ES6 模块
ES6 模块(或称为 ESM),ES6 模块不是对象,import
命令被 JavaScript 引擎静态分析,在编译的时候就引入模块代码。而不是在代码运行时加载,所以无法实现条件加载。也就使得静态分析成为可能。
export
export
可以导出对象中多个属性、方法,export default
只能导出一个可以不具名的函数。我们可以使用 import 引入。
import
import { fn } from './xxx' // export 导出的方式
import fn from 'xx' // export default 方式
ES6 模块运行机制与 CommonJS 运行机制不一样。JavaScript 引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用。等到脚本真正执行的时候。才会通过引用模块获取值,在引用到执行的过程中,模块中的值发生变化,导入的这里也会跟着发生变化。ES6 模块是动态引入的。并不会缓存值。模块里总是绑定其所在的模块。
小练习
ES6 模块有什么特点?
答案:见上文。
阅读量:1967
点赞量:0
收藏量:0