Most, if not all early stage programmers struggle with the concept and usage of this
keyword. It is one of those concepts in JS and, well programming in general, that is simple but when given the right amount of power can be a very crucial one. I know, I personally struggled with it in the beginning, so here are some of my learnings and tips to hopefully help you.
What the f**k is 'this'?
Simply put, this
points to the current execution context of whatever piece of JS code that contains it. But the world isn't that simple, after all.
Let's break into some concepts here:
- In Javascript, every function, method, object, etc. while executing has a reference to its current execution context (aka the context in which this piece of code was invoked). And
this
is that reference. - When trying to figure out the context for
this
, we only care about where and how the function is invoked, almost nothing in terms of declaration matters. - There is an exception to the point above, which we will cover later. There are some differences when used under
strict
mode andnon-strict
mode as well.
Types of 'this' bindings
1. Implicit binding:
To understand let's look at an example first,
var MyExampleObj = function (){
this.city = 'Calgary';
this.myProperty = 'property';
};
MyExampleObj.prototype.doSomeMagic = function (weather) {
console.log(this.city + ' is ' + weather + '!');
}
var obj = new MyExampleObj();
obj.doSomeMagic('cold'); // prints 'Calgary is cold!'
In the example above, we created an object called obj (creative huh ;)) as a new instance of MyExampleObject. We then call the method doSomeMagic() on this obj, which makes obj the current execution context aka this
of the invocation. This way of binding is called implicit binding.
2. Explicit binding:
Explicit binding happens when, you guessed it... we explicitly pass in a this
context to .apply()
or .call()
functions. bind()
is another one but its special, and we shall discuss it in a bit.
Both .call()
and .apply()
take an execution context as their first argument. So by using them at the time of invoking a function, one can explicitly pass in a context for this
.
.call()
and .apply()
look like as follows:
someExFunc.call(thisContext, param1, param2, ... );
someExFunc.apply(thisContext, [param1, param2, ...]);
3. Default binding:
Default binding can differ based on strict
or non-strict
mode of JS.
- Under the
strict
mode,this
isundefined
. (Check Notes below if you are curious why.) - Otherwise,
this
acts as a global context, and that context depends on where the call site is. For example, when in browsers,this
will refer to thewindow
context.
And now, time for the exceptions (drumrolls...)
With .bind():
When .bind()
is called on a function, it sets a this
context and returns a new function of the same name but this new function has a bound this
context.
For example, consider:
var myCity = function () {
console.log('I live in ' + this.name);
};
var nyc = {
name: 'New York City'
}
var myCity = myCity.bind(nyc);
myCity(); // 'I live in New York City'
Now, each time we invoke myCity
, we will get the context of nyc
, because this
has been bound to it.
Some tips on finding out the 'this' context:
- More often than not, refer to the call site. (except for in cases of
.bind()
, of course.) - If a dot notation is used, whatever is on the left of it, is the
this
. - In case
.call()
or.apply()
is used, the parameter passed in before the first comma is thethis
. - Unless
strict
mode is used, and the function is a stand alone, look for theglobal
context, that'sthis
.
To conclude, this
might seem like a tricky concept to early developers, but it doesn't have to be. Call site will more often than not be your friend here. Hope this helps, happy coding!
Notes:
In ES3, there was a big confusion because often developers would use the constructor pattern
and forget to use the new
keyword and this would, in turn, cause the this
to reference the global
object (window
object in browsers) and risk clobbering it with many variables.
That was a terrible behaviour and so people at ECMA decided to set this
to undefined
in ES5.