# AMD的代表require.js
AMD是指Asynchronous Module Definition,异步的模块加载机制。是在推广require.js时对模块规范化产出。
AMD推崇的是依赖前置。AMD对加载的模块是提前读取并加载。主逻辑一定在所有依赖加载完成后才执行。
主要用于解决下述两个问题
- 多个文件有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器。
- 加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应的时间越长。
语法:
- requireJS定义了一个函数
define,它是全局变量,用来定义模块:define(id ? dependencies ?, factory)。 - 在页面上使用模块加载函数:
require([dependencies], factory)。
// myModule.js
//定义模块
define(['dependency'], function () {
var name = 'Byron';
function printName() {
console.log(name);
}
return {
printName: printName
};
});
//加载模块
require(['myModule'], function (my) {
my.printName();
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# CommonJS的代表node.js
CommonJS:开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports,模块加载require()引入模块。
# CMD的代表sea.js
CMD是指Common Module Definition,异步的模块加载机制。是在推广sea.js时对模块规范化产出。CMD更像是AMD和CommonJS的升级版,结合了两者的优点。
CommonJS可以做到当需要这个模块时,再读取并加载。AMD可以做到避免CommonJS的“临时读取并加载文件”,它是提前读取并加载。
而CMD可以做到的是,“提前读取文件,但在需要再加载”,这样可以避免浏览器临时加载文件的假死,也可以避免提前加载引起的逻辑问题。
CMD推崇的是就近依赖。CMD对加载的模块是提前读取并不加载,而是在需要时加载。
# 区别
CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。AMD在加载模块完成后就会执行该模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。
# ES6模块与CommonJS模块的差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块
# module.exports与exports,export与export default的区别
# CommonJS规范
参考地址:module.exports与exports,export与export default的区别 (opens new window)
CommonJS模块规范和ES6模块规范完全是两种不同的概念。
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令:
var exports = module.exports;
于是我们可以直接在exports对象上添加方法,表示对外输出的接口,如同在module.exports上添加一样。
WARNING
注意,因为 Node 模块是通过module.exports导出的,如果直接将exports变量指向一个值,就切断了exports与module.exports的联系,导致意外发生:
// a.js
exports = function a() {};
// b.js
const a = require('./a.js') // a 是一个空对象
2
3
4
5
# ESModule
ES6使用export和import来导出、导入模块:
// aaa.js
export const a = 1;
export const b = 2;
// bbb.js
// 全部引入
// import aaa from './aaa.js';
// 报错 export 'default' (imported as 'aaa') was not fount in './aaa.js'
import * as aaa from './aaa.js';
// 这样是可以的
// Module {
// a: 1,
// b: 2,
// Symbol(Symbol.toStringTag): 'Module'
// }
// 使用解构引入也可以
import { a, b } from './aaa.js';
// a = 1; b = 2;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
使用export default,一个模块只能存在一个export default
// aaa.js
const data = {
a: 1,
b: 2,
}
export default data;
// bbb.js
// 全部引入
import aaa from './aaa.js';
// aaa的值为
// {
// a: 1,
// b: 2,
// }
import * as aaa from './aaa.js';
// Module {
// default: { a: 1, b: 2 },
// Symbol(Symbol.toStringTag): 'Module'
// }
// 此时不能使用解构
import { a, b } from './aaa.js';
// a/b is not found in aaa.js
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
同时引入默认导出与解构
// aaa.js
const data = {
a: 1,
b: 2,
}
export default data;
export const c = 3;
// bbb.js
import aaa, { c } from './aaa.js';
// aaa = {a:1,b:2} c=3
2
3
4
5
6
7
8
9
10
11
TIPS
element-ui中在index.js中写的export default导出的所有组件,使用的时候却可以使用解构
import { Button, Select } from 'element-ui';
使用者引用element时拿到的代码是打包过后的。根据package.json中的main字段lib/element-ui.common.js可知,其实使用者用的是打包后的代码。
webpack配置文件中build/webpack.common.js,有一个属性libraryExport: 'default',应该使得打包后的文件去掉了default那一层。
常见面试问题:
requireJS是AMD规范的实现,seaJS是CMD规范的实现。AMD推崇的是前置依赖,CMD推崇的是就近依赖。
# CommonJS和ESModule的区别
| 特性 | CommonJS | ESModule |
|---|---|---|
| 语法 | module.exports和require | export和import |
| 加载方式 | 同步加载 | 静态加载(浏览器中异步加载) |
| 适用环境 | 主要用于Node.js | 适用于浏览器和Node.js |
| 模块缓存 | 支持模块缓存 | 支持模块缓存 |
| Tree Shaking | 不支持 | 支持 |
| 动态导入 | 支持动态导入(require) | 支持动态导入(import().then()) |
| 循环依赖 | 支持,但可能导致问题 | 支持,处理方式更合理 |
模块的导出导入:
CommonJS
- 基础类型:拷贝;模块内修改,模块外不变;模块外修改,模块内不变;(互不影响)
- 对象类型:引用;模块内修改,模块外变;模块外修改,模块内变;(互相影响)
ESModule
- 基础类型:只读引用;模块内修改,模块外变;模块外不允许修改;
- 对象类型:只读引用;模块内修改,模块外变;模块外修改,模块内变;(互相影响)