The this Keyword and Binding Rules in JavaScript

The this keyword is a fundamental concept in JavaScript that often confuses developers due to its dynamic nature. It plays a crucial role in how functions access their execution context, and understanding its binding rules is essential for writing accurate and efficient code. This lesson provides an in-depth exploration of how this is determined in various scenarios, including different function invocation patterns and the use of arrow functions.

Understanding the this Keyword

What is this?

  • Definition: In JavaScript, this is a keyword that refers to an object that is executing the current piece of code.
  • Dynamic Binding: The value of this is not set at the time of definition; it depends on how the function is called (runtime binding).

Why is this Important?

  • Accessing Object Properties: Methods can access the object they belong to using this.
  • Dynamic Context: Functions can be reused with different contexts.
  • Object-Oriented Programming: Essential for implementing classes and constructors.

Binding Rules for this

JavaScript determines the value of this based on the following binding rules, applied in order:

  1. Default Binding
  2. Implicit Binding
  3. Explicit Binding
  4. New Binding
  5. Arrow Functions

Default Binding

  • Context: When a function is called without any context (standalone function invocation).
  • Non-Strict Mode: this refers to the global object (window in browsers, global in Node.js).
  • Strict Mode: this is undefined.

Example (Non-Strict Mode):

function showThis() {
  console.log(this)
}

showThis() // Outputs: Window object (in browsers)

Example (Strict Mode):

'use strict'

function showThis() {
  console.log(this)
}

showThis() // Outputs: undefined

Implicit Binding

  • Context: When a function is called as a method of an object.
  • Binding Rule: this refers to the object before the dot notation used to invoke the function.

Example:

const person = {
  name: 'Alice',
  greet: function () {
    console.log(`Hello, my name is ${this.name}.`)
  },
}

person.greet() // Outputs: Hello, my name is Alice.

Explanation:

  • this refers to person because greet is called as person.greet().

Explicit Binding

  • Context: When using .call(), .apply(), or .bind() to invoke a function.
  • Binding Rule: this is explicitly set to the first argument provided to .call(), .apply(), or .bind().

Using .call() and .apply():

  • .call(thisArg, arg1, arg2, ...): Invokes the function with this set to thisArg and arguments passed individually.
  • .apply(thisArg, [argsArray]): Invokes the function with this set to thisArg and arguments passed as an array.

Example:

function introduce(language1, language2) {
  console.log(`I'm ${this.name} and I speak ${language1} and ${language2}.`)
}

const person = { name: 'Bob' }

introduce.call(person, 'English', 'Spanish')
// Outputs: I'm Bob and I speak English and Spanish.

introduce.apply(person, ['French', 'German'])
// Outputs: I'm Bob and I speak French and German.

Using .bind():

  • Definition: Creates a new function with this permanently set to the provided value.
  • Usage:
const boundFunction = introduce.bind(person, 'Italian', 'Portuguese')
boundFunction() // Outputs: I'm Bob and I speak Italian and Portuguese.

New Binding

  • Context: When a function is invoked as a constructor using the new keyword.
  • Binding Rule: this refers to the newly created object.

Example:

function Person(name) {
  this.name = name
  console.log(this)
}

const charlie = new Person('Charlie')
// Outputs: Person { name: 'Charlie' }

Explanation:

  • A new object is created, and this refers to that object.
  • The properties and methods are added to the new object.

Arrow Functions and this

  • Definition: Arrow functions (()=>{}) do not have their own this binding.
  • Binding Rule: this in an arrow function refers to this in the enclosing (lexical) scope.
  • Cannot be used as constructors: Arrow functions cannot be used with new.

Example:

const person = {
  name: 'Diana',
  greet: function () {
    const innerFunc = () => {
      console.log(`Hello, my name is ${this.name}.`)
    }
    innerFunc()
  },
}

person.greet() // Outputs: Hello, my name is Diana.

Explanation:

  • The arrow function innerFunc inherits this from greet, which is person.

Contrast with Regular Function:

const person = {
  name: 'Eve',
  greet: function () {
    function innerFunc() {
      console.log(`Hello, my name is ${this.name}.`)
    }
    innerFunc()
  },
}

