背景
JavaScript 現(xiàn)在最主流的模塊機制是 commonjs 和 ES6 module。兩者不單是語法上有所區(qū)別,在加載的時候也有所不同,譬如 commonjs 是運行時加載,ES6模塊是編譯時輸出接口的。還有一個重要的區(qū)別是 commonjs 導(dǎo)出的東西是值拷貝,而 ES6 模塊導(dǎo)出的東西是……暫時認為是引用拷貝吧。具體表現(xiàn)出來的區(qū)別看下面的例子:
commonjs模塊
// a.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./a');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
可以看到 counter 在模塊內(nèi)部被改變,但是使用此模塊的代碼獲取 counter 始終是 export 時候的值,不會變。
ES6模塊
// a.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './a';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
ES6 模塊導(dǎo)出的變量始終指向的是模塊內(nèi)部的變量,使用時可以獲得此變量的最新值。我們叫導(dǎo)出綁定:Exporting binding。
問題
如果你去看 webpack 編譯后的實現(xiàn),它會把 counter 變量轉(zhuǎn)換成 counter 的 getter,這么就可以實現(xiàn)綁定的效果。但是在看 webpack 對默認導(dǎo)出代碼的轉(zhuǎn)換時,發(fā)現(xiàn)實現(xiàn)并不使用 getter。也就是說按這種實現(xiàn),使用 export default counter 是不會產(chǎn)生 Exporting binding??纯创a:
// a.js
let counter = 3;
export function incCounter() {
counter++;
}
export default counter;
// main.js
import counter, { incCounter } from './a';
console.log(counter); // 3
incCounter();
console.log(counter); // 3
解釋
為什么會有這種效果?其實 export default 是一種語法糖,當(dāng)模塊只有一個導(dǎo)出的時候,簡化寫代碼人的代碼量,我們把這個語法糖還原下:
// 語法糖
// myFunc.js
function myFunc() {}
export default myFunc;
// main.js
import myFunc from './myFunc';
// 非語法糖
// myFunc.js
function myFunc() {}
export { myFunc as default };
// main.js
import { default as myFunc } from './myFunc';
也就是說把 export 的東西重命名/賦值給 default,再在 import 的時候把 default 重命名為你想要的名字。問題就出在這層語法糖的轉(zhuǎn)換上,規(guī)范對于 export default x 的行為有解釋,x 的類型不同,則行為不同:
有名字的函數(shù)和類
export default function foo() {}
export default class Bar {}
相當(dāng)于
function foo() {}
export { foo as default };
class Bar {}
export { Bar as default };
沒有名字的函數(shù)和類
export default function () {}
export default class {}
相當(dāng)于
function *default*() {}
export { *default* as default };
class *default* {}
export { *default* as default };
JS會把給匿名函數(shù)或類給一個內(nèi)部的變量*default*,然后再重命名為 default 導(dǎo)出。這個內(nèi)部變量是無法通過程序獲取到的。
原始類型
export default 1;
// --- 或者 ---
let x = 4;
export default x;
相當(dāng)于
let *default* = 1
export { *default* as default };
// --- 或者 ---
let x = 4;
let *default* = x;
export { *default* as default };
當(dāng) export default x 中的 x 是沒有名字的函數(shù)或者類,又或者是原始類型,export binding 的是內(nèi)部變量*default* 并不是 x。所以改了 x 并不等于改了*default*,自然 import 的東西沒有變化。