ES Module 是在 ECMAScript 6 中引入的模塊化功能匈挖。模塊功能主要由兩個(gè)命令構(gòu)成览绿,分別是 export 和 import。export 命令用于規(guī)定模塊的對(duì)外接口掏呼,import 命令用于輸入其他模塊提供的功能蟆融。
那么在遇到 import 和 export 時(shí)發(fā)生了什么呢?ES6 的模塊加載機(jī)制可以概括為四個(gè)字 一靜一動(dòng) 属铁。
一靜:import 靜態(tài)執(zhí)行
一動(dòng):export 動(dòng)態(tài)綁定
import 靜態(tài)執(zhí)行是指眠寿,import 命令會(huì)被 JavaScript 引擎靜態(tài)分析,優(yōu)先于模塊內(nèi)的其他內(nèi)容執(zhí)行红选。
export 動(dòng)態(tài)綁定是指澜公,export 命令輸出的接口,與其對(duì)應(yīng)的值是動(dòng)態(tài)綁定關(guān)系喇肋,通過(guò)該接口可以實(shí)時(shí)取到模塊內(nèi)部的值坟乾。
其使用方式如下:
//foo.js
console.log('foo is running');
import {bar} from './bar'
console.log('bar = %j', bar);
setTimeout(() => console.log('bar = %j after 500 ms', bar), 500);
console.log('foo is finished');
//bar.js
console.log('bar is running');
export let bar = false;
setTimeout(() => bar = true, 500);
console.log('bar is finished');
//執(zhí)行 node foo.js 時(shí)會(huì)輸出如下內(nèi)容:
bar is running
bar is finished
foo is running
bar = false
foo is finished
bar = true after 500 ms
import 命令是在編譯階段執(zhí)行,在代碼運(yùn)行之前先被 JavaScript 引擎靜態(tài)分析蝶防,所以優(yōu)先于 foo.js 自身內(nèi)容執(zhí)行甚侣。同時(shí)我們也看到 500 毫秒之后也可以取到 bar 更新后的值也說(shuō)明了 export 命令輸出的接口與其對(duì)應(yīng)的值是動(dòng)態(tài)綁定關(guān)系。這樣的設(shè)計(jì)使得程序在編譯時(shí)就能確定模塊的依賴關(guān)系间学,這是和 CommonJS 模塊規(guī)范的最大不同殷费。還有一點(diǎn)需要注意的是印荔,由于 import 是靜態(tài)執(zhí)行,所以 import 具有提升效果即 import 命令的位置并不影響程序的輸出详羡。
export 和 export default
在一個(gè)文件或模塊中仍律,export 可以有多個(gè),export default 僅有一個(gè), export 類似于具名導(dǎo)出实柠,而 default 類似于導(dǎo)出一個(gè)變量名為 default 的變量水泉。同時(shí)在 import 的時(shí)候,對(duì)于 export 的變量窒盐,必須要用具名的對(duì)象去承接草则,而對(duì)于 default,則可以任意指定變量名
循環(huán)依賴
//foo.js
console.log('foo is running');
import {bar} from './bar'
console.log('bar = %j', bar);
setTimeout(() => console.log('bar = %j after 500 ms', bar), 500);
export let foo = false;
console.log('foo is finished');
//bar.js
console.log('bar is running');
import {foo} from './foo';
console.log('foo = %j', foo)
export let bar = false;
setTimeout(() => bar = true, 500);
console.log('bar is finished');
//執(zhí)行 node foo.js 時(shí)會(huì)輸出如下內(nèi)容:
bar is running
foo = undefined
bar is finished
foo is running
bar = false
foo is finished
bar = true after 500 ms
foo.js 和 bar.js 形成了循環(huán)依賴蟹漓,但是程序卻沒(méi)有因陷入循環(huán)調(diào)用報(bào)錯(cuò)而是執(zhí)行正常炕横,這是為什么呢?還是因?yàn)?import 是在編譯階段執(zhí)行的葡粒,這樣就使得程序在編譯時(shí)就能確定模塊的依賴關(guān)系份殿,一旦發(fā)現(xiàn)循環(huán)依賴,ES6 本身就不會(huì)再去執(zhí)行依賴的那個(gè)模塊了塔鳍,所以程序可以正常結(jié)束伯铣。這也說(shuō)明了 ES6 本身就支持循環(huán)依賴,保證程序不會(huì)因?yàn)檠h(huán)依賴陷入無(wú)限調(diào)用轮纫。雖然如此腔寡,但是我們?nèi)匀灰M量避免程序中出現(xiàn)循環(huán)依賴,因?yàn)榭赡軙?huì)發(fā)生一些讓你迷惑的情況掌唾。注意到上面的輸出放前,在 bar.js 中輸出的 foo = undefined
,如果沒(méi)注意到循環(huán)依賴會(huì)讓你覺(jué)得明明在 foo.js 中 export foo = false
糯彬,為什么在 bar.js 中卻是 undefined
呢凭语,這就是循環(huán)依賴帶來(lái)的困惑。在一些復(fù)雜大型項(xiàng)目中撩扒,你是很難用肉眼發(fā)現(xiàn)循環(huán)依賴的似扔,而這會(huì)給排查異常帶來(lái)極大的困難。對(duì)于使用 webpack 進(jìn)行項(xiàng)目構(gòu)建的項(xiàng)目搓谆,推薦使用 webpack 插件 circular-dependency-plugin來(lái)幫助你檢測(cè)項(xiàng)目中存在的所有循環(huán)依賴炒辉,盡早發(fā)現(xiàn)潛在的循環(huán)依賴可能會(huì)免去未來(lái)很大的麻煩。