本文同步自我的GitHub博客
前言
Base
这个模块实际上才是Arale模块系统中对外的模块,它包含了之前介绍的Class类和Events类,以及自己内部的attribute
模块和aspect
模块,因此Base
模块是真正的基础类。
由于Attribute
模块的内容太多,而Aspect
模块和它关系也不太大,因此,考虑到文章篇幅的平衡,将Base
模块的解析分成两篇,Attribute
模块的分析放在下一篇单独来写。
带注释源码
Base
源码的开头是这样的:
var Class = require('arale-class');
var Events = require('arale-events');
var Aspect = require('./aspect');
var Attribute = require('./attribute');
可见,整个Base
的实现是基于上面这四个模块的,前两个模块已经分析过了,下面来分析后面两个模块。首先是Aspect
模块,这个模块实际上只提供了两个方法before
和after
:
// `before`和`after`实际上是对`weave`方法的一次封装,提供易用的接口
// 在指定方法执行前,先执行 callback
exports.before = function(methodName, callback, context) {
return weave.call(this, 'before', methodName, callback, context);
};
// 在指定方法执行后,再执行 callback
exports.after = function(methodName, callback, context) {
return weave.call(this, 'after', methodName, callback, context);
};
// Helpers
// -------
// 事件分割
var eventSplitter = /\s+/;
/**
* 控制callback的执行时序
* @param {String} when 选择是`before`还是`after`
* @param {String} methodName 方法名字符串
* @param {Function} callback 回调函数
* @param {Object} context 上下文对象
* @return {Object} 调用此方法的对象
*/
function weave(when, methodName, callback, context) {
// 取得方法名数组
var names = methodName.split(eventSplitter);
var name, method;
// 遍历方法名数组
while (name = names.shift()) {
// 取得方法函数
method = getMethod(this, name);
// 方法是否被改造过,如果没有则进行改造
if (!method.__isAspected) {
wrap.call(this, name);
}
// 绑定一下事件
this.on(when + ':' + name, callback, context);
}
return this;
}
/**
* 取得对应名称的方法
* @param {Object} host 调用对象
* @param {String} methodName 方法名称
* @return {Function} 方法函数
*/
function getMethod(host, methodName) {
// 取得对象上对应的方法函数
var method = host[methodName];
// 如果方法不存在则报错
if (!method) {
throw new Error('Invalid method name: ' + methodName);
}
return method;
}
/**
* [wrap description]
* @param {[type]} methodName [description]
* @return {[type]} [description]
*/
function wrap(methodName) {
// 取得对象上的方法
var old = this[methodName];
// 对方法进行改造封装
// 改造过的方法执行时,会先触发'before:methodName'事件
this[methodName] = function() {
// 切分参数
var args = Array.prototype.slice.call(arguments);
// 在参数数组前添加一项'before:methodName'
var beforeArgs = ['before:' + methodName].concat(args);
// prevent if trigger return false
// 先触发`before:methodName`事件,如果存在回调函数队列且执行后返回false,则阻止进一步往下执行
if (this.trigger.apply(this, beforeArgs) === false) return;
// 执行原方法,保存返回值
var ret = old.apply(this, arguments);
// 构造参数数组,执行`after:methodName`事件
var afterArgs = ['after:' + methodName, ret].concat(args);
this.trigger.apply(this, afterArgs);
return ret;
};
// 修改方法是否被改造状态属性
this[methodName].__isAspected = true;
}
然后是Base
模块,它集成了Event
, Aspect
和Attribute
模块的各种属性,实际上是Arale类库的一个入口模块:
var Class = require('arale-class');
var Events = require('arale-events');
var Aspect = require('./aspect');
var Attribute = require('./attribute');
module.exports = Class.create({
// 混入Events, Aspect, Attribute模块的所有属性
Implements: [Events, Aspect, Attribute],
// 所有用`Base.extend()`构建的类在初始化时都会调用的方法
initialize: function(config) {
this.initAttrs(config);
// 将`this._onChangeAttr`注册为`change:attr`事件的监听函数
parseEventsFromInstance(this, this.attrs);
},
destroy: function() {
// 卸载所有事件监听
this.off();
// 清除所有属性
for (var p in this) {
if (this.hasOwnProperty(p)) {
delete this[p];
}
}
// destroy一次后this都被清除了,再次调用会报错,因此生成一个空的destroy,该方法与主同在
this.destroy = function() {};
}
});
/**
* 将`_onChangeAttr`方法注册为`change:attr`事件的监听函数
* @param {Class} host 调用对象
* @param {Object} attrs 包含所有要注册属性的对象
*/
function parseEventsFromInstance(host, attrs) {
for (var attr in attrs) {
if (attrs.hasOwnProperty(attr)) { // 检测attr是attrs的非继承属性
var m = '_onChange' + ucfirst(attr);
if (host[m]) {
host.on('change:' + attr, host[m]);
}
}
}
}
/**
* 将首字母转变为大写
* @param {String} str 要处理的字符串
* @return {String} 处理完的字符串
*/
function ucfirst(str) {
return str.charAt(0).toUpperCase() + str.substring(1);
}
源码分析
Aspect
Aspect
模块就是实现了两个方法,before
和after
。这两个方法的作用就是针对类上的某个方法,给这个方法绑定先于其执行和后于其执行的回调函数。
两个方法实际上调用的都是同一个方法weave
,只是将before和after作为参数传入,在weaver
方法中,对要进行before
和after
“伪事件”绑定的方法进行查找,找到后会检测这个方法上是否有__isAspected
属性。这个属性的作用是标示出此方法有没有被进行过伪事件的“包装”。
上一段连续提到两次“伪事件”这个词,它是我编出来的,表示的意思为before:methodName
和after:methodName
这样的事件并不能成为一个独立的事件,而是依附于methodName
这个原方法的。原来的事件执行流程是这样的。
event.trigger(eventName) +------------+
------------------------->| someMethod |----------->被触发执行
+------------+
一旦在someMethod
上注册了after
或before
事件后,someMethod
就会被封装成一个新的函数:
someMethod被封装后生成的新wrappedMethod:
|trigger()
+-------------------------------------------------------+
|wrappedMethod: |触发`before:method`事件 |
| | |
| +---------------+ return false +-----+ |
| | beforeMethod |-------------->| end | |
| +---------------+ +-----+ |
| |return true |
| | |
| +---------------+ |
| | method | |
| +---------------+ |
| |触发`after:method`事件 |
| | |
| +---------------+ |
| | afterMethod | |
| +---------------+ |
+-------------------------------------------------------+
整个模块的关键就在于wrap
这个用来封装方法的函数了,当然实现这一功能的也需要功能完备的Event
模块的支持。