seajs源码分析

seajs主要做了2件事

1.定义什么是模块,如何声明模块:id、deps、factory、exports ----define=function(id,deps,factory){return exports}

2.管理模块依赖,解决自动加载。本质其实是个加载器loader:id配上路径规则,factory可以提取依赖模块,递归遍历后自动加载js文件

  核心的代码是util-path.js和module.js

 

发现seajs已经修订到3.0.0部分实现有改动,有些地方随手写的,也没求证过估计误人子弟不少。忙着review下,后面再补了3、0版的module.js注释贻笑大方

对外接口

seajs.use    执行的入口方法

define       模块定义的初始化方法

seajs.use("a")->Module.get("a")

                           m.load();执行fetch后加载define调用Module.save跟新deps

                           m.onload();

                           m.exec();





    子模块:

             deps->Module.get()

             fetch/load 通知回调-> 主模板onload
模块状态: fetch--Module.fetch 拉取模板 saved--Module.saved 拉取模板成功后就save模块(define会提取deps) loading--m.load()   检测依赖模块是否全部加载 loaded--m.onload()  已加载全部模块后触发 executing/executed-m.exec() seajs.use->mod.onload->mod.callback()->mod.exec(); use是seajs模块入口函数 定义模块:

Module.use(seajs.use)
Module.define(global.define)

 

核心类:Module

静态方法

Module.use(使用模块load后exec)、Module.preload(获取data.preload参数后执行Module.use)、Module.get(拿到模块,若无则创建)

模块use的执行过程

load     开始加载目标主模块

get      创建主模块

fetch     发送request请求加载模块,加载成功后执行define方法会抽取依赖deps

save      加载成功后会执行define方法,解析依赖的deps。且保持Module.save

load     每次模块加载成功后onRequest回调主模块load判断是否remain==0,调用主模块onload(新版改用Module.prototype.pass)。每次子模板加载成功后,都要通知依赖自己的主模板。更新状态以便判断是否进入onload

onload    全部加载完触发onload,回调callback(seajs.use(id,callback))。只有主模板会有onload执行

exec     执行模块factory(即define(fn)的fn),且将require关键字映射exec上,依次执行所有依赖模块

/*

    创建seajs对象,标识版本号。创建data数据对象,data挂在seajs为外部访问

*/

var seajs = global.seajs = {

  // The current version of Sea.js being used

  version: "@VERSION"

}



var data = seajs.data = {}



/*

    seajs自己的事件触发,on绑定事件,off移除,emit触发

*/

/**

 * util-events.js - The minimal events support

 */



var events = data.events = {}



// Bind event

seajs.on = function(name, callback) {

  var list = events[name] || (events[name] = [])

  list.push(callback)

  return seajs

}



// Remove event. If `callback` is undefined, remove all callbacks for the

// event. If `event` and `callback` are both undefined, remove all callbacks

// for all events

seajs.off = function(name, callback) {

  // Remove *all* events

  if (!(name || callback)) {

    events = data.events = {}

    return seajs

  }



  var list = events[name]

  if (list) {

    if (callback) {

      for (var i = list.length - 1; i >= 0; i--) {

        if (list[i] === callback) {

          list.splice(i, 1)

        }

      }

    }

    else {

      delete events[name]

    }

  }



  return seajs

}



// Emit event, firing all bound callbacks. Callbacks are passed the same

// arguments as `emit` is, apart from the event name

var emit = seajs.emit = function(name, data) {

  var list = events[name], fn



  if (list) {

    // Copy callback lists to prevent modification

    list = list.slice()



    // Execute event callbacks

    while ((fn = list.shift())) {

      fn(data)

    }

  }



  return seajs

}



/*

    类型判断和计数函数

*/

/**

 * util-lang.js - The minimal language enhancement

 */



function isType(type) {

  return function(obj) {

    return Object.prototype.toString.call(obj) === "[object " + type + "]"

  }

}



var isObject = isType("Object")

var isString = isType("String")

var isArray = Array.isArray || isType("Array")

var isFunction = isType("Function")



var _cid = 0

function cid() {

  return _cid++

}



/*

    解析路径的处理方法,包括:

    dirname        获取路径的目录

    realpath    过滤目录中的./ ../

    normalize    补全文件名.js后缀

    parseAlias    解析配置别名

    parsePaths    解析配置中的path

    parseVars    解析配置中{}

    parseMap    加载路径根据配置匹配规则映射

    addBase        通过id,路由规则生成完整绝对路径

    

    id2Uri        根据id获取uri,完整的绝对路径

    

    loaderDir    seajs文件路径的获取:根据id=seajsnode查找seajs的dom,获取src后匹配目录

*/

