向 JavaScript 对象添加侦听器函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12059676/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 15:12:22  来源:igfitidea点击:

Adding listener functions to a JavaScript object

javascriptfunctioncallbacklistener

提问by Andrew

I have the following code which defines a Car. Each Carhas a color, along with a setColor(color)function. I want to add listener functions which are called whenever setColor(color)is called, and I want to be able to tack these listener functions on whenever I want. Is this a suitable approach? Is there a cleaner way?

我有以下代码定义了一个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');

Output:

输出:

>>> setColor to blue
>>> setColor to green
This is listener # 1
>>> setColor to orange
This is listener # 1
This is listener # 2

采纳答案by Bergi

I think an array to store the listeners would be a cleaner approach. Also, you should use the prototype object, or make the semiprivate properties real private variables.

我认为存储侦听器的数组将是一种更简洁的方法。此外,您应该使用原型对象,或者使半私有属性成为真正的私有变量。

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);
};

Or:

或者:

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];
    };
}

回答by Utkanos

Something like this, perhaps.

大概是这样的吧。

//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');

Note you are assigning prototype-esq methods to the instances rather than to the prototype itself - the place for inherited, reusable functionality. Inheritance is also faster in performance terms.

请注意,您将prototype-esq 方法分配给实例而不是原型本身——继承、可重用功能的地方。继承在性能方面也更快。

回答by Matt Greer

If it's working for you then I see no reason to not go with it. Some thoughts on this approach:

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

  • you can't set the context of the handlers, if the handler should be called against a certain object, you should have the addListenermethod take a context object and do callback.call(context). If you don't care about this, then no need to worry about it.
  • If an early callback blows up, the later callbacks won't get called. Not sure if you care about this, but if you do you could instead use an array to store all the callbacks in, and iterate over the array calling each one in turn. You might need to wrap the call to the callback in a try/catch block to ensure the callbacks keep getting called.
  • Do you want to pass in the current color to the callback? Or even the Car object itself, something like callback(this, this._color)?
  • Your approach uses closures a fair amount. If you find performance becoming an issue, getting rid of the closures would help that.
  • 你不能设置处理程序的上下文,如果处理程序应该针对某个对象调用,你应该让该addListener方法接受一个上下文对象并执行callback.call(context)。如果你不在乎这个,那就没必要担心了。
  • 如果早期回调失败,则不会调用后面的回调。不确定您是否关心这一点,但是如果您关心这一点,您可以改为使用一个数组来存储所有回调,然后遍历该数组依次调用每个回调。您可能需要在 try/catch 块中包装对回调的调用,以确保回调不断被调用。
  • 您想将当前颜色传递给回调吗?或者甚至是 Car 对象本身,比如callback(this, this._color)?
  • 您的方法大量使用了闭包。如果你发现性能成为一个问题,摆脱闭包会有所帮助。

Another thing to consider is using Object.defineProperty, but that's a more stylistic choice.

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