您的位置:首页 > Web前端 > JavaScript

SeaJs研究 之 关键方法实现解析

2014-07-02 17:49 253 查看
如果你作为读者,对Seajs还不是很了解,建议先去网站储备一些基础知识,然后我们可以有更多有效的沟通与互补。
就个人而言,SeaJs作为JS模块化框架,我觉得它提供给项目(产品)前端代码,更多的是规范管理与引用(CMD规范),从而避免代码的交叉混乱重复的应用。如果你想把它作为提高JS代码性能的重要手段,那我还是规劝各位攻城狮多从自己代码本身思考和优化,废话不多说...

SeaJs中整个设计是结合闭包、对象、函数回调以及事件来设计的,通过闭包来暴露关键方法提供调用,我们可以顺利访问到以下的方法和属性

Module:构建包装JS File的对象,包括对JS的规范定义、请求解析、文件下载、对象加载与执行等工作;
cache:作为JS File与Module对象缓存;
config:详细参考官网https://github.com/seajs/seajs/issues/262
emit:触发事件函数
on:绑定事件
off:解除事件
request:下载JS文件方法
require:加载JS文件的 Module
resolve:解析模块标识符为全路径
use:加载并使用JS文件

下面我针对几个关键方法实现过程分析,我们来进一步认识下seajs的实现。
seajs.use



seajs.use实质上是调用的module.use,然后通过Module.get判断是否需要为当前目录新建一个新的module,module初始化完后,则对module进行加载,在加载完毕之后会调用回调函数callback来执行自定义的方法逻辑。
seajs.use = function(ids, callback) {
//调用module.use方法,data.cwd用来获取seajs的当前路径

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

  return seajs

}

接着看module.use部分的实现,这个方法负责加载完毕当前路径中所有JS文件,并封装成逐一封装成Module对象,导出每个对象中的回调方法和属性,作为自定义函数的入参,再回调自定义函数。
Module.use = function (ids, callback, uri) {
//判断当前使用的js文件是否被缓存过,如果没有的话,会重新创建一个新module对象

  var mod = Module.get(uri, isArray(ids) ? ids : [ids])

//定义回调函数(在模块加载完毕之后,才会调用)

  mod.callback = function() {

    var exports = []
//解析js file为全路径

    var uris = mod.resolve()

//循环每个module,获取模块中可访问的方法和属性

    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

  }

//加载js文件

  mod.load()

}

Module.prototype.load部分的实现,该方法负责加载JS文件。如果当前目录中有多个JS File未加载完成,需要在全部JS File加载完毕之后才会继续加载Module。
Module.prototype.load = function() {

  var mod = this

  // 如果当前模块正在加载,那就暂不执行,等待onload事件触发

  if (mod.status >= STATUS.LOADING) {

    return

  }

//设置当前模块的状态为正在加载

  mod.status = STATUS.LOADING

//将ID转化对应JS文件的全路径

  var uris = mod.resolve()
//执行load事件函数

  emit("load", uris)

//当前需要加载的js文件数

  var len = mod._remain = uris.length

  var m

  // 初始化需要加载的模块

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

    m = Module.get(uris[i])
//如果当前目录中子Module未加载完,那就把他加入当前目录Module的等待队列._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

    }
//否则待加载数量减1

    else {

      mod._remain--

    }

  }
//如果当前目录中所有待加载JS File均已加载完毕,那么就加载当前目录Module

  if (mod._remain === 0) {

    mod.onload()

    return

  }

  var requestCache = {}

  for (i = 0; i < len; i++) {

    m = cachedMods[uris[i]]

//如果子module还未加入下载队列,那先将module加入下载队列,初始化下载信息

    if (m.status < STATUS.FETCHING) {

      m.fetch(requestCache)

    }
//如果module的下载必需信息均已保存,那就开始加载module

    else if (m.status === STATUS.SAVED) {

      m.load()

    }

  }

