支流模块标准

目前支流模块标准有:

标准名称运行环境实现加载形式
AMD(异步模块定义)客户端require.js异步
CMD(通用模块定义)客户端sea.js异步
CommonJS服务端NodeJS同步(动静加载)
es6客户端es6动态加载

AMD和CMD(ES5)

AMD 和 CMD 加载多个文件时都是异步加载

区别:

  1. AMD推崇依赖前置,在定义模块的时候就要申明其依赖的模块
  2. CMD推崇就近依赖,只有在用到某个模块的时候再去require
  • AMD 模块标准)
  • CMD 模块标准

AMD

required.js 是 AMD 的实现

应用:define(id?, dependencies?, factory);;

// 定义模块 myModule.jsdefine(['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.jsdefine(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 的模块主动采纳严格模式

长处:

  1. 编译时加载办法,效率高
  2. 不再须要对象作为命名空间了

毛病:

  1. 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 exportexport * 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() 能够像调用函数一样动静导入模块,并返回一个promise
  • import() 反对 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.jsvar b = require('b');// b.jsvar 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 = falseb.js 执行结束在 a.js 之中,b.done = truea.js 执行结束在 main.js 之中, a.done=true, b.done=true

ES6 模块

ES6 是动态加载,不会缓存后果。 它只是生成一个指向被加载模块的援用,每次都会依据援用动静去加载模块取值。

// a.mjsimport {bar} from './b';console.log('a.mjs');console.log(bar);export let foo = 'foo';// b.mjsimport {foo} from './a';console.log('b.mjs');console.log(foo);export let bar = 'bar';

执行

node --experimental-modules a.mjsb.mjsReferenceError: foo is not defined -> 起因: foo未定义。

解决:将foo写成函数,使它产生函数晋升。函数表达式不行,因为它不能晋升

// a.mjsimport {bar} from './b';console.log('a.mjs');console.log(bar());function foo() { return 'foo' }export {foo};// b.mjsimport {foo} from './a';console.log('b.mjs');console.log(foo());function bar() { return 'bar' }export {bar};

执行

$ node --experimental-modules a.mjsb.mjsfooa.mjsbar

例子2:

// even.jsimport { odd } from './odd'export var counter = 0;export function even(n) {  counter++;  return n === 0 || odd(n - 1);}// odd.jsimport { 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.counter6> m.even(20)true> m.counter17

m.even(20) 执行的时候,此时 counter 曾经等于 6,加上本人执行的 11 次,所以一共是 17 次