在 ES6模块(Module) 里介绍了ES6中利用关键词export、import使用模块,在 CommonJS的模块规范 介绍了CommonJS的模块规范。本篇主要聊一聊两种模块的加载机制。
ES6模块加载的机制,与CommonJS模块完全不同。
CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。
CommonJS模块输出的是被输出值的拷贝
也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。1
2
3
4
5
6
7
8
9// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。
1 | // main.js |
上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
1 | // lib.js |
上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。
ES6模块是动态引用,并且不会缓存值
ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
还是举上面的例子。
1 | // lib.js |
上面代码说明,ES6模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。
由于ES6输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。
1 | // lib.js |
最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
1 | // mod.js |
上面的脚本mod.js,输出的是一个C的实例。不同的脚本加载这个模块,得到的都是同一个实例。
1 | // x.js |
现在执行main.js,输出的是1。这就证明了x.js和y.js加载的都是C的同一个实例。