20

What is the correct way to preserve a this javascript reference in an event handler stored inside the object's prototype? I'd like to stay away from creating temp vars like '_this' or 'that' and I can't use a framework like jQuery. I saw a lot of people talk about using a 'bind' function but was unsure of how to implement it in my given scenario.

var Example = function(foo,bar){
    this.foo = foo;
    this.bar = bar;
};
Example.prototype.SetEvent = function(){
    this.bar.onclick = this.ClickEvent;
};
Example.prototype.ClickEvent = function(){
    console.log(this.foo); // logs undefined because 'this' is really 'this.bar'
};
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
George Hess
  • 639
  • 1
  • 8
  • 15

4 Answers4

23

I find bind() being the cleanest solution so far:

this.bar.onclick = this.ClickEvent.bind(this);

BTW the other this is called that by convention very often.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • 4
    I *hate* creating a reference to this called "that" (a la Crockford). I prefer "instance" (a la Stefanov), or "me" (a la ExtJS) – Dexygen Nov 11 '11 at 22:04
  • 5
    Actually, I hate creating `this` references, no matter how are they named. But agree, `that` isn't the best naming. – Tomasz Nurkiewicz Nov 11 '11 at 22:08
  • 1
    I use 'self', but found just recently that is a name clash with a top-level property in WebWorkers. Not that you can't have the same name, but it does confuse things. Also, I find the name 'that' to be quite the opposite to 'this' – Matt Nov 11 '11 at 22:08
  • @GeorgeJempty that is just a personal preference – Ibu Nov 11 '11 at 22:08
  • I prefer `instance` as well, but I usually favor binding instead. – Chris Baker Nov 11 '11 at 22:10
  • 1
    this of course doesn't work in old IE (8 and older) – Keith K Jul 17 '13 at 08:04
  • [the other answer](http://stackoverflow.com/a/21857704/2013920) by Paolo Moretti seems good cross browser alternative – glowka May 03 '14 at 11:25
7

Check out the MDN document on bind: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

Using this functionality, you can change the scope (what this is):

Example.prototype.SetEvent = function(){
    this.bar.onclick = this.ClickEvent.bind(this);
};

Be aware, however, that this is a new addition to EMCA and thus may not be supported in all user agents. There is a pollyfill available at the MDN document linked above.

Chris Baker
  • 49,926
  • 12
  • 96
  • 115
5

The problem with bind is that is only supported by IE9+.

The function can be polyfilled with es5-shim, but it's not completely identical to the native implementation:

  • Caveat: the bound function has a prototype property.
  • Caveat: bound functions do not try too hard to keep you from manipulating their arguments and caller properties.
  • Caveat: bound functions don't have checks in call and apply to avoid executing as a constructor.

Another alternative can be jQuery.proxy:

$(elem).on('click', $.proxy(eventHandler, this));

This is even more helpful if you want to remove the event handler later, because when a function goes through the proxy method, jQuery generates a new guid value and then applies that guid to both the core function as well as the resultant proxy function, so that you can use the original function reference to unbind an event handler callback that has been proxied:

$(elem).off('click', eventHandler);
Paolo Moretti
  • 54,162
  • 23
  • 101
  • 92
  • `.bind()` is no less fully cross-browser than `$.proxy`. It's basically a non-standard version of `.bind()`. If the caveats you listed are actually a concern, then `$.proxy` won't be any better. Not to mention that the question is clear that he can't use jQuery. –  Mar 31 '15 at 21:07
  • @squint I'm suggesting to use `$.proxy` (or a similar implementation) because it's a loose connection, and that's something really helpful in OO event handling. But thank you I'll update my answer because, like you're saying, a polyfilled `.bind()` is no less fully cross-browser than `$.proxy`. – Paolo Moretti Apr 01 '15 at 10:56
0

Other solution: use the "arrow functions" introduced by ES6. Those have the particularity to not change the context, IE what this points to. Here is an example:

function Foo(){
    myeventemitter.addEventListener("mousedown", (()=>{
        return (event)=>{this.myinstancefunction(event)}; /* Return the arrow
function (with the same this) that pass the event to the Foo prototype handler */
    })());
}
Foo.prototype.myinstancefunction = function(event){
    // Handle event in the context of your Object
}

Arrow function specs @ MDN

Edit

Be carefull with it. If you use it client-side and you can't be sure of the capabilities of the JS interpreter, note that old browser won't recognize arrow functions (see CanIUse stats). Use this method only if you KNOW what will run it (recent browsers only & NodeJS apps)