- JavaScript本身,直到ES6(ES2015)才推出了自己的模块化方案;
- 在此之前,为了让JavaScript 支持模块化,涌现出了很多不同的模块化规范:AMD、CMD、CommonJS等;
原始写法
在没有 CommonJS 和 ES6 等模块化规范的时候,我们想要达到模块化的效果可能有这么三种:
1. 一个函数就是一个模块
<script>
function m1 () {
// ...
}
function m2 () {
// ...
}
</script>
:::danger 缺点:污染了全局变量,无法保证不会与其它模块发生冲突,而且模块成员之间看不出直接关系。 :::
2. 一个对象就是一个模块
对象写法 为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
<script>
var module1 = new Object({
_sum: 0,
foo1: function () {},
foo2: function () {}
})
</script>
:::danger 缺点:会暴露所有模块成员,内部的状态可能被改写。 :::
例如,我们如果只是想暴露出两个方法而不暴露出_sum,就做不到。
而此时,_sum可能被外部改写:
module1._sum = 2;
3. 立即执行函数为一个模块
<script>
var module1 = (function() {
var _sum = 0;
var foo1 = function () {};
var foo2 = function () {};
return {
foo1: foo1,
foo2: foo2
}
})();
</script>
利用立即执行函数内的作用域已经闭包来实现模块功能,导出我们想要导出的成员。
CommonJS 规范
CommonJS 是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为 ServerJS,后来为了体现它的广泛性,修改为 CommonJS,平时我们也会简称为 CJS。
Node.js 后来也采用了 CommonJS 的模块规范。
暴露(定义)模块
暴露模块有两种方式:
module.exports = {}
module.exports = {
name: 'lindaidai',
sex: 'boy'
}
exports.xxx = 'xxx'
exports.name = 'lindaidai';
exports.sex = 'boy'
module 中的一些属性:
console.log(module);
// out:
Module {
id: '.',
path: '/Users/xxx/Desktop/esm_commonjs/commonJS',
exports: {},
filename: '/Users/xxx/Desktop/esm_commonjs/commonJS/c.js',
loaded: false,
children: [],
paths: [
'/Users/xxx/Desktop/esm_commonjs/commonJS/node_modules',
'/Users/xxx/Desktop/esm_commonjs/node_modules',
'/Users/xxx/Desktop/node_modules',
'/Users/xxx/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
- exports:这就是 module.exports 对应的值,由于还没有赋任何值给它,它目前是一个空对象。
- loaded:表示当前的模块是否加载完成。
- paths:node 模块的加载路径,这块不展开讲,感兴趣可以看node 文档
引用(引入)模块
对于模块的引用使用全局方法require()
就可以了。
:::danger 这个全局方法是 CommonJS 规范下的方法,比如 nodejs 的模块基于 CommonJS 规范,就可以使用,而浏览器 window 下面没做任何处理,想直接在 html 里用肯定就是不行的了 :::
var m1 = require('./try-module.js')
console.log(m1);
require()
的参数允许是一个表达式
var m1Url = './m1.js';
var m1 = require(m1Url);
// 做一些字符串拼接:
var m1 = require('./m' + '1.js');
require 函数中也有一些值得注意的属性:
console.log(require);
// out:
[Function: require] {
resolve: [Function: resolve] { paths: [Function: paths] },
main: Module {
id: '.',
path: '/Users/xxx/Desktop/esm_commonjs/commonJS',
exports: {},
filename: '/Users/xxx/Desktop/esm_commonjs/commonJS/c.js',
loaded: false,
children: [],
paths: [
'/Users/xxx/Desktop/esm_commonjs/commonJS/node_modules',
'/Users/xxx/Desktop/esm_commonjs/node_modules',
'/Users/xxx/Desktop/node_modules',
'/Users/xxx/node_modules',
'/Users/node_modules',
'/node_modules'
]
},
extensions: [Object: null prototype] {
'.js': [Function (anonymous)],
'.json': [Function (anonymous)],
'.node': [Function (anonymous)]
},
cache: [Object: null prototype] {
'/Users/xxx/Desktop/esm_commonjs/commonJS/c.js': Module {
id: '.',
path: '/Users/xxx/Desktop/esm_commonjs/commonJS',
exports: {},
filename: '/Users/xxx/Desktop/esm_commonjs/commonJS/c.js',
loaded: false,
children: [],
paths: [Array]
}
}
}
- main 指向当前当前引用自己的模块,所以类似 python 的 name == ‘main‘, node 也可以用 require.main === module 来确定是否是以当前模块来启动程序的。
- extensions 表示目前 node 支持的几种加载模块的方式。
cache 表示 node 中模块加载的缓存,也就是说,当一个模块加载一次后,之后 require 不会再加载一次,而是从缓存中读取。
CommonJS 规范的特点
所有代码都运行在模块作用域,不会污染全局作用域;
- 模块是同步加载的,即只有加载完成,才能执行后面的操作;
- 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存;
CommonJS输出是值的拷贝(即,require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值)。
ES6 模块化
ES6 标准出来后,ES6 Modules 规范算是成为了前端的主流吧,以import引入模块,export导出接口被越来越多的人使用。
export 导出模块
export有两种模块导出方式:
命名式导出(名称导出)
- 默认导出(自定义导出)
命名式导出
```javascript // 以下两种为错误 // 1. export 1; // 2. const a = 1; export a;
// 以下为正确 // 3. const a = 1; export { a };
// 4. 接口名与模块内部变量之间,建立了一一对应的关系 export const a = 1, b = 2;
// 5. 接口名与模块内部变量之间,建立了一一对应的关系 export const a = 1; export const b = 2;
// 或者用 as 来命名 const a = 1; export { a as outA };
const a = 1; const b = 2; export { a as outA, b as outB };
容易混淆的可能是2和4两种写法了,看着很像,但是2却不行。2直接导出一个值为1的变量是和情况一一样,没有什么意义,因为你在后面要用的时候并不能完成解构。<br />但是4中,接口名与模块内部变量之间,建立了一一对应的关系,所以可以。
<a name="l58FR"></a>
### 默认导出
默认导出会在export后面加上一个default
```javascript
// 1.
const a = 1;
export default a;
// 2.
const a = 1;
export default { a };
// 3.
export default function() {}; // 可以导出一个函数
export default class(){}; // 也可以出一个类
其实,默认导出可以理解为另一种形式上的命名导出,也就是说a这个属性名相当于是被我重写了成了default:
const a = 1;
export defalut a;
// 等价于
export { a as default }
import 导入模块
import 模块导入与 export 模块导出功能相对应,也存在两种模块导入方式:命名式导入(名称导入)和默认导入(定义式导入)。
// 某个模块的导出 moudule.js
export const a = 1;
// 模块导入
// 1. 这里的a得和被加载的模块输出的接口名对应
import { a } from './module'
// 2. 使用 as 换名
import { a as myA } from './module'
// 3. 若是只想要运行被加载的模块可以这样写,但是即使加载2次也只是运行一次
import './module'
// 4. 整体加载
import * as module from './module'
// 5. default接口和具名接口
import module, { a } from './module'
第四种写法会获取到module
中所有导出的东西,并且赋值到module
这个变量下,这样我们就可以用module.a
这种方式来引用a了。
export … from …
其实还有一种写法,可以将export
和from
结合起来用。
例如,我有三个模块a、b、c。
c模块现在想要引入a模块,但是它不不直接引用a,而是通过b模块来引用,那么你可能会想到b应该这样写:
import { someVariable } from './a';
export { someVariable };
引入someVariable然后再导出。
这还只是一个变量,我们得导入再导出,若是有很多个变量需要这样,那无疑会增加很多代码量。
所以这时候可以用下面这种方式来实现:
export { someVariable } from './a';
ES6 模块与 CommonJS 模块的差异
① CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
② CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJs导入的模块路径可以是一个表达式,因为它使用的是require()方法;而ES6 Modules只能是字符串。
ES6 Modules中没有这些顶层变量:arguments、require、module、exports、filename、dirname。
由于 CommonJS 并不是 ECMAScript 标准的一部分,所以 类似 module 和 require 并不是 JS 的关键字,仅仅是对象或者函数而已,意识到这一点很重要。
console.log(module);
console.log(require);
// out:
Module {
id: '.',
path: '/Users/xxx/Desktop/esm_commonjs/commonJS',
exports: {},
filename: '/Users/xxx/Desktop/esm_commonjs/commonJS/c.js',
loaded: false,
children: [],
paths: [
'/Users/xxx/Desktop/esm_commonjs/commonJS/node_modules',
'/Users/xxx/Desktop/esm_commonjs/node_modules',
'/Users/xxx/Desktop/node_modules',
'/Users/xxx/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
[Function: require] {
resolve: [Function: resolve] { paths: [Function: paths] },
main: Module {
id: '.',
path: '/Users/xxx/Desktop/esm_commonjs/commonJS',
exports: {},
filename: '/Users/xxx/Desktop/esm_commonjs/commonJS/c.js',
loaded: false,
children: [],
paths: [
'/Users/xxx/Desktop/esm_commonjs/commonJS/node_modules',
'/Users/xxx/Desktop/esm_commonjs/node_modules',
'/Users/xxx/Desktop/node_modules',
'/Users/xxx/node_modules',
'/Users/node_modules',
'/node_modules'
]
},
extensions: [Object: null prototype] {
'.js': [Function (anonymous)],
'.json': [Function (anonymous)],
'.node': [Function (anonymous)]
},
cache: [Object: null prototype] {
'/Users/xxx/Desktop/esm_commonjs/commonJS/c.js': Module {
id: '.',
path: '/Users/xxx/Desktop/esm_commonjs/commonJS',
exports: {},
filename: '/Users/xxx/Desktop/esm_commonjs/commonJS/c.js',
loaded: false,
children: [],
paths: [Array]
}
}
}
可以看到 module 是一个对象, require 是一个函数,仅此而已。
前面提到,CommonJS 中 module 是一个对象, require 是一个函数。而与此相对应的 ESM 中的 import 和 export 则是关键字,是 ECMAScript 标准的一部分。