切面编程能让你有效的控制方法的执行顺序。举个例子:
var Dialog = Base.extend({
...
show: function() {
console.log(2);
this.element.show();
},
...
});
var dialog = new Dialog();
dialog.before('show', function() {
console.log(1);
});
dialog.after('show', function() {
console.log(3);
});
dialog.show(); // ==> 1, 2, 3
这里有一个需要注意的点就是,如果before方法返回的值为false,那么函数本身以及after方法的回调将不再执行。
var Dialog = Base.extend({
...
show: function() {
console.log(2);
this.element.show();
},
...
});
var dialog = new Dialog();
dialog.before('show', function() {
console.log(1);
return false;
});
dialog.after('show', function() {
console.log(3);
});
dialog.show(); // ==> 1
其源码注释如下。因为这里的aspect是给Base类使用的,Base继承了Events,所以它有on,trigger等方法。
// this指向Base实例
// 在指定方法执行前,先执行 callback
// context是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+/;
function weave(when, methodName, callback, context) {
// 将方法名转化为数组
var names = methodName.split(eventSplitter);
var name, method;
while (name = names.shift()) {
method = getMethod(this, name);
// 如果没有wrap则执行wrap
if (!method.__isAspected) {
wrap.call(this, name);
}
// 继承了Events
this.on(when + ':' + name, callback, context);
}
return this;
}
// 从host宿主中取得方法名对应的方法(若是取不到则做报错处理)
function getMethod(host, methodName) {
var method = host[methodName];
if (!method) {
throw new Error('Invalid method name: ' + methodName);
}
return method;
}
// wrap方法
function wrap(methodName) {
// 保存方法名对应的方法
var old = this[methodName];
// 重新定义该方法
this[methodName] = function() {
var args = Array.prototype.slice.call(arguments);
var beforeArgs = ['before:' + methodName].concat(args);
// prevent if trigger return false
// 如果before方法返回了false,那么就不再执行原方法以及after后的方法
if (this.trigger.apply(this, beforeArgs) === false) return;
var ret = old.apply(this, arguments);
// 将原方法执行后的返回值也传给after后的执行的方法
var afterArgs = ['after:' + methodName, ret].concat(args);
this.trigger.apply(this, afterArgs);
return ret;
};
// 给对应的方法名传递特殊参数,防止二次wrap
this[methodName].__isAspected = true;
}