Understanding 'this'.

Understanding 'this'.

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:

  1. 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.
  2. 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.
  3. There is an exception to the point above, which we will cover later. There are some differences when used under strict mode and non-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.

  1. Under the strict mode, this is undefined. (Check Notes below if you are curious why.)
  2. 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 the window 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:

  1. More often than not, refer to the call site. (except for in cases of .bind(), of course.)
  2. If a dot notation is used, whatever is on the left of it, is the this.
  3. In case .call() or .apply() is used, the parameter passed in before the first comma is the this.
  4. Unless strict mode is used, and the function is a stand alone, look for the global context, that's this.

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.