Javascript this Keyword

The this keyword in javascript can really be confusing if you try to learn it without having a fundamental understanding of execution context. But if you do understand what an execution context is then it should be straight forward to understand how the this keyword works. The first thing we need to know about this is it is determined at call time.

Before getting started with learning the this keyword, it's important to understand what execution context is and understanding scope also helps to clear up any confusion. Make sure to read these two articles first so the this keyword makes sense.

this can have many different values depending on how a function is called. The value of this has everything to do with how a function is invoked, not where a function is declared.

When is the Value of this Set?

Once a function object has been instantiated and the evalaution of code begins, when a function call expression is found foo() the function object's internal [[Call]] method will be called.

[[Call]]

On step 2, PrepareForOrdinaryCall is where the execution context and function environment record or function scope are created. Step 7 is where the new global scope is created.

On step 5, OrdinaryCallBindThis is where the value of this is set.

  1. Let callerContext be the running execution context.
  2. Let calleeContext be PrepareForOrdinaryCall(F, undefined).
  3. Assert: calleeContext is now the running execution context.
  4. If F.[[IsClassConstructor]] is true, then
    1. Let error be a newly created TypeError object.
    2. NOTE: error is created in calleeContext with F's associated Realm Record.
    3. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
    4. Return ThrowCompletion(error).
  5. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
  6. Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)).
  7. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
  8. If result.[[Type]] is return, return result.[[Value]].
  9. ReturnIfAbrupt(result).
  10. Return undefined.

PrepareForOrdinaryCall

  1. Let callerContext be the running execution context.
  2. Let calleeContext be a new ECMAScript code execution context.
  3. Set the Function of calleeContext to F.
  4. Let calleeRealm be F.[[Realm]].
  5. Set the Realm of calleeContext to calleeRealm.
  6. Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
  7. Let localEnv be NewFunctionEnvironment(F, newTarget).
  8. Set the LexicalEnvironment of calleeContext to localEnv.
  9. Set the VariableEnvironment of calleeContext to localEnv.
  10. Set the PrivateEnvironment of calleeContext to F.[[PrivateEnvironment]].
  11. If callerContext is not already suspended, suspend callerContext.
  12. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
  13. NOTE: Any exception objects produced after this point are associated with calleeRealm.
  14. Return calleeContext.

OrdinaryCallBindThis

  1. Let thisMode be F.[[ThisMode]].
  2. If thisMode is lexical, return unused.
  3. Let calleeRealm be F.[[Realm]].
  4. Let localEnv be the LexicalEnvironment of calleeContext.
  5. If thisMode is strict, let thisValue be thisArgument.
  6. Else,
    1. If thisArgument is either undefined or null, then
      1. Let globalEnv be calleeRealm.[[GlobalEnv]].
      2. Assert: globalEnv is a Global Environment Record.
      3. Let thisValue be globalEnv.[[GlobalThisValue]].
    2. Else,
      1. Let thisValue be ! ToObject(thisArgument).
      2. NOTE: ToObject produces wrapper objects using calleeRealm.
  7. Assert: localEnv is a Function Environment Record.
  8. Assert: The next step never returns an abrupt completion because localEnv.[[ThisBindingStatus]] is not initialized.
  9. Perform ! localEnv.BindThisValue(thisValue).
  10. Return unused.

How Can the Value of this Be Determined?

The value of this depends on how the function is invoked, not where it's declared. Here the greet function is declared in the global scope and it's also invoked in the global scope, but notice how the value of this is set to obj using the call method.

JavaScript
const obj = {
  name: 'david'
}

function greet() {
  console.log(`hi ${this.name}`)
}

greet.call(obj) // hi david

The value of this can also be passed to another function.

JavaScript
const obj = {
  name: 'david'
}

function greet() {
  bar(this)
}

function bar(ctx) {
  console.log(`hi ${ctx.name}`)
}

greet.call(obj) // hi david

Below we will go over the different ways that the value of this is determined.

Default Binding

The default this binding is created whenever a function is invoked in global scope. Note that in strict mode you will get undefined.

JavaScript
function foo() {
  console.log(this) // window
}

foo() // here the scope is global

Implicit Binding and Object Methods

This can be confusing, it's often explained wrongly that an object owns a method, then adding to the confusion it's said that you should only use normal functions instead of arrow functions because arrow functions do not create their own this.

The value of this inside of an object is not the object itself, rather it's the same value as this outside of the object.

