Open felix-cao opened 6 years ago
在 《ES6 模块(Module)》 中介绍了 ES6 中提供了两个命令export、import 来导出和导入模块,在 《CommonJS 的模块规范》 介绍了 CommonJS 的模块规范。那么 ES6 和 CommonJS 两种模块的加载机制是怎样的呢?本文就来聊一聊这个话题:
ES6
export
import
CommonJS
模块化和块级作用域使得曾经大为流行的立即执行函数已经成为鸡肋, 模块化也使得 javascript 这门面向对象语言的封装特性得到极大的加强。
也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件 lib.js 的例子。
lib.js
// lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, };
上面代码输出内部变量 counter 和改写这个变量的内部方法 incCounter。然后,在 main.js 里面加载这个模块。
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 是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
mod.counter
// lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: function() { return counter }, incCounter: incCounter, };
上面代码中,输出的 counter 属性实际上是一个取值器函数。现在再执行 main.js ,就可以正确读取内部变量 counter 的变动了。
ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
还是举上面的例子。
// lib.js export let counter = 3; export function incCounter() { counter++; } // main.js import { counter, incCounter } from './lib'; console.log(counter); // 3 incCounter(); console.log(counter); // 4
上面代码说明,ES6模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。
由于ES6输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。
// lib.js export let obj = {}; // main.js import { obj } from './lib'; obj.prop = 123; // OK obj = {}; // TypeError
最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
// mod.js function C() { this.sum = 0; this.add = function () { this.sum += 1; }; this.show = function () { console.log(this.sum); }; } export let c = new C();
上面的脚本mod.js,输出的是一个C的实例。不同的脚本加载这个模块,得到的都是同一个实例。
// x.js import {c} from './mod'; c.add(); // y.js import {c} from './mod'; c.show(); // main.js import './x'; import './y';
现在执行main.js,输出的是1。这就证明了x.js和y.js加载的都是C的同一个实例。
在 《ES6 模块(Module)》 中介绍了
ES6
中提供了两个命令export
、import
来导出和导入模块,在 《CommonJS 的模块规范》 介绍了CommonJS
的模块规范。那么ES6
和CommonJS
两种模块的加载机制是怎样的呢?本文就来聊一聊这个话题:ES6
模块加载的机制,与CommonJS
模块完全不同。CommonJS
模块输出的是一个值的拷贝,而ES6
模块输出的是值的引用。模块化和块级作用域使得曾经大为流行的立即执行函数已经成为鸡肋, 模块化也使得 javascript 这门面向对象语言的封装特性得到极大的加强。
一、CommonJS 模块输出的是被输出值的拷贝
也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件
lib.js
的例子。上面代码输出内部变量
counter
和改写这个变量的内部方法incCounter
。然后,在main.js
里面加载这个模块。上面代码说明,
lib.js
模块加载以后,它的内部变化就影响不到输出的mod.counter
了。这是因为mod.counter
是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。上面代码中,输出的
counter
属性实际上是一个取值器函数。现在再执行main.js
,就可以正确读取内部变量counter
的变动了。二、ES6 模块是动态引用,并且不会缓存值
ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
还是举上面的例子。
上面代码说明,ES6模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。
由于ES6输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。
最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
上面的脚本mod.js,输出的是一个C的实例。不同的脚本加载这个模块,得到的都是同一个实例。
现在执行main.js,输出的是1。这就证明了x.js和y.js加载的都是C的同一个实例。