向JavaScript对象添加监听器函数

6

我有以下代码定义了一个Car。每个Car都有颜色,以及一个setColor(color)函数。我想添加监听器函数,每当调用setColor(color)时就会调用它们,并且我希望能够随时附加这些监听器函数。这种方法合适吗?是否有更简洁的方法?

function Car() {

    this._color = 'red';
    this._callbacks = {};

    this.setColor = function(color) {
        this._color = color;
        console.log(">>> set car color to " + color);
        if (this._callbacks['setColor']) {
            this._callbacks['setColor']();
        }
    };

    this.addListener = function(functionName, handler) {
        if (this._callbacks[functionName]) {
            var oldCallback = this._callbacks[functionName];
            this._callbacks[functionName] = function() {
                oldCallback();
                handler();
            }
        } else {
            this._callbacks[functionName] = function() {
                handler();
            }
        }
    };


}

var car = new Car();
car.setColor('blue');
car.addListener('setColor', function() { console.log("This is listener # 1"); });
car.setColor('green');
car.addListener('setColor', function() { console.log("This is listener # 2"); });
car.setColor('orange');

输出:

>>> setColor to blue
>>> setColor to green
This is listener # 1
>>> setColor to orange
This is listener # 1
This is listener # 2
3个回答

3
我认为将监听器存储到数组中会更加清晰。此外,您应该使用原型对象或将半私有属性变成真正的私有变量。
function Car() {
    this._color = 'red';
    this._callbacks = {setColor:[]};
};
Car.prototype.setColor = function(color) {
    this._color = color;
    console.log(">>> set car color to " + color);
    for (var i=0; i<this._callbacks['setColor'].length; i++)
        this._callbacks['setColor'][i]();
};
Car.prototype.addListener = function(functionName, handler) {
    this._callbacks[functionName].push(handler);
};

或者:

function Car() {
    var color = 'red';
    var callbacks = {};

    this.setColor = function(c) {
        color = c;
        console.log(">>> set car color to " + color);
        for (var i=0; 'setColor' in callbacks && i<callbacks['setColor'].length; i++)
            callbacks['setColor'][i]();
    };
    this.addListener = function(functionName, handler) {
        if (functionName in callbacks)
            callbacks[functionName].push(handler);
        else
            callbacks[functionName] = [handler];
    };
}

我修改了自己的代码,大致匹配了您的第二个选项。与我在上面评论中发布的类似问题:使用原型对象的优点是什么?而且我也不确定如何“将半私有属性变成真正的私有变量”。您能多解释一下吗? - Andrew
请参阅使用原型的优势,而不是直接在构造函数中定义方法?在构造函数中声明javascript对象方法与在原型中声明的区别。在第一个示例中,对象的属性是公共的(只有下划线表示它们不应从外部使用)-在第二个示例中,我使用了局部变量作用域限定于构造函数,并且它们仅对两个特权方法可访问。(足够的流行词汇可以搜索;-) - Bergi
@Utkanos的答案也很好,但我最终使用的是这里给出的第二个选项。这样可以消除变量colorcallbacks的半私有性,并将回调函数放在一个数组中而不是闭包中。 - Andrew

2
也许可以像这样。
//the 'class'
function Car() {

    //set up a static listeners object - an array for each method
    Car.listeners = {setColor: []};

    //called by methods on invocation, to fire their listeners
    Car.executeListeners = function(methodName) {
        for (var i=0, len = Car.listeners[methodName].length; i<len; i++)
            Car.listeners[methodName][i].apply(this);
    };

    //method - on invocation, fire any listeners
    this.setColor = function(color) {
        this.color = color;
        Car.executeListeners.call(this, 'setColor');
    };
}

//instance
var car = new Car();

//add a listener to instance.setColor invocations
Car.listeners.setColor.push(function() {
    alert("hello - this car's color is "+this.color);
});

//set color (triggers listener)
car.setColor('orange');

请注意,您正在将类似于原型的方法分配给实例而不是原型本身 - 这是继承、可重用功能的地方。从性能角度来看,继承也更快。


对于原型工作原理,我只有一些自信。将这些Car.{method}函数的每个实例都替换为this.{method}函数,有什么问题吗?也就是说,在Car函数体内用"this"替换三个"Car"实例的部分。 - Andrew
你为什么把监听器设为静态的?我认为这不是一个好主意。 - Bergi
@Mokt - 原型是可继承、可重用功能的地方。你现在的做法可以工作,但基于实例的方法稍微慢一些(尽管你必须有很多笨重的方法才能注意到差异),并且这意味着它们是实例自己的属性(因此它们将是可枚举的),而不是继承的属性。 - Mitya
@Bergi - 为什么这样说?您没有详细说明。看起来可以正常工作。一个静态位置用于所有方法的侦听器。在多个实例中运行良好。http://jsfiddle.net/akxVD/1 很乐意接受更正。 - Mitya
@Utkanos:是的,它确实可以工作,但我认为这不是预期的。破坏原始帖子的功能至少需要一条评论。 - Bergi

1

如果这种方法对您有效,那么我认为没有理由不使用它。关于这种方法的一些想法:

  • 您无法设置处理程序的上下文,如果处理程序应针对某个对象调用,则应使addListener方法接受一个上下文对象,并执行callback.call(context)。如果您不关心此事,则无需担心。
  • 如果早期回调失败,则不会调用后续回调。不确定您是否关心此事,但如果确实关心,则可以使用数组存储所有回调,并迭代数组依次调用每个回调。您可能需要将对回调的调用包装在try/catch块中,以确保回调继续被调用。
  • 您是否想将当前颜色传递给回调?甚至是Car对象本身,例如callback(this, this._color)
  • 您的方法在很大程度上使用了闭包。如果发现性能成为问题,消除闭包将有所帮助。

另一个要考虑的事情是使用Object.defineProperty,但这是更具风格的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接