person.greet() // Outputs: Hello, my name is undefined.

Explanation:

  • innerFunc is a regular function, so this defaults to the global object (undefined in strict mode).

Determining this Value: Precedence

When multiple binding rules apply, JavaScript determines the value of this based on the following precedence (from highest to lowest):

  1. Is the function called with new?
    • Yes: this is the newly created object.
  2. Is the function called with .call(), .apply(), or .bind()?
    • Yes: this is explicitly set to the provided object.
  3. Is the function called as a method of an object (implicit binding)?
    • Yes: this is the object before the dot.
  4. Is the function an arrow function?
    • Yes: this is the enclosing lexical scope's this.
  5. Default:
    • In strict mode: this is undefined.
    • In non-strict mode: this is the global object.

Practical Examples and Code Analysis

Example 1: Combining Binding Rules

function showName() {
  console.log(this.name)
}

const person1 = { name: 'Frank', showName: showName }
const person2 = { name: 'Grace' }

person1.showName() // Outputs: Frank (Implicit binding)
showName.call(person2) // Outputs: Grace (Explicit binding)
new showName() // Outputs: undefined (New binding)

Explanation:

  • person1.showName() uses implicit binding; this refers to person1.
  • showName.call(person2) uses explicit binding; this refers to person2.
  • new showName() uses new binding; this refers to the new object created by new.

Example 2: Arrow Functions and this

const obj = {
  value: 42,
  regularFunc: function () {
    console.log(this.value)
  },
  arrowFunc: () => {
    console.log(this.value)
  },
}

obj.regularFunc() // Outputs: 42
obj.arrowFunc() // Outputs: undefined (or error in strict mode)

Explanation:

  • regularFunc uses implicit binding; this refers to obj.
  • arrowFunc inherits this from the enclosing scope, which is the global scope in this case.

Example 3: Methods Borrowed from Other Objects

const obj1 = {
  name: 'Heidi',
  greet: function () {
    console.log(`Hi, I'm ${this.name}.`)
  },
}

const obj2 = { name: 'Ivan' }

obj2.greet = obj1.greet
obj2.greet() // Outputs: Hi, I'm Ivan.

Explanation:

  • greet is assigned to obj2.
  • When obj2.greet() is called, this refers to obj2.

Example 4: Losing this Context

const obj = {
  name: 'Jack',
  getName: function () {
    return this.name
  },
}

const getNameFunction = obj.getName
console.log(getNameFunction()) // Outputs: undefined (or error in strict mode)

Explanation:

  • getNameFunction is assigned obj.getName, but when called, it uses default binding.
  • this is undefined in strict mode or the global object in non-strict mode.

Solution: Use .bind()

const boundGetName = obj.getName.bind(obj)
console.log(boundGetName()) // Outputs: Jack

Best Practices

Use Arrow Functions for Lexical this

  • Use arrow functions when you need to access this from the enclosing scope.
  • Avoid using arrow functions as methods in objects intended to use this.

Example:

const obj = {
  count: 0,
  increment: function () {
    const innerFunc = () => {
      this.count++
    }
    innerFunc()
  },
}

obj.increment()
console.log(obj.count) // Outputs: 1

Avoid Arrow Functions as Object Methods

  • Arrow functions cannot have their this overridden.
  • If you need this to refer to the object, use regular functions.

Example:

const obj = {
  value: 10,
  getValue: () => {
    console.log(this.value)
  },
}

obj.getValue() // Outputs: undefined

Solution:

const obj = {
  value: 10,
  getValue: function () {
    console.log(this.value)
  },
}

obj.getValue() // Outputs: 10

Use .bind() to Set this Permanently

  • Use .bind() when you need to ensure a function always has the same this.

Example:

function logValue() {
  console.log(this.value)
}

const obj = { value: 5 }
const boundLogValue = logValue.bind(obj)

boundLogValue() // Outputs: 5

Be Careful with Callbacks and Event Handlers

  • When passing methods as callbacks, the this context may be lost.

Example:

const obj = {
  value: 'Hello',
  getValue: function () {
    console.log(this.value)
  },
}

setTimeout(obj.getValue, 1000) // Outputs: undefined

