Using "this” in JavaScript
JavaScript is great, but comparing to other programing languages it has many confusing parts. One of them is the use of this.
In this blog post I will explain how to use it right, and what are the tools that JavaScript provide us to ease the use of it. In the end, I will focus on react.
3 types of functions
There 3 types of functions in JavaScript:
- Constructor
- Function
- Method
constructor
Constructor is a function that is used to create new object with the same prototype using the new keyword:
function Person() {
this.name = 'amitai';
}
var newPerson = new Person();
console.log(newPerson.name); // ‘amitai’
As we can see, we used this in order to set a member of Person. When the constructor invoked, this.name got the value of ‘amitai’. And the object newPerson had the property name with the value amitai
function
Function is a “function” if it is not a property of an object:
function foo() {
console.log(this); // undefined in “strict mode” or the global variable
}
In this case, this will be the global variable, or undefined if we use “use strict”.
method
Method is a function that is defined as a property of an object:
var person = {
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // ‘Amitai Barnea’
In methods, this is used to refer to other properties of the object, in this case, to firstName and lastName.
What’s the problem?
After we understand the differences between the different function types, it is time to describe the problem.
Unlike variables, the this keyword does not have a scope, and nested functions do not inherit the this value of their caller. If a nested function is invoked as a method, its this value is the object it was invoked on. (JavaScript: The Definitive Guide, David Flanagan)
It means that there are times that we expect that this will be defined with our object, but it is something completely different!
var person = {
fullName: function () {
var calcFullName = function () {
return this.firstName + ' ' + this.lastName
}
return calcFullName();
}
}
person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // Undefined!!!
The inner function (calcFullName) doesn’t aware of this and returns undefined.
This example is not very usefull, but it is very common to use inner functions, especially in callbacks, for instance:
var person = {
fullName: function (cb) {
$.get('api/getStatus')
.then(function(data) {
this.status = data;
})
}
}
It will not work, person.status will be undefined because this was not the object in the callback function.
The solution
There are 3 solutions for this problem. As JavaScript got more mature the solutions has been improved.
ES3
In ES3 there was no solution to this problem, but there was a work around:
var person = {
fullName: function () {
var that = this;
var calcFullName = function () {
return that.firstName + ' ' + that.lastName
}
return calcFullName();
}
}
person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // 'Amitai Barnea'
Because JavaScript uses closures, we took this and put it into that. that is in the scope of the inner function and the method is working.
ES5
ES5 introduced a more elegant solution using the bind method. The bind method is part of the function prototype. It takes the function and bind it to the object that it got in its arguments.
function addToMe(y) {
return this.x + y;
}
var num = {
x: 3
}
var AddToNum = addToMe.bind(num);
console.log(AddToNum(4)); // 7
After the bind, the function is acting on the object it got. So how can it solve our problem? We will use bind to connect the inner function to this:
var person = {
fullName: function () {
var calcFullName = function () {
return this.firstName + ' ' + this.lastName
}
calcFullName = calcFullName.bind(this);
return calcFullName();
}
}
person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // 'Amitai Barnea'
It is working! Actually, bind is even stronger, and can pass additional parameters to the function I will not go any deeper on this subject here.
ES6
ES6 fixed this flaw of the language with its arrow functions. Not only that arrow functions are more elegant, they are also inject this into the in their invocation. Now we can write the code like that;
var person = {
fullName: function () {
var calcFullName = () => {
return this.firstName + ' ' + this.lastName
}
return calcFullName();
}
}
person.firstName = 'Amitai';
person.lastName = 'Barnea';
console.log(person.fullName()); // 'Amitai Barnea'
And it is working!
How to imply it in React?
In react we use components that are classes that derived from React.Component. Many times we invoke functions from the component’s controllers that need data either from the component’s props or state by using this.props or this.state.
Because this is an inner function, this is not available in the function. We can solve it in two ways:
Using bind in the constructor of the function:
import React, {Component} from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit() {
this.props.submit(this.state)
}
render() {
return (
<form onSubmit={this.onSubmit}>
...
</form>
);
}
}
export default MyComponent;
Using arrow functions:
import React, {Component} from 'react';
class MyComponent extends Component {
onSubmit = () => {
this.props.submit(this.state)
}
render() {
return (
<form onSubmit={this.onSubmit}>
...
</form>
);
}
}
export default MyComponent;
Of course, if you are using ES6, use the arrow function to get cleaner and elegant solution.
references
- JavaScript: The Definitive Guide, David Flanagan