首頁  >  文章  >  php教程  >  seajs模組之間依賴的載入以及模組的執行

seajs模組之間依賴的載入以及模組的執行

高洛峰
高洛峰原創
2016-12-28 13:55:151507瀏覽

本文介紹的是seajs模組之間依賴的載入以及模組的執行,下面話不多說直接來看詳細的介紹。

入口方法

每個程式都有一個入口方法,類似c的main函數,seajs也不例外。系列一的demo在首頁使用了seajs.use() ,這就是入口方法。入口方法可以接受2個參數,第一個參數為模組名稱,第二個為回呼函數。入口方法定義了一個新的模組,這個新定義的模組依賴入參提供的模組。然後設定新模組的回呼函數,用以在loaded狀態之後呼叫。此回調函數主要是執行所有依賴模組的工廠函數,最後在執行入口方法提供的回調。

// 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並設定回調函數,最後載入新模組的所有依賴模組。

載入相依之load方法

load方法可謂是seajs的精華所在。此方法主要載入依賴模組並依序執行依賴模組的回呼函數,最終執行的回呼函數則是透過seajs.use(“./name”)所建立的新模組的回調,也就是mod.callback 。

load方法遞歸載入依賴模組,如果依賴模組也依賴其他模組,再載入這個模組。這是透過Module類別中的_waitings和_remain來實現的。

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&#39;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。

例證

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

tst.html
 
<script>
  seajs.use(&#39;./b&#39;);
</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的次序:

seajs模組之間依賴的載入以及模組的執行

最後可看出,匿名模組的狀態碼為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模組之間依賴的載入以及模組的執行相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn