您好,登錄后才能下訂單哦!
寫筆記的時候才注意到我看的源代碼是 3.0.0 的,但是官方發(fā)布的最新版本是 2.3.0。相信大部分是相同的,所以先把這個記完,再看一次 2.3.0 的代碼。
seajs 的源代碼可以在 github上獲取。seajs 在文檔“如何參與開發(fā)”中說明了閱讀順序,當(dāng)然為了便于閱讀,在了解了目錄結(jié)構(gòu)之后,我直接閱讀了合并好的 sea-debug.js。
整個seajs采用的是2空格縮進(jìn),避免分號的寫法,我不是很習(xí)慣,但不影響閱讀。
文檔中各個源文件所包含的內(nèi)容大致如下:
intro.js
文件頭
sea.js
定義全局global.seajs
對象和seajs.data
對象
util-lang.js
,語言相關(guān)工具
定義用于判斷對象類型的isXxxxx()
函數(shù),以及一個與語言無關(guān)的cid()
。
util-events.js
定義 seajs 的事件處理相關(guān)函數(shù),包括on()
、off()
、emit()
util-path.js
定義用于路徑處理的工具函數(shù)
util-request.js
定義請求文件的工具函數(shù)seajs.request()
等
util-deps.js
定義用于分析依賴關(guān)系的parseDependencies()
module.js
seajs 的核心,模塊類。
也包含部分 seajs 的方法
config.js
定義seajs.config()
,以及data
部分屬性的默認(rèn)值
所以合并之后的整個 seajs 代碼看起來就像這樣
(function(global, undefiend) { global.seajs = { data: {} } var isObject = function() {} var isString = function() {} var isArray = function() {} var isFunction = function() {} var cid = function() {} data.events = {} seajs.on = function() {} seajs.off = function() {} seajs.emit = function() {} // path utils, and seajs.resolve = function() {} var loaderDir var parseDependencies = function() {} function Module() {} seajs.config = function() {} })(this);
首先是定義了一個產(chǎn)生 isXxxx 的函數(shù)工廠 isType()
function isType(type) { return function(obj) { return {}.toString.call(obj) == "[object " + type + "]" } }
從這個工廠的代碼可以看出來,isXxxx()
主要是通過 Object.prototype.toString
的值來判斷對象類型的。
當(dāng)然也有例外:
// 畢竟 Array.isArray() 是 [native code],效果會高得多 var isArray = Array.isArray || isType("Array")
這里有幾件事情我不是很明白:
就是為什么不使用
typeof
運(yùn)算符來判斷類型,一般語言中運(yùn)算符實(shí)現(xiàn)會比比較字符串快得多。
{}.toString.call(obj)
和Object.prototype.toString.call(obj)
的作用是一樣的,但是在運(yùn)行時,每執(zhí)行一次isXxxx
就會產(chǎn)生一個新的{}
對象;而Object.prototype
始終都是同一個對象,似乎可以減少不少開銷jQuery 的
isFunction()
等方法都是通過jQuery.type()
來實(shí)現(xiàn)的,而jQuery.type
中則是通過定義了一個class2type
字典對象來做類型映射,
2014-09-09 補(bǔ)充
我在 seajs 的 issue 上問了這個問題,于是上面的幾個問題就都找到答案了:
https://github.com/seajs/seajs/issues/1314
參考玉伯的博客
https://github.com/lifesinger/lifesinger.github.com/issues/175
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); });
這種作法也會比每次都拼字符串(
"[object " + type + "]"
)來比較要效率高一些吧。不過 seajs 和 jquery 都沒有直接依賴
typeof
運(yùn)算符來實(shí)現(xiàn)isXxxx
,我相信絕對不是偶然,一定有啥原因,不過這個原因我目前就不清楚了,希望玉伯能看到這個博客,稍作解釋
seajs 只為全局對象 seajs
添加了事件處理。事件的回調(diào)函數(shù)鏈保存在 seajs.data.events
中,以事件名稱為 key,Array
對象保存的回調(diào)函數(shù)鏈為 value。
因?yàn)?seajs 的事件主要為了插件而定義,所以對參數(shù)并沒有嚴(yán)格的校驗(yàn)。比如
// 自定義一個比較奇葩的事件,這不會報錯 seajs.on(null, "fake callback"); // 但是執(zhí)行就會出錯了 seajs.emit("null") // 輸出:TypeError: string is not a function
看樣子,插件開發(fā)者得自己注意下這個問題了。
看完 seajs 的源碼,大概定義了這么一些事件
error
,貌似跟 NodeJS 有點(diǎn)關(guān)系,沒仔細(xì)看
load
,在模塊對象狀態(tài)變成 LOADING
后觸發(fā),參數(shù)是所有依賴模塊的 URI。
// Emit `load` event for plugins such as combo plugin var uris = mod.resolve() emit("load", uris)
exec
,在模塊對象狀態(tài)變成 EXECUTED
后觸發(fā),參數(shù)就是模塊對象本身
fetch
,在模塊對象狀態(tài)剛變成 FETECHING
時觸發(fā),參數(shù)是一個臨時對象emitData
,事件結(jié)果保存在 emitData.requestUri
,用于后面的 request
請求數(shù)據(jù)。
// Emit `fetch` event for plugins such as combo plugin var emitData = { uri: uri } emit("fetch", emitData) var requestUri = emitData.requestUri || uri
request
,在 fetch
事件后對 emitData.requestUri || uri
進(jìn)行了處理之后,通過 seajs.request()
請求數(shù)據(jù)之前觸發(fā),參數(shù)是一個臨時對象,變量名復(fù)用的 emitData
。事件處理完成后根據(jù) emitData.requested
的值來判斷是否需要調(diào)用 seajs.request
請求數(shù)據(jù)。
// Emit `request` event for plugins such as text plugin emit("request", emitData = { uri: uri, requestUri: requestUri, onRequest: onRequest, charset: isFunction(data.charset) ? data.charset(requestUri) || 'utf-8' : data.charset }) if (!emitData.requested) { // ... }
resolve
,在 Module.resolve
中調(diào)用 seajs.resolve()
之前觸發(fā),參數(shù)是一個臨時對象 emitData
。事件中如果產(chǎn)生了有效的 emitData.uri
,則不再調(diào)用seajs.resolve()
// Emit `resolve` event for plugins such as text plugin var emitData = { id: id, refUri: refUri } emit("resolve", emitData) return emitData.uri || seajs.resolve(emitData.id, refUri)
config
,在 seajs.config()
中,完成對 config 對象的處理之后觸發(fā),參數(shù)就是 config 對象。
seajs.config = function(configData) { // ... emit("config", configData) return seajs }
Module
類才是 seajs 的重頭戲,核心的核心。seajs 作為一個模塊加載器,所以模塊都是以一個 Module
對象保存在 cachedMods
中的。
var cachedMods = seajs.cache = {}
每個模塊都有 8 種狀態(tài),它一定是在這 8 種狀態(tài)之一,而且貌似狀態(tài)改變還是不可逆的。
var STATUS = Module.STATUS = { // 沒定義狀態(tài)值為 0 的狀態(tài)常量,這是初始狀態(tài) // 1 - The `module.uri` is being fetched FETCHING: 1, // 2 - The meta data has been saved to cachedMods SAVED: 2, // 3 - The `module.dependencies` are being loaded LOADING: 3, // 4 - The module are ready to execute LOADED: 4, // 5 - The module is being executed EXECUTING: 5, // 6 - The `module.exports` is available EXECUTED: 6, // 7 - 404 ERROR: 7 }
其中 SAVED
狀態(tài)可以理解為 FETCHED
狀態(tài)。除了初始狀態(tài) 0
和錯誤狀態(tài) ERROR: 7
之外,其它 6 個狀態(tài)都是成對出現(xiàn)的,即 ING
狀態(tài)和 ED
狀態(tài),這三對狀態(tài)清晰的劃分出來三個處理過程:fetch、load、exec,對應(yīng)于模塊對象的 3 個方法:fetch()
, load()
, exec()
。
從代碼內(nèi)容來看,這三個主要過程方法主要功能分別可以用一句話說明:
fetch
,從 URL 加載模塊定義,得到 factory 函數(shù),并將 factory 函數(shù)賦值給對應(yīng)的模塊對象(通過 Module.get()
創(chuàng)建或獲取);
load
,fetch 并 load 所有依賴模塊,并在保證所有依賴模塊都是 LOADED 狀態(tài)之后,調(diào)用入口模塊(_entry
)的 callback
(貌似只有通過 seajs.use()
創(chuàng)建的匿名模塊才有 callback
)
exec
,在調(diào)用這個方法的時候,可以保證所有依賴模塊都已經(jīng)是 LOADED 狀態(tài)了,所以 exec
就只是簡單的執(zhí)行 factory 函數(shù),并返回 exports
。factory 只執(zhí)行一次,然后將 exports
緩存下來。
現(xiàn)在來看看入口函數(shù) seajs.use()
、模塊定義函數(shù) define()
、模塊關(guān)系過程處理方法 fetch()
,load()
, exec()
的主要調(diào)用關(guān)系:
// seajs.use 只調(diào)用了 Module.use,所以它們的調(diào)用關(guān)系可以看作等同 seajs.use = Module.use = function() { Module.get() exec() // 通過 _entry.callback 調(diào)用 load() }
define = Module.define = function() { Module.save(id, factory) }
Module.prototype.fetch = function() { define() // 通過 seajs.request() 調(diào)用 load() }
Module.prototype.load = function() { pass() fetch() // [遞歸] // 結(jié)束的條件是 _entry.remain === 1, // 即當(dāng)前是最后一個依賴模塊 // 遞歸結(jié)束時調(diào)用 _entry.callback,即調(diào)用了 exec load() }
Module.prototype.exec = function() { exec() // 通過 define 中定義的 factory 函數(shù)調(diào)用 }
Module.prototype.pass = function() { // [遞歸] // 結(jié)束條件是把 _entry 傳遞到最末一層依賴 // 遞歸過程通過 _entry.remain 進(jìn)行了引用記數(shù) pass() }
各函數(shù)和方法具體處理過程看代碼就明白了,因?yàn)檫f歸關(guān)系有點(diǎn)復(fù)雜,還有一些回調(diào)關(guān)系在里面,所以看起來有點(diǎn)繞,不過還算是看得明白。
http://seajs.org 引用的 seajs 版本是 2.2.1,從這引頁面的控制輸出 seajs.Modules.prototype
來看,并沒有定義 pass()
方法,所以對 _entry 的處理可能會有點(diǎn)不一樣,稍后看了 seajs 2.2.3 版本的代碼就知道了。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。