Solution: Use Arrow Function or .bind()

setTimeout(() => obj.getValue(), 1000)
// or
setTimeout(obj.getValue.bind(obj), 1000)

Exercises

Exercise 1: Identifying this

Question:

What will be the output of the following code?

var name = 'Global'

const person = {
  name: 'Alice',
  getName: function () {
    return this.name
  },
}

const getName = person.getName

console.log(person.getName()) // Output?
console.log(getName()) // Output?

Answer:

  • person.getName() outputs 'Alice' because this refers to person.
  • getName() outputs 'Global' (or undefined in strict mode) because this refers to the global object.

Exercise 2: Binding with .bind()

Question:

Modify the following code so that getName() always returns the correct name property.

const person = {
  name: 'Bob',
  getName: function () {
    return this.name
  },
}

const getName = person.getName
console.log(getName()) // Outputs: undefined

Answer:

Use .bind() to permanently bind this:

const getName = person.getName.bind(person)
console.log(getName()) // Outputs: 'Bob'

Exercise 3: Arrow Functions and this

Question:

Explain why the following code outputs undefined and how to fix it.

const user = {
  name: 'Carol',
  greet: () => {
    console.log(`Hello, ${this.name}`)
  },
}

user.greet() // Outputs: Hello, undefined

Answer:

  • Arrow functions do not have their own this and inherit from the global scope.
  • In the global scope, this.name is undefined.

Fix: Use a regular function:

const user = {
  name: 'Carol',
  greet: function () {
    console.log(`Hello, ${this.name}`)
  },
}

user.greet() // Outputs: Hello, Carol

Exercise 4: Understanding new Binding

Question:

What will be the output of the following code?

function Person(name) {
  this.name = name
  this.getName = function () {
    return this.name
  }
}

const person1 = Person('Dave')
console.log(name) // Output?

const person2 = new Person('Eve')
console.log(person2.getName()) // Output?

Answer:

  • Person('Dave'); is called without new, so this refers to the global object. It sets name on the global object.
  • console.log(name); outputs 'Dave'.
  • new Person('Eve'); creates a new object, and this refers to that object.
  • person2.getName(); outputs 'Eve'.

Exercise 5: Combining Binding Rules

Question:

Predict the output of the following code:

function sayHello() {
  console.log(`Hello, ${this.name}`)
}

const obj1 = { name: 'Frank' }
const obj2 = { name: 'Grace' }

sayHello() // Output?
sayHello.call(obj1) // Output?
sayHello.apply(obj2) // Output?
const boundSayHello = sayHello.bind({ name: 'Heidi' })
boundSayHello() // Output?
new sayHello() // Output?

Answer:

  • sayHello(); outputs Hello, undefined (or Hello, in strict mode) because this refers to the global object, and name is undefined.
  • sayHello.call(obj1); outputs Hello, Frank because this is explicitly set to obj1.
  • sayHello.apply(obj2); outputs Hello, Grace because this is explicitly set to obj2.
  • boundSayHello(); outputs Hello, Heidi because this is bound to { name: 'Heidi' }.
  • new sayHello(); outputs Hello, undefined because this refers to the new empty object, which doesn't have a name property.

Understanding how this works in JavaScript, including its binding rules and behavior in different contexts, is crucial for writing reliable and maintainable code. By mastering the intricacies of this, you'll be better equipped to handle object-oriented programming patterns, avoid common pitfalls, and confidently tackle this-related questions in technical interviews.

Practice Problems

Arrow Function and ContextDifficulty: Medium

Why does using an arrow function as a method in an object result in `this` being undefined?

Loading...

JavaScript Context of thisDifficulty: Medium

What is the value of `this` in different contexts in JavaScript?

Loading...

How does the `.call()` method differ from `.bind()` in setting the value of `this`?

Loading...

Explain how the `new` keyword affects the value of `this` in a constructor function.

Loading...

Code Execution OutcomeDifficulty: Hard

What is the outcome of the following code, and why?

Loading...

Let's continue exploring the next page. Take your time, and proceed when you're ready.

Lesson completed?

Found a bug, typo, or have feedback?

Let me know