41

I have a problem with an object I have created that looks something like this:

var myObject = {

    AddChildRowEvents: function(row, p2) {
        if(document.attachEvent) {
            row.attachEvent('onclick', function(){this.DoSomething();});
        } else {
            row.addEventListener('click', function(){this.DoSomething();}, false);
        }
    },

    DoSomething: function() {
        this.SomethingElse(); //<-- Error here, object 'this' does not support this method.
    }
}

The problem is that when I am inside the 'DoSomething' function, 'this' does not refer to 'myObject' what am I doing wrong?

4
  • 1) stupid question, but does your object have a SomethingElse method? 2) try doing "function() {this.DoSomething(this);}" in the closure, and have DoSomething either take 0 args or 1 arg - see what happens and post your results. Commented Dec 6, 2008 at 7:19
  • Your comment says the error is on the call to SomethingElse, but isn't it actually on the call to DoSomething? Commented Nov 13, 2018 at 4:29
  • Closures don't have anything to do with this example actually, the value if this is not coming from a closure here. Commented Mar 13, 2019 at 6:57
  • Does this answer your question? How to access the correct `this` inside a callback? Commented Jun 15, 2020 at 17:05

5 Answers 5

39

WHen the function is called, "this" refers to row. If you want to have the object, you can do it something like this: ]

AddChildRowEvents: function(row, p2) {
    var theObj = this;
    if(document.attachEvent) {
         row.attachEvent('onclick', function(){theObj.DoSomething();});
    } else {
         row.addEventListener('click', function(){theObj.DoSomething();}, false);
    }
},

When the function is called, it has access to the variable theOBj which was in scope when the function was defined.

Sign up to request clarification or add additional context in comments.

1 Comment

took me a while to get it - the reason is that row.attachEvent attaches an event to the row, and when that event fires, the row is the one that calls the function (it likely does something like f.apply(this, args)). am i correct?
12

this always refers to the inner function, if you have nested functions, you have to create another variable and point that to this.

var myObject = {
    AddChildRowEvents: function(row, p2) {
        var that = this;
        if(document.attachEvent) {
            row.attachEvent('onclick', function(){that.DoSomething();});
        } else {
            row.addEventListener('click', function(){that.DoSomething();}, false);
        }
    }
}

5 Comments

Are you sure that "that = this;" is valid in case of defining it inside object literal? @svinto
@GnrlBzik: Good catch! It should have a var in front of it.
dude this is an object, it expects key:value pairs. run this code against jshint.com
It seems I was very tired or something, sorry for the stupidity.
happens : ) i do it all the time ; )
10

The problem come from how this is managed in JS, wich can be quickly, especially when you have callback called asynchronously (and when a closure is created, bound to this).

When the AddChildRowEvents method is called, the this references well the variable myObject. But the aim of this method is to put a handler for click. So the event will be triggered sometime in the future. At that time, what object will really trigger the event? In fact, it's the DOM element on which the user will click. So this will reference this DOM element and not the myObject variable.

For a more modern solution than those presented, one can use bind method or arrow function.

1. Solution with arrow function

let handler = {
	addEventHandler: function(row) {
	  console.log("this", this)
  	row.addEventListener("click", () => {
    	console.log("this", this)
    	this.doSomethingElse()
    })
  },
  
  doSomethingElse: function() {
  	console.log("something else")
  }
}
var div = document.querySelector("div")
handler.addEventHandler(div)
<div>one</div>

With arrow function (which are available since ES2015), the this context in not the execution context but the lexical context. The difference between the two is that lexical context is found at build time not at execution time, so the lexical context is easier to track. Here, inside the arrow function, this references the same this of the outside function (i. e. addEventHandler) so reference myObject.

For more explanation of the properties of fat arrow function vs. regular functions: https://dmitripavlutin.com/6-ways-to-declare-javascript-functions/

2. Solution with bind

let handler = {
	addEventHandler: function(row) {
	  console.log("this", this)
  	row.addEventListener("click", function() {
    	console.log("this", this)
    	this.doSomethingElse()
    }.bind(this))
  },
  
  doSomethingElse: function() {
  	console.log("something else")
  }
}
var div = document.querySelector("div")
handler.addEventHandler(div)
<div>one</div>

This time, when the callback function is passed to addEventListener, one have bound the this context to the this element in the outside addEventHandler function.

1 Comment

.bind() is slow (ref:sitepoint.com/…)
3

This is a common issue with closures. To resolve it try something like this:

var myObject = {    
    AddChildRowEvents: function(row, p2) { 
        var self = this;

        if(document.attachEvent) {            
             row.attachEvent('onclick', function(){this.DoSomething(self);});        
        } else {            
             row.addEventListener('click', function(){this.DoSomething(self);}, false);        
        }    
    },    

    DoSomething: function(self) {       
        self.SomethingElse(); 
    }
}

1 Comment

FYI, 'self' is a very dangerous variable name in the context of Javascript. Depending on your scope, you may actually be getting a reference to the document.window object.
2

The problem with your code is here, in the following line of code you have defined an anonymous function and passed it as an event handler for the onClick event, in the following lines:

    row.attachEvent('onclick', function(){this.DoSomething();});

and

    row.addEventListener('click', function(){this.DoSomething();}, false);

when onclick event raised and it calls the anonymous function that you have passed, this context is referring to the object that has been raised the event, but not myObject. because at this point executing context is in the event handler context.

The solution: you can do the trick that Mike Kantor has been told, or using jQuery, use the proxy method, to define this context in the anonymous function.

So your code would be like this:

var myObject = {

    AddChildRowEvents: function(row, p2) {
        if(document.attachEvent) {
            row.attachEvent('onclick', $.proxy(function () {
                this.DoSomething();
            }, this));
        } else {
            row.addEventListener('click', $.proxy(function () {
                this.DoSomething();
            },this), false);
        }
    },

    DoSomething: function() {
        this.SomethingElse(); //<-- Error here, object 'this' does not support this method.
    }
}

you have mentioned that the error is in this.SomthingElse( ), but your code does not show it. If you really getting error at that line of code, it may you are using the method DoSomthing somewhere else as an event handler.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.