深刻seajs源碼系列三

入口方法

       每一個程序都有個入口方法,相似於c的main函數,seajs也不例外。系列一的demo在首頁使用了seajs.use(),這即是入口方法。入口方法能夠接受2個參數,第一個參數爲模塊名稱,第二個爲回調函數。入口方法定義了一個新的模塊,這個新定義的模塊依賴入參提供的模塊。而後設置新模塊的回調函數,用以在loaded狀態以後調用。該回調函數主要是執行全部依賴模塊的工廠函數,最後在執行入口方法提供的回調。 html

// Public API
// 入口地址
seajs.use = function(ids, callback) {
  Module.preload(function() {
    Module.use(ids, callback, data.cwd + "_use_" + cid())
  })
  return seajs
}

// Load preload modules before all other modules
Module.preload = function(callback) {
  var preloadMods = data.preload
  var len = preloadMods.length

  if (len) {
    Module.use(preloadMods, function() {
      // Remove the loaded preload modules
      preloadMods.splice(0, len)

      // Allow preload modules to add new preload modules
      Module.preload(callback)
    }, data.cwd + "_preload_" + cid())
  }
  else {
    callback()
  }
}

// Use function is equal to load a anonymous module
Module.use = function (ids, callback, uri) {
  var mod = Module.get(uri, isArray(ids) ? ids : [ids])

  mod.callback = function() {
    var exports = []
    var uris = mod.resolve()

    for (var i = 0, len = uris.length; i < len; i++) {
      exports[i] = cachedMods[uris[i]].exec()
    }
    // 回調函數的入參對應依賴模塊的返回值
    if (callback) {
      callback.apply(global, exports)
    }

    delete mod.callback
  }

  mod.load()
}

Module.preload用於預加載seajs提供的插件plugins,非主要功能,能夠忽略。而Module.use則是核心方法,該方法正如以前所說,建立新的module並設置回調函數,最後加載新模塊的全部依賴模塊。app

加載依賴之load方法

         load方法可謂是seajs的精華所在。該方法主要加載依賴模塊並依次執行依賴模塊的回調函數,最終執行的回調函數則是經過seajs.use(「./name」)建立的新模塊的回調,也就是mod.callback。異步

         load方法遞歸加載依賴模塊,若是依賴模塊還依賴其餘模塊,則再加載這個模塊。這是經過Module類中的_waitings和_remain來實現的。 async

Module.prototype.load = function() {
  var mod = this

  // If the module is being loaded, just wait it onload call
  if (mod.status >= STATUS.LOADING) {
    return
  }

  mod.status = STATUS.LOADING

  // Emit `load` event for plugins such as combo plugin
  var uris = mod.resolve()
  emit("load", uris, mod)

  var len = mod._remain = uris.length
  var m

  // Initialize modules and register waitings
  for (var i = 0; i < len; i++) {
    m = Module.get(uris[i])

    // 修改  依賴文件 的 _waiting屬性
    if (m.status < STATUS.LOADED) {
      // Maybe duplicate: When module has dupliate dependency, it should be it's count, not 1
      m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
    }
    else {
      mod._remain--
    }
  }

    //  加載完依賴,執行模塊
  if (mod._remain === 0) {
    mod.onload()
    return
  }

  // Begin parallel loading
  var requestCache = {}

  for (i = 0; i < len; i++) {
    m = cachedMods[uris[i]]

    // 該依賴並未加載,則先fetch,將seajs.request函數綁定在對應的requestCache上,此時並未加載模塊
    if (m.status < STATUS.FETCHING) {
      m.fetch(requestCache)
    }
    else if (m.status === STATUS.SAVED) {
      m.load()
    }
  }

  // Send all requests at last to avoid cache bug in IE6-9. Issues#808
  // 加載全部模塊
  for (var requestUri in requestCache) {
    if (requestCache.hasOwnProperty(requestUri)) {
      // 此時加載模塊
      requestCache[requestUri]()
    }
  }
}

// 依賴模塊加載完畢執行回調函數
// 並檢查依賴該模塊的其餘模塊是否能夠執行
Module.prototype.onload = function() {
  var mod = this
  mod.status = STATUS.LOADED

  if (mod.callback) {
    mod.callback()
  }
    console.log(mod)
  // Notify waiting modules to fire onload
  var waitings = mod._waitings
  var uri, m

  for (uri in waitings) {
    if (waitings.hasOwnProperty(uri)) {
      m = cachedMods[uri]
      m._remain -= waitings[uri]
      if (m._remain === 0) {
        m.onload()
      }
    }
  }

  // Reduce memory taken
  delete mod._waitings
  delete mod._remain
}

首先初始化模塊的_waitings和_remain屬性,若是_remain爲0,則意味着沒有依賴或者依賴已加載,能夠執行onload函數;若是不爲0,則fetch未加載的模塊。在這裏有個實現的小技巧,就是同時加載全部依賴:requestCache對象保存加載函數:(在fetch函數中定義) 函數

if (!emitData.requested) {
    requestCache ?
        requestCache[emitData.requestUri] = sendRequest :
        sendRequest()
  }

其中,sendRequest函數定義以下: 工具

function sendRequest() {
    seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
  }

並行加載全部依賴,當依賴加載完畢,執行onRequest回調,向上冒泡,加載依賴的依賴,直至沒有依賴模塊。學習

當最上層的依賴已沒有依賴模塊時,執行onload函數,在函數體內設置狀態爲loaded,執行mod.callback,並檢查並設置該模塊的_waitings屬性,判斷下層模塊是否還有依賴,若沒有則執行下層模塊的mod.callback,這一依次回溯,最終將會執行經過seajs.use建立的匿名模塊的mod.callback。fetch

例證

       經過一個簡單的例子,論證上述過程: ui

tst.html

<script>
        seajs.use('./b');
</script>
-------------------------------------
a.js

define(function(require,exports,module){
    exports.add = function(a,b){
        return a+b;
    }
})
------------------------------------
b.js

define(function(require,exports,module){
    var a = require("./a");
    console.log(a.add(3,5));
})

經過調試工具,能夠看出執行onload的次序:this

圖像 1

最後可看出,匿名模塊的狀態碼爲4,也就是該模塊並未執行.確實,也沒有給匿名模塊定義工廠函數,沒法執行.

模塊執行之exec

        模塊執行是在seajs.use中定義的mod.callback中調用的,依次調用全部依賴的exec方法,執行程序邏輯。exec方法中有commonJS的一些重要關鍵字或者函數,如require,exports等,讓咱們一看究竟:

Module.prototype.exec = function () {
  var mod = this

  // When module is executed, DO NOT execute it again. When module
  // is being executed, just return `module.exports` too, for avoiding
  // circularly calling
  if (mod.status >= STATUS.EXECUTING) {
    return mod.exports
  }

  mod.status = STATUS.EXECUTING

  // Create require
  var uri = mod.uri

  function require(id) {
    return Module.get(require.resolve(id)).exec()
  }

  require.resolve = function(id) {
    return Module.resolve(id, uri)
  }

  require.async = function(ids, callback) {
    Module.use(ids, callback, uri + "_async_" + cid())
    return require
  }

  // Exec factory
  var factory = mod.factory

  // 工廠函數有返回值,則返回;
  // 無返回值,則返回mod.exports
  var exports = isFunction(factory) ?
      factory(require, mod.exports = {}, mod) :
      factory

  if (exports === undefined) {
    exports = mod.exports
  }

  // Reduce memory leak
  delete mod.factory

  mod.exports = exports
  mod.status = STATUS.EXECUTED

  // Emit `exec` event
  emit("exec", mod)

  return exports
}

require函數獲取模塊並執行模塊的工廠函數,獲取返回值。require函數的resolve方法則是獲取對應模塊名的絕對url,require函數的async方法異步加載依賴並執行回調。對於工廠方法的返回值,若是工廠方法爲對象,則這就是exports的值;or工廠方法有返回值,則爲exports的值;or module.exports的值爲exports的值。當能夠獲取到exports值時,設置狀態爲executed。

值得注意的一點:當想要經過給exports賦值來導出一個對象時

define(function(require,exports,module){
    exports ={
        add: function(a,b){
             return a+b;
        }
    }
})

是不成功的.咱們經過執行上述方法來判斷最終導出exports的值.首先,函數沒有返回值,其次,mod.exports爲undefined,最終導出的exports爲undefined。爲何會出現這種狀況呢?是由於js中引用賦值所形成的。js的賦值策略是「按共享傳遞」,雖然說初始時exports === module.exports,可是當給exports賦一個對象時,此時exports指向該對象,module.exports卻仍未初始化,爲undefined,所以會出錯。

正確的寫法爲

define(function(require,exports,module){
    module.exports ={
        add: function(a,b){
             return a+b;
        }
    }
})

總結

        能夠說,seajs的核心模塊的實現已講解完畢,經過閱讀玉伯的源碼,見識了很多編碼技巧,領略了回調模式的巧妙,以及於細微處的考量。代碼的每一處都考慮到了內存泄露和this指針引用偏移的危險,作了積極的預防,這種精神值得學習。

       對於seajs,前先後後花了不下一個星期來閱讀源碼,從剛開始的只知其一;不知其二到現在的拜服,我真切的領會到了設計思想的重要性。以前我不怎麼重視實現的技巧性,認爲可以實現,不出bug,健壯性良好便可,可是如今我意識到錯了,尤爲是在load依賴模塊那部分實現中,技巧性十足。

      這段時間最大的收貨就是:「閱讀大牛的代碼,品味,咀嚼,消化」。

相關文章
相關標籤/搜索