/**

 * util-path.js - The utilities for operating path such as id, uri

 */



var DIRNAME_RE = /[^?#]*\//



var DOT_RE = /\/\.\//g

var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\//



// Extract the directory portion of a path

// dirname("a/b/c.js?t=123#xx/zz") ==> "a/b/"

// ref: http://jsperf.com/regex-vs-split/2

function dirname(path) {

  return path.match(DIRNAME_RE)[0]

}



// Canonicalize a path

// realpath("http://test.com/a//./b/../c") ==> "http://test.com/a/c"

function realpath(path) {

  // /a/b/./c/./d ==> /a/b/c/d

  path = path.replace(DOT_RE, "/")



  // a/b/c/../../d  ==>  a/b/../d  ==>  a/d

  while (path.match(DOUBLE_DOT_RE)) {

    path = path.replace(DOUBLE_DOT_RE, "/")

  }



  return path

}



// Normalize an id

// normalize("path/to/a") ==> "path/to/a.js"

// NOTICE: substring is faster than negative slice and RegExp

function normalize(path) {

  var last = path.length - 1



  // If the uri ends with `#`, just return it without '#'

  if (path.charAt(last) === "#") {

    return path.substring(0, last)

  }



  return  (path.substring(last - 2) === ".js" ||

      path.indexOf("?") > 0 ||

      path.substring(last - 3) === ".css") ? path : path + ".js"

}





var PATHS_RE = /^([^/:]+)(\/.+)$/

var VARS_RE = /{([^{]+)}/g



function parseAlias(id) {

  var alias = data.alias

  return alias && isString(alias[id]) ? alias[id] : id

}



function parsePaths(id) {

  var paths = data.paths

  var m



  if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) {

    id = paths[m[1]] + m[2]

  }



  return id

}



function parseVars(id) {

  var vars = data.vars



  if (vars && id.indexOf("{") > -1) {

    id = id.replace(VARS_RE, function(m, key) {

      return isString(vars[key]) ? vars[key] : m

    })

  }



  return id

}



function parseMap(uri) {

  var map = data.map

  var ret = uri



  if (map) {

    for (var i = 0, len = map.length; i < len; i++) {

      var rule = map[i]



      ret = isFunction(rule) ?

          (rule(uri) || uri) :

          uri.replace(rule[0], rule[1])



      // Only apply the first matched rule

      if (ret !== uri) break

    }

  }



  return ret

}





var ABSOLUTE_RE = /^\/\/.|:\//

var ROOT_DIR_RE = /^.*?\/\/.*?\//



function addBase(id, refUri) {

  var ret

  var first = id.charAt(0)



  // Absolute

  if (ABSOLUTE_RE.test(id)) {

    ret = id

  }

  // Relative

  else if (first === ".") {

    ret = realpath((refUri ? dirname(refUri) : data.cwd) + id)

  }

  // Root

  else if (first === "/") {

    var m = data.cwd.match(ROOT_DIR_RE)

    ret = m ? m[0] + id.substring(1) : id

  }

  // Top-level

  else {

    ret = data.base + id

  }



  return ret

}



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

}





var doc = document

var loc = location

var cwd = dirname(loc.href)

var scripts = doc.getElementsByTagName("script")



// Recommend to add `seajsnode` id for the `sea.js` script element

var loaderScript = doc.getElementById("seajsnode") ||

    scripts[scripts.length - 1]



// When `sea.js` is inline, set loaderDir to current working directory

var loaderDir = dirname(getScriptAbsoluteSrc(loaderScript) || cwd)



function getScriptAbsoluteSrc(node) {

  return node.hasAttribute ? // non-IE6/7

      node.src :

    // see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx

      node.getAttribute("src", 4)

}



/*

    request     动态加载css或者script到head

                创建dom原生link、script标签

    addOnload    定义浏览器兼容的异步请求回调

    getCurrentScript    获取当前资源加载状态为进行中interactive的资源

*/

/**

 * util-request.js - The utilities for requesting script and style files

 * ref: tests/research/load-js-css/test.html

 */



var head = doc.getElementsByTagName("head")[0] || doc.documentElement

