Skip to content

amd,umd,commonJs,ES6模块的相关总结

AMD/CMD 模块(requireJS/seaJs)(即将退出历史舞台)

  • AMDAsynchronous Module Definition 异步模块定义)和 CMDCommon Module Definition 通用模块定义)是基于浏览器使用并且是异步执行
  • AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块
  • CMD 推崇就近依赖,只有在用到某个模块的时候再去 require
javascript
// AMD
// 定义模块 myModule.js
define(['dependency'], function () {
  var name = 'Byron';
  function printName() {
    console.log(name);
  }

  return {
    printName: printName,
  };
});

// 加载模块
require(['myModule'], function (my) {
  my.printName();
});

// CMD
// 定义模块  myModule.js
define(function (require, exports, module) {
  var $ = require('jquery.js');
  $('div').addClass('active');
});

// 加载模块
seajs.use(['myModule.js'], function (my) {});
  • AMD 在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入 require 的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。
  • CMD 加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到 require 语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。

commonJS 模块(nodejs)

  • 使用 require 来引入其他模块的代码,使用 module.exports 来引出。
  • exportsmodule.exports 的初始指针相同,即 module.exports === exports,如果 exports 一旦指向了其他对象,即不能用于导出。
  • 运行时加载,输出的是一个值的拷贝。

值的拷贝

require 引入的是值的拷贝(基本类型拷贝值,引用类型拷贝地址)。

javascript
//4.js
var age = 0;
exports.age = age;
exports.getAge = () => {
  age = age + 1;
};
setTimeout(() => {
  console.log(age); //1 原本的会改变
}, 2000);

//5.js
const a = require('./4');
var age = a.age;
var getAge = a.getAge;
getAge();
console.log(age); //0 不会改变age,一旦生成缓存后就会从缓存里读这个值

commonjs 循环加载

javascript
//4.js
console.log('4开始执行');
setTimeout(() => {
  exports.name = 'qxq1';
});
exports.name = 'qxq';
console.log('4执行一半');
const b = require('./5');
console.log('in 4, b.done =', b.done);
exports.done = true;
console.log('4执行结束');
javascript
//5.js
console.log('5开始执行');
exports.done = false;
const a = require('./4');
console.log('in 5, a.name =', a.name);
console.log('in 5, a.done =', a.done);
exports.done = true;
console.log('5执行结束');
setTimeout(() => {
  console.log(666, a);
}, 5000);
javascript
//打印结果
4开始执行
4执行一半
5开始执行
in 5, a.name = qxq
in 5, a.done = undefined
5执行结束
in 4, b.done = true
4执行结束
666 Object {name: "qxq1", done: true}
  • 执行到 require 那行才会去加载该脚本
  • require 命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象,本质就是一个一次性赋值操作。
  • 再次执行 require 命令,也不会再次执行该模块,而是到缓存之中取值。
  • 一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

ES6 模块

  • import/export 命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,则会报错。
  • ES6 模块输入是 export 的动态 只读视图(live read-only views)

import

使用 import 命令加载其他模块,import 命令输入的变量都是 只读 的,因为它的本质是 输入接口

  1. 语法查阅

    javascript
    //默认导出的导入
    import defaultExport from "module-name";
    
    //整体导入
    import * as name from "module-name";
    
    //导入单个接口
    import { export } from "module-name";
    
    //重命名接口
    import { export as alias } from "module-name";
    
    // 导入多个接口
    import { export1 , export2 } from "module-name";
    import { foo , bar } from "module-name/path/to/specific/un-exported/file";
    import { export1 , export2 as alias2 , [...] } from "module-name";个接口
    
    //同时导入默认和多个接口
    import defaultExport, { export [ , [...] ] } from "module-name";
    import defaultExport, * as name from "module-name";
    
    //只运行模块代码不导入接口
    import "module-name";
  2. 如果多次重复执行同一句 import 语句,那么只会执行一次.

  3. import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

export

  1. 使用 export 命令规定对外的 接口,必须与模块内部的变量建立一一对应关系。export 语句输出的接口,与其对应的值是 动态绑定 关系,即通过该接口,可以取到模块内部实时的值。

  2. 语法查阅

    javascript
    // 导出单个特性
    export let name1, name2, …, nameN; // also var, const
    export let name1 = …, name2 = …, …, nameN; // also var, const
    export function FunctionName(){...}
    export class ClassName {...}
    
    // 导处列表
    export { name1, name2, …, nameN };
    
    // 重命名导出
    export { variable1 as name1, variable2 as name2, …, nameN };
    
    // 默认导出
    export default expression;
    export default function (…) { … } // also class, function*
    export default function name1(…) { … } // also class, function*
    export { name1 as default, … };
    
    // 复合导出
    export * from …;
    export { name1, name2, …, nameN } from …;
    export { import1 as name1, import2 as name2, …, nameN } from …;
    export { default } from …;
  3. 默认导出 export default

    javascript
    // 正确
    export var a = 1;
    // 正确
    export default 42;
    
    // 正确
    var a = 1;
    export default a;
    
    // 正确
    let k;
    export default k = 12;
    
    // 错误
    export default var a = 1;

    因为 export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后。

值的引用

javascript
//4.js
import { age, getAge } from './5.js';
console.log(age); //原本为0
getAge();
console.log(age); //因为是值的引用,所以要变,为1

//5.js
export var age = 0;
export function getAge() {
  age = age + 1;
}
setTimeout(() => {
  console.log(age); //要变,为1
}, 2000);

ES6 循环加载

  • import 命令会被 JavaScript 引擎静态分析,具有提升效果,会提升到整个模块的头部,首先执行。
  • export 命令会有变量声明提前的效果。
  • ES6 模块遇到模块加载命令 import 时,不会去执行模块,而是只生成一个 引用。等到真的需要用到时,再到模块里面去取值。
  • ES6 根本不会关心是否发生了"循环加载",只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
  • 通常存在强耦合,应避免出现。

例 1

javascript
// a.js
import { foo } from './b';
console.log('a.js');
export const bar = 1;
export const bar2 = () => {
  console.log('bar2');
};
export function bar3() {
  console.log('bar3');
}

// b.js
export let foo = 1;
import * as a from './a';
console.log(a);
// 注意函数表达式和函数声明的区别(提升)

例 2

javascript
// a.js
console.log('a starting');
import { foo } from './b';
console.log('in b, foo:', foo);
export var bar = 2;
console.log('a done');

// b.js
console.log('b starting');
import { bar } from './a';
export var foo = 'foo';
console.log('in a, bar:', bar);
setTimeout(() => {
  console.log('in a, setTimeout bar:', bar);
});
console.log('b done');

// babel-node a.js
// 执行结果:
// b starting
// in a, bar: undefined
// b done
// a starting
// in b, foo: foo
// a done
// in a, setTimeout bar: 2
// 注意该例不能用const或let,否则报错:Cannot access 'bar' before initialization 暂时性死区
// export变量声明提升

高版本浏览器可以直接使用 es6 module

点此查询版本支持

  1. script 标签加 type='module' 属性启动支持

  2. 支持相对路径和绝对路径

    html
    <script type="module">
      import { getName } from 'utils.js'; // error
      import { getName } from './utils.js'; // right
    </script>
  3. 使用 nomodule 向下兼容

    html
    <script type="module" src="module.js"></script>
    <script nomodule src="fallback.js"></script>

    因老版本不识别 type="module" 即不会执行 module.js,同时不识别 nomodule 即忽略该属性 参考

  4. 加载方式默认使用 defer

  5. 只执行一次

    html
    <!-- 1.js 只会被加载执行一次-->
    <script type="module" src="1.js"></script>
    <script type="module" src="1.js"></script>
    <script type="module">
      import './1.js';
    </script>
    <!--  普通JS 也只会被加载一次,但是会被执行多次-->
    <script src="2.js"></script>
    <script src="2.js"></script>
  6. type="module" 默认不支持跨域,需要服务器设置 cors

  7. 服务器必须要设置有效的 MIME typestext/javascript

动态异步加载 import()

  • import()返回一个promise对象,可以用在任何地方,运行时加载
  • 主要用在按需加载条件加载
  • 使用 babel 编译时,需要添加syntax-dynamic-import插件

UMD 模块

实际上就是 amd/cmd + commonjs + 全局变量 这三种风格的结合,对当前运行环境的判断,如果是 Node 环境 就是使用 CommonJs 规范, 如果不是就判断是否为 AMD 环境, 最后导出全局变量。(AMD

javascript
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory()) : typeof define === 'function' && define.amd ? define(factory) : (global.libName = factory());
})(this, function () {
  'use strict';
});