關(guān)于JS模塊化時(shí)循環(huán)加載的那些事兒(二)
上一篇文章中介紹了ES6和CommonJS語法分別對(duì)于循環(huán)加載時(shí)的處理情況,本期介紹兩種語法混合使用時(shí)對(duì)于循環(huán)加載的處理。
為什么能夠同時(shí)使用兩種語法呢?這樣歸功于babel插件:@babel/plugin-transform-modules-commonjs,在經(jīng)過插件處理后我們可以同時(shí)使用兩種語法而不會(huì)報(bào)錯(cuò)柜与。
來看一個(gè)比上一期稍微復(fù)雜一點(diǎn)的例子。在a.js中同時(shí)使用了兩種語法,ES6 import導(dǎo)入频鉴,CommonJS module.exports導(dǎo)出,同時(shí)構(gòu)建了一個(gè)a->b->c->a的循環(huán)加載場(chǎng)景
1恋拍、a.js
//a.js es6 with commonjs
import * as b from './b'
console.log('a.js中的b:', b);
const sayHi = (name) => {
console.log('Hi,', name);
}
const callFunc = (name) => {
b.sayBye(name);
}
module.exports={
sayHi:sayHi,
callFunc:callFunc
}
2垛孔、b.js
//b.js es6
import * as c from './c'
console.log('b.js中的c:', c);
const sayBye = (name) => {
console.log('Bye,', name);
}
const callFunc = (name) => {
c.sayHello(name);
}
export {
sayBye,
callFunc
}
3、c.js
//c.js es6
import * as a from './a'
console.log('c.js中的a:', a);
const sayHello = (name) => {
console.log('Hello,', name);
}
const callFunc = (name) => {
a.sayHi(name);
}
export {
sayHello,
callFunc
}
4施敢、main.js
//main.js es6
import * as a from './a.js'
import * as b from './b.js'
import * as c from './c.js'
const f = () => {
a.sayHi('zzx')
b.sayBye('zzx');
c.sayHello('zzx');
a.callFunc('zzx')
b.callFunc('zzx');
c.callFunc('zzx');
}
export { f }
5.TestImportExport.js
//TestImportExport.js es6
import React from 'react';
import * as testmodule from './test_ES6/main';
function TestImportExport() {
console.log(testmodule);
return (
<div>
<button onClick={ testmodule.f}>測(cè)試</button>
</div>
);
}
export default TestImportExport;
OK,現(xiàn)在來看一看控制臺(tái)的輸出
打開網(wǎng)頁時(shí):
從圖中可以看到周荐,c.js中記載進(jìn)來的模塊是一個(gè)帶有default屬性但是沒有任何值的空對(duì)象,而b.js和a.js中加載進(jìn)來的模塊都是正常的僵娃。為什么呢概作?我們來看一看執(zhí)行順序就明白了。
首先是main.js加載a.js,執(zhí)行a.js時(shí)發(fā)現(xiàn)加載了b.js又去執(zhí)行b.js,執(zhí)行b.js時(shí)發(fā)現(xiàn)加載了c.js又去執(zhí)行c.js,此時(shí)a.js還沒執(zhí)行完默怨,module.exports還未完成賦值讯榕,所以此時(shí)控制臺(tái)輸出的a是一個(gè)空對(duì)象,至于default屬性的存在是因?yàn)槭褂昧薸mport語法導(dǎo)入經(jīng)過了babel轉(zhuǎn)換先壕。執(zhí)行完c后返回執(zhí)行b再執(zhí)行a瘩扼,這就是我們看到的輸出順序。
再來看一看點(diǎn)擊按鈕后的控制臺(tái)輸出:
從圖中可以看到垃僚,在執(zhí)行c中的callFunc時(shí)出現(xiàn)錯(cuò)誤集绰,因?yàn)閍在加載時(shí)并沒有真正的值,所以無法執(zhí)行谆棺。
現(xiàn)在我們來修復(fù)一下這個(gè)問題:
對(duì)a.js做一下修改
//a.js es6
import * as b from './b'
console.log('a.js中的b:', b);
const sayHi = (name) => {
console.log('Hi,', name);
}
const callFunc = (name) => {
b.sayBye(name);
}
module.exports={
sayHi:sayHi,
callFunc:callFunc
}
//新加ES6語法的導(dǎo)出
export {
sayHi,
callFunc
}
OK栽燕,我們?cè)賮砜匆豢纯刂婆_(tái)的輸出:
打開網(wǎng)頁時(shí):
從圖中可以看到,修改之后c.js中加載的模塊已經(jīng)和其余a.js改淑、b.js中加載的模塊形式上保持一致了碍岔,但是相應(yīng)的值還是undefined,展開后又能看到真實(shí)的值朵夏,這是混用module.exports和export的結(jié)果蔼啦,babel會(huì)把export轉(zhuǎn)換為commonjs語法,我們來看一下a.js編譯后的代碼:
從圖中可以看到仰猖,babel轉(zhuǎn)換后的代碼中export消失了捏肢,取而代之的是exports,并且在加載b.js之前已經(jīng)給exports賦值callFunc和sayHi并且值為undefined奈籽,所以在c.js中加載a時(shí)顯示出來的就是圖中的結(jié)果,真實(shí)運(yùn)行時(shí)已經(jīng)是執(zhí)行完a.js鸵赫,此時(shí)callFunc和sayHi已被重新賦值衣屏,所有函數(shù)都能正常執(zhí)行。
再來看看點(diǎn)擊按鈕后的結(jié)果:
從圖中可以看到辩棒,所有函數(shù)都能正常運(yùn)行狼忱。
經(jīng)過插件轉(zhuǎn)換后的export會(huì)把所有導(dǎo)出的內(nèi)容都提前賦值為undefined,然后在執(zhí)行到模塊時(shí)重新賦值一睁。修改一下a.js
//a.js es6 with commonjs
import * as b from './b'
console.log('a.js中的b:', b);
const sayHi = (name) => {
console.log('Hi,', name);
}
const callFunc = (name) => {
b.sayBye(name);
}
//增加一個(gè)常量
const name = 'zzx';
module.exports={
sayHi:sayHi,
callFunc:callFunc
}
export {
sayHi,
callFunc,
name
}
再來看看編譯后的代碼:
此時(shí)打開網(wǎng)頁的控制臺(tái)輸出如下:
我們?cè)賹?duì)c.js做一下修改钻弄,看一看加載后不立即使用a模塊的情形。修改一下c.js
//c.js es6
import * as a from './a'
console.log('c.js中的a:', a)
setTimeout(()=>{console.log('c.js中的a(1ms以后):', a)},1);
const sayHello = (name) => {
console.log('Hello,', name);
}
const callFunc = (name) => {
a.sayHi(name);
}
export {
sayHello,
callFunc
}
來看看此時(shí)的控制臺(tái)輸出
從圖中可以看到卖局,加載模塊后立即使用是只有exports提前賦值的值斧蜕,而在模塊全部加載完之后再使用是OK的。
綜上所述砚偶,babel插件會(huì)把ES6語法轉(zhuǎn)換為CommonJS語法并且做一些額外的操作(如提前賦值)以保證轉(zhuǎn)換后的表現(xiàn)與ES6語法期待的表現(xiàn)一致批销,出現(xiàn)循環(huán)加載時(shí)可以正常運(yùn)行,除非你在加載模塊后立即使用染坯。
本期介紹暫告一段落均芽,下期一起去看一看webpack對(duì)模塊時(shí)如何加載管理的。