var baseElement = head.getElementsByTagName("base")[0]



var IS_CSS_RE = /\.css(?:\?|$)/i

var READY_STATE_RE = /^(?:loaded|complete|undefined)$/



var currentlyAddingScript

var interactiveScript



// `onload` event is supported in WebKit < 535.23 and Firefox < 9.0

// ref:

//  - https://bugs.webkit.org/show_activity.cgi?id=38995

//  - https://bugzilla.mozilla.org/show_bug.cgi?id=185236

//  - https://developer.mozilla.org/en/HTML/Element/link#Stylesheet_load_events

var isOldWebKit = (navigator.userAgent

    .replace(/.*AppleWebKit\/(\d+)\..*/, "$1")) * 1 < 536





function request(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)



  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) {

  var missingOnload = isCSS && (isOldWebKit || !("onload" in node))



  // for Old WebKit and Old Firefox

  if (missingOnload) {

    setTimeout(function() {

      pollCss(node, callback)

    }, 1) // Begin after node insertion

    return

  }



  node.onload = node.onerror = node.onreadystatechange = function() {

    if (READY_STATE_RE.test(node.readyState)) {



      // 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()

    }

  }

}



function pollCss(node, callback) {

  var sheet = node.sheet

  var isLoaded



  // for WebKit < 536

  if (isOldWebKit) {

    if (sheet) {

      isLoaded = true

    }

  }

  // for Firefox < 9.0

  else if (sheet) {

    try {

      if (sheet.cssRules) {

        isLoaded = true

      }

    } catch (ex) {

      // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"

      // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0

      // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"

      if (ex.name === "NS_ERROR_DOM_SECURITY_ERR") {

        isLoaded = true

      }

    }

  }



  setTimeout(function() {

    if (isLoaded) {

      // Place callback here to give time for style rendering

      callback()

    }

    else {

      pollCss(node, callback)

    }

  }, 20)

}



function getCurrentScript() {

  if (currentlyAddingScript) {

    return currentlyAddingScript

  }



  // For IE6-9 browsers, the script onload event may not fire right

  // after the the script is evaluated. Kris Zyp found that it

  // could query the script nodes and the one that is in "interactive"

  // mode indicates the current script

  // ref: http://goo.gl/JHfFW

  if (interactiveScript && interactiveScript.readyState === "interactive") {

    return interactiveScript

  }



  var scripts = head.getElementsByTagName("script")



  for (var i = scripts.length - 1; i >= 0; i--) {

    var script = scripts[i]

    if (script.readyState === "interactive") {

      interactiveScript = script

      return interactiveScript

    }

  }

}



/**

 * module.js - The core of module loader

 */

//系统全局对象,用来保存全局数据和状态

var cachedMods = seajs.cache = {}

var anonymousMeta



var fetchingList = {}

var fetchedList = {}

var callbackList = {}



var STATUS = Module.STATUS = {

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

}



//模块类   id、deps、exports 构造函数

function Module(uri, deps) {

  this.uri = uri

  this.dependencies = deps || []

  this.exports = null

  this.status = 0//模块状态

  // The number of unloaded dependencies

  this._remain = 0

  // Who depend on me

  this._waitings = {}

}



//Module静态方法,Module.use使用模块,Module.preload调用data.preload后回调Module.use

// 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()

  }

}



//入口模块,ids--依赖模块,uri模块id:获取uri模块

// 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()

}



//获取uri模块,若cachedMods中无则创建个新模块

Module.get = function(uri, deps) {

  return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))

}



// seajs.use 入口方法

// 执行过程:A依赖B,B依赖C,seajs.use(A):

/*

fetch            获取A模块的依赖模块(B),B拉取成功后回调A.load(每次依赖的文件onRquest,要跟新自己的状态)

load            遍历A模块依赖的所有模块状态(B/C),直到remain==0,触发onload

onload            当A模块所有依赖都已加载,触发A.callback

exec            seajs.use 将执行模块的callback设置为exec,调用A.exec,且A模块中的require映射了exec方法,链式的执行下去。。

*/

seajs.use = function(ids, callback) {

  Module.preload(function() {

    Module.use(ids, callback, data.cwd + "_use_" + cid())

  })

  return seajs

}



