I am beginning to define classes in JavaScript, and I have a lot of troubles with the keyword this
.
Here is an example of what I want to do:
function MyMap() {
this.map = new google.maps.Map(....);
google.maps.event.addListener(this.map, 'idle', function() {
this.mapIdle(); // PROBLEM: "this" undefined
});
this.mapIdle = function() {
google.maps.event.addListener(marker, 'click', function() {
$("button").click(function() {
$.ajax({
success: function() {
this.map.clearInfoWindows(); // PROBLEM: "this" undefined
}
});
});
});
}
}
As you can see in the comments, this
won't work here, because it is used inside a closure.
I have started using workarounds like:
var that = this;
google.maps.event.addListener(this.map, 'idle', function() {
that.mapIdle();
});
Or even where you have to define a callback function around your callback function (seriously!!).
This is extremly ugly and doesn't work everywhere. When I get a lot of nested lambda functions (as in the example I gave), I have no idea how to use a class attribute.
What is the best and most correct way to do that?
The easiest way is to define a self
variable (or that
, if you don't mind a non-semantic variable name) as you already mentioned:
function MyMap() {
var self = this;
// in nested functions use self for the current instance of MyMap
}
Noting that you have to do it again for methods you add to the prototype (if they use nested functions):
MyMap.prototype.myMethod = function() {
var self = this;
// use self in nested functions
};
You should also read up on the .bind()
method, noting that it doesn't work for IE <= 8.
The question you linked to about defining a callback around your callback is to solve a different problem, i.e., setting up an appropriate closure structure to allow functions that are nested inside a loop to have access to the appropriate loop counter value(s). That has nothing to do with the this
issue. (And can easily be combined with the self
technique when needed.)
public
to contrast from a private
scope/sub-object - Umbrella 2012-04-03 23:00
If you're using jQuery, $.proxy
is handy for this:
You can just assign the object and the function to local varibles, then they are included in the closures for the callback functions:
function MyMap() {
var map = new google.maps.Map(....);
var mapIdle = function() {
google.maps.event.addListener(marker, 'click', function() {
$("button").click(function() {
$.ajax({
success: function() {
map.clearInfoWindows();
}
});
});
});
};
this.map = map;
this.mapIdle = mapIdle; // Is this needed?
google.maps.event.addListener(this.map, 'idle', function() {
mapIdle();
});
}
In JavaScript, this
is reassigned with every single function call.
It's just something you have to be aware of in JavaScript. It can be confusing at first, but once you know a few simple rules, it's actually pretty straightforward.
If it's a method call like myObj.doSomething()
, then this
will be automatically set to myObj
inside of doSomething()
.
If you want to explicitly control the value of this
when making a function call, you can use doSomething.apply()
or doSomething.call()
to control what this
is set to inside the function. That's what event handler callbacks do. They explicitly set this
to point to the object that created the event (something which is very useful). You can read more about .apply()
and .call()
on MDN.
If you just call a regular function, then this
will be set to the global object which in a browser is the window
object.
All callback functions will have their value of this
messed with because every function call changes this
. Since your event handler is a callback and the success handler is a callback in the Ajax function, you should expect that the value of this
will not be preserved from the surrounding code. There are work-arounds using proxy or bind functions, but usually it's just as easy to capture the previous value of this
in a closure and just access it from there with something like var self = this;
.
In your circumstance, when you want access to a this
pointer from outside the event handler, the right thing is to just save it to a local variable that you will have access to in the event handler or even in the Ajax call that is in the event handler. There is no cleaner way to do it. This way you have access to both the this
pointer from the event or the Ajax call and the this
pointer from your calling object like this:
var self = this;
self.mapIdle = function() {
google.maps.event.addListener(marker, 'click', function() {
$("button").click(function() {
$.ajax({
success: function() {
self.map.clearInfoWindows(); // PROBLEM: "this" undefined
}
});
});
});
}
}