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 familliar wit them.
This, a reserved word in javascript, refers to some object. Its value is detemined at execution.
I am going to explain how to determine the value of the this keyword using the terms default bindg, implicit binding, explicit binding, and 'new' bindg from my favorite javascript book You Don't Know JS by kyle Simpson.
Defaut 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.
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 whild, outside of a funciton 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.
'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 a decalred 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.universiy}`
},
checkThisContext: function() {
return this === SchoolProfile
},
courses: {
listCourses: function() {
return `At the department of ${this.department}, we study python`
},
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
console.log(schoolProfile.course.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 departmet, 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 next section.
Explicit binding
To explicitly determing which object the this keyword refers to, we can use one of three methods: call, apply, or bind. These funcitons 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 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: "Khole",
person3: "Kylie"
}
function greet(myName) {
return `Hi, my name is ${myName}, my classmates are ${this.person1}, ${this.person3},
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 functions arguments into actual array.
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 argumentArr = [].slice.call(arguments)
return argumentArr.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
//Normal use of Math.max()
console.log(Math.max(1,16,22,30))
// Output: 30
//Math.max() - passing an array to it, we get NaN
console.log(Math,max([1,16,22,30]))
// Output: NaN
//apply() to rescue!
console.log(Math.max.apply(this, [1,16,22,30]))
// Output: 30
Another helpful use case is in flattering an array of arrays:
const arrays = [[29, 392, 18], ['Orange', 'Mango'], ['London', 'Canda']]
let flatenedArr = [].concat.apply([], arrays)
console.log(flateneArr) //Output: [29,392,18,"Orange", "Mango", "London, "Canda"]
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
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.
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(christCalc);
// Output: it will output a function definition
console.log(christCalc(10));
// Output: Chritiene calculated 15
Another use case is to set the context o the this keyword for a function that will be called later.
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 array function, using bind in this way is no longer required since the array 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:
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 funciton, 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('Jonh Doe', 35)
console.log(aboutJohn); //undefined
console.log(window.name); //Jonh Doe
console.log(window.age); //35
To change the value of this to the consrtuctor funciton we use the new keywork.
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', 35}
This is what the new keyworkd did above
1. it creates a new empty object.
2. sets the context of the this keyword to the created object.
3. adds an implicit return this to the function.