模組載入器

發表於2016-04-12

最近在做新專案的時候自己利用一點業餘時間寫了一個簡單的js模組載入器。後來因為用了webpack就沒有考慮把它放到專案裡面去,也沒有繼續更新它了。模組載入器開源的有很多,一般來說seaJS和reqiureJS都能滿足基本需求。本篇博文主要分享一下滷煮寫這個載入器的一些想法和思路,作為學習的記錄。

js模組化載入已經不是一個新鮮概念了,很多人都一再強調,大型專案要使用模組化開發,因為一旦隨著專案的增大,管理和組織程式碼的難度會越來越難,使得我們對程式碼的管理變得重要起來。當然,在後端模組化已經相當成熟,而作為前端的模組化概念,是很久之後才提出來的。模組化好處是使得程式碼結構更加清晰,高的內聚,功能獨立,複用等等。在服務端,隨著nodejs 的興起,js模組化被越來越多地引起人們的注意。但是對於後端和前端來說,最大的區別就是同步和非同步載入的問題,因為伺服器上獲取模組是不需要花費很多的,模組載入進來的時間就作業系統檔案的時間,這個過程可以看成是同步的。而在瀏覽器的前端卻需要傳送請求到伺服器來獲取檔案,這導致了一個非同步延遲的問題,針對這個問題,以AMD規範的非同步模組載入器requireJS應運而生。

載入原理

以上簡單介紹了一下前端模組化的歷程,下面主要介紹一下模組載入主要原理:

1. createElement(‘script’)和appendChild(script) 動態建立指令碼,新增到head元素中。

2. fn.toString().match(/.require((“|’)[^)]*(“|’))/g) 將模組轉換為字串,然後通過正規表示式,匹配每個模組中的的依賴檔案。

3. 建立指令碼載入佇列。

4.遞迴載入,分析完依賴之後,我們需要按照依賴出現的位置,將它們載入到客戶端。

5.為每一個命名的模組建立快取,即 module[name] = callback;

6.currentScript : 對於匿名模組,通過currentScript 來獲取檔名,存入到快取中。

下面貼出對應主要的程式碼:

一、動態建立指令碼

建立指令碼較為簡單,主要是用createElement方法和appendChild。在建立指令碼函式中,我們需要為該指令碼繫結一個onload事件,這個事件是為了通知載入指令碼佇列執行的時間,告訴它什麼時候可以載入下一個js檔案了。

二、分析依賴建立

分析依賴是模組載入器中最重要的環節之一。每個模組可能會依賴不同的模組,我們需要理清楚這些模組之間的依賴關係,然後分別將它們載入進來。為了分析依賴關係,我們使用toString的方法,將模組轉化為一個string,然後去其中尋找依賴。

三、建立指令碼載入佇列

分析完依賴之後,我們可以得到一個指令碼名稱的棧,我們從其中獲取指令碼名稱,依次按照順序地載入它們。因為每個指令碼載入過程都是非同步的,所以,我們需要有一個非同步載入機制。在這裡,我們使用了設計模式中的職責鏈條模式來完成整個非同步載入過程。通過在onload事件通知佇列載入的完成情況。下面是職責鏈模式的實現程式碼

每個指令碼載入完畢後呼叫next函式,可以通知職責鏈中的下一個函式繼續執行,這樣解決了非同步載入問題。這裡將模式的實現程式碼放到模組載入器中是不太合適的,一般情況下我們可以將它獨立出來,放入公共模組當中,為其他的模組共同使用。但這裡純粹是一個單檔案的專案,所以就暫時將它放入此處。

四、遞迴載入

根據模組中的依賴出現的次序,依次載入各個模組。

五、為模組建立快取物件

六、currentScript

currentScript主要是用來解決獲取那些未命名的模組的js檔名,如 define(function(){})這樣的模組是匿名的,我們通過這個方法可以獲取正在執行的指令碼檔名,從而為其建立快取。

七、定義module

最後我們需要做的事給出定義模組的方法,一般情況下定義方法主要分以下幾種:

1.define(‘a’, function(){})

2.define(function(){})

第一種是命名的模組,第二種是未命名的模組,我們需要對它們分別處理。用typeof方法分析引數,建立以string方法為基礎的載入模式:

結束

以上就是一個實現模組載入器的主要原理,滷煮寫完發現也只有四百行的程式碼,實現了最基本的模組載入功能。當然,其中還有很多細節沒有實現,比起大而全的requireJs來說,只是一個小兒科而已。但是明白了主要這幾項後,對於我們來說就足夠理解一個模組載入器的實現方式了。程式碼存入github上: https://github.com/constantince/require

相關文章