RequireJS框架源代碼解析
閱讀提示:
1.有部分思維導圖很大,需要點開才能看清。
2.文章內容比較多,可以關注之後慢慢看。
1.前言
require.js的誕生,是為了解決兩個問題:
(1)實現js文件的非同步載入,避免網頁失去響應;
(2)管理模塊之間的依賴性,便於代碼的編寫和維護。
管理模塊這個需求,又細分為三塊內容
1.管理符合RequireJS要求的AMD規範的模塊
2.管理老式的JS模塊,比如JQuery之類
3.兼容CommonJS的模塊
那麼,requirejs是怎麼做到這一點的?下面先從AMD規範簡單說起。
2.AMD規範簡單說明
The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.
非同步模塊定義規範(AMD)制定了定義模塊的規則,這樣模塊和模塊的依賴可以被非同步載入。這和瀏覽器的非同步載入模塊的環境剛好適應(瀏覽器同步載入模塊會導致性能、可用性、調試和跨域訪問等問題)。
2.1 定義模塊
按AMD規範,一個模塊有定義部分define
比如
define({
method1:function(){},
method2:function(){},
});
或者
define(["module1","module2"],function(m1,m2){
...
});
2.2 調用按AMD規範定義的模塊
以Requirejs本身為例子,是通過require方法來調用事先定義好的模塊
require(["foo","bar"],function(foo,bar){
foo.doSomething();
});
就這樣,定義和調用結合,共同組成了最簡單的AMD設計規範。requirejs主要是針對AMD規範設計的,所以本文重點分析對AMD模塊的載入和管理功能。
RequireJS可以通過require.config方法來配置一些參數,包括path,baseUrl,shim,用於配製默認載入路徑,以及不符合AMD規範的庫。框架在初始化的時候,會首先初始化config參數,並使用配置好的參數來控制載入路徑,已知依賴等等一些前端項目公用的一些參數設定。
了解了一些基礎知識之後,我們開始進入源代碼的世界,來看看requirejs是怎麼設計的,並怎麼實現的。
首先必須要了解框架初始化過程。從初始化過程,我們可以看到很多設計理念。
3.RequireJS的初始化過程
3.1 Sample code
首先我們需要一個sample,來作為講解的初始。下面的這個app.js是依據官方文檔給出的最簡單的sample了。
其功能很簡單。首先我們設置了一個baseUrl,告訴requireJS框架,我們的js文件全部放在js/lib路徑下面。其次,app.js會調用requirejs方法,把模塊的載入管理移交給requirejs。最後,代碼並提供了一個空的工廠方法,等待requirejs操作完畢,載入jquery,canvas,app/sub三個模塊之後進行回調。
requirejs.config({
baseUrl: "js/lib",
paths: {
app: "../app"
}
});
// Start the main app logic.
requirejs(["jquery", "canvas", "app/sub"],
function ($, canvas, sub) {
//jQuery, canvas and the app/sub module are all
//loaded and can be used here now.
});
3.2 requirejs方法解析
在代碼app.js中用到了一個方法,requirejs方法。這個是整個requirejs框架的入口點。通過仔細閱讀requirejs方法的源代碼,我們可以發現這個方法里包含了很多操作。簡單梳理下,requirejs方法,也就是req方法,包含了以下的一些操作
其中req.load()是真正載入js文件的代碼,提供了載入js文件的方法。load的內部邏輯相對比較簡單
中req.createNode()僅用於瀏覽器下的代碼載入。
但現在還看不出requirejs是怎麼管理module。把這個放一邊,先來看下app.js調用requrejs之後的後續邏輯過程。
3.3 內部初始化邏輯
仔細分析RequireJS初始化源代碼,當存在一個符合requirejs要求用戶腳本文件app.js(app.js的例子見上文)。整個框架初始化過程,如果忽略到其他的一些代碼,核心的調用順序是這樣的:
整個初始化過程,全部依賴於context來進行非同步載入操作。newContext方法才是整個requirejs的本體。核心的操作全部在裡面。
而context.nextTick負責非同步操作,以避免瀏覽器失去響應。Module初始化的部分代碼,負責具體module 代碼,也就是js文件的載入。
4.requirejs內部邏輯解析
那麼回過頭來看下define(),也就是req.define方法到底做了什麼?
實際上只是把module相關的name,deps和callback放到了隊列之中。
關鍵代碼就是這句
context.defQueue.push([name, deps, callback]);
所以define並非是定義了一個module,而只是進行了註冊操作。即不存在自定的moudle原型對象,也不存在面向對象編程那種傳統一樣上的class,struct或者object對象。只是把相關信息:模塊的名字,模塊的依賴模塊,和初始化以後的回調函數,進行了註冊操作。
那麼基本的設計邏輯,我們已經可以看的出來了。整體上requirejs只做了下面一些操作。
1.註冊:收集用戶的偽module信息,放置到context的隊列defQueue之中。
2.依賴管理:依據隊列之中的deps(模塊依賴列表),梳理載入的順序
3.載入:依次載入每一個define模塊所依賴的js文件
4.清理註冊:載入完成之後,清理掉defQueue中的信息。
5.回調:回調用戶指定的callback(工廠方法),把代碼運行權利交還給用戶代碼。
5.Module的初始化過程導圖
從上圖來看,大致分成以下幾個步驟。第一是沒有初始化,就去載入js文件。第二是回調用戶代碼,也就是factory方法。但有些只是支持模塊,並不存在工廠方法,那麼就根據實際定義來決定是不是要輸出一個變數。
6.完整的requirejs方法導圖
從這個圖中,可以看到一些設計上的想法
1。運行配置是context的一部分,通過config來初始化context。
2。瀏覽器本身需要做一些初始化的操作。
3。對data-main的操作,是requirejs初始化的一部分的。不需要額外的啟動入口。
4。IE環境是個大麻煩。
7.缺少部分
寫到這裡,還是有些細節沒有寫出來,包括對傳統舊模塊的載入控制,怎麼解決依賴循環問題,插件的載入。這些以後再做補遺文章吧。
8.後記
通過對requirejs源代碼的閱讀,重新理解了javascript的原型編程。甚好。
從列印源代碼,閱讀,寫稿,修改,斷斷續續的花了大概十來天的時間。累。
如有錯誤的地方,請各位大牛指正。謝謝。
多謝各位的閱讀,將繼續努力為大家閱讀和分析更多源代碼。
完全原創,任何轉載請關注公眾號之後,留言諮詢。
歡迎關注,本公眾號是對前端源代碼的閱讀和分析。


TAG:前端思維導圖 |