Adding listener functions to a JavaScript object

旧时模样 提交于 2021-02-07 06:57:13

问题


I have the following code which defines a Car. Each Car has 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?

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

回答1:


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



回答2:


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.




回答3:


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 addListener method 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.

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



来源:https://stackoverflow.com/questions/12059676/adding-listener-functions-to-a-javascript-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!