Different behavior of 'this' keyword in arrow and non-arrow function

There are two primary reason arrow function is introduced in ES6(also known as ES2015).

  1. Shorter syntax
  2. No binding of this

The first reason is very obvious, we are going to dive deep into second reason.

Before we understand this in arrow functions, we need to understand it in non-arrow functions. There are mainly following ways to define this object

  1. When an object obj is created using new keyword, this inside that object is pointed to the obj
  2. this is undefined in free function in strict mode
  3. this is pointed to the base object if it’s inside a function which is a method of a class.
  4. this is pointed to whatever is bound to the function call via call, apply or bind methods.
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
41
42
43
44
45
46
"use strict";
// case 1
function Car(brand) {
this.brand = brand;
console.log(this);
}
const myCar = new Car('toyota'); // this points to object: car { brand: 'toyota' }

// case 2
function sum(a, b) {
console.log(this);
return a + b;
}
sum(1, 2); // this points to undefined

function Car(brand) {
this.brand = brand;
function start() {
console.log(this);
}
start();
}
const myCar = new Car('toyota'); // this points to undefined because start() is a free function

// case 3
function Car(brand) {
this.brand = brand;
this.start = function() {
console.log(this);
}
}
const myCar = new Car('toyota');
myCar.start(); // this points to object: Car { brand: 'toyota', start: [Function] }

// case 4
function Car(brand) {
this.brand = brand;
this.start = function() {
console.log(this);
}
}
const myToyotaCar = new Car('toyota');
const myFordCar = new Car('ford');
myFordCar.start = myFordCar.start.bind(myToyotaCar);
myFordCar.start(); // this points to object: Car { brand: 'toyota', start: [Function] }
myToyotaCar.start.call(myFordCar); // this points to object: Car { brand: 'ford', start: [Function: bound ] }

Now you understand how this works in non-arrow functions, let’s look at what’s different in arrow functions. I mentioned this early that the second reason arrow function is introduced is to get rid of binding and having more logical this object. Instead of binding, this is derived from enclosing context. Let’s modify above examples to use arrow functions and see what’s different.

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
41
"use strict";

// case 2
const sum = (a, b) => {
console.log(this);
return a + b;
}
sum(1, 2); // this points to undefined, the same as non-arrow function because it's global free function

function Car(brand) {
this.brand = brand;
let start = () => {
console.log(this);
}
start();
}
const myCar = new Car('toyota'); // this points to object: Car { brand: 'toyota' }. This is different than non-arrow function, and often what we want

// case 3
function Car(brand) {
this.brand = brand;
this.start = () => {
console.log(this);
}
}
const myCar = new Car('toyota');
myCar.start(); // this points to object: Car { brand: 'toyota', start: [Function] }, the same as non-arrow function which is a method of a class.

// case 4
function Car(brand) {
this.brand = brand;
this.start = () => {
console.log(this);
}
}
const myToyotaCar = new Car('toyota');
const myFordCar = new Car('ford');
myFordCar.start = myFordCar.start.bind(myToyotaCar);
myFordCar.start(); // this points to object: Car { brand: 'ford', start: [Function] }
myToyotaCar.start.call(myFordCar); // this points to object: Car { brand: 'toyota', start: [Function] }
// as you can see, bind doesn't have any effect on arrow functions.

As summary, using arrow function give more logical this, most of the time it is what you want. But it loses the ability to override this by calling bind, call or apply.

Reference:
Arrow Functions