4

I am probably doing something wrong but I found some interesting behavior when trying to apply some object oriented programming to Javascript. Consider the following

function Bug(element) {
    this.focusedCell = null;
    element.addEventListener('click', this.onClick, true);
};

Bug.prototype.onClick = function(event){
    console.log("this is: ");
    console.log(this);
};

When I call the method from the console, I see the correct instance of "this" but when I click the element in the document I see the document element in lieu of the instance. So... chances are it's probably not a good idea to use event listeners with instance methods, at least the way I'm doing it.

So the question is:

  • Is it possible to have an event listener like this that calls an instance method of a javascript object, while preserving the instance in the call?

  • Is there a better pattern for doing this?

Edit: I haven't tried this in anything but Chrome. But I would imagine that the behavior is the same.

lukecampbell
  • 14,728
  • 4
  • 34
  • 32
  • The function creates a new scope. – adeneo May 11 '13 at 23:21
  • Thanks, sorry didn't know it was a duplicate search didn't show up with anything. – lukecampbell May 11 '13 at 23:21
  • @FelixKling Wow sweet you found an perfect dup.. – Andreas Louv May 11 '13 at 23:22
  • 1
    There are actually tons of duplicates, but they probably have weird titles. – Felix Kling May 11 '13 at 23:23
  • I [googled](https://www.google.com/search?q=event+listener+overrides+this&aq=f&oq=event+listen&aqs=chrome.0.59j57j0j60l2j0.1574j0&sourceid=chrome&ie=UTF-8) it too but nada. I searched for "overriding this" on SO but didn't find it. I didn't know that it was explicitly related to the event listener that was overriding, it. – lukecampbell May 11 '13 at 23:25

3 Answers3

6

There's a better pattern, and doesn't require much change. I'll show the code first.

function Bug(element) {
    this.focusedCell = null;
    // --------------------------------v----pass the object, not a function
    element.addEventListener('click', this, true);
};

// Implement the `EventListener` interface
Bug.prototype.handleEvent = function(event) {
    if (event.type === "click")
        this.onClick(event);
}

Bug.prototype.onClick = function(event) {
    console.log(JSON.stringify(this));         // '{"focusedCell":null}'
    console.log(event.currentTarget.nodeName); // "DIV"
};

By adding the handleEvent method, we make Bug implement the EventListener interface. This allows us to pass the new Bug object as the second argument to addEventListener() instead of a function.

Now when the "click" event happens, the .handleEvent() method will be invoked, and the value of this in that method will be the Bug object that was bound.


Since this is a reference to the Bug instance, it obviously won't be a reference to the element anymore. But it's not necessary, since the element is available via event.currentTarget.

Of course, you could add the element directly to your Bug object in the constructor if desired.

DEMO: http://jsfiddle.net/CnZTa/


  • This is very elegant, but will also require some sort of polyfill for IE8 and earlier. – bfavaretto May 11 '13 at 23:31
  • @bfavaretto: True, but OP is using `addEventListener` already in the question. Don't know if a shim is needed or already being used. –  May 11 '13 at 23:32
  • I like simplicity behind this, my actual class has a lot more event handling and this looks to be the nicest fit. But as bfavaretto pointed out, I will have to look at older browser support. – lukecampbell May 11 '13 at 23:32
  • Oh the irony... my own answer is using `addEventListener` too! Just not `handleEvent`. – bfavaretto May 11 '13 at 23:34
  • @lukecampbell: Yeah, most browsers support `.addEventListener()`, but as bfavaretto pointed out, IE8 and lower will need a shim. –  May 11 '13 at 23:35
  • @bfavaretto: ...not to mention `Function.prototype.bind()`. ;-) Though you did mention the need for a polyfill. If OP doesn't support IE8, then no need for either shim! –  May 11 '13 at 23:35
  • Thanks all, I tried both `bind` and this one out and they both work, at least on Chrome ;) – lukecampbell May 11 '13 at 23:38
  • 1
    @lukecampbell: If you have lots of other event types, consider naming your methods after the events. Then your `handleEvent()` method can simply do `return this[event.type] && this[event.type](event);` –  May 11 '13 at 23:41
  • 2
    I wish I could upvote that more than once squint. – lukecampbell May 11 '13 at 23:43
  • 2
    [The code here](http://www.thecssninja.com/javascript/handleevent) can be used to provide compatibility with older browsers. – bfavaretto May 11 '13 at 23:44
  • +1... that's great! I've been developing JavaScript applications for a while now and I did not even know this feature existed! So much more efficient than `bind` in terms of memory consumption. – plalx May 12 '13 at 01:28
  • @plalx: Yeah, I think it's the best kept secret in JavaScript. Provides such a nice coupling between JS data/classes and DOM elements. –  May 12 '13 at 01:37
  • 1
    @squint hehe, I guess there will be much more to learn once Harmony kicks-in ;) – plalx May 12 '13 at 01:41
  • 1
    I guess someone doesn't like *(or more likely understand)* this solution? If you're not familiar with this technique, by all means, ask about it. I'd be happy to explain more. –  May 12 '13 at 12:44
5

You can use Function.prototype.bind to create a listener bound to whatever this value you want:

function Bug(element) {
    this.focusedCell = null;
    element.addEventListener('click', this.onClick.bind(this), true);
};

Older (non-ES5) browsers will need a polyfill such as the one from MDN.

bfavaretto
  • 71,580
  • 16
  • 111
  • 150
1

This is normal behavior in JavaScript. You can preserve your expected this by passing an function the the listener:

function Bug(element) {
    var self = this; // Store a reference to this
    this.focusedCell = null;
    element.addEventListener('click', function() {
        // in here this refers to element
        self.onClick(event);
    }, true);
};

Bug.prototype.onClick = function(event){
    console.log("this is: "); 
    console.log(this); // In here this refers the an instance of Bug.
};
Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
  • The code is right, but the explanation is not very clear. `"by passing a function to the listener"`... this is what the OP does already. The difference is that the function is defined inline in your case, so it creates a closure over the parent function's scope which allows to access the `self` variable from the handler function. – plalx May 12 '13 at 01:20