溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

seajs 3.0.0 源碼閱讀筆記

發(fā)布時間:2020-10-12 19:50:27 來源:網(wǎng)絡(luò) 閱讀:1570 作者:邊城__ 欄目:web開發(fā)

寫筆記的時候才注意到我看的源代碼是 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í)慣,但不影響閱讀。

文件/目錄結(jié)構(gòu)

文檔中各個源文件所包含的內(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);

isXxxx 用于判斷對象類型

首先是定義了一個產(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")


這里有幾件事情我不是很明白:

  1. 就是為什么不使用 typeof 運(yùn)算符來判斷類型,一般語言中運(yùn)算符實(shí)現(xiàn)會比比較字符串快得多。

  2. {}.toString.call(obj) 和 Object.prototype.toString.call(obj) 的作用是一樣的,但是在運(yùn)行時,每執(zhí)行一次 isXxxx 就會產(chǎn)生一個新的 {} 對象;而 Object.prototype 始終都是同一個對象,似乎可以減少不少開銷

  3. 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 的源碼,大概定義了這么一些事件

  1. error,貌似跟 NodeJS 有點(diǎn)關(guān)系,沒仔細(xì)看

  2. load,在模塊對象狀態(tài)變成 LOADING 后觸發(fā),參數(shù)是所有依賴模塊的 URI。

      // Emit `load` event for plugins such as combo plugin
      var uris = mod.resolve()
      emit("load", uris)
  3. exec,在模塊對象狀態(tài)變成 EXECUTED 后觸發(fā),參數(shù)就是模塊對象本身

  4. 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
  5. 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) {
        // ...
      }
  6. 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)
  7. config,在 seajs.config() 中,完成對 config 對象的處理之后觸發(fā),參數(shù)就是 config 對象。

    seajs.config = function(configData) {
      // ...
      emit("config", configData)
      return seajs
    }

Module 類

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 版本的代碼就知道了。


向AI問一下細(xì)節(jié)

免責(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)容。

AI