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.

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.

this is not scope. Scope is another term for an Environment Record. Each time a function is invoked a new execution context is created, then a new Function Environment Record or function scope is created, and its LexicalEnvironment and VariableEnvironment state components are pointed to the new function environment record.this is a value that is set when a function objects internal [[Call]] method is called, it is set in the OrdinaryCallBindThis abstract operation.

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'
Bind only works once! You cannot use bind two times in a row.
JavaScript
function greet() {
  console.log(`hi ${this.name}`)
}

const obj1 = {
  name: 'john'
}

const obj2 = {
  name: 'david'
}

const boundFn = greet.bind(obj1)
boundFn() // 'hi john'

const boundFn2 = boundFn.bind(obj2)
boundFn2() // 'hi john'

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.

Let's see how the forEach would work. Pay attention to the highlighted line, we have to bind this

JavaScript
function forEach(array, callbackFn) {
  for (let i = 0; i < array.length; i++) {
    callbackFn(array[i], i, array)
  }
}

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

forEach(obj.tasks, obj.listTasks)
// 'must eat'
// 'must clean'

This example is a little bit harder but it is a great example to understand why arrow functions can be so useful. Now inside the forEach function let's see what it looks like at invocation time.

JavaScript
function forEach(array, callbackFn) {
  array = ['eat', 'clean']
  callbackFn = obj.listTasks

  for (let i = 0; i < array.length; i++) {
    callbackFn(array[i], i, array)
  }
}

For each iteration of the loop, the callback function is invoked but without a context object. We could use bind and pass in a callback that has had its this value bound like this obj.listTasks.bind(obj).

JavaScript
function forEach(array, callbackFn) {
  for (let i = 0; i < array.length; i++) {
    callbackFn(array[i], i, array)
  }
}

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

forEach(obj.tasks, obj.listTasks.bind(obj))

But what if we wanted to pass the callback as an inline function like usually is the case?

JavaScript
function forEach(array, callbackFn) {
  for (let i = 0; i < array.length; i++) {
    callbackFn(array[i], i, array)
  }
}

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

The code would look like this inside of the forEach function.

JavaScript
function forEach(array, callbackFn) {
  array = ['eat', 'clean']

  // callbackFn has been assigned to a function that
  // has already been bound to obj
  callbackFn = function(task, index, array) {
    console.log(`${this.name} must ${task}`)
  }

  for (let i = 0; i < array.length; i++) {
    callbackFn(array[i], i, array)
  }
}

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.

Arrow functions inherit the lexical context from where they are defined which means this code will not work because of where the arrow function was defined, not where it's used.

Here the value of this the arrow function captures is the window.

JavaScript
const logTask = task => {
  console.log(`${this.name} must ${task}`)
}

const obj = {
  name: 'john',
  tasks: ['clean', 'eat'],
  listTasks() {
    this.tasks.forEach(logTask)
  }
}

obj.listTasks()
// must clean
// must eat

Here the this value the arrow function captures is the context object that was used when obj.listTasks() is invoked.

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

obj.listTasks()
// david must eat
// david must clean

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.

JavaScript
const obj = {
  name: 'john',
  tasks: ['sleep', 'clean'],
  listTasks() {
    debugger
    this.tasks.forEach(task => {
      debugger
      console.log(`${this.name} must ${this}`)
    })
  }
}

obj.listTasks()

Now when looking at the call stack, the call site can be found and this can be determined.

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()