Hey! Welcome to part two of Let’s settle ‘this’ where we are trying to demystify one of JavaScript’s least understood aspect — the ‘this’ keyword. If you haven’t checked out part one yet you might want to do so. In Part One we went over Default and Implicit binding rules through 15 different examples. We saw how the value of ‘this’ for a function changes depending on how the function is invoked. Towards the end we also familiarized ourselves with arrow functions and how they undergo lexical binding. I hope you remember everything.
In this part we will be discussing two new rules, beginning with new binding, we will get into innards and see how everything works. Next we will go over explicit binding and how we can bind any object as ‘this’ to any function through the call(…), apply(…) and bind(…) methods.
Let us continue where we left off. Your task is the same, to guess what gets logged to the console. Remember WGL?
But before we dive in, let’s warm up a little.
Example #16
function foo() {}foo.a = 2;
foo.bar = {
b: 3,
c: function() {
console.log(this);
}
}foo.bar.c();
I know right now you’re like “What’s going on? Why are properties being assigned to a function here? Won’t this result in an error?”. Well, no first of all this will not result in an error. Every function in JavaScript is also an object. So like any other regular objects you can assign properties to a function too!
With that out of the way let’s now figure out what gets logged to the console. If you notice, Implicit binding is at work here. The object reference directly preceding c’s invocation is bar isn’t it? Therefore ‘this’ inside c is bar and hence bar is what gets logged to the console.
The one thing you should carry away from this example is that functions in JavaScript are also objects and like any other objects they can be assigned properties.
Example #17
function foo() {
console.log(this);
}new foo();
So, what gets logged? Or does anything gets logged at all?
The answer is an empty object. Yes, not a not foo just an empty object. Let us see how that works.
First of all notice how the function is invoked. It’s not a standalone invocation nor it has an object reference preceding it. What it has though is new in front of the call. Any function in JavaScript can be invoked with a new keyword in front of it. And when that happens, when a function is invoked with new roughly four events occur, two of which are,
- An empty object is created.
- The newly created object is bound as ‘this’ to the function call.
And that (2.) is the reason why when you execute the piece of code shown above you get an empty object logged to the console. You might ask “How is this even useful?”. We’ll get to that it’s all a little controversial.
Example #18
function foo(id, name) {
this.id = id;
this.name = name;
}foo.prototype.print = function() {
console.log( this.id, this.name );
};var a = new foo(1, ‘A’);
var b = new foo(2, ‘B’);a.print();
b.print();
Intuitively it’s very easy to guess what gets logged to the console in this example, but are you right technically? Let’s see.
To recap, when a function is invoked with the new keyword four events occur.
- An empty object is created.
- The newly created object is bound as ‘this’ to the function call.
- The newly created object is prototypically linked to function’s prototype object.
- The function is executed normally and at the end the newly created object is returned*.
We have verified in the previous example that the first 2 events in fact happen, that is why we got an empty object logged to the console. Forget about 3 for now let’s focus on the 4th event. There is nothing stopping the function’s execution, the function is executed with its parameters as any other normal JavaScript function except for the fact that ‘this’ inside the function is a newly created empty object. So when inside the function, foo in our case, we do something like this.id = id we are actually assigning properties to the newly created empty object that was bound as ‘this’ to the function on call. Read that again. And once the function has finished executing the same newly created object is returned. Since in the example above we assigned an id and a name property to the object the returned object has those as well. The returned object then can be assigned to whatever we want like we did to a and b in the example above.
Every function call with new results in creation of a brand new empty object, its optional augmentation (this.propName = …) inside the function and its return at the end of function execution. Therefore in the end our variables a and b look something like this.
var a = {
id: 1,
name: ‘A’
};var b = {
id: 2,
name: ‘B’
};
Great! we have just learnt a new way for creating objects. But a and b have something in common, they are both prototypically linked to foo’s prototype (event 4) and therefore have access to its properties ( variables, functions e.t.c ). And just because of that we can call a.print() and b.print() since print is a function we created in foo’s prototype. Quick question, what binding occurs when I call a.print()? You’re absolutely right if you said Implicit. Therefore, on calling a.print() ‘this’ inside print is a and the first thing to get logged to the console is 1, A and similarly when we call b.print() 2, B gets logged.
Example #19
function foo(id, name) {
this.id = id;
this.name = name; return {
message: ‘Got you!’
};
}foo.prototype.print = function() {
console.log( this.id, this.name );
};var a = new foo(1, ‘A’);
var b = new foo(2, ‘B’);console.log( a );
console.log( b );
Almost everything is same as the code in the previous example except notice that foo now returns an object. Alright, do one thing go back to the previous example and re read the 4th event would you? Notice the *? When a function is called with the new keyword the newly created object is returned at the end of execution unless you return your own custom object like we are doing in this example.
So? What gets logged? It’s very obvious to see that it’s the returned object the one with the message property that gets logged to the console, twice. It’s so easy to break the whole construct isn’t it? Just return a meaningless object and everything fails. Moreover you now cannot call a.print() or b.print() since a and b are assigned what is returned and our returned object is not prototypically linked to foo’s prototype.
But wait what if instead of returning an object we returned a string like ‘abc’ or a number or a boolean value or a function or null or undefined or an array? As it turns out whether the construct breaks or not depends on what you return. See a pattern here?
return {}; // Breaks
return function() {}; // Breaks
return new Number(3); // Breaks
return [1, 2, 3]; // Breaks
return null; // Doesn’t break
return undefined; // Doesn’t break
return ‘Hello’; // Doesn’t break
return 3; // Doesn’t break
...
Why this happens is a topic for another article. I mean we are already a little off course here, this example has not much to with ‘this’ binding right?
The whole creating objects through new binding has been and being used (misused?) to fake traditional classes in JavaScript since long. In reality there are no classes in JavaScript the new class syntax in ES2015 is just that, syntax. Behind the scenes new binding is what happens there is no change there. I for one don’t care if you use new binding to fake classes until your program works and the code is extensible, readable and maintainable. But then again how can you have extensible, readable and maintainable code with all the package and fragility new binding brings?
That might have been a lot to take in. You should re read it if you’re still a little lost. It’s important that you understand how new binding works probably to never use it again :).
Enough serious talk, let’s move on.
Consider the code below. Refrain yourself from guessing what gets logged in this example, we’ll continue “the guessing game” from the next example onwards :).
var expenses = {
data: [1, 2, 3, 4, 5],
total: function(earnings) {
return this.data.reduce( (prev, cur) => prev + cur ) — (earnings || 0);
}
};var rents = {
data: [1, 2, 3, 4]
};
The expenses object has data and total properties. data holds some numbers whereas total is a function that takes in earnings as a parameter and returns the sum of all numbers in data minus the earnings. Very straightforward.
Now look at rents, just like expenses it has a data property too. Now say, for some reason, just being hypothetical here, you’d like to run the total function over rent’s data array and since we’re good programmers we don’t like repeating ourselves. We definitely can’t do rents.total() and have rents implicitly bound as ‘this’ to the total function since rents.total() is an invalid call owing to the fact that rents has no property called total. Now only if there was a way to bind rents as ‘this’ to the total function. Well guess what? There is, allow me to introduce you to call() and apply().
You see call and apply do the exact same thing, they allow you to bind the object you want to the function you want. Which means I can do this…
console.log( expenses.total.call(rents) ); // 10
.. .and this.
console.log( expenses.total.apply(rents) ); // 10
Which is great! Both of the above lines of code result in total being called with ‘this’ as the rents object. call and apply as far as ‘this’ binding is concerned only differ in the way you pass in arguments.
Notice that the function total takes in an argument earnings, let’s pass it.
console.log( expenses.total.call(rents, 10) ); // 0 Works!
console.log( expenses.total.apply(rents, 10) ); // Error
Passing arguments to the target function (total in our case) via call is simple you just pass in a comma separated list of arguments like to any other JavaScript function .call(customThis, arg1, arg2, arg3…). In the code above we passed in 10 for the earnings parameter and everything worked as expected.
apply though requires you to pass in arguments to the target function (total in our case) wrapped in an array .apply(customThis, [arg1,arg2, arg3…]) Which notice we didn’t do in the snippet above resulting in an error. The error can definitely be fixed by wrapping arguments for the target function in an array, like so.
console.log( expenses.total.apply(rents, [10]) ); // 0 Works!
The mnemonic I use to remember the difference between call and apply goes something like this. A is for apply and A is for array as well! So arguments to the target function via apply are passed in wrapped in an array. Just a stupid little mnemonic, but it works 😬.
Now what if we pass in a number or a string or a boolean value or null/undefined instead of an object literal for ‘this’ to call, apply and bind (discussed next). What happens then? Nothing much, say if you pass in a number 2 for ‘this’ it gets wrapped in its object form new Number(2) similarly if you pass in a string it becomes new String(…) boolean values become new Boolean(…) so on and so forth and this new object whether String or Number or Boolean gets bound as ‘this’ to the called function. Passing in null and undefined though results in something different. If you pass in null or undefined for ‘this’ the function is called as if it underwent Default binding which means the global object is bound as ‘this’ to the called function.
There’s yet another way to bind ‘this’ to a function, this time through a method called, wait for it, bind!
Let’s see if you can figure this out. What gets logged in the example below?
Example #20
var expenses = {
data: [1, 2, 3, 4, 5],
total: function(earnings) {
return this.data.reduce( (prev, cur) => prev + cur ) — (earnings || 0);
}
};var rents = {
data: [1, 2, 3, 4]
};var rentsTotal = expenses.total.bind(rents);console.log(rentsTotal());
console.log(rentsTotal(10));
The answer to this example is 10 followed by 0. Notice what’s happening right below the declaration of rents object. We are creating a new function rentsTotal from the function expenses.total. That’s what bind does it creates a new function which when called has its ‘this’ keyword set to the provided value (rents in our case). Therefore when we call rentsTotal() which though is a standalone invocation has its ‘this’ already set to rents and Default Binding cannot override that. This call results in 10 getting printed to the console.
In the next line calling rentsTotal with a parameter (10) is exactly like calling expenses.total with the same paramter (10) it is only in the value of ‘this’ where they differ. The result of this call is 0.
Moreover you can also bind parameters for the target function (expenses.total in our case) using bind. Consider this.
var rentsTotal = expenses.total.bind(rents, 10);
console.log(rentsTotal());
What do you think gets logged to the console? 0 of course as 10 has been bound to the target function (expenses.total) as earnings by bind.
Let us take a look at an example that illustrates a real life usage of bind.
Example #21
// HTML<button id=”button”>Hello</button>// JavaScriptvar myButton = {
elem: document.getElementById(‘button’),
buttonName: ‘My Precious Button’,
init: function() {
this.elem.addEventListener(‘click’, this.onClick);
},
onClick: function() {
console.log(this.buttonName);
}
};myButton.init();
We have created a button in HTML and then we’re targeting that same button in our JavaScript code as myButton. Notice inside init we are also attaching a click event listener to the button.Your question now is what gets logged to the console when the button is clicked?
If you guessed it right undefined is what gets logged. The reason for this “sorcery” is that functions passed as callback (this.onClick in our case) to event listeners has the target element bound as ‘this’ to them. Which means when onClick is called ‘this’ inside it is the DOM object button (elem) and not our myButton object and because the DOM object button has no property with the name buttonName it results in undefined being logged to the console.
But there is a way to fix this (pun intended). All we need to do is add one, just one extra line of code.
Fix #1
var myButton = {
elem: document.getElementById(‘button’),
buttonName: ‘My Precious Button’,
init: function() {
this.onClick = this.onClick.bind(this);
this.elem.addEventListener(‘click’, this.onClick);
},
onClick: function() {
console.log(this.buttonName);
}
};
Notice in the previous snippet (#21) the way function init is invoked. Exactly, Implicit binding binds myButton as ‘this’ to the init function. Now notice how in the new line we are binding myButton to the the onClick function. Doing so creates a new function which is exactly onClick except for the fact that it has its ‘this’ as myButton object. The newly created function then is reassigned to myButton.onClick. That’s all, when you click the button now you’ll have “My Precious Button” logged to the console.
You could have fixed the code with arrow functions too. Here’s how. I will leave it up to you to figure why these work.
Fix #2
var myButton = {
elem: document.getElementById(‘button’),
buttonName: ‘My Precious Button’,
init: function() {
this.elem.addEventListener(‘click’, () => {
this.onClick.call(this);
});
},
onClick: function() {
console.log(this.buttonName);
}
};
Fix #3
var myButton = {
elem: document.getElementById(‘button’),
buttonName: ‘My Precious Button’,
init: function() {
this.elem.addEventListener(‘click’, () => {
console.log(this.buttonName);
});
}
};
That’s it. We’re done… almost. There are still questions like is there an order of precedence? What if there is a clash between two rules trying to bind a ‘this’ to the same function? This is a topic for another article. Part 3? Probably and to be honest it’s rare you’ll run into such clashes. So for now we’re done and let’s summarize what we have learnt in the two parts.
Summary
In the first part we saw how ‘this’ for a function is not fixed and can change depending on how the function is invoked. We went over Default binding which applies when a function undergoes standalone invocation, Implicit binding which applies when a function is invoked with an object reference preceding it and arrow functions and how ‘this’ to them are bound lexically. Towards the end of the first part we also went over a self referencing quirk in JavaScript objects.
In this part we started out with new binding and how it works and how the whole construct can easily be broken. Latter half of this part was dedicated to explicitly binding ‘this’ to functions using call, apply and bind. I also embarrassingly shared my mnemonic with you about how to remember the difference between call and apply. Hope you remember it.