this, call(), apply(), bind() and Function Currying in JavaScript
this keyword
this is a predefined keyword in JavaScript. When used in a function, this keyword simply points to an object to which it is bound. It answers the question of where it should get some value or data from.
function alert() {
console.log(this.name + ' is calling');
}
In the function above, this keyword is referring to an object to which it is bound so it gets the "name" property from there. But how do you know which object the function is bound to? How do you find out what this is referring to?
A Bit of Background
In JavaScript, functions are objects. Thus, all the JavaScript functions have properties and methods, in the same way, that objects have properties and methods.
Whenever a function gets executed, it gets assigned its own 'this' property - a variable with the value of the object that invokes the function where 'this' is used.
There are times when we need this to access methods and properties of the object invoking ourFunction because we don't always know the name of the invoking object. Sometimes, there is no name to use to refer to the invoking object like in callbacks, anonymous or immediately invoked functions.
Understanding "this" binding in JavaScript
To understand "this" keyword clearly, we need to go through how the execution context works first. Every time your run some JavaScript code, the engine creates a Global Execution Context. Every time a function is invoked, a brand new Local Execution Context is created for that function. Each function has its own execution context, but it's created when the function is called or invoked. There can be only one Global Execution Context, there can be any number of Local Execution Contexts in one program.
What execution context looks like?
The execution context is created during the creation phase. Following things happen during the creation phase:
A Lexical Environment component is created.
A variable Environment component is created.
What Lexical Environment looks like?
Let's look at following example to understand the Lexical Environment:
const person = {
name: 'ABC',
birthYear: 1991,
calcAge: function() {
console.log(2018- this.birthYear);
}
}
person.calcAge();
const calculateAge = person.calcAge;
calculateAge();
The following code snippet shows how the lexical environment looks conceptually
GlobalExecutionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object", // Identifier bindings go here
}
outer: <null>,
this: <global object>
}
}
FunctionExecutionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative" // Identifier bindings go here
}
outer: < Global or outer function environment reference> ,
this: depends on how function is called
}
}
As you can see above example, each Lexical environment has three components:
Environment Record
Reference to the outer environment
this binding
What is "this" binding
As you can see above example, "this" binding is a property of an execution context (global, function or eval). It behaves depending on the execution context.
Global Execution Context
In the global execution context, the value of "this" refers to the global object. (in browsers, this refers to the Window object).
Function Execution Context
In the function execution context, the value of "this" depends on how the function is called. Each function invocation defines its context, therefore "this" behaves differently than you may expect.
Types of Binding
There are generally four kinds of bindings:
Default Binding
Implicit Binding
Explicit Binding
Constructor Call Binding
Default Binding
One of the first rules to remember is that if the function housing this reference is a standalone function, then that function is bound to the global object.
// Standalone function
function alert() {
console.log(this.name + ' is calling');
}
const name = 'Kingsley';
alert(); // Kingsley is calling
As you can see, name() is a standalone, unattached function, so it is bound to the global scope. As a result, this.name reference resolves to the global variable. This rule, however, doesn't hold if the name() were to be defined in strict mode.
function alert() {
'use strict';
console.log(this.name + ' is calling');
}
const name = 'Kingsley';
alert(); // Typeerror: 'this' is undefined
When set in strict mode, this reference is set to undefined.
Implicit Binding in JavaScript
Another scenario to look out for is whether the function is attached to an object (its context) at the call site. According to the binding rule in JavaScript, a function can use an object as its context only is that object is bound to it at the call site. This form of binding is known as implicit binding.
function alert() {
console.log(this.age + ' years old');
}
const myObject = {
age: 22,
alert: alert
}
myObject.alert(); // 22 years old
When you call a function using dot notation, this is implicitly bound to the object the function is being called from. In the above example, since an alert is being called from myObject, this keyword is bound to myObject. So, when an alert is called with myObject.alert(), this.age is 22 which is the age property of myObject.
Let's look at another example:
function alert() {
console.log(this.age + ' years old');
}
const myObject = {
age: 22,
alert: alert,
nestedObject: {
age: 26,
alert: alert
}
}
myObject.nestedObject.alert(); // 26years old
Here, because alert is ultimately being called from nestedObject, this is implicitly bound to nestedObject instead of myObject.
An easy way to figure out which object this is implicitly bound to be to look at which object is to the left of the dot(.)
function alert() {
console.log(this.age + ' years old');
}
const myObject = {
age: 22,
alert: alert,
nestedObject: {
age: 26,
alert: alert
}
}
myObject.alert(); // 'this' is bound to myObject' - 22 years old
myObject.nestedObject.alert(); // 'this' is bound to nestedObject -- 26 years old
Explicit Binding
We saw that implicit binding had to do with having a reference in that object. But what if we want to force a function to use an object as its context without putting a property function reference on the object?
We have two utility methods to achieve this: call() and apply(). Along with a couple of other sets of utility functions, these two utilities are available to all functions in JavaScript via the [[Prototype]] mechanism.
To explicitly bind a function call to a context, you simply have to invoke the call() on that function and pass in the context object as a parameter.
function alert() {
console.log(this.age + ' years old');
}
const myObject = {
age: 22
}
alert.call(myObject); // 22years old
Even if you were to pass around that function multiple times to new variables(currying), every invocation will use the same context because it has been locked (explicitly bound) to that object. This is called as 'hard binding'.
// Hard Binding
function alert() {
console.log(this.age);
}
const myObject = {
age: 22
}
const bar = function() {
alert.call(myObject);
}
bar(); // 22
setTimeOut(bar, 100); // 22
// a hard-bound 'bar' can no longer have its 'this' context overridden
bar.call(window); // still 22
Hard binding is a perfect way to lock a context into a function call and truly make that function into a method.
Constructor call Binding
The final and most interesting kind of binding is the new binding which also accentuates the unusual behaviour of JavaScript in comparison to other class-based languages.
When a function is invoked with the new keyword in front of it, otherwise known as a constructor call, the following things occur:
A brand new object is created (or constructed).
The newly constructed object is [[Prototype]]-linked to the function that constructed it.
The newly constructed object is set as this binding for that function call.
function giveAge(age) {
this.age = age;
}
const bar = new giveAge(22);
console.log(bar.age); // 22
By calling giveAge(...) with new in front of it, we have constructed a new object and set that new object as this for the call of giveAge(...). So, new is the final way that you can bind a function call's this.
"this" - Summary
The "this" keyword can lead to some headaches in JavaScript- this summary hopefully acts as a remedy. "this" refers to different things, depending on where it's used and how (if used in a function) a function is called.
Generally, "this" refers to the thing called a function (if used inside a function). That can be the global context, an object or some bound data/ object (e.g, when the browser binds this to the button that triggered a click event).
this in a Global Context (i.e., outside of any function)
function something() {...} console.log(this); // logs global object (window in browser) always (also in strict mode)
this in a Function (non-Arrow) - Called in the global Context
- When a function is invoked straightforwardly, its function context (that is this value) can be two things. In non-strict mode, the global context (the window object) becomes the function context. In strict mode, it will be undefined.
function something() {
console.log(this);
}
something(); // logs global object (window in browser) in non strict mode, undefined in strict mode.
The outcome is the same even if the function is defined within a function, as long as it is invoked in straight forward manner
function outer() { function inner() { console.log(this); } function strictInner() { "use strict" console.log(this); } inner(); // Window Object strictInner(); // undefined } outer();
this in an Arrow function - called in the global context
const something = () => { console.log(this); } something(); // Logs global object (window in browser) - ALWAYS (also in strict mode)
this in a Method (Non-Arrow) - Called on an Object
- Functions can also be properties in objects and in that capacity, they are known as methods. When function is invoked through an object (as a method), the object itself becomes the function context and is accessible within the body of the method via this parameter
const person = {
name: 'ABC',
greet: function() { // or use method shorthand: greet() {...}
console.log(this.name);
}
};
person.greet(); // logs 'ABC', "this" refers to the "person" object
this in a Method (Arrow Function) - Called on an Object
const person = { name: 'ABC', greet: () => { console.log(this.name); } } person.greet(); // Logs nothing ( or some global name on window object), "this" refers to global(window) object, even in strict mode.
- Within the greet() method, this now references the window object as opposed to the parent object. Arrow functions always stick to this value existing at the time of their definition means that they won't take their parent objects as their function context when they are invoked through them as methods. In this case, person is created in global context where the value of this is the window object and that is what the arrow function expression uses.
"this" can refer to unexpected things if you call it on some other object
const person = {
name: 'ABC',
greet() {
console.log(this.name);
}
};
const anotherPerson = {name: 'XYZ'}; // does not have a built in greet method.
anotherPerson.sayHi = person.greet; // greet is not called here, it's just assigned to a new property/method on the "anotherPerson" object.
anotherPerson.sayHi(); // logs 'XYZ' because method is called on "anotherPerson" object -> "this" refers to the "thing" which called it.
If in doubt, a console.log(this); can always help you find out what this is referring to at that moment!
Test your Knowledge of "this"
What is the purpose of this inside of an object method (in non-arrow functions) ?
Provide you access to the object (from inside the method)
Provide you access to whatever called the method
Provide you access to the global object (no matter how the method you called).
Answer: B - Provide you access to whatever called the metho
What will this refer to in the below example?
const person = { name:'ABC', greet() { console.log(this); // ??? console.log(this.name); } }; person.greet();
this refers to the person Object
this refers to the global Object
the above snippet will throw error
Answer: this refers to the person Object
What will this refer to in the below example
const person = { name: 'XYZ', greet() { console.log(this); // ??? console.log(this.name); } }; const admin = { age: 30 }; admin.greet = person.greet; admin.greet();
this refers to the person object
this refers to the global object
this refers to the admin object
Answer: this refers to the admin object
What will this refer in the below example?
const person = { name: 'XYZ', greet() { console.log(this); // ??? console.log(this.name); } }; const { greet } = person; greet();
this will refer to the person object
this will refer to the admin object
The global object or undefined
Answer: The global Object
What will this refer to in the below example ?
const person = { name: 'ABC', greet() { console.log(this); // ??? console.log(this.name); } }; const { greet } = person; greet.call(this);
this will refer to the person object
this will refer to the global object
The snippet will throw an error
Answer: this will refer to the global Object
What will this refer to in the below example?
const person = { name: 'ABC', greet() { console.log(this); // ??? console.log(this.name); } }; const { greet } = person; greet.call(person);
this will refer to the person object
this will refer to the global object
this will throw an error
Answer: this will refer to the person object
call(), apply() and bind()
The call, apply and bind methods are very useful. But a lot of us don't use them to their potential simply because we don't understand it. So exactly what are they? When can we use them and how do we know when to use each one? Let's start by understanding why they exist.
Dominos, a very successful pizzeria wants to know the average amount of money they earn in a week. That's where you come in, you create this object with a very useful method. Just to refresh your memory, a method is a function that is called within an object. And in JavaScript, everything is an object including a function!!
const dominos = {
name: 'Dominos',
dailyEarnings: [2300, 1900, 3000, 1000, 4000, 4920, 9828];
calcAverageEarning(earnings = this.dailyEarnings) {
let sum = 0;
for (let i=0; i < earnings.length; i++) {
sum += earnings[i];
}
this.average = Math.round(sum / earnings.length);
return this.average;
},
});
Let me explain what's going on in this code. Here, we have an object that contains details about dominos, their name, the money they earn daily stored in an array and a method that calculates the average amount of money they earn in a week. The method has a default parameter that uses the dailyEarnings property if no value is passed to the function.
The "this" keyword in a method references the current object where it is being called. So in this case, this.dailyEarnings is the same thing as writing dominos.dailyEarnings. But it's better to use the "this" keyword just in case we decide to change the variable name of the object, we don't have to rewrite it in every instance that it was mentioned. We also create a new property average that was not previously defined in our dominos object and set the average value to it.
This object can be reused for other restaurants but define a new method for every single restaurant that you have worked for. You want to reuse your method for other restaurants with the same formula.
call()
The call method allows us to reuse our calcAverageEarning method in different objects by allowing us to set our own this property which would be the object that we pass to it. Let's say we have another pizza chain that we have to calculate their average earning for, with it's own object.
const galaxyPizza = {
name: 'Galaxy Pizza',
dailyEarnings: [500, 900, 1200, 210, 700, 400]
};
We want o reuse the calcAverageEarning method to calculate the average earning galaxy pizza makes in a week.
const earnings = dominos.calcAverageEarning;
earnings();
We will get an error as Cannot read properties of undefined on the "this.dailyEarnings" array, that's because the "this" keyword in a method refers to the current object where it is being called. So, we can't call our method this way because there is no longer a dominos.dailyEarnings property. That is when the call method comes in handy.
const earnings = dominos.calcAverageEarnings;
earnings.call(galaxyPizza);
Now, the call method allows us to set our own "this" keyword and in this case, it's the object galaxyPizza. When we log the galaxyPizza object, we see that it now has the property average with a value assigned to it, and we no longer get the undefined error.
call with arguments
The call method also allows you to call a method with arguments too. For example, we want to call our method calcAverage method but with different values for dailyEarnings. Remember that calcAverageEarning is a function that accepts an array parameter called earnings.
Syntax: call(thisArg, arg1, /*....*/ argsN)
const array = [200, 1200, 2135, 500, 1000, 600, 800];
const earnings = dominos.calcAverageEarnings;
earnings.call(galaxyPizza, array);
It will work the same way, except this time we are passing in our own earnings value rather than using the default one from the Object because the call method allows for you to pass in arguments too.
apply()
The apply() method does the same job that call does, except it accepts an array of arguments instead with this syntax.
apply(thisArg, argsArray);
Let's modify our calcAverageEarning method to accept an additional parameter called name
const dominos = {
name: 'dominos',
dailyEarnings: [2300, 1900, 3000, 1000, 4000, 4920, 9829],
calcAverageEarning(earnings = this.dailyEarnings,
restaurantName: this.name) {
let sum = 0;
for(let i=0; i < earnings.length; i++) {
sum += earnings[i];
}
this.average = `The average ${restaurantName} earns in a week is
${Math.round(sum / earnings.length)}`;
return this.average;
}
};
Now, we can apply method this way
earnings.apply(galaxyPizza, [arr, 'Galaxy Pizza']);
The first argument is the array we defined earlier, and the second argument is the name of the restaurant.
bind()
The bind method also works like the call and apply method with the below syntax
bind(thisArg, arg1, arg2, /* ....*/ argN)
The difference is bind returns a new function and does not immediately call the function.
const galaxyPizzaEarnings = earnings.bind(galaxyPizza);
galaxyPizzaEarnings();
bind also accepts arguments.
Polyfills for bind()
In the previous section, we have seen how bind can be used to set this to a particular object which can be invoked at a later point in time. What if we have to write our own prototype/polyfill for the bind method? We have to keep the following points in mind.
Our polyfill method must be accessible to all the functions. i.e., it should be a function prototype
Handling all the arguments passed and references properly.
It should return a function
const object1 = {
favouriteActor: 'Mahesh Babu'
}
const object2 = {
favouriteActor: 'Dhanush'
}
function getActor(cinemaIndustry, country){
console.log("My favourite actor is " + this.favouriteActor + " who works for " + cinemaIndustry + " Film Industry. He belongs to " + country);
}
const myFunction = getActor.bind(object1, "South Indian");
myFunction("India");
We can see that the getActor.bind method returns a bound function that is stored in the myFunction variable. Also, arguments have been passed while applying bind and when myFunction is invoked. So, we have to handle all these things in our polyfill. It will look something like this:
const object1 = {
favouriteActor: "Mahesh Babu"
}
const object2 = {
favouriteActor: "Dhanush"
}
function getActor(cinemaIndustry, country) {
console.log("My favourite actor is " + this.favouriteActor + " who works for " + cinemaIndustry + " and is from the country " + country);
}
Function.prototype.myBind = function(...args) {
let object = this;
params = args.slice(1);
return function(...args2) {
object.apply(args[0],[...params, ...args2]);
}
}
const myFunction1 = getActor.bind(object1, "South");
myFunction1("India");
const myFunction2 = getActor.myBind(object2, "South");
myFunction2("India");
Let's understand this above code line by line
We start with writing a function prototype and name our polyfill myBind. Since, it returns a function, we are also returning a function in myBind.
When this returned function is invoked, we have to make sure that getActor() function is executed. that is how our bind works.
So, we can implement this with the help of this because inside myBind(), this will point to getActor(). That's why we have stored the value of this in a variable and calling getActor() through this variable inside the returned function.
Now, let's take care of the arguments. So we have arguments being passed in our myBind method as well as returned function. To handle these scenarios, we pass ...args in myBind function definition and ...args2 in the function returned.
See how the args array has stored both object reference as well as next argument. So, we have to pass the first element args[0] as the first argument in our apply method and combine elements excluding the first of args as well as args2 to pass the second argument.
We use the spread operator to combine both arrays. And this is the output after the final execution
Function Currying in JavaScript
Currying is a process in functional programming in which we can transform a function with multiple arguments into a sequence of nesting functions. It returns a new function that expects the next argument inline. It's helpful when we have many places that use a function in the same way.
Currying can be implemented through both bind method and closure concept
function abc(a, b) {
console.log("A: " + a + " B: " +b);
}
const function1 = abc.bind(this, 2);
function1(3);
const function2 = abc.bind(this, 2);
function2(5);
//Using Closure concept
function abc(a) {
return function(b) {
console.log("A: " + a + "B: " + b);
}
}
const function3 = abc(2);
function3(3);
const function4 = abc(2);
function4(5);
The output of the above code will be same in both the methods:
A: 2 B: 3
A: 2 B: 5
A: 2 B: 3
A: 2 B: 5
When using the bind method, we are explicitly setting the default value of A as 2 and passing variable values of B while invoking the returned function.
When using closures, the inner function in abc forms a closure with the lexical scope of its parent i.e., abc and hence has access to both the variables a and b. Again, we are setting the default value of A as 2 and passing different values of B in different functions.
This is how currying is used in JavaScript to reuse a function in multiple places.
Thanks for reading the article!!!
Happy Coding!!!