//发送所有下载队列中的module的请求,下载js文件

  for (var requestUri in requestCache) {

    if (requestCache.hasOwnProperty(requestUri)) {

      requestCache[requestUri]()

    }

  }

}

再进一步看看Module.onload方法,主要负责执行回调函数,并且再次检查待下载队列中的Module数量,如果数量为0,那会再次执行onload方法。主要就是做一下善后工作,收拾下残局,
Module.prototype.onload = function() {

  var mod = this
//将当前module的状态置为已加载

  mod.status = STATUS.LOADED

//这里就开始执行回调函数了(自定义的,往上看看callback里面干了什么事儿)

  if (mod.callback) {

    mod.callback()

  }

  var waitings = mod._waitings

  var uri, m

//循环待下载的module的集合

  for (uri in waitings) {
    if (waitings.hasOwnProperty(uri)) {
//从缓存中拿module对象

      m = cachedMods[uri]
//待下载的文件个数减少
      m._remain -= waitings[uri]

      if (m._remain === 0) {

        m.onload()

      }

    }

  }

  // 删除引用,节约内存空间

  delete mod._waitings

  delete mod._remain

}

不过,在以上的方法中,对Module原型中的方法fetch 和 exec没有详细说明,这里是对一个流程上所见的重要方法进行详解,在下面两个方法中会具体来说

seajs.define





Module.define = function (id, deps, factory) {
//获取参数

  var argsLen = arguments.length

//如果参数1个,那么id是function,但模块就没有标示符,成了一个匿名模块
//define(factory)

  if (argsLen === 1) {

    factory = id

    id = undefined

  }
//如果参数2个,deps就是function,id是模块标识符
  else if (argsLen === 2) {

    factory = deps

    // define(deps, factory)

    if (isArray(id)) {

      deps = id

      id = undefined

    }

    // define(id, factory)

    else {

      deps = undefined

    }

  }

//将function转化为字符串,然后匹配到reqiure方法中请求JS文件的路径

  if (!isArray(deps) && isFunction(factory)) {

    deps = parseDependencies(factory.toString())

  }

//设置元数据

  var meta = {

    id: id,
//将JS File相对路径转化为全路径

    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", meta)

//保存module对象信息,或者保存至匿名对象anonymousMeta 

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

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

      anonymousMeta = meta

}

//保存元数据到Module对象中

Module.save = function(uri, meta) {

  var mod = Module.get(uri)

//如果模块的状态还是未被保存,那就保存模块信息(标识符、依赖、函数以及状态)

  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)

  }

}

seajs.require



seajs.require = function(id) {
//根据Id解析并获取JS的Module对象

  var mod = Module.get(Module.resolve(id))
//如果module还未被执行,就先执行

  if (mod.status < STATUS.EXECUTING) {

    mod.onload()

    mod.exec()

  }

  return mod.exports

}
这里我们重点去看看mod.exec()方法,这里可谓是是整个seajs的万里长城的最后一步
Module.prototype.exec = function () {

  var mod = this

//不解释,大家都懂

  if (mod.status >= STATUS.EXECUTING) {

    return mod.exports

  }

  mod.status = STATUS.EXECUTING

  // Create require

  var uri = mod.uri

//定义define中回调函数的参数require,exports

  function require(id) {

    return Module.get(require.resolve(id)).exec()

  }

//解析id的方法

  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

//这里是一个亮点,这里就会去执行define中的回调函数,并把集合exports作为执行结果返回出来

  var exports = isFunction(factory) ?

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

      factory

  if (exports === undefined) {

    exports = mod.exports

  }

  // 回收限制对象,做好善后工作

  delete mod.factory

  mod.exports = exports

  mod.status = STATUS.EXECUTED

  emit("exec", mod)

  return exports

}

总的来说,seajs的核心是Module,一个JS File会对应一个Module,每个Module均有自己状态、属性和方法,Module中的核心是加载执行JS代码的方法(load&exec),同时结合回调函数来串联自定义逻辑代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: