Accessing an object's property from an event listener call in JavaScript
Asked Answered
P

7

27

Below I am creating an object in JavaScript. Within the constructor I am setting up an event listener. The problem is that when the event gets fired, this.prop cannot be found, and undefined prints out. How do I solve this?

var someObj = function someObj(){
   this.prop = 33;
    this.mouseMoving = function() { console.log(this.prop);}
    
    document.getElementById("someDiv").addEventListener('mousemove', this.mouseMoving, true);
}
Parenthesize answered 4/7, 2009 at 4:35 Comment(0)
M
27

When the event handler gets called, "this" no longer references the "someObj" object. You need to capture "this" into a local variable that the mouseMoving function will capture.

var someObj = function someObj(){
    this.prop = 33;
    var self = this;
    this.mouseMoving = function() { console.log(self.prop);}

    document.getElementById("someDiv").addEventListener('mousemove', this.mouseMoving, true);
}

I'm assuming "someObj is a constructor, i.e. intended to be called with as new someObj(), otherwise "this" will be the global scope.

The "this" keyword can be confusing in JavaScript, because it doesn't work the same way as in other languages. The key thing to remember is that it is bound to the calling object when the function is called, not when the function is created.

Middy answered 4/7, 2009 at 4:44 Comment(2)
the var name probably should not be "self" though, as it is a pre-defined JS variable.Laryngitis
There is a global "self" variable, but there's not a conflict because local variables override globals. If it's confusing, you could use something else. Some people use "that", but I find it counter-intuitive for "this" and "that" to be the same object.Middy
G
15

The javascript built-in Function.prototype.bind() is intended for this purpose.
For example:

var someObj = function someObj(){
       this.prop = 33;
        this.mouseMoving = function() { console.log(this.prop);}

        document.getElementById("someDiv").addEventListener('mousemove', this.mouseMoving.bind(this),true);

 }

More on the bind method here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Other wise you have to pass a reference of the object someObj to the element and use that reference in the line:

console.log(this.referenceToObject.prop); //this references the DOM element in an event.
Girder answered 4/7, 2009 at 4:44 Comment(0)
H
6

From Section 4.3 of JavaScript: The Good Parts by Douglas Crockford:

Invoking a function suspends the execution of the current function, passing control and parameters to the new function. In addition to the declared parameters, every function receives two additional parameters: this and arguments. The this parameter is very important in object oriented programming, and its value is determined by the invocation pattern. There are four patterns of invocation in JavaScript: the method invocation pattern, the function invocation pattern, the constructor invocation pattern, and the apply invocation pattern. The patterns differ in how the bonus parameter this is initialized.

Crockford continues to explains the binding of 'this' in each of these patterns, as follows:

The Method Invocation Pattern: When a function is stored as a property of an object, we call it a method. When a method is invoked, this is bound to that object.

The Function Invocation Pattern: When a function is invoked with this pattern, this is bound to the global object. This was a mistake in the design of the language.

The Constructor Invocation Pattern: If a function is invoked with the new prefix, then a new object will be created with a hidden link to the value of the function's prototype member, and this will be bound to that new object.

The Apply Invocation Pattern: The apply method lets us construct an array of arguments to use to invoke a function. It also lets us choose the value of this. The apply method takes two parameters. The first is the value that should be bound to this. The second is an array of parameters.

Hyperion answered 4/7, 2009 at 6:2 Comment(0)
A
3

First, you need to understand how 'this' works in JavaScript. 'this' keyword doesn't behave how it behaves in other languages like C# or Java. Read following post to understand more,

What is the rationale for the behavior of the 'this' keyword in JavaScript?

Once you understand that, as Matthew outlined in his code, you can save reference to 'this' and use that reference inside the mouseMoving function.

Though overall, I will advise that you use a JavaScript framework (e.g. jQuery, YUI, MooTools) which will take care of these issues for you. E.g. In Internet Explorer, you use addEvent to attach event and not addEventListenr.

Autobus answered 4/7, 2009 at 5:3 Comment(2)
Thanks for posting that link. I was looking for it for my answer, but couldn't find it.Middy
Not a problem. I think it's important to inform new JavaScript users about how 'this' works. Unless they understand 'this', I don't think they will be able to write proper JavaScript or debug.Autobus
S
3

You could use a variable named 'me', to avoid conflict with the global JavaScript variable 'self':

function someObj() {
  var me = this;
  this.prop = 33;

  this.mouseMoving = function() {
    alert(me.prop);
  }

  document.getElementById("someDiv").addEventListener('mousemove', this.mouseMoving, true);
}
Spittle answered 1/7, 2014 at 15:16 Comment(0)
C
0

You have some typos on your function declaration.

Your prop variable is also defined as a "public" or "visible" member (by using this.prop), doing so forces you to store the reference of this from the outer function (that is actually a reference to the object instance), as a "private" member of the function (using var) to get access the instance of the created object and read the "public" prop member.

You have some alternatives to rewrite this code:

function someObj (){
    var self = this;
    this.prop = 33;
    this.mouseMoving = function() { alert(self.prop);} // You access the current
                                                       // instance, stored in *self*
                                                       // since *this*, inside the 
                                                       // function, is in another 
                                                       // context.
    //...
}

var mySomeObj = new someObj(); // Object instantiation

Or you could:

function someObj (){
    var prop = 33;
    this.mouseMoving = function() { alert(prop);} 

    //...
}
var mySomeObj = new someObj(); // Object instantiation

The variables declared with var, are accesible to the functions declared inside of the major constructor function, this feature is known as Closures.

Curzon answered 4/7, 2009 at 5:3 Comment(0)
P
0

This answer was likely the cleanest solution at the time; using bind() allows the listener to inherit the value of this from the calling scope.

But modern JS has a better solution: the arrow function, which does not have its own this, so automatically binds the one from its called context.

Using that, the original code would look like this:

var someObj = function someObj(){
    this.prop = 33;
    this.mouseMoving = function() { console.log(this.prop);}
    
    document.getElementById("someDiv").addEventListener('mousemove', e => this.mouseMoving(), true);
}

Or, using the modern class syntax:

class someObj {
    prop;

    constructor() {
        this.prop = 33;
        document
            .getElementById("someDiv")
            .addEventListener("mousemove", e => this.mouseMoving(), true);
    }

    mouseMoving() {
        console.log(this.prop);
    }
}

foo = new someObj();

Reference: Arrow function expressions on MDN

Peanut answered 9/7 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.