//将依赖的模块通过fetch方法注册,最终执行load请求js,onRequest回调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()//获取依赖uris

  emit("load", uris)



  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])//获取依赖module

    if (m.status < STATUS.LOADED) {//模块未加载

      // Maybe duplicate

      m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1//记录下有几个模块依赖自己

    }

    else {

      mod._remain--//加载后--

    }

  }



   //若所有依赖都加载后,执行onload

  if (mod._remain === 0) {

    mod.onload()

    return

  }



  // Begin parallel loading

  var requestCache = {}



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

    }

  }

}



//将模块的所有依赖的uri都注册到此模块的requestCache列表中

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

  }



  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() {

    request(emitData.requestUri, emitData.onRequest, emitData.charset)

  }



  function onRequest() {

    delete fetchingList[requestUri]

    fetchedList[requestUri] = true



    // Save meta data of anonymous module

    if (anonymousMeta) {

      save(uri, anonymousMeta)

      anonymousMeta = null

    }



    // Call callbacks

    var m, mods = callbackList[requestUri]

    delete callbackList[requestUri]

    while ((m = mods.shift())) m.load()

  }

}



//若模块的依赖加载全部加载触发onload回调mod.callback,同时通知所有依赖他的模块

// Call this method when module is loaded

Module.prototype.onload = function() {

  var mod = this

  mod.status = STATUS.LOADED



  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) {

        m.onload()

      }

    }

  }



  // Reduce memory taken

  delete mod._waitings

  delete mod._remain

}



//解析模块依赖的uris 获取绝对uris

// 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] = resolve(ids[i], mod.uri)

  }

  return uris

}



//触发resolve事件,返回id2Uri

// Helpers

function resolve(id, refUri) {

  // Emit `resolve` event for plugins such as text plugin

  var emitData = { id: id, refUri: refUri }

  emit("resolve", emitData)

  return emitData.uri || id2Uri(emitData.id, refUri)

}



//加载完所有依赖的模块后,执行自身模块,同时通过关键字require将依赖的模块顺序执行

// Execute a module

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



  //执行require的id

  function require(id) {

    return cachedMods[require.resolve(id)].exec()

  }

    

  //获得id模块的绝对路径  

  require.resolve = function(id) {

    return resolve(id, uri)

  }



  //异步请求uri

  require.async = function(ids, callback) {

    Module.use(ids, callback, uri + "_async_" + cid())

    return require

  }



  // Exec factory

  var factory = mod.factory



  var exports = isFunction(factory) ?

      factory(require, mod.exports = {}, mod) :

      factory



  if (exports === undefined) {

    exports = mod.exports

  }



  // Emit `error` event

  if (exports === null && !IS_CSS_RE.test(uri)) {

    emit("error", mod)

  }



  // Reduce memory leak

  delete mod.factory



  mod.exports = exports

  mod.status = STATUS.EXECUTED



  // Emit `exec` event

  emit("exec", mod)



  return exports

}



//对外的接口 define(function(){...})用于定义模块,初始化模块的基础属性(并不执行模块factory方法)

Module.define.cmd = {}

global.define = 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 = undefined

    }

    // 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: 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)



  meta.uri ? save(meta.uri, meta) :

      // Save information for "saving" work in the script onload event

      anonymousMeta = meta

}



function save(uri, meta) {

  var mod = Module.get(uri)



  // Do NOT override already saved modules

  if (mod.status < STATUS.SAVED) {

    mod.id = meta.id || uri

    mod.dependencies = meta.deps || []

    mod.factory = meta.factory

    mod.status = STATUS.SAVED

  }

}



// For Developers 调试接口

seajs.Module = Module

data.fetchedList = fetchedList

data.cid = cid

//获取模块id

seajs.resolve = id2Uri

//获取模块exports接口

seajs.require = function(id) {

  return (cachedMods[resolve(id)] || {}).exports

}

 

 

 附commonjs、seajs、requirejs比较

#commonjs 与seajs
* commonjs :node的编写规范,为运行在服务器端的非浏览器环境的模块系统定义。模块间的依赖通过require同步加载,导致浏览器端无法采用这种设计
* seajs:将commonjs的规范扩展后,可以在浏览器环境下运行的模式系统设计规范。通过define(function(){});
 
#requirejs 与seajs
requirejs是amd规范,seajs是cmd规范
RequireJS 和 SeaJS 区别: http://www.zhihu.com/question/20342350/answer/14828786  ,执行define的延迟  http://www.douban.com/note/283566440/
seajs源码分析
 
 
补充Sea.js 3.0.0 module.js注释
function Module(uri, deps) {

  this.uri = uri

  this.dependencies = deps || []

  this.status = 0



  this._entry = []

}



