Hi friend, welcome to my blog!π
In this tutorial, we are going to explore the javascript this
keyword...
The this
keyword in Javascript is always a hard nut to crack for people learning Javascript. However, the rules governing this
are relatively straightforward once you get more familiar with them.
This
, a reserved word in Javascript, refers to some object. Its value is determined at execution.
I am going to explain how to determine the value of the this
keyword using the terms default binding, implicit binding, explicit binding, and 'new' binding from my favorite javascript book You Don't Know JS by Kyle Simpson.
Default Binding:
The default binding exists when the this
keyword is in the global context. When the this
keyword is out of a declared object, its value is the global object, which is the Window object in the browser.
Eg:
console.log(this) // Output: window object
let item = this;
console.log(item); // Output: window object
function defaultBindingExample() {
return this;
}
console.log(defaultBindingExample()); // Output: window object
Note: In strict mode, when the this
keyword is out in the wild, outside of a function or class, its value is still the global object, however, inside a function, if the value of this
is not set when entering an execution context, it remains undefined.
Eg:
'use strict'
console.log(this) // Output: window object
let item = this;
console.log(item); // Output: window object
function defaultBindingExample() {
return this;
}
console.log(defaultBindingExample()); // Output: undefined
Implicit binding
When the keyword this
is inside of a declared object, the value is the closest parent object
const schoolProfile = {
name: 'Amaka',
university: 'University of Nigeria',
department: 'Computer Science',
greeting: function () {
return `Hi, my name is ${this.name} I attend ${this.university}`
},
checkThisContext: function () {
return this === SchoolProfile
},
courses: {
listCourses: function () {
return `At the department of ${this.department}, we study python programming`
},
checkThisContext: function () {
return this === SchoolProfile
}
}
}
console.log(schoolProfile.greeting())
// Output: Hi, my name is Amaka I attend University of Nigeria
console.log(schoolProfile.checkThisContext()) // Output: true
console.log(schoolProfile.courses.listCourses())
// Output: At the department of undefined, we study python programming
console.log(schoolProfile.courses.checkThisContext()) // Output: false
Following the implicit binding rule, this
in schoolProfile.greeting()
refers to schoolProfile
which is the closest parent object, in the same way, this
in schoolProfile.courses.listCourses()
refers to courses
, and because the courses object does not have a key named department, the value is undefined.
To solve this, we need to explicitly change the value of this
and that is where the call, bind, apply
comes in as we will see in the next section.
Explicit binding.
To explicitly determine which object the this
keyword refers to, we can use one of three methods: call, apply, or bind
. These functions allow us to choose what we want the context of this
to be.
Note that the three methods can only be used by functions.
Call()
The first argument in the call method is whatever you want the value of this
to be. The second to nth parameters of call are the parameters of the function you are invoking, separated by commas.
Note: call
will immediately invoke the function that it is attached to.
const studentGreetings = {
greeting: function () {
return `${this.name} is a ${this.gender} studying ${this.department}`
}
}
const student1 = {
name: 'Solomon Ant',
gender: 'male',
department: 'Computer Science'
}
const student2 = {
name: 'Amanda Brandy',
gender: 'female',
department: 'Medicine and Surgery'
}
console.log(studentGreetings.greeting.call(student1))
//Output: Solomon Ant is a male studying Computer Science
console.log(studentGreetings.greeting.call(student2))
//Output: Amanda Brandy is a female studying Medicine and Surgery
With argument:
const classmates = {
person1: "Kim",
person2: "Khloe",
person3: "Kylie"
}
function greet(myName) {
return `Hi, my name is ${myName}, my classmates are ${this.person1}, ${this.person2}, and ${this.person3}.`;
}
console.log(greet.call(classmates, 'Amaka'))
// Output: Hi, my name is Amaka, my classmates are Kim, Khloe, and Kylie.
One of the most common use cases of the call method is to convert array-like object eg function arguments into actual array.
Eg:
function addArguments1() {
return arguments.reduce((acc, next) => {
return acc + next;
}, 0);
}
console.log(addArguments1(1, 2, 3, 4, 5))
// Output: TypeError: arguments.reduce is not a function at addArguments1
function addArguments2() {
// using the slice method, make a copy of the array using call to set arguments as the context
let argumentsArr = [].slice.call(arguments)
return argumentsArr.reduce((acc, next) => {
return acc + next;
}, 0)
}
console.log(addArguments2(1, 2, 3, 4, 5))
// Output: 15
Apply()
Apply is almost identical to call, the only difference is that it takes two parameters at most.
The first is what we want the value of the keyword this
to be, the second is an array of arguments we want to pass the function in which we are changing the value of this
. Apply also immediately invokes the function that it is attached to.
let aboutNick = {
name: 'Nick',
sayHi: function () {
return `Hi ${this.name}`
}
}
let aboutTom = {
name: 'Tom'
}
console.log(aboutNick.sayHi()) // Output: Hi Nick
console.log(aboutNick.sayHi.apply(aboutTom)) // Output: Hi Tom
With arguments:
const person = {
firstName: 'Adam',
lastName: 'Levine'
}
function greet(greeting, message) {
return `${greeting} ${this.firstName}. ${message}`;
}
console.log(greet.apply(person, ['Hello', 'How are you?']));
// Output: Hello Adam. How are you?
One of my favorite use cases of the apply method is when used with Math.max function. The Math.max function return the largest number among a group of numbers passed to it. When used with apply, you can pass in an array as parameter and get the largest number
Eg:
//Normal use of Math.max()
console.log(Math.max(1, 16, 22, 30, 23)) // Output: 30
//Math.max()- passing an array to it, we get NaN
console.log(Math.max([1, 16, 22, 30, 23])) // Output: NaN
//apply() to the rescue!
console.log(Math.max.apply(this, [1, 16, 22, 30, 23])) // Output: 30
Another helpful use case is in flattering an array of arrays:
Eg:
const arrays = [[29, 392, 18], ['Orange', 'Mango'], ['London', 'Canada']]
let flatenedArr = [].concat.apply([], arrays)
console.log(flatenedArr) // Output: [29, 392, 18, "Orange", "Mango", "London", "Canada"]
Bind()
Bind is also similar to call, the difference is instead of invoking the function immediately, it returns a function definition. Bind is powerful and is the building block for some advanced programming concepts eg Currying.
const person = {
firstName: 'Adam',
lastName: 'Levine'
}
function greet(greeting, message) {
return `${greeting} ${this.firstName}. ${message}`;
}
let adamGreeting = greet.bind(person, 'Hi', 'How are you?')
console.log(adamGreeting);
// Outputππ: bind() returns a function definition
// greet(greeting, message) {
// return `${greeting} ${this.firstName}. ${message}`;
// }
console.log(adamGreeting());
// Hi Adam. How are you?
Bind is useful when we do not know the whole argument that will be passed to a function, which means we don't want to invoke the function right away, we just want to return a function definition.
Eg:
const student = {
name: 'Christiene',
}
function calculation(x, y, z) {
return `${this.name} calculated ${x + y + z}`;
}
let chrisCalc = calculation.bind(student, 2, 3)
console.log(chrisCalc);
// Output: ππ
//calculation(x, y) {
//return `${this.name} calculated ${x + y}`;
//}
console.log(chrisCalc(10));
// Christiene calculated 15
Another use case is to set the context of the this
keyword for a function that will be called later.
Eg:
In the code below, the this
in the asynchronous setTimeOut() function, loses its context to the global object.
const aboutNick = {
name: 'Nick',
sayHi: function () {
setTimeout(function () {
console.log(`Hi ${this.name}`)
}, 1000)
}
}
aboutNick.sayHi() // Hi undefined (after one sec)
Using bind(), we can set the context of this
to be the object it's in
const aboutNick = {
name: 'Nick',
sayHi: function () {
setTimeout(function () {
console.log(`Hi ${this.name}`)
}.bind(this), 1000)
}
}
aboutNick.sayHi() // Hi Nick (after one sec)
Important Note
With the introduction of ES6 arrow function, using bind in this way is no longer required since the arrow function retain the scope of this
.
const aboutNick = {
name: 'Nick',
sayHi: function () {
setTimeout(() => { console.log(`Hi ${this.name}`) }, 1000)
}
}
aboutNick.sayHi() // Hi Nick
Summary of the call, bind, apply in a tabular form:
Name of Method | Parameters | Invoked Immediately? |
Call | thisArg, a, b, c, d, ... | Yes |
Apply | thisArg, [a, b, c, d, ...] | Yes |
Bind | thisArg, a, b, c, d, ... | No |
The new
keyword
Consider the following code:
function Person (name, age){
this.name = name,
this.age= age
}
Notice the capital letter P
I am using to start the name of the function, it's just a convention for writing a special type of function called the constructor function.
Let's see what happens when we invoke this function.
let aboutJohn = Person('John Doe', 35)
console.log(aboutJohn); // undefined
oops! we get undefined. Rightly so, there's no return keyword on the function, there's no explicit binding of the this
keyword, the Person function isnβt called inside of a parent object, the default binding took place.
function Person(name, age) {
this.name = name,
this.age = age
}
let aboutJohn = Person('John Doe', 35)
console.log(aboutJohn); // undefined
console.log(window.name) // John Doe
console.log(window.age) // 35
To change the value of this
to the constructor function we use the new
keyword.
Let's see how that works below:
function Person(name, age) {
this.name = name,
this.age = age
}
let aboutJohn = new Person('John Doe', 35)
console.log(aboutJohn); // {name: "John Doe", age: 35}
This is what the new
keyword did above
- it creates a new empty object.
- sets the context of the
this
keyword to the created object . - adds an implicit
return this
to the function.
Conclusion
You will encounter the this
keyword a lot in object oriented programming and understanding how its value is assigned is very important to avoid sneaky bugs. In the article I explained the rules guiding the determining the value of this
keyword. They are not official javascript rules, they are just a way to make you understand how the this
keyword gets assigned a value.
Let me know what you think in the comments section!