CommonJS Modules和ECMAScript Modules中变量导出的区别
CommonJS是Node.js中流行的模块方案,ECMAScript Modules是ECMAScript中定义的标准模块方案(简称ES6模块),后者在Node.js 10中可以通过node --experimental-modules index.mjs
打开,在浏览器中的支持也越来越好。
目前主流的开发方式是,将ES6模块转码为ES5的语法在浏览器中使用,说明大家已经接受了import
和export
语法的简洁,在这种事实标准的情况下,随着浏览器的升级,采用标准的模块化方案是趋势。
之前记得某篇文章中写过,两者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变量的本质是变量对象,此处是变量对象的导出;
它们有两个重大差异。
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。