關于JS模塊化時循環(huán)加載的那些事兒

關于JS模塊化時循環(huán)加載的那些事兒

關于JS模塊化的相關知識有很多,一些基本的知識平時都會有注意到藕帜,但是在實踐中發(fā)現(xiàn)對于模塊循環(huán)加載的情景不太注意,本文主要記錄一下在webpack中使用模塊化的一些心得票渠。
說到模塊化拂檩,離不開CommonJS和ES6,有的項目在使用時也會同時存在這兩種語法啊送,至于AMD和CMD不在本文討論范圍內(nèi)偿短。

CommonJS中的循環(huán)加載

來看一個簡單的例子,定義了四個文件a.js馋没、b.js、main.js降传、TestImportExport.js篷朵,其中a.js和b.js相互引用,形成一個循環(huán)引用婆排,main.js中引用a.js和b.js声旺,TestImportExport.js用于測試。
1段只、a.js

//a.js
const b = require('./b.js');
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
const a = require('./a.js');
console.log('b.js中的a:',a);

const sayBye = (name)=>{
  console.log('Bye,',name);
}
const callFunc=(name)=>{
  a.sayHi(name);
}
module.exports={
  sayBye:sayBye,
  callFunc:callFunc
}

3、main.js

//main.js
const a = require('./a.js');
const b = require('./b.js');

const f = () => {
  a.sayHi('zzx');
  b.sayBye('zzx');
  a.callFunc('zzx');
  b.callFunc('zzx');
}
export { f }

4赞枕、TestImportExport.js

//TestImportExport.js
import React from 'react';
import * as testmodule from './test_import_export/main';
function TestImportExport() {
  console.log(testmodule);
  return (
    <div>
      <button onClick={ testmodule.f}>測試</button>
    </div>
  );
}

export default TestImportExport;

OK,現(xiàn)在問題來了澈缺,我在瀏覽器中打開test頁面,日志臺會輸出什么結(jié)果炕婶?點擊‘測試’按鈕之后又會輸出什么結(jié)果姐赡?
直接上圖
打開時:


打開頁面時控制臺的輸出

從圖中可以看到,打開時console的輸出順序是:b.js-a.js-TestImportExport.js,為什么呢?
原因是webpack會先執(zhí)行import或者require的模塊柠掂。所以執(zhí)行順序是TestImportExport先去運行main项滑,運行main時發(fā)現(xiàn)main引用了a,又去運行a,運行a時發(fā)現(xiàn)a引用了b,又去運行b涯贞。此時b又引用了a枪狂,但是剛剛已經(jīng)運行過a了危喉,所以b會接著往下執(zhí)行,輸出第一個console州疾,此時由于a只執(zhí)行了部分所以此時引入的a是一個空對象姥饰。執(zhí)行完b之后繼續(xù)執(zhí)行a,輸出b。執(zhí)行完a之后繼續(xù)執(zhí)行main孝治,此時不會重復去加載b列粪,而是使用在a中已經(jīng)加載好的b。最后輸出TestImportExport中的console谈飒。

點擊按鈕后:


點擊按鈕后控制臺的輸出

點擊按鈕后回去執(zhí)行f中的函數(shù)岂座,a.sayHi、b.sayBye杭措、a.callFunc都能正常運行费什,b.callFunc會報錯,因為b中的a在執(zhí)行時就確定了是一個空對象手素,所以無法獲取相應的函數(shù),執(zhí)行出錯鸳址。
現(xiàn)在,我們來看一看另外一種寫法的CommonJS,修改一下a.js文件,其余文件不變

//a.js
const b = require('./b.js');
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
// }
exports.sayHi=sayHi;
exports.callFunc=callFunc;

OK,再來看一看現(xiàn)在的結(jié)果
打開時:


打開頁面時控制臺的輸出

點擊按鈕后:


點擊按鈕后控制臺的輸出

從圖中可以看到泉懦,在b.js中加載后立即使用的a仍然是一個空對象稿黍,因為此時a還沒有執(zhí)行完;在點擊按鈕后函數(shù)調(diào)用時又能正常運行崩哩,原因是exports是module.exports的一個引用巡球,即使已經(jīng)緩存了a這個模塊,但是在執(zhí)行完b.js繼續(xù)執(zhí)行a.js時通過exports仍然可以對a模塊的module.exports進行修改邓嘹,而實際函數(shù)調(diào)用是在執(zhí)行完a.js之后酣栈,所以實際函數(shù)調(diào)用時是有值的,在執(zhí)行完a.js之前是無值的汹押。

引用阮一峰老師在ES6入門一書中所寫的內(nèi)容:

總之矿筝,CommonJS 輸入的是被輸出值的拷貝,不是引用棚贾。
另外窖维,由于 CommonJS 模塊遇到循環(huán)加載時,返回的是當前已經(jīng)執(zhí)行的部分的值鸟悴,而不是代碼全部執(zhí)行后的值陈辱,兩者可能會有差異。所以细诸,輸入變量的時候沛贪,必須非常小心。

ES6中的循環(huán)加載

來自阮一峰老師ES6入門一書對ES6處理‘循環(huán)加載’的描述

