seajs 模块源码解读

入口 seajs.use


seajs.use直接调用Module.use(),Module.use的源码如下:

// 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函数


// 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事件。


// 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/seajs

相关推荐