天天看點

淺談子產品化加載的實作原理

為什麼他們會想到使用子產品化加載呢,我覺得主要是兩點。

第二點,應該也是從伺服器那邊參考而來的,伺服器腳本很多都是以檔案為機關分離的,如果要利用其它檔案的功能,可以輕而易舉的 require 或者 include 進來,我沒有去研究這些加載函數的内部實作原理,稍微猜猜應該是把檔案寫入到緩存,遇到 include 之類的加載函數,暫停寫入,找到需要 include 的檔案位址,把找到的檔案接着上面繼續寫入緩存,以此類推,直到結束,然後編譯器進行統一編譯。

先不考慮各種子產品定義規範,本文目的隻是簡要的分析加載原理, CMD / AMD 規範雖内容然不多,但是要實作起來,工程量還是不小。文章後面會提到。

既然是子產品化加載,想辦法把子產品内容拿到當然是重頭戲,無論是 script 還是 css 檔案的加載,一個 script 或者 link 标簽就可以搞定問題,不過我這裡采用的是 ajax,目的是為了拿到 script 的代碼,也是為了照顧後面要說的 CMD 規範。

建立 script 标簽加載腳本不會存在跨域問題,不過拿到的腳本會被浏覽器立馬解析出來,如果要做同異步的處理就比較麻煩了。沒有跨域的檔案我們就通過上面的方式加載,如果腳本跨域了,再去建立标簽,讓文檔自己去加載。

子產品之間存在依賴關系是十分正常的,如一個工程的檔案結構如下:

而這裡幾個子產品的依賴關系是:

我們要從 index.html 中利用 require.js 擷取這一連串的依賴關系,一般采用的方式就是正則比對。如下:先拿到 function 的代碼,然後正則比對出第一層的依賴關系,接着加載比對到關系的代碼,繼續比對。

整個函數的入口是 start,正規表達式為:

由此我們拿到了第一層的依賴關系,

接着要拿到 a.js 和 b.js 的檔案層次依賴,之前我們寫了一個 require 函數,這個函數可以拿到腳本的代碼内容,不過這個 require 函數要稍微修改下,遞歸去查詢和下載下傳代碼。

上面的代碼已經可以很好地拿到檔案遞歸關系了:

淺談子產品化加載的實作原理

但是我們有必要先把 responseText 緩存起來,如果不緩存檔案,直接 eval 得到的 responseText 代碼,想想會發生什麼問題~ 如果子產品之間存在循環引用,如:

那 start 和 require 将會陷入死循環,不斷的加載代碼。是以我們需要先拿到依賴關系,然後解構關系,分析出我們需要加載哪些子產品。值得注意的是,我們必須按照加載的順序去 eval 代碼,如果 a 依賴 b,先去執行 a 的話,一定會報錯!

有兩個問題我糾結了半天,上面的請求方式,何時會結束?用什麼方式去記錄檔案依賴關系?

最後還是決定将 start 和 require 兩個函數的互相遞歸修改成一個函數的遞歸。用一個對象,發起請求時把 URL 作為 key,在這個對象裡儲存 XHR 對象,XHR 對象請求完成後,把抓取到的新請求再用同樣的方式放入這個對象中,同時從這個對象中把自己删除掉,然後判斷這個對象上是否存在 key, 如果存在說明還有 XHR 對象沒完成。

上面的代碼已經基本完成了檔案依賴分析,檔案的加載和緩存工作了,我寫了一個demo,有興趣可以看一看。這個demo的檔案結構為:

淺談子產品化加載的實作原理

上面寫了一大堆内容,也實作了子產品加載器的原型,但是放在實際應用中,他就是個廢品,回到最開始,我們為什麼要使用子產品化加載。目的是為了不去使用麻煩的命名空間,把複雜的子產品依賴交給 require 這個函數去管理,但實際上呢,上面拿到的所有子產品都是暴露在全局變量中的,也就是說,如果 a.js 和 b.js 中存在命名相同的變量,後者将會覆寫前者,這是我們不願意看到的。為了處理此類問題,我們有必要把所有的子產品都放到一個閉包中,這樣一來,隻要不使用 window.vars 命名,閉包之間的變量是不會互相影響的。我們可以使用自己的方式去管理代碼,不過有人已經研究處理一套标準,而且是全球統一,那就拿着用吧~

剩下的工作就是針對 CMD 規範寫一套符合标準的代碼接口,這個比較瑣碎,就不寫了。

上面的代碼中提到了關于 Event 的事件管理。在子產品全部加在完畢之後,需要有個東西告訴你,是以順手寫了一個 Event 的事件管理器。

我覺得 seajs 是一個很不錯的子產品加載器,如果感興趣,可以去看看他的源碼實作,代碼不長,隻有一千多行。子產品的加載它采用的是建立文本節點,讓文檔去加載子產品,實時檢視狀态為 interactive 的 script 标簽,如果處于互動狀态就拿到他的代碼,接着删除節點。當節點數目為 0 的時候,加載工作完成。

本文沒有考慮 css 檔案的加載問題,我們可以把它當做一個沒有 require 關鍵詞的 js 檔案,或者把它比對出來之後另作處理,因為他是不可能存在子產品依賴關系的。

然後就是很多很多細節,本文的目的并不是寫一個類似 seajs 的子產品管理工具,隻是稍微說幾句自己對這玩意兒的看法,如果說的有錯,請多多吐槽!

繼續閱讀