討論 Node 加載 ES6模塊以前,必須瞭解 ES6模塊與 CommonJS模塊徹底不一樣。node
它們有兩個重大差別。es6
CommonJS模塊輸出的是一個值的拷貝,ES6模塊輸出的是值的引用。
CommonJS模塊是運行時加載,ES6模塊是編譯時輸出接口。
第二個差別是由於 CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完纔會生成。而 ES6模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。緩存
下面重點解釋第一個差別。babel
CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。請看下面這個模塊文件lib.js的例子。函數
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
上面代碼輸出內部變量counter和改寫這個變量的內部方法incCounter。而後,在main.js裏面加載這個模塊。ui
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
上面代碼說明,lib.js模塊加載之後,它的內部變化就影響不到輸出的mod.counter了。這是由於mod.counter是一個原始類型的值,會被緩存。除非寫成一個函數,才能獲得內部變更後的值。this
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
上面代碼中,輸出的counter屬性其實是一個取值器函數。如今再執行main.js,就能夠正確讀取內部變量counter的變更了。對象
$ node main.js
3
4
ES6模塊的運行機制與 CommonJS 不同。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。換句話說,ES6 的import有點像 Unix 系統的「符號鏈接」,原始值變了,import加載的值也會跟着變。所以,ES6模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。接口
仍是舉上面的例子。get
// 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內部的變化。
再舉一個出如今export一節中的例子。
// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);
上面代碼中,m1.js的變量foo,在剛加載時等於bar,過了 500 毫秒,又變爲等於baz。
讓咱們看看,m2.js可否正確讀取這個變化。
$ babel-node m2.js
bar
baz
上面代碼代表,ES6模塊不會緩存運行結果,而是動態地去被加載的模塊取值,而且變量老是綁定其所在的模塊。
因爲 ES6 輸入的模塊變量,只是一個「符號鏈接」,因此這個變量是隻讀的,對它進行從新賦值會報錯。
// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
上面代碼中,main.js從lib.js輸入變量obj,能夠對obj添加屬性,可是從新賦值就會報錯。由於變量obj指向的地址是隻讀的,不能從新賦值,這就比如main.js創造了一個名爲obj的const變量。
最後,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。
$ babel-node main.js 1 這就證實了x.js和y.js加載的都是C的同一個實例。