共计 4661 个字符,预计需要花费 12 分钟才能阅读完成。
支流模块标准
目前支流模块标准有:
标准名称 | 运行环境 | 实现 | 加载形式 |
---|---|---|---|
AMD(异步模块定义) | 客户端 | require.js | 异步 |
CMD(通用模块定义) | 客户端 | sea.js | 异步 |
CommonJS | 服务端 | NodeJS | 同步(动静加载) |
es6 | 客户端 | es6 | 动态加载 |
AMD 和 CMD(ES5)
AMD 和 CMD 加载多个文件时都是异步加载
区别:
- AMD 推崇依赖前置,在定义模块的时候就要申明其依赖的模块
- CMD 推崇就近依赖,只有在用到某个模块的时候再去 require
- AMD 模块标准)
- CMD 模块标准
AMD
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();
});
CMD
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(ES5)
CommonJS 模块就是对象,输出时必须查找对象属性。
- CommonJS 的加载称为“运行时加载”或者 动静加载
- 一个文件就是一个模块,每个模块都是独自的作用域
特点:
- 所有代码都运行在模块作用域,不会净化全局作用域。
- 模块能够屡次加载,然而只在第一次加载时运行一次,而后运行后果就被缓存了,当前再加载,就间接读取缓存后果。要想让模块再次运行,必须革除缓存。
- 模块加载的程序,依照其在代码中呈现的程序。
module.exports
每个模块外部,都有一个 module 对象,代表以后模块。它有以下属性。
- module.id 模块的辨认符,通常是带有绝对路径的模块文件名。
- module.filename 模块的文件名,带有绝对路径。
- module.loaded 返回一个布尔值,示意模块是否曾经实现加载。
- module.parent 返回一个对象,示意调用该模块的模块。
- module.children 返回一个数组,示意该模块要用到的其余模块。
- module.exports 示意模块对外输入的值。
module.exports = 123
exports
为了不便,Node 为每个模块提供一个 exports
变量,指向 module.exports
。
留神:
- exports 变量间接指向一个繁多值。繁多值只能应用 module.exports 输入
// 相当于每个模块顶部都有这个申明
var exports = module.exports;
// 能够给导出的对象增加属性
exports.area = function (r) {return Math.PI * r * r;};
// 不能够导出一个繁多值
exports = 123; // 有效
require
require
命令用于加载模块文件,返回该模块的 exports
对象
commonJS 标准
Module(ES6)
ES6 模块不是对象,而是通过 export 命令显式指定输入的代码,再通过 import 命令输出。
- ES6 的模块是“编译时加载”或者 动态加载
- ES6 应用 基于文件的模块,也就是说一个文件一个模块
- ES6 模块是单例。也就是说,模块只有一个实例,其中保护了它的状态。每次向其余模块导入这个模块的时候,失去的是对单个核心实例的援用。
- ES6 模块的 API 是动态的。导出后的API 是只读状态。
- 模块的公开 API 中裸露的属性和办法并不仅仅是一般的值或援用的赋值。它们是到外部模块定义中的标识符的理论绑定(简直相似于指针)。
- ES6 的模块主动采纳严格模式
长处:
- 编译时加载办法,效率高
- 不再须要对象作为命名空间了
毛病:
- es6 模块不是对象,所以无奈援用模块自身
export
通过 export
命令规定模块的对外接口。有两种模式:
- 命名导出(能够多个):export **
- 默认导出(只有一个):export default
// 导出单个
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
是动态的,在编译时就加载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()
能够实现动静加载
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');
通常,” 循环加载 ” 示意存在强耦合,如果解决不好,还可能导致递归加载,使得程序无奈执行。
如何解决?commonJS 和 Es6 的 module 给出了不同的解决方案。
commonJS
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
b.js 执行结束
在 a.js 之中,b.done = true
a.js 执行结束
在 main.js 之中, a.done=true, b.done=true
ES6 模块
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
m.even(20) 执行的时候,此时 counter 曾经等于 6,加上本人执行的 11 次,所以一共是 17 次