1

I have a problem that I can't work around.

The context is: I want to have an inheritance chain, and a method of objects that belong to this inheritance has to be a event handler, and at the same time be able to reach the object properties.

I am giving a try to writing JavaScript without the "new" word, and using instead Object.create() with some inheritance hierarchy. So first this approach.

So I have a blueprint for the rest of my objects (myProto), and then I create objects with Object.create (so that there is no closure where I can do the trick of assigning this to that or self). Now, when I use a method of this object to handle, for instance, a click event on a div, this obviously will refer to the DOM object, and I lose the posibility of accessing the properties of my object.

var myProto = {
    init: function (name, value) {
        this.name = name;
        this.value = value;

        return this;
    },
    someHandler: function (e) {
        // Normally I would use this instead of e.target...

        e.target.innerHTML = this.name + this.value; // This does not refer to the object.
    }
};

var myObject = Object.create(myProto).init('myName', 'myValue');
document.getElementById('myDiv').onclick = myObject.someHandler;

Here the fiddle: http://jsfiddle.net/pgPHM/

And now the "classical" approach: If I was using the new Constructor form, it would be easy to assign this in the closure and then access it, but there is the problem that the functions in the Constructor.prototype

var Constructor = function (name, value) {
    var self = this;

    self.name = name;
    self.value = value;
};

Constructor.prototype.someHandler = function () {/*self does not reach this here*/};

jsfiddle: http://jsfiddle.net/ZcG3J/2/

I really don't get why JS objects do not have a real this or self or whatever to refer to themselves without these tricky contexts, closures, etc...

Basically the question is:

How can I use a method of an object as an event handler, and still be able to reach the object?

Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
bgusach
  • 13,019
  • 10
  • 44
  • 61

3 Answers3

2

Except for when using new, this in Javascript is set by according to how a function is called. Call it the right way and this will be what you want. I can't really tell what problem you're trying to solve in your question, but here's how this is determined.

  1. Calling obj.method() - this will be set to obj inside of method().
  2. Use function.call() or function.apply() to control what this will be yourself.
  3. Make a normal function call such as func() and this will be set to either the global object or undefined depending upon whether you're in strict mode.
  4. Use .bind() (in modern browsers) to create a function stub that will automatically use .apply() internally to "bind" a value of this to the execution of the function.
  5. When you call a constructor using new, this will be set to the newly created object inside the constructor.
  6. When you pass a callback function as an argument to any function call (such as addEventListener(), it is the calling function's responsibility to decide what it wants the this pointer to be set to and won't be bound to your own object. addEventListener() sets this to be the DOM object that caused the event.

For event handlers that you want to be method calls on a particular object, you have to create a way to associate the method call with the object. You can either use .bind() as specified above or use an anonymous function that can reference a saved value of this in a closure.

var self = this;
document.addEventListener('keyup', function(e) {
    self.handleKeys(e);
})

or, using .bind():

document.addEventListener('keyup', this.handleKeys.bind(this));

FYI, there's not any real functional difference between these two methods because .bind() just creates a closure and does what the first example does, but does it for you.

jfriend00
  • 580,699
  • 78
  • 809
  • 825
2

Simple. If a function is called as a method instead of "bare" the this always refers to the word before the last dot. So instead of:

document.getElementById('myDiv').onclick = myObject.someHandler;

// You're just passing the function here, not calling it.
// It will be called by the onclick handler so `this` is changed

do this:

document.getElementById('myDiv').onclick = function(){
    myObject.someHandler();

    // Here, you're actually calling it. So this is the word
    // before the last dot. Which is myObject
}

In more modern javascript you can of course use bind:

document.getElementById('myDiv').onclick = myObject.someHandler.bind(myObject);
slebetman
  • 93,070
  • 18
  • 116
  • 145
  • For a detailed explanation of how `this` works see: http://stackoverflow.com/questions/13441307/how-does-the-this-keyword-in-javascript-act-within-an-object-literal/13441628#13441628 – slebetman Oct 27 '13 at 21:32
1

The first problem in your jsfiddle is that self is a local variable for Constructor and it is not available outside of the function. What you think about the following code:

var Constructor = function(name, value) {
    var self = this;
    self.name = name;
    self.value = value;
    self.someHandler = function(e) {
        e.target.innerHTML = self.name + self.value; // self undefined    
    }
    return self;
};

var myObject = Constructor('myName', 'myValue');
document.getElementById('myDiv').onclick = myObject.someHandler;

JsFiddle -> http://jsfiddle.net/ZcG3J/4/

Is it structured as you want?

Krasimir
  • 12,605
  • 3
  • 34
  • 52
  • That's easy and works, but that is not using the .prototype object of the constructor. If you want to instantiate many times the Constructor it's not so efficient. Basically you are constructing an object from scratch instead of using prototypical inheritance. – bgusach Oct 27 '13 at 21:35
  • Hm ... as far as I know if you use the *new* keyword you are also creating new objects. The prototype inheritance may be very confusing in some cases. That's why I prefer the reveal module pattern. – Krasimir Oct 27 '13 at 21:38
  • And how do you combine the reveal pattern with the inheritance? Seems hard to me to make an object inherit from another object created with that pattern, or the Crockford's "SuperConstructor" which is kind of the same. – bgusach Oct 28 '13 at 09:51
  • I'll suggest to read this article http://krasimirtsonev.com/blog/article/JavaScript-is-cool-modular-programming-extending There I explain my approach. – Krasimir Oct 28 '13 at 10:00
  • Uhm, JS does not even have classes... and your approach for inheritance just consists of overwriting properties here and there. I really think there must be a more subtle and "javascriptian" way of inheritance. I found some other stuff interesint anyway. Thanks – bgusach Oct 28 '13 at 20:25