JavaScript
// this === window
const obj = {
  foo: this === window,
  name: 'david',
  // this === window
  greet: function() {
    // this === window
    return `hi ${this.name}`
  },
  greetAgain: () => {
    // this === window
    return `hi ${this.name}`
  }
}

console.log(obj)
/*
const obj = {
  foo: true,
  name: 'david',
  greet: function() {
    return `hi ${this.name}`
  },
  greetAgain: () => {
    return `hi ${this.name}`
  }
}
*/

The only difference is how the function is invoked as we already mentioned. To call the greet() method we use dot notation. At the time of invocation, the value of this is determined by what is on the left hand side of the dot, which in more technical terms is called a context object.

Here when greet() is called, it's preceded by a reference to a context object called obj which will be used as the value of this.

JavaScript
obj.greet() // logs 'hi david'

The above code could be rewritten as follows:

JavaScript
function greet() {
  return `hi ${this.name}`
}

const obj = {
  name: 'david',
  greet
}

Now we can see there is nothing special about an object method.

Below note that if there is a chain, only the last object property reference matters to the function call.

JavaScript
function greet() {
  console.log(`hi ${this.name}`)
}

const obj = {
  name: 'john',
  obj2
}

const obj2 = {
  name: 'david',
  greet
}

obj.obj2.greet() // `hi david`

Explicit Binding

Explicit binding happens when using call(), apply(), and bind().

This is when we state explicitly what the value of this will be. call() and apply() are basically the same, just the way you pass arguments to them differs. call() accepts a comma seperated list, whereas apply() accepts an array.

JavaScript
function greet() {
  return `hi ${this.name}`
}

const obj = {
  name: 'john',
  greet
}

const obj2 = {
  name: 'david',
  greet
}

obj.greet() // hi john

obj.greet.call(obj2) // hi david

Implicitly Lost

This is a common source of confusion for developers. Let's say you have the following code and you want to save an object's method so you can call it later.

JavaScript
const obj = {
  name: 'david',
  greet() {
    return `hi ${this.name}`
  }
}

const greetMethodForLater = obj.greet

greetMethodForLater() // 'hi '
// 'this' is lost

We stored a reference to the obj.greet method in the greetMethodForLater variable and when we invoked it, it was a normal undecorated function invocation in global scope. When greetMethodForLater() is invoked, there is no context object and this does not refer to obj.

In this situation we need to use bind, call will invoke a function immediately with the specified this value, bind will return a new function with its this value set for later use.

JavaScript
const obj = {
  name: 'david',
  greet() {
    return `hi ${this.name}`
  }
}

const greetMethodForLater = obj.greet.bind(obj)

greetMethodForLater() // 'hi david'

Callbacks

Callbacks are another way we can lose the value of this. The below example doesn't work as we already mentioned because there is no context object, it's just a normal function invocation.

JavaScript
const obj = {
  name: 'david',
  greet() {
    return `hi ${this.name}`
  }
}

const greetMethodForLater = obj.greet

greetMethodForLater() // `hi `

This same thing happens with callbacks. Inside of foo, obj.greet is being assigned to callbackFn.

JavaScript
function foo(callbackFn) {
  callbackFn()
}

const obj = {
  name: 'david',
  greet() {
    console.log(`hi ${this.name}`)
  }
}

foo(obj.greet)

The reason this happens is because inside of the function foo what is happening is that obj.greet is passed in and assigned to callbackFn.

JavaScript
function foo(callbackFn) {
  // callbackFn = obj.greet
  callbackFn()
}

const obj = {
  name: 'david',
  greet() {
    console.log(`hi ${this.name}`)
  }
}

foo(obj.greet)

Here is a more advanced example utilizing the same concept.

Many developers find this confusing because they don't first learn the fundamentals, like execution context for example. So rather than trying to understand how this actually works, you will often see code being written trying to make use of lexical scope rather than using bind like we did above. The reason this happens is because anytime function() {} is invoked it creates a new value of this which normally is just the global value if a context object is not used or if call, apply, or bind are not being used.

The callback below is a normal function and not an arrow function so it creates its own this value which is the window if you run this in a console. For that reason we need to capture the value of this lexically with _this or use bind.

JavaScript
const obj = {
  name: 'david',
  tasks: ['eat', 'clean'],
  listTasks() {
    const _this = this
    this.tasks.forEach(function(task) {
      console.log(`${_this.name} must ${task}`)
    })
  }
}

