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),同时结合回调函数来串联自定义逻辑代码。
就个人而言,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),同时结合回调函数来串联自定义逻辑代码。
相关文章推荐
- 网络中树型菜单实现方法及其效率研究
- 网络中树型菜单实现方法及其效率研究 (代码3)
- 逻辑综合中对关键路径处理方法的研究
- PHP实现异步调用方法研究[转]
- 转载: 自定义浏览DWG控件的实现方法, 有时间研究一下
- GC关键方法解析
- 基于数据挖掘的课程相关 性方法研究与实现
- 案例研究:用于实现绿色解决方案的智能 SOA 方法
- 达芬奇处理器的H264编码关键模块的实现研究----笔记
- 网络中树型菜单实现方法及其效率研究
- 基于44B0平台的uC-Linux Web服务器实现方法研究
- ASP中实现文件上传方法的研究
- 详细解析用Squid实现反向代理的方法[转]
- ValueTpye boxing及虚方法重写及CallVirt指令实现解析
- 网络中树型菜单实现方法及其效率研究
- Dom4j递归解析XML实现JS的getElementsByName类似方法
- 实现GPRS数据传输新方法的研究
- 18.3.5 SHTTPD方法解析的实现
- 源码解读:java 解析字符串为boolean四种实现方法的细节
- 详细解析用Squid实现反向代理的方法