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
Have trouble remembering the difference between `https://t.co/gdUSChIRgv` and `fn.apply` in JavaScript?
— Gavin Joyce (@gavinjoyce) January 9, 2018
A useful mnemonic from https://t.co/HekOteBawB:
"A for array and C for comma."
fn.apply(context, [a, b, c]);https://t.co/gdUSChIRgv(context, a, b, c);
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
.