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

本文深入探讨SeaJs的实现,重点关注其关键方法,如Module、cache、config、emit、on、off、request、require、resolve和use。通过分析`seajs.use`、`Module.prototype.load`和`Module.prototype.exec`等方法,揭示SeaJs如何通过闭包、对象和事件处理来组织和执行前端代码。SeaJs的核心是Module,每个JS文件对应一个Module,通过加载和执行JS文件,结合回调函数执行自定义逻辑。
摘要由CSDN通过智能技术生成

如果你作为读者,对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),同时结合回调函数来串联自定义逻辑代码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值