Codementor Events

The "this" binding in JavaScript

Published Sep 20, 2020Last updated Mar 18, 2021
The "this" binding in JavaScript

In this article I talk about what I've learned about how to know where this points to in a given function. Basically this is me sharing with you, in my own words how to do so.

And yes, I did that weird drawing at the top ๐Ÿ˜€

Firstly, it is important to understand that the this binding is not determined when a function is declared, instead, it is determined when a function is invoked, and also based on how that function was invoked.

Step 1: WHERE

The first thing we need to do is find where the function was invoked in our program. It could have been invoked from either the global execution context or from a local execution context , and the only way to find our function's call-site (besides watching directly in our code) is by looking at the call stack. Here's a very simple example that you can try in the console in order to see the stack.

First, copy and paste the following code in your browser's console:

function baz() {
    bar()
}

function bar() {
    foo()
}

function foo() {
    debugger
}

baz()

Then, in the devtools, under the sources tab, and then under the Call Stack section, you will see a list of functions. This way we can know for sure that foo() call-site is bar() , and bar() call-site is baz(), and finally baz() call-site is the global execution context, which in this case is shown as anonymous.

foo         (VM431:10)
bar          (VM431:6)
baz          (VM431:2)
(anonymous) (VM431:13) 

Alt Text

Now that we know how to find our function (where) , let's talk about the set of rules that determine the this binding (how) .

Step 2: HOW

When a function is invoked, a new Local Execution Context is created. The Local Execution Context has information about the function (its place in the call stack, the arguments length and - among other things - a property called this).

The value of the this (what object is it pointing to) is determined based on how the function is invoked.

We can invoke our functions in 4 different ways, following 4 different rules, namely:

  • Default Binding
  • Implicit Binding
  • Explicit Binding
  • New Binding

Extra: I will also talk about how the this binding is determined on arrow functions.

Default Binding

var x = 20

function foo() {
  console.log(this.x)
}

foo.x = 40

foo()  // 20 

A default binding is made when we do a regular function call, like we did here with foo(). In non-strict mode the this binding will reference the global object, but on strict mode it will be undefined.

It's worth mentioning that in the first line we declare a variable x and assign the value of 20. And this is like doing window.x = 20. Long story short, a property is created in the global object, and this is the reason why this.x is 20.

When foo is invoked, something like this happens under the hood:

foo.call(window)   // non-strict

foo.call(undefined)   // strict

Even though we will revisit this subject later in one of the 4 rules, I'll briefly explain what's the call() method doing here: The call() method is explicitly setting to what object this will be bound to.

Implicit Binding

When we invoke a function in the context of an object, this will point to that object. Let's take a look at the following code:

var x = 20 

const myObj = {
  x: 50,
  foo: function() {
     console.log(this.x)
  }
}

myObj.foo() // 50

I would like to clarify that the anonymous function declaration in myObj.foo (aka method, since it is declared inside an object) does not belong to myObj. Remember that since functions are callable objects, they are assigned by reference (like all objects are), unlike the primitive values, which are assigned by copy.

In order to illustrate my point, consider the following code:

var x = 20 

const myObj = {
  x: 50,
  foo: function() {
     console.log(this.x)
  }
}

myObj.foo()  // 50

const foo = myObj.foo
foo()  // 20

When we declare const foo, we assign a reference to the same function myObj.foo is pointing to, and then, by doing a stand-alone invocation of foo, the default binding rule is applied, and since we're not using strict-mode, this will point to the global object, in this case, the window.

As you can see, and like I said before, the binding of this is not determined when the function declared, but when the function is invoked and most importantly on how that function is invoked.

Explicit Binding

All functions have access to three different methods that allow us to invoke them and explicitly set the object that the this will be bound to. I'm talking about the call(), apply() and bind() methods.

Consider the following code:

const obj = {
  x: 'Hi there'
}

function foo(name, age) {
  console.log(
    `${this.x}, my name is ${name}, and I'm ${age} years old`
  )
}

foo.call(obj, 'Diego', 31)  
// 'Hi there, my name is Diego, and I'm 31 years old'

