seajs 模塊源碼解讀

入口 seajs.use node


seajs.use直接調用Module.use(),Module.use的源碼以下:jquery

// Use function is equal to load a anonymous module
// ids:模塊標識,uri是dirname + '_use_0' + 數字自增變量
Module.use = function (ids, callback, uri) {
 // 從緩存cachedMods中獲取已存在的模塊對象或者新建module對象,並將緩存寫入其中,返回當前模塊對象
  var mod = Module.get(uri, isArray(ids) ? ids : [ids])

  // 對使用seajs.use或者require.async的模塊掛在一個callback。seajs.use的callback在全部依賴都loaded完畢以後才執行,經過require遞歸的exec()各個已經loaded的依賴模塊。這個callback做爲seajs的入口,是全部模塊的factory開始執行的線頭起點。
  mod.callback = function() {
    var exports = []
    // resolve經過正則表達式解析當前模塊的依賴deps,並返回解析後的完成路徑,其值爲一個數組
    var uris = mod.resolve()

    // seajs.use入口處的依賴模塊依次開始執行,此爲模塊執行的起點
    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()
}

// Get an existed module or create a new one
Module.get = function(uri, deps) {
  return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
}

// Module構造函數
function Module(uri, deps) {
  this.uri = uri
  this.dependencies = deps || []
  this.exports = null
  this.status = 0

  // Who depends on me
  this._waitings = {}  // 依賴當前模塊的對象

  // The number of unloaded dependencies
  this._remain = 0 // 依賴的未加載完畢的模塊個數
}

load:加載當前模塊的依賴,而且在當前模塊的全部依賴都加載完畢後,觸發onload函數 git


// Load module.dependencies and fire onload when all done
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
  // 依次初始化當前mod依賴的deps爲module對象,而且初始化_waitings,並存入cacheMods
  for (var i = 0; i < len; i++) {
    m = Module.get(uris[i])

    if (m.status < STATUS.LOADED) {
      // Maybe duplicate: When module has dupliate dependency, it should be it's count, not 1
      // 若是當前依賴爲未加載完成狀態,則該模塊的waiting + 1
      m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
    } else {
      // 當前依賴已經加載完成,則模塊的remain - 1
      mod._remain--
    }
  }
  
  // 當前模塊的全部依賴都已經加載完畢時,觸發onload
  if (mod._remain === 0) {
    mod.onload()
    return
  }

  // Begin parallel loading
  var requestCache = {}
  // 經過fetch函數,加載當前mod依賴的全部模塊
  for (i = 0; i < len; i++) {
    m = cachedMods[uris[i]]

    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]()
    }
  }
}

// Call this method when module is loaded
// 模塊的全部依賴都加載完畢後,執行onload
Module.prototype.onload = function() {
  var mod = this
  mod.status = STATUS.LOADED
  
  // 對於使用require.async定義的模塊,有callback函數,在全部依賴模塊加載完畢後執行callback函數
  if (mod.callback) {
    mod.callback()
  }

  // 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) {
        // 遞歸調用依賴當前模塊的模塊,執行onload函數,直到module.use頂端
        m.onload()
      }
    }
  }

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

fetch: fetch主要是經過建立<script async=true charset></script>標籤而且append到head來實現依賴的加載。這裏的依賴都是經過async來異步加載的,加載完畢以後馬上執行define函數,在模塊文件執行完畢後(包括define和其餘js代碼),觸發script的onload事件。 github


// Fetch a module
Module.prototype.fetch = function(requestCache) {
  var mod = this
  var uri = mod.uri

  mod.status = STATUS.FETCHING

  // Emit `fetch` event for plugins such as combo plugin
  var emitData = {
    uri: uri
  }
  emit("fetch", emitData)
  var requestUri = emitData.requestUri || uri

  // Empty uri or a non-CMD module
  if (!requestUri || fetchedList[requestUri]) {
    mod.load()
    return
  }

  // fetchingList和callbackList爲兩個全局對象,分別存放正在加載的模塊列表?????
  if (fetchingList[requestUri]) {
    callbackList[requestUri].push(mod)
    return
  }

  fetchingList[requestUri] = true
  callbackList[requestUri] = [mod]

  // Emit `request` event for plugins such as text plugin
  emit("request", emitData = {
    uri: uri,
    requestUri: requestUri,
    onRequest: onRequest,
    charset: data.charset
  })

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

  function sendRequest() {
    seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
  }
  
  // script標籤的onload事件發生後,觸發onRequest方法
  function onRequest() {
    delete fetchingList[requestUri]
    fetchedList[requestUri] = true

    // Save meta data of anonymous module
    if (anonymousMeta) {
      Module.save(uri, anonymousMeta)
      anonymousMeta = null
    }

    // Call callbacks
    var m, mods = callbackList[requestUri]
    delete callbackList[requestUri]
    while ((m = mods.shift()))
      m.load()
  }
}

