CommonJS的概念
CommonJS是一种规范,Node是这种规范的实现。在Node中如何体现CommonJS规范呢?就在于模块化。require(),exports,module.exports这三者共同实现了这种模块化。
// foo.js
const name = 'zengge'
const age = 22
exports.name = name
exports.age = age
// main.js
const fooObj = require('./foo')
在foo文件中我们使用exports导出了两个变量name,age,我们在main文件中使用了require()函数引入了foo文件并返回了一个对象,我们用常量fooObj接收,那么现在fooObj就指向了由name='zengge',age=22所组成的对象。
// main.js
const fooObj = require('./foo')
console.log(fooObj) // {name: 'zengge', age: 22}
既然是对象,我们就可以使用解构,也可以这样书写代码:
// main.js
const { name, age } = require('./foo')
在Node中我们也经常使用module.exports来导出,那module.exports又是什么呢?它和exports的区别是什么呢?
CommonJS中其实并没有module.exports这个概念的,但是Node为了实现模块的导出,Node使用了Module类。
所以真正导出的并不是export,而是module.exports。那有人会问了,我直接通过exports也可以导出啊,这是因为module.exports和exports实际上指向的是同一个对象,也就是我们刚刚提到的{name: 'zengge', age: 22}这个对象,所以如果将module.exports赋值给一个新的对象,那么exports所指向的对象就会变成module.exports赋值的新对象,从而证实真正的导出者是module.exports。
CommonJS的缺点
我们知道使用CommonJS加载模块是同步的,这在服务器上并不会有什么问题,服务器加载的都是本地文件,加载速度很快。
但是如果将它运用在浏览器上,即使是很简单的DOM操作,也将会阻塞代码的运行,造成浏览器的"假死"。
AMD
所以在早期,浏览器会使用一种叫AMD(Asyncchronous Module Definition)的一种规范,它主要是采用了异步加载模块,可以让代码异步加载,从而解决浏览器"假死"。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback); 第一个参数
[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:
require(['math'], function (math) {
math.add(2, 3);
});
math.add()与math模块加载不是同步的,浏览器不会发生"假死"。所以很显然,AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。
事实上AMD规范早于CommonJS,但是CommonJS目前仍然在被使用,而AMD使用的较少了。
CMD
CMD是SeaJS在推广过程中对模块定义的规范化产出
CMD和AMD的区别有以下几点:
-
对于依赖的模块
AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。 -
AMD推崇依赖前置(在定义模块的时候就要声明其依赖的模块),CMD推崇依赖就近(只有在用到某个模块的时候再去require——按需加载)。
//AMD
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
ES Module
在以前Javascript本身并没有模块化,这也一直是一个痛点。所以以上的CommonJS,AMD,CMD这些规范在Javascript中使用,但是ES Module推出之后,一切都变得不一样了。
导出方式
- 导出单个变量
export const name = "张三"
export function add(a, b) {
return a + b;
}
export const obj = { name: 'jack' }
- 导出多个变量
const name = "张三";
function add(a, b) {
return a + b;
}
const obj = { name: 'jack' };
export {
name,
add,
obj
}
- 导出默认变量
const name = "张三";
function add(a, b) {
return a + b;
}
const obj = { name: 'jack' };
export default name
默认导出只能有一个
- 导出时起别名
const name = "张三";
function add(a, b) {
return a + b;
}
const obj = { name: 'jack' };
export {
name as tempName
add as tempAdd
obj as tempObj
}
export和import结合导出
export { name, obj, add } from './foo.js'
常见于封装组件时的统一导出
引入方式
- 普通引入
import { name, age, add } from './foo.js'
- 别名引入
import { name as tempName, age as tempName, add as tempAdd } from './foo.js'
* as foo引入
import * as foo from './foo.js'
- 动态引入
const importPromise = import('./foo.js')
importPromise.then((res) => {
console.log(res.name)
}).cache((err) => {
console.log(err)
})
import的动态引入本质上返回的是一个promise,所以这样就可以使用then和cache实现动态引入,也可以使用async/await语法糖。