区别

  1. CommonJS模块输出是一个值的拷贝,ES6模块输出的值是引用,同时会继承当前环境
  2. CommonJS是运行时候加载,ES6模块的编译的时候输出接口
  3. CommonJS是require()同步加载模块,ES6模块是import命令是异步加载,有一个独立的模块依赖的解析阶段

CommonJS模块输出是一个值的拷贝,ES6模块输出的值是引用

CommonJS

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

例子 来自

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。

// main.js
var mod = require('./lib');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3

上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  // 取值器函数
  get counter() {
    return counter
  },
  incCounter: incCounter,
};

ES6 模块 (module)

ES6 模块是引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);

// 输出
bar
baz

注意

export 后面不可以跟一个引用变量(export命令用于规定模块的对外接口)所以后面是声明(var function 这样的关键字)或者{ }
inport 和 export 是在一个独立的模块依赖的解析阶段

// 报错
export 1;

// 报错
var m = 1;
export m;

// 报错
function f() {}
export f;
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

// 正确
export function f() {};

// 正确
function f() {}
export {f};

export default

因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;

import

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

// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段(提升)就会生成。

CommonJS是require()同步加载模块,ES6模块是import命令是异步加载,有一个独立的模块依赖的解析阶段

最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此

注意

commonJS 中 module.exports 和 exports区别

module.exports 和 export 之间有什么区别?

前者公开了它指向的对象。 后者公开了它指向的对象的属性。

其实在 Node 的源码中,有几行代码能很好的解释他们的关系:

// 1. 用 = 赋值,使 exports 成为 module.exports 的一份副本;默认是空对象
// 其中`this`就是我们的 module 实例
const exports = this.exports; // 等价于 const exports = module.exports;

// 2. require函数的返回值如下(这里有做逻辑上的简化处理)
const require = function(id) {
  // ...
  return module.exports;
};

正常使用

// moduleA.js
module.exports.name = "I am moduleA";
exports.age = 20;

// index.js
const moduleA = require("./moduleA.js");
console.log("moduleA: ", moduleA); // moduleA:  { name: 'I am moduleA', age: 20 }

非正常使用

// moduleA.js
exports.age = 20;

// 重新赋值了一个新的对象
module.exports = {
  score: 100,
};

// index.js
const moduleA = require("./moduleA.js");
console.log("moduleA: ", moduleA); // moduleA:  { score: 100 }

注意

  • import 后面的{} 其实并不是解构赋值
  • import 和 export 不可以在块作用域中声明
  • 静态执行和异步加载不冲突
  • import() 在webpack是可以按需加载的