Demystifying this, call, apply and bind

Demystifying this, call, apply and bind.

The this keyword is one of the concepts you must encounter in your journey as a JavaScript developer. Yet, it is misunderstood by a lot of developers. this is determined by the context of the code being executed. It varies implicitly or explicitly using call, apply and bind.

In this article, you'll learn what this refers to and how to determine its value implicitly and explicitly using call, apply and bind.

To determine the context of this keyword, you need to look at where the fuction is invoked.

Implicit Context of this

There are different contexts of the this keyword when used implicitly.

  • Global object
  • Method of an Object
  • Constructor

Global Object

When you're working with the browser,this refers to the Window object.

The output when logged to the console is:

Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}

You may be surprised to know that the value of this in a top-level function is also the Window object.

function learnToSoar() {
  console.log(this);
}

learnToSoar(); // Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}

However, using the strict mode, you can combat this weird behaviour.

'use strict'

function learnToSoar() {
  console.log(this);
}

learnToSoar(); // undefined

Method of an Object

A method of an object refers to the properties of the object using the this keyword.

Given an object;

 const person = {
  name: 'Chiamaka',
  scope: 'web',
  info() {
   return `${this.name} writes articles about the ${this.scope} platform.`;
  }
 }

To invoke the info method of the person object,

person.info(); // "Chiamaka writes articles about the web platform."

Now, to determine what the this keyword within the object references, take a look at the left hand side of the dot notation. It is apparent that the value of this in the context is the person object.

Let's take this further using a nested object to get a better grasp of the concept,

 const person = {
  name: 'Chiamaka',
  watchword: 'Find your greatest life and live it.',
  scope: 'web',
  readAnalogy() {
    return `${this.watchword} ${this.name} and keep writing about the
    ${this.scope} platform`;
  },
  friend: {
    name: 'Rita',
    watchword: 'Don\'t sink into the life you have now.',
    readAnalogy() {
      return `Keep winning ${this.name}. ${this.watchword}`;
    }
  },
 }

In the snippet above, this.name within the person object refers to person.name whereas within the friend object, it refers to person.friend.name.

Constructor

You can use the new keyword to create an instance of a class. In the constructor context this is bound to the instance of the class.

class Person {
  constructor(name, watchword) {
    this.name = name,
    this.watchword = watchword,
  }

  readAnalogy() {
    return `${this.name}'s watchword is "${this.watchword}"`;
  }
}


const human = new Person('Uzoma',
'Be at the right place at the right time, with the right person.');

human.readAnalogy(); //"Uzoma's watchword is 'Be at the right place at the right time, with the right person.'"

In the example above, this is bound to the instance of Person which is human.

Event Handler

The context of this for event handlers called using addEventListener is the element that is being listened to event.currentTarget or event.target.

<button class='cta_button'>
  Click the button
</button>
const button = document.querySelector('.cta_button');

button.addEventListener('click', function(event) {
  console.log(this) //"<button>Click me</button>"
})

Explicit Context of this

You can use call, apply, or bind to determine the context of this explicitly.

Call

The call() method calls a function with a given this value and arguments provided individually.

Given the object,

 const person = {
  name: 'Chiamaka',
  watchword: 'Find your greatest life and live it',
  scope: 'web'
 }

and the standalone function,

  function readAnalogy() {
    return `${this.watchword} and keep writing about the
    ${this.scope} platform`;
  }

Looking at where the readAnalogy function is invoked, the context of this is Window.

The output of readAnalogy() would be:

"undefined and keep writing about the undefined platform"

To invoke readAnalogy with the this keyword referencing the person object, using person.readAnalogy() would not work. It would output:

Uncaught TypeError: person.readAnalogy is not a function at <anonymous>

The call and apply methods are used to achieve this purpose. To invoke readAnalogy within the person context, pass the person as an argument to the call method. Simply put, the first argument passed to the call method is what the this keyword inside that function will reference.

readAnalogy.call(person);

//or

readAnalogy.apply(person);

To pass arguments to the function being invoked with the call method, it should be passed after the first argument which is the execution context.

const person = {
  name: 'Chiamaka',
  jobTitle: 'Software Engineer'
}

function describe(stack1, stack2, stack3) {
  return `My name is ${this.name} and I am a ${this.jobTitle}
  building solutions using ${stack1}, ${stack2} and ${stack3}`
}

const stack = ['JavaScript', 'React', 'Redux']

describe.call(person, stack[0], stack[1], stack[2])

Given a lot of arguments, using the call method becomes tiring. The apply method solves the problem.

Apply

The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).

This is similar to the call method. However, using the apply method, you pass an array as the second argument which is spread out as arguments.

When you try to use the syntax for call on apply

describe.apply(person, stack[0], stack[1], stack[2])

The output is

VM505:1 Uncaught TypeError: CreateListFromArrayLike called on non-object
at <anonymous>:1:10

To use apply, the code above will be refactored to:

const person = {
  name: 'Chiamaka',
  jobTitle: 'Software Engineer'
}

function describe(stack1, stack2, stack3) {
  return `My name is ${this.name} and I am a ${this.jobTitle}
  building solutions using ${stack1}, ${stack2} and ${stack3}`
}

const stack = ['JavaScript', 'React', 'Redux']

describe.apply(person, stack) // Note the difference here

Gavin Joyce has a useful mnemonic to help remember the difference between fn.call and fn.apply

Bind

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Using bind is similar to call, but you need to create a new function that you can invoke later.

const person = {
  name: 'Chiamaka',
  jobTitle: 'Software Engineer'
}

function describe(stack1, stack2, stack3) {
  return `My name is ${this.name} and I am a ${this.jobTitle}
  building solutions using ${stack1}, ${stack2}, ${stack3}`
}

const stack = ['JavaScript', 'React', 'Redux'];

const boundedFunction = describe.bind(person, stack[0], stack[1], stack[2]);

boundedFunction();

Arrow Functions

For arrow functions, this refers to the lexical scope. It inherits the context of the parent.

const functionType = {
type: 'Regular function',
describe: function() {
  return this.type;
},
child: {
  type: 'Arrow function',
  describe: () => {
    return this.type;
  }
},
}

functionType.describe(); // "Regular function"
functionType.child.describe(); // undefined

You can use arrow function when you want this to reference the outer context. For instance, attaching an event listener to an element in an outer scope and referencing it from within a class.

Button A:

<button class='cta_buttonA'>
  Click Button A
</button>
const buttonA = document.querySelector('.cta_buttonA');

class ButtonA {
  constructor() {
    this.buttonBackground = '#ffdd40';

    buttonA.addEventListener('click', function(event) {
      event.target.style.backgroundColor = this.buttonBackground;
    })
  }
}

new ButtonA();

Button B:

<button class='cta_buttonB'>
  Click Button B
</button>
const buttonB = document.querySelector('.cta_buttonB');

class ButtonB {
  constructor() {
    this.buttonBackground = '#ffdd40';

    buttonB.addEventListener('click', event => {
      event.target.style.backgroundColor = this.buttonBackground;
    })
  }
}

new ButtonB();

If you click Button A which uses a regular function, this would be equal to event.currentTarget and cannot be used to access a value within the class without explicitly binding it whereas clicking on Button B which uses arrow function would change the background color of the button.

Conclusion

In a nutshell, through this article, you've learned about this and its value in different use cases - implicit and explicit. Having learned this, you should be comfortable using it during development without questioning the result of this.

Article Tag  JavaScript

Share this article:

Stay Updated

    More Articles


    I write about accessibility, performance, JavaScript and workflow tooling. If my articles have helped or inspired you in your development journey, or you want me to write more, consider supporting me.