Pattern to deal with 'this' in JavaScript event handlers
I'm trying to encapsulate some code to grab and release the onLoad event for a tab in a Firefox Extension such that as necessary, I call:
var onLoad = new MyHandler_onLoad();
And then when I'm done, I call:
onLoad.unregister();
In principle, this code implements the above fine until you delve in to the grittier details.
function bind(scope, fn) {
retur开发者_开发问答n function(){
fn.apply(scope, arguments);
};
function MyHandler_onLoad()
{
this.scan = function() {
do_scan(this.browser); // this.browser == undefined
};
this.register = function() {
this.tab.addEventListener("load", this.scan, false);
};
this.unregister = function() {
this.tab.removeEventListener("load", this.scan, false);
};
this.tab = gBrowser.selectedTab;
this.browser = gBrowser.selectedBrowser;
this.register();
window.addEventListener("unload", bind(this, this.unregister), false);
};
Due to the behaviour of JavaScript's this
, I'm struggling. I want to be able to access this.browser from my scan function, but can't.
I've used bind
to ensure that unregister
gains the appropriate context on unload
. But, I can't do this with the call to scan
as I'll not be able to remove it later if I don't have a name.
Is there a good pattern for doing this sort of thing in JavaScript?
I've tried storing the result of bind(this, this.scan) as a variable in the constructor, but it doesn't help and am now struggling for options.
this
, in JavaScript, always points to the current object. If there is no current object, this
points to window
, which is always the top-level scope (in a browser anyway)
By example:
function(){
...
this.foo = function(){
this;
// There is no object here, so `this` points to `window`
}
}
function foo(){
this;
// again, no object so `this` points to window`
}
foo();
function foo(){
this;
// because initialized with `new`, `this` points to an instance of `foo`
this.bar = function(){
this;
// no object, `this` points to `window`
}
}
var foobar = new foo();
// above is roughly equivalent to: foo.apply({}, arguments); Note the new object
foobar.bar();
var foo = {
bar : function(){
this;
// `this` points at `foo` -- note the object literal
}
};
function foo(){
}
foo.prototype.bar = function(){
this;
// `this` points to the instance of `foo`
}
var foobar = new foo();
foobar.bar();
The concept of binding allows you to lock the this
variable to whatever you want since the final call to the function is via .apply(scope, params)
, so going back to your original question, my last example above will work, so will this:
function foo(){
this.scan = bind(this, function(){
this;
// `this` points to instance of `foo` IF `foo` is instantiated
// with `new` keyword.
});
}
new foo()
If you want to understand all of this more, I have two articles I wrote ages back that should help:
http://www.htmlgoodies.com/primers/jsp/article.php/3600451/Javascript-Basics-Part-8.htm http://www.htmlgoodies.com/primers/jsp/article.php/3606701/Javascript-Basics-Part-9.htm
function MyHandler_onLoad()
{
var self = this;
Having done this, self
will always point to the correct object in your handlers.
Solution: Don't use this
.
Here is an alternative way to define MyHandler_onLoad
function MyHandler_onLoad() {
var onload_handler = {
scan: function() {
do_scan(onload_handler.browser); // onload_handler.browser == undefined
},
register = function() {
onload_handler.tab.addEventListener("load", onload_handler.scan, false);
},
unregister = function() {
onload_handler.tab.removeEventListener("load", onload_handler.scan, false);
}
};
onload_handler.tab = gBrowser.selectedTab;
onload_handler.browser = gBrowser.selectedBrowser;
onload_handler.register();
window.addEventListener("unload", bind(onload_handler, onload_handler.unregister), false);
return onload_handler;
}
Even better? Move global dependencies up and no access to tab and browser properties (ie making them 'private')
You could even choose to hide register and unregister functions as I'm not sure you even need them, since it seems to attach itself already.
var handler = MyHandler_onLoad(gBrowser.selectedTab, gBrowser.selectedBrowser);
function MyHandler_onLoad(tab, browser) {
var onload_handler = {
scan: function() {
do_scan(browser); // browser == undefined
},
register = function() {
tab.addEventListener("load", onload_handler.scan, false);
},
unregister = function() {
tab.removeEventListener("load", onload_handler.scan, false);
}
};
onload_handler.register();
window.addEventListener("unload", bind(onload_handler, onload_handler.unregister), false);
return onload_handler;
}
Specifically your problem with this
is that it points to the scan function, not your handler object. If you don't use this
at all then you will never run into these kinds of bugs.
Oh, and you don't need to use new
either.
精彩评论