request: 將script添加到head,而且定義onload事件 正則表達式


seajs.request = function (url, callback, charset) {
    var isCSS = IS_CSS_RE.test(url)
    var node = doc.createElement( isCSS ? "link" : "script")

    if (charset) {
      var cs = isFunction(charset) ? charset(url) : charset
      if (cs) {
        node.charset = cs
      }
    }

    addOnload(node, callback, isCSS, url)

    if (isCSS) {
      node.rel = "stylesheet"
      node.href = url
    } else {
      node.async = true
      node.src = url
    }

    // For some cache cases in IE 6-8, the script executes IMMEDIATELY after
    // the end of the insert execution, so use `currentlyAddingScript` to
    // hold current node, for deriving url in `define` call
    currentlyAddingScript = node

    // ref: #185 & http://dev.jquery.com/ticket/2709
    baseElement ? head.insertBefore(node, baseElement) : head.appendChild(node)

    currentlyAddingScript = null
  }

  function addOnload(node, callback, isCSS, url) {
    var supportOnload = "onload" in node

    // for Old WebKit and Old Firefox
    if (isCSS && (isOldWebKit || !supportOnload)) {
      setTimeout(function() {
        pollCss(node, callback)
      }, 1) // Begin after node insertion
      return
    }

    if (supportOnload) {
      node.onload = onload
      node.onerror = function() {
        emit("error", {
          uri: url,
          node: node
        })
        onload()
      }
    } else {
      node.onreadystatechange = function() {
        if (/loaded|complete/.test(node.readyState)) {
          onload()
        }
      }
    }

    function onload() {
      // Ensure only run once and handle memory leak in IE
      node.onload = node.onerror = node.onreadystatechange = null

      // Remove the script to reduce memory leak
      if (!isCSS && !data.debug) {
        head.removeChild(node)
      }

      // Dereference the node
      node = null

      callback()
    }
  }

Module.define 數組


// Define a module
Module.define = function(id, deps, factory) {
  var argsLen = arguments.length

  // define(factory)
  if (argsLen === 1) {
    factory = id
    id = undefined
  } else if (argsLen === 2) {
    factory = deps

    // define(deps, factory)
    if (isArray(id)) {
      deps = id
      id = undefined1 `1` // define(id, factory)
      ``
    } else {
      deps = undefined
    }
  }

  // Parse dependencies according to the module factory code
  if (!isArray(deps) && isFunction(factory)) {
    deps = parseDependencies(factory.toString())
  }

  var meta = {
    id: id,
    uri: Module.resolve(id),
    deps: deps,
    factory: factory
  }

  // Try to derive uri in IE6-9 for anonymous modules
  if (!meta.uri && doc.attachEvent) {
    var script = getCurrentScript()

    if (script) {
      meta.uri = script.src
    }

    // NOTE: If the id-deriving methods above is failed, then falls back
    // to use onload event to get the uri
  }

  // Emit `define` event, used in nocache plugin, seajs node version etc
  emit("define", meta)
  
  // Save information for "saving" work in the script onload event
  meta.uri ? Module.save(meta.uri, meta) : anonymousMeta = meta
}

resolve 緩存


// Resolve module.dependencies
Module.prototype.resolve = function() {
  var mod = this
  var ids = mod.dependencies
  var uris = []

  for (var i = 0, len = ids.length; i < len; i++) {
    uris[i] = Module.resolve(ids[i], mod.uri)
  }
  return uris
}

// Resolve id to uri
Module.resolve = function(id, refUri) {
  // 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)
}


function id2Uri(id, refUri) {
  if (!id) return ""

  id = parseAlias(id)
  id = parsePaths(id)
  id = parseVars(id)
  id = normalize(id)

  var uri = addBase(id, refUri)
  uri = parseMap(uri)

  return uri
}

seajs.resolve = id2Uri;
參考文獻

https://seajs.github.io/seajs...
https://github.com/seajs/exam...
https://github.com/seajs/seajsapp

相關文章
相關標籤/搜索