Skip to content

ES6 模块、Commond、AMD、CMD的区别 #24

@TieMuZhen

Description

@TieMuZhen

前言

es6: import / export
commonjs: require / module.exports / exports
amd: require / defined

Commond、AMD、CMD的区别

  • Commond是服务器端的,是同步的。
  • AMD和CMD是浏览器端的,是异步的。

CommonJS规范

Node 应用由模块组成,采用 CommonJS 模块规范。

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

上面代码通过module.exports输出变量x和函数addX

require方法用于加载模块。

var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6

CommonJS模块的特点如下

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

AMD、CMD规范

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范

AMD规范使用define方法定义模块,下面就是一个例子:

define(['package/lib'], function(lib){
  function foo(){
    lib.log('hello world!');
  }

  return {
    foo: foo
  };
});

define 函数:

define([module-name?],[array-of-dependencies?],[module-factory-or-object]);   
  • module-name:模块标识,可以省略。define 函数允许省略第一个参数,因此定义一个匿名模块。这时候模块文件的文件名就是模块标识,即如果这个模块文件名为 A.js ,那么 A 就是模块名。这将带来一个好处,就是模块的高度可重用的。你拿来一个匿名模块,随便放在一个位置就可以使用它,模块名就是它的文件路径。这也很好的符合了 DRY(Don’t Repeat Yourself)原则。
  • array-of-dependencies:所依赖的模块数组,可以省略
  • module-factory-or-object:模块的实现或者一个 JavaScript 对象

define 函数具有异步性,其首先会异步加载第二个参数中列出的依赖模块,当所有的模块被加载后,执行第三个参数的回调函数。

AMD 和 CMD 的区别
CMD 推崇依赖就近,AMD 推崇依赖前置

// CMD
define(function(require, exports, module) {   
    var a = require('./a')   
    a.doSomething()   
    var b = require('./b') // 依赖可以就近书写  
    b.doSomething()  
})
// AMD 
define(['./a', './b'], function(a, b) {   // 依赖必须一开始就写好  
    a.doSomething()     
    b.doSomething()  
})

AMD:速度快、预先加载所有的依赖,直到使用的时候才执行,与CMD相比推荐此法。
CMD:只有真正需要才加载依赖、直到使用的时候才定义依赖。

AMD规范允许输出的模块兼容CommonJS规范,这时define方法需要写成下面这样:

define(function (require, exports, module){
  var someModule = require("someModule");
  var anotherModule = require("anotherModule");

  someModule.doTehAwesome();
  anotherModule.doMoarAwesome();

  exports.asplode = function (){
    someModule.doTehAwesome();
    anotherModule.doMoarAwesome();
  };
});

ES6 模块

require/exports规范是 JavaScript 社区中的开发者自己草拟的规则,得到了大家的承认或者广泛的应用。比如CommonJSAMDCMD 等等。import/export 则是名门正派。TC39 制定的新的 ECMAScript 版本,即 ES6(ES2015)中包含进来。

Node.js 无法直接兼容 ES6。所以现阶段 require/exports 任然是必要且实必须的,所以,目前编写的import/export最终都是编译为require/exports来执行的。

ES6模块和CommonJS模块的差异?

  1. ES6模块在编译时,就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 模块,运行时加载。
  2. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
  3. require 可以做动态加载,import 语句做不到,import 语句必须位于顶层作用域中。
  4. ES6 模块中顶层的 this 指向 undefined,commonJS 模块的顶层 this 指向当前模块。
  5. ES6 模块自动采用严格模式,无论模块头部是否写了 "use strict"。

CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。如:

//name.js
var name = 'William';
setTimeout(() => name = 'Yvette', 200);
module.exports = {
    name
};
//index.js
const name = require('./name');
console.log(name); //William
setTimeout(() => console.log(name), 300); //William

对比 ES6 模块看一下:

ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import ,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

//name.js
var name = 'William';
setTimeout(() => name = 'Yvette', 200);
export { name };
//index.js
import { name } from './name';
console.log(name); //William
setTimeout(() => console.log(name), 300); //Yvette

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions