开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