模塊化如今應該已經成爲了稍微複雜一點前端開發的標配了。在es6中,都已經支持了的模塊化。 前端
以前的面試中,一直感受模塊化AMD,CMD沒有什麼能夠問的,不過昨天面試忽然想到一個題目:
對於一個AMD的模式下git
文件d.js
以下es6
define(function (require) { // ... 不少代碼 require('a'); // ... 不少代碼 require(['b'], function (b) {}); // ... 不少代碼 require('c'); });
a.js
,b.js
,c.js
文件分別是何時加載的,如何加載的?github
題目不難面試
答案是a.js
和 c.js
是在加載完d.js
後就加載。b.js
是在執行到這一行時異步加載的。瀏覽器
具體分析:異步
我沒有看過require.js
的源碼,咱們使用的是esl.js
(也是一個AMD的模塊加載器),可是他們的實現原理應該差很少。模塊化
我從esl.js
的角度解讀一下:性能
首先你們須要知道AMD裏面一個同步加載和異步加載的概念。ui
從概念上面理解,同步就是當我執行到require('a');
時,我須要同步的執行a.js
裏面的內容,也就是須要在執行到這句話時a.js
必須已經加載好了,這樣才能到達同步。
而對於 require(['b'], function (b) {});
,我執行到這一步時,是異步的發出請求,而後異步等待b.js
的返回+執行。
咱們從概念上面理解的同步加載的原理,如今看看esl.js
的實踐。
這裏面須要處理兩個核心步驟
執行到require('a');
時,a.js
必須已經加載好了;
a.js
文件裏面的全部require('*')
,也都必須加載好了,保證在執行a.js
時,全部a.js
依賴的同步文件都能同步執行;
對於第一步的實現,大概原理是這樣的,在加載好了d.js
後,會正則匹配一次文件裏面的同步依賴require('*');
,例如匹配出了 a.js
和c.js
,而後繼續加載a.js
和c.js
。
對於第二部,其實就是一個遞歸處理,直到沒有下一步的依賴爲止。
上面有一部正則邏輯,可見若是使用這種方式,在執行代碼前,js須要所有正則一次全部模塊化代碼的。這樣性能是否是有一個無謂的耗損。
那麼咱們通常怎麼處理了?
你們通常都瞭解過打包編譯,例如在使用Requirejs
時,線上環境的代碼會通過r.js
處理一次。
那麼d.js
文件應該會處理以下
define( 'path/b', ['require', 'a', 'b'], function (require) { // ... 不少代碼 require('a'); // ... 不少代碼 require(['b'], function (b) {}); // ... 不少代碼 require('c'); });
define方法會增長第一個和第二個參數
第一個參數是按照路徑生成一個具名id
第二個參數是此文件所依賴的同步文件
這時當模塊在解析這個b,js
文件時,發現若是存在第二個參數,就會直接解析所需依賴部分,而省去了正則這一步。
咱們正則這一步轉換到了打包編譯中去分析,這樣就省掉了瀏覽器加載時去正則全部AMD文件這一步。
那麼爲何咱們不在開發環境中直接使用['require', 'a', 'b']
方式,我理解目的是爲了提升開發便捷性,咱們不須要再增長一個require('*')
都在中括號內配置一次,一樣刪除時也不用去刪掉配置。
由於這一步徹底能夠在編譯時處理。
不知道你們有沒有看過編譯後的代碼和開發環境代碼的區別,對於這個d.js
文件,編譯後應該是:
define( 'path/a', ['require'], function (require) { // ... 不少代碼 }); define( 'path/c', ['require'], function (require) { // ... 不少代碼 }); define( 'path/b', ['require', 'a', 'b'], function (require) { // ... 不少代碼 require('a'); // ... 不少代碼 require(['b'], function (b) {}); // ... 不少代碼 require('c'); });
上面可見,a.js
和c.js
這兩個文件被合併到了d.js
中,全部文件都加上了具名id。並且這個id的生成規則是更具路徑生成的。
而咱們異步加載的b.js
文件就沒有被打包進來。這是由於咱們指望b.js
是懶加載的,當使用時在加載,這樣也能達到按需加載的目的。