CommonJS是Node.js中流行的模块方案,ECMAScript Modules是ECMAScript中定义的标准模块方案(简称ES6模块),后者在Node.js 10中可以通过node --experimental-modules index.mjs打开,在浏览器中的支持也越来越好。

目前主流的开发方式是,将ES6模块转码为ES5的语法在浏览器中使用,说明大家已经接受了importexport语法的简洁,在这种事实标准的情况下,随着浏览器的升级,采用标准的模块化方案是趋势。

之前记得某篇文章中写过,两者export出来的变量会有些许的不同,特写这边博客以测试释疑。

测试的范围

JavaScript中有原始类型和引用类型两种变量,从变量类型这个维度进行测试。 引用类型比较容易理解,直观的引用值而已。下面的测试值对原始类型进行测试。

测试过程

测试CommonJS模块

代码如下,通过node index.cjs执行:

// counter.cjs
let count = 1;

function inc() {
    count += 1;
}

module.exports = {
    count,
    get count2 () {
        return count;
    },
    inc,
};
// index.cjs
const counter = require('./counter.cjs');

console.log(counter.count);     // 1
console.log(counter.count2);    // 1

counter.inc();

console.log(counter.count);     // 1
console.log(counter.count2);    // 2


const counter2 = require('./counter.cjs');

console.log(counter2.count);    // 1
console.log(counter2.count2);   // 2

上面的这种情况比较容易理解,require会将模块的导出结果缓存,count将原始类型值直接复制到count的导出对象而已。

测试ES6模块

代码如下,通过node --experimental-modules index.mjs执行:

// counter.mjs
export let count = 1;

export function inc() {
    count += 1;
}

setTimeout(function () {
    console.log('setTimeout');
    count = 500;
}, 500);

export default {
    count,
    inc,
};
// index.mjs
import counter, { count, inc } from './counter.mjs';
import { count as myCount } from './counter.mjs';

console.log(counter.count);     // 1
console.log(count);             // 1
console.log(myCount);           // 1

counter.inc();
inc();

console.log(counter.count);     // 1
console.log(count);             // 3
console.log(myCount);           // 3

import { count as myCount2 } from './counter.mjs';
console.log(myCount2);

setTimeout(function () {
    console.log(counter.count); // 1
    console.log(count);         // 500
    console.log(myCount);       // 500
    console.log(myCount2);      // 500
}, 2000);

这个测试结果,稍微有些出乎意料,上面提到的疑惑出现了。默认导出的counter测试结果同上例的解释。 为什么export let count = 1;这行的导出结果不太一样呢?解释如下:

ES6 模块输出的是值的引用,虽然这是一个原始类型值,但JavaScript变量的本质是变量对象,此处是变量对象的导出;

它们有两个重大差异。

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

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

——摘自阮一峰老师《ES6-模块与-CommonJS-模块的差异》

参考

  1. ES6-模块与-CommonJS-模块的差异
  2. Module 模块化笔记
  3. 深入理解 ES6 模块机制
  4. 使用 AMD、CommonJS 及 ES Harmony 编写模块化的 JavaScript
  5. AMD/CMD/commonjs/ESM
  6. JavaScript Module Systems Showdown: CommonJS vs AMD vs ES2015