Understanding 'this'

mpratapdev

Mahesh Pratap

Posted on November 30, 2020

Understanding 'this'

One of the most confusing mechanisms in JavaScript is this keyword. Even a seasoned developer can get bewildered sometimes. So, let’s see if it's really that complicated!

this is a special keyword that’s automatically defined in the scope of every function. But how JavaScript engines decide what value should it assign to this.

Default Binding

In default binding, this refers to the global object. Any plain undecorated function call will invoke default binding if the scope in which this is used is not using strict mode. Consider:

function foo() {
  console.log(this.a);
}

var a = 2;

foo(); // 2
Enter fullscreen mode Exit fullscreen mode

Variables declared in global scope as var a = 2, function foo() {…} are synonymous with global-object properties of the same name. They are not copies of each other, they are each other. In this snippet foo() is called with a plain, undecorated function reference. Hence, the default binding applies here i.e., global object.

If strict mode is in effect, the global object is not eligible for the default binding, so the this is instead set to undefined.

function foo() {
  "use strict";
  console.log(this.a);
}

var a = 2;

foo(); // TypeError: 'this' is 'undefined'
Enter fullscreen mode Exit fullscreen mode

Although overall this binding rules are entirely based on the call-site, the global object is only eligible for the default binding if the contents of foo() are not running in strict mode. The strict mode state of the call-site of foo() is irrelevant.

Implicit Binding

Implicit binding is invoked if the call-site has a context object. Now, what’s a context object!
Consider:

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo
};

obj.foo(); // 2
Enter fullscreen mode Exit fullscreen mode

Here the call-site uses the obj context to reference the function, so we can say obj 'owns' or 'contains' the function reference at the time function is called. Now consider this:

function foo() {
  console.log(this.a);
}

var obj = {
  a: 12,
  foo: foo
};

var obj1 = {
  a: 2,
  obj: obj
};

obj1.obj.foo(); // 12
Enter fullscreen mode Exit fullscreen mode

This may look confusing but only the last level of an object property reference chain matters to the call-site. So this in foo() refers to obj for the above function call.

Gotchas

Consider the following code:

function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo
}

var bar = obj.foo;

var a = 'Oops, global';

bar(); // 'Oops, global'
Enter fullscreen mode Exit fullscreen mode

Even though bar appears to be a reference to obj.foo, in fact, it’s really just another reference to foo itself. Moreover, the call-site is what matters, and the call-site is bar(), which is a plain, undecorated call, and thus the default binding applies.

There is one more way to lose implicit binding i.e. when we are using callbacks. You don’t know how your passed function is being called in the callback. Trust issues 😐️

Are default and implicit binding the same?

Consider the following code:

function foo() {
  console.log(this.a);
}

var a = 2;

window.foo(); // 2
Enter fullscreen mode Exit fullscreen mode

As discussed earlier variables declared in the global scope are synonymous with global-object properties of the same name. We can refer to these variables as window.a, window.foo respectively.

So whenever we are using plain, undecorated functions calls that means its context object is window and hence we get window as this in those cases.

Explicit Binding

Now let’s take the controls into our hands 😎️.

Alt Text

Functions have some utilities available to them (via their [[Prototype]]), which can be useful for this task. Specifically bind(…), call(…), and apply(…) methods.

call(), apply() & bind()

  • We can specify what we want our this to be, using these functions.
  • One common thing about these functions is, it takes the first parameter - an object which is assigned as this on function invocation.
  • call() & apply() invokes the function immediately while bind() just returns a new function.
function foo() {
  console.log(this.a);
}

var obj = {
  a: 2
};

foo.call(obj); // 2
foo.apply(obj); // 2

var bar = foo.bind(obj);

bar(); // 2
Enter fullscreen mode Exit fullscreen mode

These are very useful functions in JavaScript and can’t be covered in this post so we’ll talk about these functions in-depth in another article.

new Binding

When a function is invoked with new in front of it, the following things happen:

  • A brand new object is created out of thin air.
  • The newly constructed object is set as the this binding for that function call.
function foo(a) {
  this.a = a;
}

var bar = new foo(2);

console.log(bar.a); // 2 
Enter fullscreen mode Exit fullscreen mode

The new foo(2) will return {a:2} in the above snippet.

There are few other things that happen too, but that is out of scope for this topic.

Conclusion

Once you know the call-site of a function, 4 rules can be applied to the call-site, in this order of precedence to determine this binding:

  • Called with new? Use the newly constructed object.
  • Called with call(), apply(), bind() ? Use the specified object.
  • Called with context object owning the call? Use that context object.
  • Default: undefined in strict mode, global object otherwise.

For a more in-depth understanding of this I highly recommend reading this & OBJECT PROTOTYPES of YOU DON’T KNOW JS series by Kyle Simpson.

💖 💪 🙅 🚩
mpratapdev
Mahesh Pratap

Posted on November 30, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Understanding 'this'
javascript Understanding 'this'

November 30, 2020