我有一个鼠标事件类。我正在使用dojo b/c我喜欢它的OO方法
dojo.declare("MouseObject", null, {
constructor: function(){},
onclick : function(){...},
_onClick : function(){...}
});
_onClick()
监听窗口生成的鼠标按下/释放事件,并确定是否发生了单击。如果有,则调用onClick()
。onClick()
执行所有可能的点击所共有的功能,因此每次用户点击时都需要调用它。
有时用户可能想要扩展onClick()
的功能。有没有办法在不复制粘贴的情况下合并原始功能?我能想到的两种方式都不喜欢,那就是
dojo.declare("MouseObjectChild", [MouseObject], {
constructor: function(){},
onclick : function(){this.inherited(arguments);...}
});
它的缺点是我必须不断地创建我并不真正需要的新类,并且两个添加了一个中间函数
dojo.declare("MouseObject", null, {
constructor: function(){},
onclick : function(){this._onClick()...}, //child onClick
_onClick : function(){...}, //parent onClick
__onClick : function(){...} //listener
});
但这看起来不像是好的代码
我想得到关于如何最好地处理程序用户交互的专家建议。如果程序提供了一个关键功能,比如在画布上画一个圆圈,那么用户如何最好地与之交互。如果用户想在圆后面画一个三角形怎么办?圆圈前面的正方形?然后该程序将不得不提供这样的预处理和后处理方法:
beforeDraw();
draw();
afterDraw();
这被认为是好的设计吗?我应该把函数指针放在数组中并按顺序调用它们吗?
正如"我应该把函数放在他们自己的数组中并按顺序调用它们吗"one_answers"构建你自己的事件系统"的想法所建议的那样,你可能想看看扩展标准库中捆绑的dojox.lang.aspx库。它基本上应该是"dojo.connect"解决方案的更高级版本。
dojo.require('dojox.lang.aspect');
var aop = dojox.lang.aspect;
function log(msg){ return function(){
console.log(msg);
};}
var obj = {
draw : log('draw circle');
};
obj.foo();
//prints
// draw circle
aop.advise(obj, 'draw', [
{before: log('draw triangle')},
{after: log('draw a square')},
{after: log('done!')}
]);
obj.foo();
//prints
// draw a triangle
// draw a circle
// draw a square
// done!
如果有人只是想在调用onclick()时运行一些代码,他可以直接连接到它。注意,你也可以使用dojo.connect()连接到函数调用,而不仅仅是事件。
dojo.connect(obj, 'onclick', function(){
// Some extra code that runs when obj.onclick() is called
});
如果他想创建一个新的类并扩展调用this.inherited(arguments);
的功能是最好的方法。
为什么不创建自己的简单事件模型呢?
// store our callbacks
var hooks = {};
// add a callback
var hook = function(name,fn){
if(typeof hooks[name] === 'undefined'){
hooks[name] = [];
}
hooks[name].push(fn);
return true;
};
// remove a callback
var unhook = function(name,fn){
if(typeof hooks[name] === 'undefined'){ return false }
var i, u = hooks[name].length;
for(i=0;i<u;i++){
if(hooks[name][i] === fn){
hooks[name].splice(i,1);
return true;
}
}
};
// trigger a callback
var callHook = function(name, data){
if(typeof hooks[name] === 'undefined'){ return false }
var i, u = hooks[name].length;
for(i=0;i<u;i++){
hooks[name][i](data);
}
};
稍后(示例通过上下文):
hook('beforeDraw',function(ctx){
console.log('I am called prior to drawing');
});
hook('afterDraw',function(ctx){
console.log('I am called after drawing');
});
var drawCircle = function(){
callHook('beforeDraw', ctx);
// drawing code here
callHook('afterDraw', ctx);
};
或者,如果您想将范围传递给回调,可以更改callHook
方法以接受范围参数,然后使用call
或apply
并传入this
或其他对象:
var callHook = function(name, scope, data){
if(typeof hooks[name] === 'undefined'){ return false }
var i, u = hooks[name].length;
for(i=0;i<u;i++){
hooks[name][i].call(scope, data);
}
};
// later
var drawCircle = function(){
callHook('beforeDraw', this, ctx);
// or
callHook('beforeDraw', someObject, ctx);
};
取决于&在定义事件模型函数的地方,您可以将功能范围扩展到对象的特定实例、对象的每个实例、全局等等,具体取决于您希望如何分配、更改和触发这些回调。
您有几个选项
1.Promise模式越来越受欢迎(尽管有相互竞争的规格):http://wiki.commonjs.org/wiki/Promises/B.jQuery有一个接近B的实现,并且有很好的文档记录。它有最多的选择,它很坚固,但一开始不容易让你不知所措。
- AOP风格的编程允许您篡改之前和之后的建议。然而,要做到这一点,您需要停止将所有内容放入匿名函数中,并决定是在实例化后应用建议,还是通过将功能烘焙到类中来计划使用建议。这需要在代码结构上进行更多的规划,但提供了大量的实用性
例如:
var MYAPP = {
onClick: function () {
//do something...
}
};
dojo.declare("MouseObject", null, {
constructor: function(){},
onclick : function(){...},
_onClick : function(){
return MYAPP.onClick.apply(this, arguments);
}
});
由于我的类每次都调用MYAPP(是的,这需要范围界定费用),我可以操作挂在MYAPP上的函数,任何使用它的人都会受益。
MYAPP.after("onClick", function () {...}); //lots of ways to write .after, just google some AOP js or use a library
另一面:
var MYAPP = {
onClick: function () {
//do something...
}
};
dojo.declare("MouseObject", null, {
constructor: function(){},
onclick : function(){...},
_onClick : MYAPP.onClick
});
现在我无法更改MYAPP并查看MouseObject或其实例中的更改,因为该函数已通过引用传递给MouseObject。我只能用AOP更改实例(或者通过更改类上的原型来更改所有未来的实例)。
类似于:
var m = new MouseObject();
m.after("onClick", function () {...}); //you need to add .after to your class or use an apply pattern from library code
它只是取决于你认为它将如何使用:某些实例,所有未来实例(直到关闭),或者一切。
- 建议使用"事件队列"。这更像是一个活动对象模式,使用对象数组和队列管理器来管理任务。这是最容易实现的,但也有一些缺点,队列可能会"堵塞",不再处理。我为这种类型的管理写了一个库,叫做proto-q(http://code.google.com/p/proto-q/),基于我做的一个iPhone应用程序。它对我来说效果很好,但并不是每个人都适用
__fn变得越来越混乱。我的朋友,那是通往地狱的路。
这是使用魔术按钮方法的症状。您应该考虑使用许多OO js库转移到实际的基于继承的类模型。然后您就可以调用$base.fn()或$super.fn(
如果我理解您的问题,那么您需要针对特定情况修改功能。如果是这种情况,并且您不想要任何开销,那么我将依赖于实例化时重写方法。
覆盖模型
你的班级:
dojo.declare("some.path.DrawingThing", [dijit._Widget], {
draw: function () {
this.inherited(arguments); // Call super class methods
// override this method to do specialized things that're specific
// to an instance.
}
});
客户端实例化:
var drawer = new some.path.DrawingThing({
draw: function () {
beforeDraw();
// Access the pre-existing functionality in a somewhat dirty way.
this.constructor.prototype.draw.apply(this, arguments);
afterDraw();
}
});
如果你遵循这个模型,代码会更少,但也不那么干净。另一种选择是编写一个真正的API,请参见下文。
事件模型
如果你想要一个真正的API,那么我会提供事件:
// Dojo 1.6 style declaration.
dojo.declare("some.path.DrawingThing", [dijit._Widget], {
onBeforeDraw: function(){
// Hook into this method to do something before draw
},
onDraw: function(){
// Hook into this method to do something after draw
},
draw: function () {
// This method is actually called when the draw even fires
this.onBeforeDraw();
this.inherited(arguments); // Call super class methods
this.onDraw();
}
});