目前主流模块规范有:
规范名称 | 运行环境 | 实现 | 加载方式 |
---|---|---|---|
AMD(异步模块定义) | 客户端 | require.js | 异步 |
CMD(通用模块定义) | 客户端 | sea.js | 异步 |
CommonJS | 服务端 | NodeJS | 同步(动态加载) |
es6 | 客户端 | es6 | 静态加载 |
AMD 和 CMD 加载多个文件时都是异步加载
区别:
required.js
是 AMD 的实现
使用:define(id?, dependencies?, factory);
;
// 定义模块 myModule.js
define(['dependency'], function(){
var name = 'Byron';
function printName(){
console.log(name);
}
return {
printName: printName
};
});
// 加载模块
require(['myModule'], function (my){
my.printName();
});
sea.js
是 CMD 的实现
CMD 中一个模块就是一个文件
使用:define(id?, dependencies?, factory);
;
// 定义模块 myModule.js
define(function(require, exports, module) {
var $ = require('jquery.js')
$('div').addClass('active');
});
// 加载模块
seajs.use(['myModule.js'], function(my){
});
CommonJS 模块就是对象,输入时必须查找对象属性。
特点:
每个模块内部,都有一个module对象,代表当前模块。它有以下属性。
module.exports = 123
为了方便,Node为每个模块提供一个 exports
变量,指向 module.exports
。
注意:
// 相当于每个模块顶部都有这个声明
var exports = module.exports;
// 可以给导出的对象添加属性
exports.area = function (r) {
return Math.PI * r * r;
};
// 不可以导出一个单一值
exports = 123; // 无效
require
命令用于加载模块文件,返回该模块的 exports
对象
ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。
优点:
缺点:
通过 export
命令规定模块的对外接口。有两种模式:
// 导出单个
export let name1 = 1;
// 导出多个
export { name1, name2, ...};
// 重命名导出
export {
name1 as master
}
// 解构导出并重命名
export const { name1, name2: bar } = o;
// 默认导出
export default expression;
export { name1 as default, … };
// 聚合模块 - 输入后立马输出
export * from …; // does not set the default export
export * as name1 from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;
通过 import
命令加载模块
import
是静态的,在编译时就加载import
命令会提升到顶部使用:
*
指定一个对象,所有输出值都加载在这个对象上as
可以设置别名import
某个模块,会执行,但不会输入任何值export default
// 执行 lodash 模块,但不输入任何值
import 'lodash';
// 非默认导出需有大括号。可以使用 as 设置别名
import { firstName as fn, persion } from './profile.js';
// export default 默认导出不需要括号
import class from './class.js';
// 变量不允许改写
fn = 123; // Syntax Error : 'firstName' is read-only;
// 对象属性可以改写 - 不建议
persion.age = 18;
import()
可以实现动态加载
import()
可以像调用函数一样动态导入模块,并返回一个promiseimport()
支持 await
关键字import()
可以在commonJS 模块中运行使用场景:
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
async getModule() {
if (true) {
let module = await import('/modules/my-module.js');
}
}
"循环加载"(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。
// a.js
var b = require('b');
// b.js
var a = require('a');
通常,"循环加载"表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序boom....无法执行。
如何解决?commonJS 和 Es6 的module 给出了不同的解决方案。
commonJS 是动态加载,执行一次就会缓存结果。如果出现某个模块被"循环加载",返回的是当前已经执行的部分的值,而不是代码全部执行后的值。所以,输入变量时需要非常小心。
a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
执行main.js
node main.js
在 b.js 之中,a.done = false [解释1]
b.js 执行完毕
在 a.js 之中,b.done = true [解释2]
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true
ES6 是静态加载,不会缓存结果。 它只是生成一个指向被加载模块的引用,每次都会根据引用动态去加载模块取值。
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';
执行
node --experimental-modules a.mjs
b.mjs
ReferenceError: foo is not defined -> 原因: foo未定义。
解决:将foo写成函数,使它产生函数提升。函数表达式不行,因为它不能提升
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};
执行
$ node --experimental-modules a.mjs
b.mjs
foo
a.mjs
bar
例子2:
// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
counter++;
return n === 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {
return n !== 0 && even(n - 1);
}
执行
$ babel-node
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
> m.even(20)
true
> m.counter
17
https://www.leftso.com/article/2408051021282428.html