If you ever programmed in another Object Oriented language, “this” keyword in JavaScript might be very confusing for you. Bear with me, I will show you some Java code.
1 2 3 4 5 6 7 8 9 |
public class Point { int x; int y; public Point(int x, int y){ this.x = x; this.y = y; } } |
Here, the “this” keyword is a reference to the current object, so calling
1 |
this.x = x; |
in the constructor will assign a value to the x property of an instance of Point class. This is how “this” works in languages like Java or C++. It is not entirely true when it comes to JavaScript, though.
The way of handling “this”
In JavaScript, “this” is a reference dependant on a call-site of a function (location where a function was called in). In the global context of the browser (outside of any function), “this” is a reference to the window object. It will be undefined, though, if you’re in strict mode.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function getThis(){ return this; } const component = { getThis } getThis().character = 'a'; component.getThis().character = 'b'; console.log(window.character); // 'a' console.log(component.character); // 'b' |
If you wonder how does this object initialization work, it is a feature from ES6 and it works the same as the following:
1 2 3 |
const component = { getThis: getThis } |
Pretty neat, isn’t it?
As you can see, if getThis is a method of the component and it is called from the component, this now refers to it. The context in which you declare it does not matter. You can still change the context of the component.getThis method though:
1 |
console.log(component.getThis.call(this)); // window |
You can execute a call method on any function. The first argument it takes is a context of your choosing, then the arguments that you would like to pass for it. There is a very similar method called apply and it works almost the same, but it takes an array of arguments instead of a list.
1 2 |
component.getThis.call(this, argument1, argument2, argument3); component.getThis.apply(this, [ argument1, argument2, argument3 ]); |
Let’s consider a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function getThis(){ return this; } const component = { getThis, getThat() { return getThis(); }, getIt() { return component.getThis(); } } getThis(); // window component.getThis(); // component component.getThat(); // window component.getIt(); // component |
The confusing part here could be the getThat function: it returns window, even though getThis is called inside of a method of the component.
This is caused by the fact, that what getThat simply does is:
1 2 3 |
getThat() { return window.getThis(); } |
It happens because declaring a named function or declaring a var globally saves the reference to them in the window.
1 2 3 4 5 |
function getThis(){ return this; } var getToThis = getThis; console.log(window.getThis && window.getThis === window.getToThis); // true |
Let’s imagine a more complex situation with some classes involved:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class Modal { constructor(){ this.domElement = document.createElement('div'); this.domElement.classList.add('closed'); } open(){ if(this.onOpen && typeof this.onOpen === 'function'){ this.onOpen(); } this.domElement.classList.add('opened'); } close(){ if(this.onClose && typeof this.onClose === 'function'){ this.onClose(); } this.domElement.classList.remove('opened'); } } const modalStates = { opened: 0, closed: 1 } class Component { constructor(){ this.modal = new Modal(); this.modalState = modalStates.closed; } openModal() { this.modal.onOpen = function() { this.modalState = modalStates.opened } this.modal.open(); } } const component = new Component(); component.openModal(); console.log(component.modalState === modalStates.opened) // false |
Whoa, what happened? You might think for a moment that it would update the state when the modal opens.
There is a catch, though! The reference that “this” leads to in onOpen method changed and it now refers to modal itself, not the component. There are a few options:
1 2 3 4 5 6 7 |
openModal() { const self = this; this.modal.onOpen = function() { self.modalState = modalStates.opened } this.modal.open(); } |
In this piece of code, we save the reference that “this” leads to, therefore we can use it later. That is not really readable, isn’t it?
1 2 3 4 5 6 |
openModal() { this.modal.onOpen = function() { this.modalState = modalStates.opened }.bind(this); this.modal.open(); } |
A little better! Calling bind on a function creates a new function with “this” set to the value provided as an argument.
Arrow functions
The situation described above can be resolved in a more elegant way.
1 2 3 4 5 6 |
openModal() { this.modal.onOpen = () => { this.modalState = modalStates.opened } this.modal.open(); } |
This is a working solution because arrow function does not have its own “this” and it will use the same reference that “this” led to while the arrow function was declared.
That’s not all when it comes to the arrow functions, as they simplify more stuff.
1 2 |
const squareField = side => side * side; const rectangleField = (sideA, sideB) => sideA * sideB; |
1 2 3 4 5 6 |
const squareField = function(side) { return side * side; } const squareField = function(sidea, sideB) { return sideA * sideB; } |
These two work in the same way. If you do not use curly braces in the body of your arrow function, it will just return the value that is a result of the given expression. Just note that if you want to provide more than one argument, you need to wrap them in parentheses.
Final notes
JavaScript runs not only in the browser though! The rule of “this” leading to a window can’t apply here. There is an object called global, which is a top-level scope. Using strict mode won’t delete this reference.
Writing your code in a way that won’t leave a doubt about what does “this” refers to is definitely a good practice, but we can’t escape from the need for understanding how it works. Without this knowledge, we are seriously handicapped and it can result in many situations in which the lack of it can be very confusing.
This is gold
Thank you!