//获取模板的全路径

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

}



//定义一个模块mod

// 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 = undefined

    }

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



  meta.uri ? Module.save(meta.uri, meta) :

    // Save information for "saving" work in the script onload event

    anonymousMeta = meta

}



//生成mod,赋值一些基本属性

// Save meta data to cachedMods

Module.save = function(uri, meta) {

  var mod = Module.get(uri)



  // Do NOT override already saved modules

  if (mod.status < STATUS.SAVED) {

    mod.id = meta.id || uri

    mod.dependencies = meta.deps || []

    mod.factory = meta.factory

    mod.status = STATUS.SAVED



    emit("save", mod)

  }

}



//创建mod,缓存到cachedMods

// Get an existed module or create a new one

Module.get = function(uri, deps) {

  return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))

}



//load执行一个匿名module

// 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._entry.push(mod)

  mod.history = {}

  mod.remain = 1



  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

    delete mod.history

    delete mod.remain

    delete mod._entry

  }



  mod.load()

}

/*************************************************************

    对外只暴露

    Module.use(seajs.use)

    Module.define(global.define)

 ************************************************************/



//获得mod的所有dep的uri

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

}



//递归遍历自身依赖的子模板,判断全部加载完毕后执行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)



  //将依赖uri遍历,判断是否全部加载

  // Pass entry to it's dependencies

  mod.pass(uris)



  //如果全部加载,执行onload

  // If module has entries not be passed, call onload

  if (mod._entry.length) {

    mod.onload()

    return

  }



  // Begin parallel loading

  var requestCache = {}

  var m

  //对依赖的模板执行拉取,拉取后执行load递归子依赖

  for (var i = 0, len = uris.length; i < len; i++) {

    m = cachedMods[uris[i]]



    if (m.status < STATUS.FETCHING) {

      m.fetch(requestCache)

    }

    //若模板已经拉取过,跳过拉取执行load

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

    }

  }

}



//将依赖uri遍历,判断是否全部加载

Module.prototype.pass = function(uris) {

  var mod = this



  uris = uris || mod.resolve()

  var len = uris.length



  //use时mod._entry.push(mod)

  for (var i = 0; i < mod._entry.length; i++) {

    var entry = mod._entry[i]

    var count = 0

    for (var j = 0; j < len; j++) {

      var m = Module.get(uris[j])

      // If the module is unload and unused in the entry, pass entry to it

      if (m.status < STATUS.LOADED && !entry.history.hasOwnProperty(m.uri)) {

        entry.history[m.uri] = true

        count++

        m._entry.push(entry)

        if(m.status === STATUS.LOADING) {

          m.pass()

        }

      }

    }

    // If has passed the entry to it's dependencies, modify the entry's count and del it in the module

    if (count > 0) {

      entry.remain += count - 1

      mod._entry.shift()

      i--

    }

  }

}



//拉取模板,成功拉取后save并通知依赖自己的模板更新load

// 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.hasOwnProperty(requestUri)) {

    mod.load()

    return

  }



  if (fetchingList.hasOwnProperty(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)

  }



  function onRequest(error) {

    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())) {

      // When 404 occurs, the params error will be true

      if(error === true) {

        m.error()

      }

      else {

        m.load()

      }

    }

  }

}



//全部加载完触发onload,回调callback(seajs.use(id,callback))

// Call this method when module is loaded

Module.prototype.onload = function() {

  var mod = this

  mod.status = STATUS.LOADED



  for (var i = 0, len = mod._entry.length; i < len; i++) {

    var entry = mod._entry[i]

    if (--entry.remain === 0) {

      entry.callback()

    }

  }



  delete mod._entry

}



//seajs.use->mod.onload->mod.callback()->mod.exec();

// Execute a module

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



  if (mod._entry && !mod._entry.length) {

    delete mod._entry

  }



  //non-cmd module has no property factory and exports

  if (!mod.hasOwnProperty('factory')) {

    mod.non = true

    return

  }



  // Create require

  var uri = mod.uri



  function require(id) {

    var m = Module.get(require.resolve(id))

    if (m.status == STATUS.ERROR) {

      throw new Error('module was broken: ' + m.uri);

    }

    return m.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



  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

}

 

 
 

 

你可能感兴趣的:(seajs)