Arrow Functions in JavaScript

Arrow Functions in JavaScript

Arrow functions are a new way of writing a traditional function. This way the function is shorter and quicker to write. Arrow functions were introduced in ES6.

Let's see how we write functions in a traditional way:

const sum = function(a, b) {
  return a + b;
}

Now let's transform this traditional function into an arrow function:

const sum = (a, b) => {
  return a + b;
}

So what do you notice? The function keyword is removed and an arrow => is added for the expression.

We can simplify the above example a little more by removing the return statement:

const sum = (a, b) => a + b;

Do you notice how the curly braces and return is gone? Remember, that you can only do this if the first line of the function body is a return statement.

In the above example, we have two parameters a and b. But what if there was only one parameter? Then we can make it even shorter:

const income = salary => salary * 10;

We have removed the parenthesis as well because there was only one parameter salary. Remember that we can't do this if there is no parameter or parameters are more than one.

Let's do the sorting of numbers with both traditional and arrow functions:

// Traditional style
const numbers = [36, 12, 16, 22];

numbers.sort(function(a, b) { 
    return a - b; 
});

console.log(numbers); // [12, 16, 22, 36]

Let's do the same example with the arrow function:

// Arrow function
const numbers = [36, 12, 16, 22];

numbers.sort((a, b) => a - b);

console.log(numbers); // [12, 16, 22, 36]

That's short and simple. ❤

You might want to watch out while returning an object in an arrow function. You can't do it like this:

// won't work ❌
const profile = () => { name: 'Dovahkiin', address: 'The Cloud District, Whiterun' };

The correct way is, by wrapping the object in parenthesis like this:

// it works ✔
const profile = () => ({ name: 'Dovahkiin', address: 'The Cloud District, Whiterun' });

That's the cosmetic difference between our good old traditional function declaration and the new arrow function. But there is more to it than these fancy changes. ⚡

Let's talk about this

Traditional functions have their own this binding while arrow functions don't. Arrow functions can only access this from their parent scope. So if you use this in an arrow function then it will refer to the object of that arrow function's parent.

Let's understand this by an example:

window.age = 130;

function EdwardCullen() {
  this.age = 17;

  setTimeout(function () {
    // this.age is not declared in this scope so it defaults to the window scope
    console.log(`Edward Cullen is ${this.age} old.`); // "130"
  }, 1);
}

const vampire = new EdwardCullen();

In the above example, we have set the age to 130 on window object. Now inside the EdwardCullen we have setTimeout declared in a traditional way by using function().

Now if you try to print this.age in setTimeout, you will get 130 which was declared directly as window object.

Do you know why? Because setTimeout does not have it's own this.age declared inside it. So it fetches it by default from the window object. It doesn't care about this.age declared in the parent scope.

Let's see the above example using the arrow function:

window.age = 130;

function EdwardCullen() {
  this.age = 17;

  setTimeout(() => {
    // there is no `this` scope in arrow function so it takes this.age from it's parent scope 
    console.log(`Edward Cullen is ${this.age} old.`); // "17"
  }, 1);
}

const vampire = new EdwardCullen();

Now this.age inside setTimeout prints 17 instead of printing the age from window scope. Our arrow function setTimeout tries to find this in the immediate lexical scope of the parent rather than looking in the window scope.

This eliminates the confusion and issues with traditional function declaration where this might work in an unexpected and confusing way.

Let's take another example to understand this:

// Traditional way
function EdwardCullen() {
  this.age = 17;

  this.realAge = function (age) {
    this.age = age;

    setTimeout(function () {
      console.log(this.age); // undefined
    }, 1);
  };
}

const vampire = new EdwardCullen();
vampire.realAge(120);

The above example prints undefined because this.age is not defined inside setTimeout function. Our traditional function, expects it to be defined in the function itself, if not then it seeks that information in window object. Here we don't have age in window object either. So it prints undefined.

Can we fix this without using an arrow function? Let's see:

function EdwardCullen() {
  this.age = 17;

  this.realAge = function (age) {
    this.age = age;
    const self = this;

    setTimeout(function () {
      console.log(self.age); 120
    }, 1);
  };
}

const vampire = new EdwardCullen();
vampire.realAge(120);

Do you notice let self = this? This way we can store this in another variable named self and then we can use self.age inside setTimeout instead of using this. Now it prints 120 correctly. This is how we used to solve this problem before arrow functions.

Well, we have the arrow functions now so we don't have to do this little hack any more:

function EdwardCullen() {
  this.age = 17;

  this.realAge = function (age) {
    this.age = age;

    setTimeout(() => console.log(this.age); 120, 1);
  };
}

let vampire = new EdwardCullen();
vampire.realAge(120);

Here we changed the traditional setTimeout function to an arrow function and it printed 120 correctly. We didn't have to do the self hack to achieve this. It worked because the arrow function got no binding for this for itself so it tries to find the binding from the parent function which is declared as 120 in age parameter of realAge method.

Limitations of Arrow functions

Arrow functions remove the complexity of this but that comes with some limitations:

1. Shouldn't be used as methods

It is not recommended to use arrow functions as methods because of the unexpected results. Let's understand this by an example:

const edwardCullen = {
  age: 17,

  getAge: function() {
    console.log(this.age);
  },

  getRealAge: () => console.log(this.age),
}

edwardCullen.getAge(); // 17
edwardCullen.getRealAge(); // undefined

As you can see in the above example, getAge is using the window object with normal function so it prints 10 while the getRealAge function has no binding and no near lexical scope inside edwardCullen so it will print undefined

In this, particular case, we should stick with getAge by using traditional function declaration. Arrow functions are not a good fit for such cases as evident in the above example.

2. apply, bind and call shouldn't be used

apply,bind, and call work as expected with traditional functions, because we establish the scope for each of the methods but it's not the case with arrow functions.

Let's take a look at the traditional way first:

// Traditional function
const edwardCullen = {
  age: 17
};

window.age = 120;

const realAge = function(a, b, c) {
  return this.age + a + b + c;
}

// call
const firstGuess = realAge.call(edwardCullen, 10, 20, 30);
console.log(firstGuess); // 77

// apply
const secondGuess = realAge.apply(edwardCullen, [10, 20, 30]);
console.log(secondGuess); // 77

// bind
const thirdGuess = realAge.bind(edwardCullen);
console.log(thirdGuess(10, 20, 30)); // 77

We get the age 77 if we use the traditional function.

// Arrow function
const edwardCullen = {
  age: 17
};

window.age = 120;

const realAge = (a, b, c) => this.age + a + b + c;

// call
const firstGuess = realAge.call(edwardCullen, 10, 20, 30);
console.log(firstGuess); // 180

// apply
const secondGuess = realAge.apply(edwardCullen, [10, 20, 30]);
console.log(secondGuess); // 180

// bind
const thirdGuess = realAge.bind(edwardCullen);
console.log(thirdGuess(10, 20, 30)); // 180

We get the age 180 if we use the arrow function. 😁

Now you can see the difference and why it is suggested NOT to use apply, bind and call with arrow functions.

3. Can not be used as constructors

Arrow functions cannot be used as constructors and they don't have prototype property as well:

const nightCreature = () => 'Edward Cullen';
const vampire = new nightCreature(); // TypeError: nightCreature is not a constructor
const nightCreature = () => 'Edward Cullen';
console.log(nightCreature.prototype); // undefined

I hope you learned something today!

Stay awesome! 🌟