seajs 3.0.0 源码阅读笔记
2016-07-10 10:28
471 查看
seajs 的源代码可以在 github上获取。seajs 在文档“如何参与开发”中说明了阅读顺序,当然为了便于阅读,在了解了目录结构之后,我直接阅读了合并好的 sea-debug.js。
整个seajs采用的是2空格缩进,避免分号的写法,我不是很习惯,但不影响阅读。
文档中各个源文件所包含的内容大致如下:
文件头
定义全局
定义用于判断对象类型的
定义 seajs 的事件处理相关函数,包括
定义用于路径处理的工具函数
定义请求文件的工具函数
定义用于分析依赖关系的
seajs 的核心,模块类。
也包含部分 seajs 的方法
定义
所以合并之后的整个 seajs 代码看起来就像这样
(function(global, undefiend) {
global.seajs = {
data: {}
}
var isObject = function() {}
var isString = function() {}
var isArray = function() {}
var isFunction = function() {}
var cid = function() {}
data.events = {}
seajs.on = function() {}
seajs.off = function() {}
seajs.emit = function() {}
// path utils, and
seajs.resolve = function() {}
var loaderDir
var parseDependencies = function() {}
function Module() {}
seajs.config = function() {}
})(this);
首先是定义了一个产生 isXxxx 的函数工厂
function isType(type) {
return function(obj) {
return {}.toString.call(obj) == “[object " + type + "]”
}
}
从这个工厂的代码可以看出来,
当然也有例外:
// 毕竟 Array.isArray() 是 [native code],效果会
4000
高得多
var isArray = Array.isArray || isType(“Array”)
这里有几件事情我不是很明白:
就是为什么不使用
jQuery 的
jQuery.each(“Boolean Number String Function Array Date RegExp Object Error”.split(” “), function(i, name) {
class2type[ “[object " + name + "]” ] = name.toLowerCase();
});
这种作法也会比每次都拼字符串(
不过 seajs 和 jquery 都没有直接依赖
seajs 只为全局对象
key,
因为 seajs 的事件主要为了插件而定义,所以对参数并没有严格的校验。比如
// 自定义一个比较奇葩的事件,这不会报错
seajs.on(null, “fake callback”);
// 但是执行就会出错了
seajs.emit(“null”)
// 输出:TypeError: string is not a function
看样子,插件开发者得自己注意下这个问题了。
看完 seajs 的源码,大概定义了这么一些事件
URI。
// Emit `load` event for plugins such as combo plugin
var uris = mod.resolve()
emit(“load”, uris)
// Emit `fetch` event for plugins such as combo plugin
var emitData = { uri: uri }
emit(“fetch”, emitData)
var requestUri = emitData.requestUri || uri
// Emit `request` event for plugins such as text plugin
emit(“request”, emitData = {
uri: uri,
requestUri: requestUri,
onRequest: onRequest,
charset: isFunction(data.charset)
? data.charset(requestUri) || ‘utf-8′
: data.charset
})
if (!emitData.requested) {
// …
}
// 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)
config 对象的处理之后触发,参数就是 config 对象。
seajs.config = function(configData) {
// …
emit(“config”, configData)
return seajs
}
var cachedMods = seajs.cache = {}
每个模块都有 8 种状态,它一定是在这 8 种状态之一,而且貌似状态改变还是不可逆的。
var STATUS = Module.STATUS = {
// 没定义状态值为 0 的状态常量,这是初始状态
// 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,
// 7 – 404
ERROR: 7
}
其中
3 个方法:
从代码内容来看,这三个主要过程方法主要功能分别可以用一句话说明:
factory 函数赋值给对应的模块对象(通过
LOADED 状态之后,调用入口模块(
状态了,所以
只执行一次,然后将
现在来看看入口函数
// seajs.use 只调用了 Module.use,所以它们的调用关系可以看作等同
seajs.use = Module.use = function() {
Module.get()
exec() // 通过 _entry.callback 调用
load()
}
define = Module.define = function() {
Module.save(id, factory)
}
Module.prototype.fetch = function() {
define() // 通过 seajs.request() 调用
load()
}
Module.prototype.load = function() {
pass()
fetch()
// [递归]
// 结束的条件是 _entry.remain === 1,
// 即当前是最后一个依赖模块
// 递归结束时调用 _entry.callback,即调用了 exec
load()
}
Module.prototype.exec = function() {
exec() // 通过 define 中定义的 factory 函数调用
}
Module.prototype.pass = function() {
// [递归]
// 结束条件是把 _entry 传递到最末一层依赖
// 递归过程通过 _entry.remain 进行了引用记数
pass()
}
各函数和方法具体处理过程看代码就明白了,因为递归关系有点复杂,还有一些回调关系在里面,所以看起来有点绕,不过还算是看得明白。
http://seajs.org 引用的 seajs 版本是 2.2.1,从这引页面的控制输出
_entry 的处理可能会有点不一样,稍后看了 seajs 2.2.3 版本的代码就知道了。
整个seajs采用的是2空格缩进,避免分号的写法,我不是很习惯,但不影响阅读。
文件/目录结构
文档中各个源文件所包含的内容大致如下:intro.js
文件头
sea.js
定义全局
global.seajs对象和
seajs.data对象
util-lang.js,语言相关工具
定义用于判断对象类型的
isXxxxx()函数,以及一个与语言无关的
cid()。
util-events.js
定义 seajs 的事件处理相关函数,包括
on()、
off()、
emit()
util-path.js
定义用于路径处理的工具函数
util-request.js
定义请求文件的工具函数
seajs.request()等
util-deps.js
定义用于分析依赖关系的
parseDependencies()
module.js
seajs 的核心,模块类。
也包含部分 seajs 的方法
config.js
定义
seajs.config(),以及
data部分属性的默认值
所以合并之后的整个 seajs 代码看起来就像这样
(function(global, undefiend) {
global.seajs = {
data: {}
}
var isObject = function() {}
var isString = function() {}
var isArray = function() {}
var isFunction = function() {}
var cid = function() {}
data.events = {}
seajs.on = function() {}
seajs.off = function() {}
seajs.emit = function() {}
// path utils, and
seajs.resolve = function() {}
var loaderDir
var parseDependencies = function() {}
function Module() {}
seajs.config = function() {}
})(this);
isXxxx 用于判断对象类型
首先是定义了一个产生 isXxxx 的函数工厂 isType()
function isType(type) {
return function(obj) {
return {}.toString.call(obj) == “[object " + type + "]”
}
}
从这个工厂的代码可以看出来,
isXxxx()主要是通过
Object.prototype.toString的值来判断对象类型的。
当然也有例外:
// 毕竟 Array.isArray() 是 [native code],效果会
4000
高得多
var isArray = Array.isArray || isType(“Array”)
这里有几件事情我不是很明白:
就是为什么不使用
typeof运算符来判断类型,一般语言中运算符实现会比比较字符串快得多。
{}.toString.call(obj)和
Object.prototype.toString.call(obj)的作用是一样的,但是在运行时,每执行一次
isXxxx就会产生一个新的
{}对象;而
Object.prototype始终都是同一个对象,似乎可以减少不少开销
jQuery 的
isFunction()等方法都是通过
jQuery.type()来实现的,而
jQuery.type中则是通过定义了一个
class2type字典对象来做类型映射,
jQuery.each(“Boolean Number String Function Array Date RegExp Object Error”.split(” “), function(i, name) {
class2type[ “[object " + name + "]” ] = name.toLowerCase();
});
这种作法也会比每次都拼字符串(
"[object " + type + "]")来比较要效率高一些吧。
不过 seajs 和 jquery 都没有直接依赖
typeof运算符来实现
isXxxx,我相信绝对不是偶然,一定有啥原因,不过这个原因我目前就不清楚了,希望玉伯能看到这个博客,稍作解释
事件
seajs 只为全局对象 seajs添加了事件处理。事件的回调函数链保存在
seajs.data.events中,以事件名称为
key,
Array对象保存的回调函数链为 value。
因为 seajs 的事件主要为了插件而定义,所以对参数并没有严格的校验。比如
// 自定义一个比较奇葩的事件,这不会报错
seajs.on(null, “fake callback”);
// 但是执行就会出错了
seajs.emit(“null”)
// 输出:TypeError: string is not a function
看样子,插件开发者得自己注意下这个问题了。
看完 seajs 的源码,大概定义了这么一些事件
error,貌似跟 NodeJS 有点关系,没仔细看
load,在模块对象状态变成
LOADING后触发,参数是所有依赖模块的
URI。
// Emit `load` event for plugins such as combo plugin
var uris = mod.resolve()
emit(“load”, uris)
exec,在模块对象状态变成
EXECUTED后触发,参数就是模块对象本身
fetch,在模块对象状态刚变成
FETECHING时触发,参数是一个临时对象
emitData,事件结果保存在
emitData.requestUri,用于后面的
request请求数据。
// Emit `fetch` event for plugins such as combo plugin
var emitData = { uri: uri }
emit(“fetch”, emitData)
var requestUri = emitData.requestUri || uri
request,在
fetch事件后对
emitData.requestUri || uri进行了处理之后,通过
seajs.request()请求数据之前触发,参数是一个临时对象,变量名复用的
emitData。事件处理完成后根据
emitData.requested的值来判断是否需要调用
seajs.request请求数据。
// Emit `request` event for plugins such as text plugin
emit(“request”, emitData = {
uri: uri,
requestUri: requestUri,
onRequest: onRequest,
charset: isFunction(data.charset)
? data.charset(requestUri) || ‘utf-8′
: data.charset
})
if (!emitData.requested) {
// …
}
resolve,在
Module.resolve中调用
seajs.resolve()之前触发,参数是一个临时对象
emitData。事件中如果产生了有效的
emitData.uri,则不再调用
seajs.resolve()
// 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)
config,在
seajs.config()中,完成对
config 对象的处理之后触发,参数就是 config 对象。
seajs.config = function(configData) {
// …
emit(“config”, configData)
return seajs
}
Module 类
Module类才是 seajs 的重头戏,核心的核心。seajs 作为一个模块加载器,所以模块都是以一个
Module对象保存在
cachedMods中的。
var cachedMods = seajs.cache = {}
每个模块都有 8 种状态,它一定是在这 8 种状态之一,而且貌似状态改变还是不可逆的。
var STATUS = Module.STATUS = {
// 没定义状态值为 0 的状态常量,这是初始状态
// 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,
// 7 – 404
ERROR: 7
}
其中
SAVED状态可以理解为
FETCHED状态。除了初始状态
0和错误状态
ERROR: 7之外,其它 6 个状态都是成对出现的,即
ING状态和
ED状态,这三对状态清晰的划分出来三个处理过程:fetch、load、exec,对应于模块对象的
3 个方法:
fetch(),
load(),
exec()。
从代码内容来看,这三个主要过程方法主要功能分别可以用一句话说明:
fetch,从 URL 加载模块定义,得到 factory 函数,并将
factory 函数赋值给对应的模块对象(通过
Module.get()创建或获取);
load,fetch 并 load 所有依赖模块,并在保证所有依赖模块都是
LOADED 状态之后,调用入口模块(
_entry)的
callback(貌似只有通过
seajs.use()创建的匿名模块才有
callback)
exec,在调用这个方法的时候,可以保证所有依赖模块都已经是 LOADED
状态了,所以
exec就只是简单的执行 factory 函数,并返回
exports。factory
只执行一次,然后将
exports缓存下来。
现在来看看入口函数
seajs.use()、模块定义函数
define()、模块关系过程处理方法
fetch(),
load(),
exec()的主要调用关系:
// seajs.use 只调用了 Module.use,所以它们的调用关系可以看作等同
seajs.use = Module.use = function() {
Module.get()
exec() // 通过 _entry.callback 调用
load()
}
define = Module.define = function() {
Module.save(id, factory)
}
Module.prototype.fetch = function() {
define() // 通过 seajs.request() 调用
load()
}
Module.prototype.load = function() {
pass()
fetch()
// [递归]
// 结束的条件是 _entry.remain === 1,
// 即当前是最后一个依赖模块
// 递归结束时调用 _entry.callback,即调用了 exec
load()
}
Module.prototype.exec = function() {
exec() // 通过 define 中定义的 factory 函数调用
}
Module.prototype.pass = function() {
// [递归]
// 结束条件是把 _entry 传递到最末一层依赖
// 递归过程通过 _entry.remain 进行了引用记数
pass()
}
各函数和方法具体处理过程看代码就明白了,因为递归关系有点复杂,还有一些回调关系在里面,所以看起来有点绕,不过还算是看得明白。
http://seajs.org 引用的 seajs 版本是 2.2.1,从这引页面的控制输出
seajs.Modules.prototype来看,并没有定义
pass()方法,所以对
_entry 的处理可能会有点不一样,稍后看了 seajs 2.2.3 版本的代码就知道了。
相关文章推荐
- Seajs 简易文档 提供简单、极致的模块化开发体验
- 深入探寻seajs的模块化与加载方式
- javascript模块化简单解析
- seajs中模块的解析规则详解和模块使用总结
- SeaJS入门教程系列之使用SeaJS(二)
- LABjs、RequireJS、SeaJS的区别
- seajs加载jquery时提示$ is not a function该怎么解决
- JavaScript模块化开发之SeaJS
- 基于gulp合并压缩Seajs模块的方式说明
- SeaJS 与 RequireJS 的差异对比
- jQuery-digest | 咀嚼jQuery源码
- 用grunt构建seajs项目遇到的各种坑
- 使用 angular-async-loader 来实现异步加载 angular 模块
- Seajs 简易文档 提供简单、极致的模块化开发体验
- javascript模块化简单解析
- PM有那么多的书要看,怎么样才能高效阅读?
- icon
- 数字阅读令美国杂志销量大减
- 阅读的境界
- 中科院大牛博士是如何进行文献检索和阅读的(好习惯受益终生)