Let's start with a plain old function:
var myFunc = function () { console.log(this); }
If we run that function we will see that this is window/global (depending on whether you are in the browser or in node:
myFunc() // Window{...}
So window/global is the default this
of a function call. Nothing fancy there.
Okay, what about if we have an object with a function (method):
x = {
aFunc: function () { console.log('this: ', this); }
};
If we run this, we'll see that this
, is x
x.aFunc() // this: x{...}
Which makes sense, right? It's a function on x
, so of course this
is x
. So why? Is it something special about the function, well no. Look what happens when we do this:
var someFunc = x.aFunc; //grab aFunc off x and stick it in a variable
someFunc() // this: Window{...}
this
is window
again? Wtf? It turns out this
is defined at the moment you call the function, not when you create the object. So it can change really easily. So how do you know what this
will be? It's actually really easy: look at what's on the left of the function call. That's what will be this
. If there's nothing, it'll be window. Let's try a few examples:
var x = {
aFunc: function () { console.log('this:', this);
};
x.aFunc() // this: x{...}
// ^-- x is on the left, so this is x
var someFunc = x.aFunc; //save aFunc to a variable
someFunc() // this: Window{...}
// ^- there's nothing on the left, so this is window
var y = {};
y.aFunc = x.aFunc; //steal aFunc from x and attach it to y
y.aFunc() //this: y{...}
// ^-y is on the left, so this is y
Capiche?
When functions are used as callbacks, the exact same principles apply, it can just be a bit harder to follow. Here's an example:
setTimeout
takes a function and a delay, and calls the function after a set amount of time:
var x = {
aFunc: function () { console.log('this:', this); }
};
setTimeout(x.aFunc, 1000);
So what will this
be here? It looks like it should be x, right, because x
is to the left of aFunc
? But it's not, it's window
again!
Why? Well, remember, this
will be thing on the left of the function when the function is called. We aren't calling the function here, we are passing it into setTimeout as an argument, and setTimeout will call it later. Which is basically the same as saving the function off to a variable and calling it later.
Let's demonstrate by writing a function which takes a callback and calls it:
var x = {
aFunc: function () { console.log('this:', this); }
};
var takesACallback: function (callback) {
callback();
//^- uh, oh. nothing on the left here...
}
takesACallback(x.aFunc); // this: Window{...}
There are ways to fix this, but we need to learn about call, apply and bind quickly.
Call and apply are basically the same, they just accept arguments slightly differently. They both allow you to specify the this
inside a function, by passing the object you want to be "this" as the first argument.
var myFunc = function () { console.log('this:', this); }
var x = {};
var y = {};
myFunc() // this: Window{...}
myFunc.call(x) //this: x{...}
myFunc.apply(x) //this: x{...}
myFunc.call(y) //this: y{...}
myFunc.apply(y) //this: y{...}
So call and apply allow you to set the this
that you want inside the function. What's the difference? Well if your function takes arguments, you need a way to pass them in. Call let's you pass them as normal. Apply lets you pass them as an array. that's the only difference:
var x = {};
var myFunc = function (a, b) { console.log('this:', this, 'a:', a, 'b:', b); };
myFunc(1,2) // this: Window{...} a:1 b:2
myFunc.call(x,1,2) // this: x{...} a:1 b:2
myFunc.apply(x, [1,2]) // this: x{...} a:1 b:2
//^---^ note we're passing an array with apply
Function bindings allows us to set this
on a function for anytime it's called in the future no matter how without calling the function. Let's just look at an example:
var x = {
myFunc: function () { console.log('this:', this); }
};
var someFunc = x.myFunc.bind(x);
someFunc() // this: x{...}
Woah! So even though there's nothing on the left of someFunc, it's this
is still x
. That's the power of bind. Note that bind doesn't call the function, it just returns a new function, with this
bound to something.
This is really useful for our callbacks example. Remember this:
var x = {
aFunc: function () { console.log('this:', this); }
};
setTimeout(x.aFunc, 1000); // this: Window{...}
Because we passed the function to setTimeout, when it gets called later, this is Window, which kinda sucks, as we probably wanted it to be x. One way to fix this is using bind:
setTimeout(x.aFunc.bind(x), 1000);
A great explanation, thank you.