Here is an example using bind. This code is unnecessary because we can just use an arrow function which will inherit the this value of the surrounding scope.

JavaScript
const obj = {
  name: 'david',
  tasks: ['eat', 'clean'],
  listTasks() {
    this.tasks.forEach(function(task) {
      console.log(`${this.name} must ${task}`)
    }.bind(this))
  }
}

Arrow Functions

Arrow functions inherit their this value from the surrounding lexical scope, they do not create their own this value unlike regular functions. The value of this inside the object is window in the case of a browser. When obj.foo() is called, the value of this is set to the context object, obj, and when obj.bar() is called, because arrow functions do not create a new value of this, the value of this is the surrounding lexical scope which is the window object. Regular functions do define their own this value which is why code such as obj.foo() works.

JavaScript
// this === window
const obj = {
  // this === window
  x: 1,
  foo() {
    console.log(this.i, this)
  },
  bar: () => console.log(this.i, this)
}

Here is another example.

JavaScript
const obj = {
  name: 'david',
  tasks: ['sleep', 'clean'],
  listTasks() {
    // obj is the context object at invocation time
    // obj is the `this` value the arrow function inherits
    this.tasks.forEach(task => {
      console.log(`${this.name} must ${this}`)
    })
  }
}

obj.listTasks()

Remember, arrow functions do not create their own this. A normal function() {} has a this value can that be changed implicitly with dot or bracket notation or explictly using call, apply, or bind.

We noted that arrow functions capture this at call time so lets take a look at another example.

JavaScript
function greet() {
  return (name) => {
    console.log(`hi ${this.name}`)
  }
}

const obj = {
  name: 'david'
}

const obj2 = {
  name: 'john'
}

const foo = greet.call(obj)
foo() // 'hi david'

The reason this happens is because const foo = greet.call(obj) saves the returned function with a captured this which is set to obj. When foo() is invoked, it is invoking the arrow function that was returned from greet().

JavaScript
foo.call(obj2)
// 'hi david'
// not 'hi john' as we might expect

This is due to the fact that the arrow function captured the value of this at call time. When we first called greet.call(obj) the value of this was captured as obj and the arrow function was returned. Any attempt to change the this value of the arrow function will fail.

Get Information About a Function While it's Executing

Variables only exist while a function is executing then they cease to exist, except in the case of closures. During function execution, you can use a debugger statement in your code to pause execution at that point and see what the state of your program looks like.

Do call, apply and bind have a higher priority than using a context object?

If there are multiple rules such as using an implicit binding and an explicit binding one must have a higher priority over the other. Below when invoking obj.greet() 'hi john is logged to the console as we would expect, and invoking obj.greet.call(obj2) logs 'hi david'. This means when we inspect a call site using the execution context stack in our dev tools, if using call, apply or bind, this has a higher priority than just using a context object, i.e. obj.greet().

JavaScript
const obj = {
  name: 'john',
  greet() {
    return `hi ${this.name}`
  }
}

const obj2 = {
  name: 'david',
  greet() {
    return `hi ${this.name}`
  }
}

obj.greet() // 'hi john'

obj.greet.call(obj2) // 'hi david'

new Operator

What happens if we use the new operator with an object?

JavaScript
function setAge(age) {
  this.age = age
}

const obj = {
  name: 'david'
}

// bar is hard bound to obj
const foo = setAge.bind(obj)
foo(30)
console.log(obj.age) // 30

// here foo is called with the new keyword and baz.age === 40 not 30 as we might expect
const bar = new foo(40)
console.log(bar.age) // 40

The new keyword has the highest precedence when determining the value of this. When you examine the call site, ask yourself, is the function using the new binding? if not, is the function using explicit binding? if not, is the function called with implicit binding? If the answer is still no, then we know that the default binding applies.

Review

  • this is determined at function invocation time.
  • Regular functions create a new value of this when they are invoked, this is window in the case of the browser.
  • Arrow functions do not create a new this, they inherit the this of the surrounding lexical scope.
  • The this value is created for a regular function() {} in three ways:
    1. Implicitly - contextObj.foo()
    2. Explictly - foo.call(someObj)
    3. With the new operator - new someConstructorFunction()
  • The order of precendence in determing the the value of this occurs in this order from highest to lowest.
    1. new operator
    2. call, apply, and bind
    3. Using a context obj, i.e. contextObj.foo()
    4. Default binding, ie. foo()