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