摘要:參考精讀模塊化發(fā)展模塊化七日談前端模塊化開(kāi)發(fā)那點(diǎn)歷史本文先發(fā)布于我的個(gè)人博客模塊化開(kāi)發(fā)的演進(jìn)歷程,后續(xù)如有更新,可以查看原文。
Brendan Eich用了10天就創(chuàng)造了JavaScript,因?yàn)楫?dāng)時(shí)的需求定位,導(dǎo)致了在設(shè)計(jì)之初,在語(yǔ)言層就不包含很多高級(jí)語(yǔ)言的特性,其中就包括模塊這個(gè)特性,但是經(jīng)過(guò)了這么多年的發(fā)展,如今對(duì)JavaScript的需求已經(jīng)遠(yuǎn)遠(yuǎn)超出了Brendan Eich的預(yù)期,其中模塊化開(kāi)發(fā)更是其中最大的需求之一。
尤其是2009年Node.js出現(xiàn)以后,CommonJS規(guī)范的落地極大的推動(dòng)了整個(gè)社區(qū)的模塊化開(kāi)發(fā)氛圍,并且隨之出現(xiàn)了AMD、CMD、UMD等等一系列可以在瀏覽器等終端實(shí)現(xiàn)的異步加載的模塊化方案。
此前,雖然自己也一直在推進(jìn)模塊化開(kāi)發(fā),但是沒(méi)有深入了解過(guò)模塊化演進(jìn)的歷史,直到最近看到了一篇文章《精讀JS模塊化發(fā)展》,文章總結(jié)了History of JavaScript這個(gè)開(kāi)源項(xiàng)目中關(guān)于JavaScript模塊化演進(jìn)的部分,細(xì)讀幾次之后,對(duì)于一些以前模棱兩可的東西,頓時(shí)清晰了不少,下面就以時(shí)間線總結(jié)一下自己的理解:
在1999年的時(shí)候,絕大部分工程師做JS開(kāi)發(fā)的時(shí)候就直接將變量定義在全局,做的好一些的或許會(huì)做一些文件目錄規(guī)劃,將資源歸類整理,這種方式被稱為直接定義依賴,舉個(gè)例子:
// greeting.js var helloInLang = { en: "Hello world!", es: "?Hola mundo!", ru: "Привет мир!" }; function writeHello(lang) { document.write(helloInLang[lang]); } // third_party_script.js function writeHello() { document.write("The script is broken"); } // index.htmlBasic example
但是,即使有規(guī)范的目錄結(jié)構(gòu),也不能避免由此而產(chǎn)生的大量全局變量,這就導(dǎo)致了一不小心就會(huì)有變量沖突的問(wèn)題,就好比上面這個(gè)例子中的writeHello。
于是在2002年左右,有人提出了命名空間模式的思路,用于解決遍地的全局變量,將需要定義的部分歸屬到一個(gè)對(duì)象的屬性上,簡(jiǎn)單修改上面的例子,就能實(shí)現(xiàn)這種模式:
// greeting.js var app = {}; app.helloInLang = { en: "Hello world!", es: "?Hola mundo!", ru: "Привет мир!" }; app.writeHello = function (lang) { document.write(helloInLang[lang]); } // third_party_script.js function writeHello() { document.write("The script is broken"); }
不過(guò)這種方式,毫無(wú)隱私可言,本質(zhì)上就是全局對(duì)象,誰(shuí)都可以來(lái)訪問(wèn)并且操作,一點(diǎn)都不安全。
所以在2003年左右就有人提出利用IIFE結(jié)合Closures特性,以此解決私有變量的問(wèn)題,這種模式被稱為閉包模塊化模式:
// greeting.js var greeting = (function() { var module = {}; var helloInLang = { en: "Hello world!", es: "?Hola mundo!", ru: "Привет мир!", }; module.getHello = function(lang) { return helloInLang[lang]; }; module.writeHello = function(lang) { document.write(module.getHello(lang)); }; return module; })();
IIFE可以形成一個(gè)獨(dú)立的作用域,其中聲明的變量,僅在該作用域下,從而達(dá)到實(shí)現(xiàn)私有變量的目的,就如上面例子中的helloInLang,在該IIFE外是不能直接訪問(wèn)和操作的,可以通過(guò)暴露一些方法來(lái)訪問(wèn)和操作,比如說(shuō)上面例子里面的getHello和writeHello2個(gè)方法,這就是所謂的Closures。
同時(shí),不同模塊之間的引用也可以通過(guò)參數(shù)的形式來(lái)傳遞:
// x.js // @require greeting.js var x = (function(greeting) { var module = {}; module.writeHello = function(lang) { document.write(greeting.getHello(lang)); }; return module; })(greeting);
此外使用IIFE,還有2個(gè)好處:
提高性能:通過(guò)IIFE的參數(shù)傳遞常用全局對(duì)象window、document,在作用域內(nèi)引用這些全局對(duì)象。JavaScript解釋器首先在作用域內(nèi)查找屬性,然后一直沿著鏈向上查找,直到全局范圍,因此將全局對(duì)象放在IIFE作用域內(nèi)可以提升js解釋器的查找速度和性能;
壓縮空間:通過(guò)參數(shù)傳遞全局對(duì)象,壓縮時(shí)可以將這些全局對(duì)象匿名為一個(gè)更加精簡(jiǎn)的變量名;
在那個(gè)年代,除了這種解決思路以外,還有通過(guò)其它語(yǔ)言的協(xié)助來(lái)完成模塊化的解決思路,比如說(shuō)模版依賴定義、注釋依賴定義、外部依賴定義等等,不過(guò)不常見(jiàn),所以就不細(xì)說(shuō)了,究其本源,它們想最終實(shí)現(xiàn)的方式都差不多。
不過(guò),這些方案,雖然解決了依賴關(guān)系的問(wèn)題,但是沒(méi)有解決如何管理這些模塊,或者說(shuō)在使用時(shí)清晰描述出依賴關(guān)系,這點(diǎn)還是沒(méi)有被解決,可以說(shuō)是少了一個(gè)管理者。
沒(méi)有管理者的時(shí)候,在實(shí)際項(xiàng)目中,得手動(dòng)管理第三方的庫(kù)和項(xiàng)目封裝的模塊,就像下面這樣把所有需要的JS文件一個(gè)個(gè)按照依賴的順序加載進(jìn)來(lái):
如果頁(yè)面中使用的模塊數(shù)量越來(lái)越多,恐怕再有經(jīng)驗(yàn)的工程師也很難維護(hù)好它們之間的依賴關(guān)系了。
于是如LABjs之類的加載工具就橫空出世了,通過(guò)使用它的API,動(dòng)態(tài)創(chuàng)建