commonJS:
//a.js var b = require('./b'); console.log(b.a); setTimeout(() => { console.log(b.a); console.log(require('./b').a); }, 1000); //b.js let a = 1; setTimeout(() => a = 2, 500); module.exports = { a };node a 结果:1 1 1
es6:
//a.mjs import { a as b } from './b'; console.log(b); setTimeout(() => { console.log(b); }, 1000); //b.mjs export let a = 1; setTimeout(() => a = 2, 500);node --experimental-modules a.mjs 结果:1 2
可以发现两者的区别在于,CommonJS 模块输出的是值的拷贝,众所周知,CommonJS引入模块需要路径分析、文件定位、编译执行、对于文件扩展名为js的文件先通过fs同步读取文件然后编译执行,在编译的过程中会将读取文件添加以下包装
(function (exports, require, module, __filename, __dirname) {xxx}; 包装后执行类似eval的方法runInThisContext返回一个具体的函数(不然就是一堆字符串了),然后就是把当前模块的一些参数和模块对象自身等一些参数传进去了,看上去是不是像闭包?其实就是一个闭包,使得文件之间的作用域隔离了,模块的exports属性返回给调用方(值拷贝),别的一些变量在这个编译函数执行完就被销毁了。那既然这个是闭包,那是否可以利用闭包的特性访问到模块内的局部变量并且当其值,外部也能给获取到改变的值呢,答案是可以的。a.js:
var b = require('./b'); console.log(b.fn()); setTimeout(() => { console.log(b.fn()); }, 1000);b.js:
let a = 1; setTimeout(() => a = 2, 500); module.exports = { fn:function(){ return a } };node a 结果:1 2
es6就比较厉害了,在使用模块时,通过入口和import语句,会先建立一个模块依赖图,然后将这些文件解析为一个个的模块记录的数据结构,然后就是实例化,将所有导出的值存放在合适的内存中,然后导出和导入都指向同一个内存地址,最后就是执行编码,将值放在对应的内存地址中。总的来说就是构造、实例化、求值。所以说由于es6是事先建立一个模块依赖图,然后求值,所以模块标识符不能有变量(现在有一个dynamic import提案可以解决这个问题),这就是所谓的静态,然commonJS是运行时加载的,就是所谓的动态
es6模块的静态编译有两个特点:
1.import优先于模块内的其他内容执行
2.export 命令会有变量声明。
//a.mjs console.log(a); import { a } from './b'; //b.mjs console.log(a) export var a = 1; console.log("我是模块内部")node --experimental-modules a.mjs 结果:undefined 我是模块内部 1
推荐一篇文章:ES modules: A cartoon deep-dive
对应的译文地址:深入理解 ES Modules (手绘示例)