ES6 處理“循環(huán)加載”與 CommonJS 有本質(zhì)的不同。ES6 模塊是動態(tài)引用利赋,如果使用import從一個模塊加載變量(即import foo from 'foo')水评,那些變量不會被緩存,而是成為一個指向被加載模塊的引用媚送,需要開發(fā)者自己保證中燥,真正取值的時候能夠取到值。

來看一個簡單的例子塘偎,定義了四個文件a.js疗涉、b.js、main.js吟秩、TestImportExport.js咱扣,其中a.js和b.js相互引用,形成一個循環(huán)引用涵防,main.js中引用a.js和b.js闹伪,TestImportExport.js用于測試。
1壮池、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);
}

export {
  sayHi,
  callFunc
}


2偏瓤、b.js

//b.js es6
import * as a from './a'
console.log('b.js中的a:', a);

const sayBye = (name) => {
  console.log('Bye,', name);
}
const callFunc = (name) => {
  a.sayHi(name);
}
export {
  sayBye,
  callFunc
}

3、main.js

//main.js es6
import * as a from './a.js'
import * as b from './b.js'
const f = () => {
  a.sayHi('zzx');
  b.sayBye('zzx');
  a.callFunc('zzx');
  b.callFunc('zzx');
}
export { f }

4椰憋、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}>測試</button>
    </div>
  );
}

export default TestImportExport;

OK,再來看一看ES6循環(huán)加載時控制臺會輸出什么厅克,直接上圖:
打開頁面時:


打開頁面時控制臺的輸出

從圖中看到,ES6的import加載在webpack中會被一個視作Module熏矿,建立一個引用鏈接已骇,在真正使用時才會鏈接地址取值
點擊按鈕后:


點擊按鈕后控制臺的輸出

可以看到即使出現(xiàn)了循環(huán)加載ES6仍然能夠正確運行。原因就是webpack會在真正使用模塊中的內(nèi)容時才會去鏈接地址取值票编。

ES6循環(huán)加載就不會出錯嗎?

不是的卵渴,雖然大多數(shù)情況下ES6中即使循環(huán)加載了也能正確處理慧域,但是也有一些特殊情況下會出錯,比如在import后立即調(diào)用import的值浪读,而不是寫在函數(shù)中昔榴。
一個簡單的例子,將上面ES6中的a.js和b.js做簡單的修改:

//a.js  es6
import * as b from './b'
console.log('a.js中的b:', b);
b.sayBye('zzx');//在import之后立即調(diào)用
const sayHi = (name) => {
  console.log('Hi,', name);

}
const callFunc = (name) => {
  b.sayBye(name);
}

export {
  sayHi,
  callFunc
}

//b.js es6
import * as a from './a'
console.log('b.js中的a:', a);
a.sayHi('zzx');//在import之后立即調(diào)用
const sayBye = (name) => {
  console.log('Bye,', name);
}
const callFunc = (name) => {
  a.sayHi(name);
}
export {
  sayBye,
  callFunc
}

打開網(wǎng)頁時控制臺輸出如下:

ES6循環(huán)加載出錯的情形

從圖中可以看到,在b中引入a之后立即調(diào)用a中的內(nèi)容是會出錯的碘橘,因為此時還未完成整個引用鏈路的鏈接互订。
綜上,在ES6語法中出現(xiàn)循環(huán)加載是能夠正確處理的痘拆,但是不能在import之后立即使用加載的模塊仰禽,盡量把所有操作都寫在函數(shù)中。
但是從代碼質(zhì)量上來說我們在書寫代碼時應該避免這種循環(huán)加載的情形,需要仔細去考慮代碼的拆分吐葵。
本期介紹暫告一段落规揪,下期介紹CommonJS和ES6語法共用的情形。
參考鏈接:
關于CommonJS語法參考:https://javascript.ruanyifeng.com/nodejs/module.html#toc5
關于exports和module.exports參考:https://juejin.im/post/597ec55a51882556a234fcef

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末温峭,一起剝皮案震驚了整個濱河市猛铅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凤藏,老刑警劉巖奸忽,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異揖庄,居然都是意外死亡栗菜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門抠艾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苛萎,“玉大人,你說我怎么就攤上這事检号‰缜福” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵齐苛,是天一觀的道長翘盖。 經(jīng)常有香客問我,道長凹蜂,這世上最難降的妖魔是什么馍驯? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮玛痊,結(jié)果婚禮上汰瘫,老公的妹妹穿的比我還像新娘。我一直安慰自己擂煞,他們只是感情好混弥,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著对省,像睡著了一般蝗拿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒿涎,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天哀托,我揣著相機與錄音,去河邊找鬼劳秋。 笑死仓手,一個胖子當著我的面吹牛胖齐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俗或,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼市怎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辛慰?” 一聲冷哼從身側(cè)響起区匠,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帅腌,沒想到半個月后驰弄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡速客,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年戚篙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溺职。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡岔擂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浪耘,到底是詐尸還是另有隱情乱灵,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布七冲,位于F島的核電站痛倚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏澜躺。R本人自食惡果不足惜蝉稳,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掘鄙。 院中可真熱鬧耘戚,春花似錦、人聲如沸操漠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颅夺。三九已至,卻和暖如春蛹稍,著一層夾襖步出監(jiān)牢的瞬間吧黄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工唆姐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拗慨,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像赵抢,于是被迫代替她去往敵國和親剧蹂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容