问题
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 docallback.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