foo.apply(obj, ['Diego', 31])  
// 'Hi there, my name is Diego, and I'm 31 years old'

const bar = foo.bind(obj, 'Diego', 31)
bar()  // 'Hi there, my name is Diego, and I'm 31 years old'

Let's talk about each of the call methods in our snippet:

  • call() : Invokes and receives (as its first parameter) an object that will explicitly be bound to this. It also receives the function's arguments separated by a comma.

  • apply() : It does the same thing as call(), but the only difference is that the arguments are passed inside an array.

  • bind() : It's also similar to call() but instead of immediately invoking the function, it returns a function with this bound to the object passed as its first argument. In this snippet we store the returned function in a const and below that we make the invocation.

New Binding

A function invocation with the new keyword at the beginning is referred to as a constructor call. Let's now consider the following code snippet:

function foo(name, age) {
   this.name = name
   this.age = age
}

const bar = new foo('Diego', 31)

console.log(
`My name is ${bar.name}, and I'm ${bar.age} years old`
) 

// My name is Diego, and I'm 31 years old

When we do a constructor call on the foo method, this is what happens:

  1. First, it creates and return a new object. Something like Object.create({}).

  2. this will point to the newly created object, which in this case is: bar.

  3. And lastly, the newly created object is linked to the function's prototype. In other words, the bar object delegates its [[Prototype]] / __proto__ to the foo's prototype object.

Alt Text

Just as a refresher, all functions have a prototype object. It only has one property, constructor , which happens to be a reference to the function itself.

foo.prototype
/*
Output:

{ constructor: ฦ’ foo(name, age), __proto__: Object.prototype }
*/

bar.__proto__    

// or

Object.getPrototypeOf(bar)

/* 
Output:

{ constructor: ฦ’ foo(name, age), __proto__: Object.prototype }
*/

foo.prototype === bar.__proto__  // true
foo.prototype === Object.getPrototypeOf(bar) // true

These are the 4 rules that will determine the this binding of a function. So now we know the questions we need to ask ourselves in order to know where this is pointing, namely:

  • where has the function been invoked?
  • how the function was invoked?

Arrow functions and this

But there's one more thing to consider...

Unlike the 4 rules above, the this binding in arrow functions is determined by its parent scope. In other words, the this binding of an arrow function is the same as its container function:

var name = 'Global'

function foo() {

  const bar = () => {
      console.log(this.name)
  }
  
  return bar
}

const obj = {
  name: 'Diego'
}

const fn = foo()
fn()  // 'Global'

const fn2 = foo.call(obj)
fn2()  // 'Diego'

When the foo function is invoked, the arrow function will inherit the this from foo.

In const fn = foo() since foo() invocation is a regular/normal function call, the Default Binding rule is applied, so in this case the foo's this points to the window object (if we are on strict mode it will be undefined).

But, in const fn2 = foo.call(obj) , the Explicit Binding rule is applied, since we're explicitly setting the obj that will be bound to foo's this, which is the obj object.

And even if we do a fn2() (invoking our returned arrow function) which according to the 4 rules is a Default Binding, it will ignore those rules, and use the this binding of foo's invocation, in this case obj.

Final words

Like I said at the begging, this post is me writing in my own words what I learned from the YDKJS book series, specifically the from the this & Object Prototypes book from Kyle Simpson. I fully recommend all the books from the series.

Discover and read more posts from Diego Palacios Lepore
get started
post commentsBe the first to share your opinion
Hwan
2 years ago

Thank you
No matter how much I read the book, I couldnโ€™t understand itโ€ฆ but Your writing is easy to understand.

Sakar SR
4 years ago

Really a wonderful article on JS [this] concept. After a long period of time Iโ€™m happy to read a good blog. Keep the good work goingโ€ฆ
Good health and happy blogging.

Diego Palacios Lepore
4 years ago

Thank you Sakar! Iโ€™m glad you liked it and found it useful! ๐Ÿ‘‹ ๐Ÿ™‚

Show more replies