How to use arrow functions in Javascript

The arrow functions syntax was introduced with ECMAScript6: by using this new syntax, in some (but not all) cases, we can produce more concise and readable code, especially when our function contains only one expression. In this tutorial we will see how we can define an arrow function, what are the differences with standard functions and what are the cases in which the use of arrow functions is not appropriate.

In this tutorial you will learn:

  • What is an arrow function.
  • How an arrow function is defined.
  • The differences between arrow functions and standard functions.
  • The cases in which arrow functions cannot be used.

javascript-logo

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Operating system agnostic.
Software An installation of node to follow this tutorial in a non-browser environment.
Other Knowledge of Javascript and object oriented concepts.
Conventions # – requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux commands to be executed as a regular non-privileged user

What is an “arrow function”?

Arrow functions were introduced with ECMAScript6: by using this new syntax we can often obtain more concise code, in some cases translating multi-line callbacks to one-liners, thanks to features like the implicit return. Because of its peculiarities, however, arrow functions cannot replace standard functions everywhere: there are some contexts where we cannot use them, and we will see why.



From standard functions to arrow functions

In this paragraph we will see an example of how we can replace a standard function with an arrow function: we will use an higher order function callback as a perfect example of when performing such a substitution is completely fine.

As you surely know, an higher order function is a function that returns another function, or accepts another function as an argument. In this example we will use filter, or array.prototype.filter if you like. This method of the array object, takes a function as its argument, and returns a new array, populated by all the elements of the original array which are positive to the test implemented inside the callback function.

Let’s see an example of using filter with a classic function. Imagine we have an array of objects, each of them representing characters from the “Lord Of The Rings” book:

const characters = [
  { name: 'Frodo', race: 'Hobbit' },
  { name: 'Sam', race: 'Hobbit' },
  { name: 'Legolas', race: 'Elf' },
  { name: 'Aragorn', race: 'Man' },
  { name: 'Boromir', race: 'Man' }
]

The characters array contains 5 elements; each of them has two properties: name and race. Now, suppose we want to create a new array populated only by the characters which belong to the race of men. Using filter and the standard function syntax, we write:

const men = characters.filter(function filterMen(element) {
  return element.race == 'Man';
});

As said before, filter, takes a function as an argument: when using the standard syntax, this function can be either named or anonymous. In most situations anonymous functions are used as callbacks, but for the sake of this example, and to later highlight one of the differences between standard and arrow functions syntax, we gave a name to our function: filterMen.

The callback function to be used with filter, takes only one mandatory parameter, which is the element of the original array which is being processed each time. If the function returns true, the element is inserted as a member of the new array, if the function returns false the element is not. In this specific case, we defined a simple test:

character.race == 'Man'

This test returns true if the raceproperty of the element which is being processed, equals to the string ‘Man’. Here is the result of what we wrote above:

[ { name: 'Aragorn', race: ''Man' },
  { name: 'Boromir', race: ''Man' } ]

Now, suppose we want to refactor the code above by using an arrow function. We would write:

const men = characters.filter(element => element.race == 'Man');

By using the arrow functions syntax, we have been able to accomplish the same result of the previous example with just one line of code: how nice is that?!. Don’t worry if at first glance the new syntax confuses you, just continue reading.

The arrow function syntax

While we define a standard function by using the function keyword, an arrow function is defined by using the => symbol. This, obviously, is not the only difference between the two: one of the most important ones we should highlight here is that while classic functions, in function expressions, can be either named or anonymous, arrow functions are always anonymous.



Defining arguments in arrow functions

In the previous example, since we get rid of the function keyword, the first thing we can read is element, which is the argument accepted by the arrow function. The rule to follow when defining the arguments expected by an arrow function is simple: if the function accepts multiple arguments, or no arguments at all, we must enclose them between parenthesis; if the function contains only one argument, as it is the case in our example, we can omit the parenthesis completely.

As an example, imagine we want to define a function which returns the product of two numbers passed as its arguments. We would write:

// Since the function takes two parameters, we must use parenthesis
const multiply = (a,b) => a * b;

Implicit return and curly braces

In all the examples above, you may have notice the absence of another thing: the curly braces which delimit the body of the function. Why do we omitted them? If the body of the arrow function consists of only one expression, the curly braces can be omitted: if it is the case, the result of the expression is implicitly returned:

// If we omit curly braces the result of the expression is implicitly returned
const multiply = (a,b) => a * b;
multiply(2,3);
6 // Result is 6: it is implicitly returned

// If we use curly braces, the result is not implicitly returned
const multiply = (a,b) => { a * b }
multiply(2,3);
undefined // Result will be undefined, since we didn't explicitly return the result of the expression

In the code above we defined a very simple function, multiply: this function expects two parameters, therefore we must enclose them between parenthesis. The => symbol defines the arrow function. In the first example, since we have only one expression, which returns the product of the two numbers passed as parameters, we can omit curly braces and take advantage of the implicit return feature.

In the second example we used the curly braces, therefore the function returned undefined, since we have no implicit return: to obtain the expected result we should have used return explicitly.

Multiple statements or expressions in the function body

Curly braces are also the only way we can specify multiple statements or expressions inside of an arrow function. For example, suppose that instead of returning the product of two numbers, we want our function to output a string, displaying it:

const multiply = (a,b) => {
  const product = a*b;
  console.log(`The product of ${a} and ${b} is ${product}`);
}

multiply(2,3);
The product of 2 and 3 is 6

What if our arrow functions has to return an object literal, itself delimited by curly braces? In that case, we must enclose the object literal between parenthesis:



const createChar = (characterName, characterRace) => ({ name: characterName, race: characterRace });
createChar('Gimli', 'dwarf')
{ name: ''Gimli', race: ''dwarf' }

How this behaves inside arrow functions

One of the most relevant, if not the most relevant difference between classic functions and arrow functions is how the this works. This difference is the main reason why in some cases we cannot use arrow functions, as we will see soon. Before highlighting the differences, let’s recap how this works when it is used in standard functions. The first thing to remember is that, the value of this is determined by how the function itself is called, let’s see some examples.

The default: this is a reference to the global scope

When this is used inside a standalone function, and we are not working in strict mode, it is references the global scope, which is the window object on a browser environment, or the global object in Node.js. In the same situation, but in strict mode, this will be undefined and we will receive an error:

var i = 20; // Here we used var instead of let because the latter doesn't create a property on the global scope.

function foo() {
  console.log(this.i);
}

// Non-strict mode
foo()
20

// Strict mode
foo()
TypeError: Cannot read property 'i' of undefined

Implicit binding

When a standard function is referenced inside an object, and that function is called with that object as a context, using the dot notation, this becomes a reference to that object. This is what we call implicit binding:

function foo() {
  console.log(this.i);
}

let object = {
  i: 20,
  foo: foo // The foo property is a reference to the foo function
}

object.foo() // this is a reference to object, so this.i is object.i
20

Explicit binding

We say that we are using an explicit binding when we are explicitly declaring what this should reference. It can be accomplished by using the call, apply or bind methods of a function (which in Javascript is itself a first-class object. Remember the first case we mentioned above, when the default binding applies:

var i = 20;

function foo() {
  console.log(this.i);
}

const object = {
  i: 100
}

foo() // This will output 20 or generate a TypeError in strict mode.

// If we explicitly set this to be a reference to object the things changes.

// call and apply execute the function immediately with the new context:
foo.call(object)  // Output is 100
foo.apply(object) // Output is 100

// bind instead, returns a new function with the specified context.
let boundFoo = foo.bind(object)
boundFoo() // Output is 100

The are some differences between call, apply and bind: the relevant is that the latter returns a new function bound to the specified context, while with the other two, the function, bound to the specified context, is executed immediately. There are other differences, but we will not see them here. The important thing is to understand how explicitly binding works.

How arrow functions are different in this regard?

In all the cases and examples above, we saw how, when using standard functions, the value of this depends on how the function is called. Arrow functions, instead, use the lexical this: they don’t have their own this, but always use the this from their enclosing scope. A typical example where this could produce unexpected effects is on event listeners. Suppose we have a button with id “button1”, and we want to change its text when it is clicked:



<button id="button1" type="button">Click me!</button>

// The event listener with a standard function as a callback
document.getElementById('button1').addEventListener('click', function() {
  this.innerText = "Clicked!";
})

The code works perfectly, and once the button is clicked, its text changes as expected. What if we use an arrow function in this case? Suppose we write it like this:

document.getElementById('button1').addEventListener('click', () => this.innerText = "Clicked!"; )

The code above doesn’t work, why? Easy: because, as we said before, while in the first example, this inside the standard callback function references the object on which the event occurs (the button), when we use the arrow function this is inherited from the parent scope, which in this case is the window object. For the sake of completeness, we should say that the example above could be easily fixed to work with an arrow function:

document.getElementById('button1').addEventListener('click', event => event.target.innerText = "Clicked!"; )

This time the code works because we didn’t use this to reference the button, but we let our function accept one argument, which is event. In the function body we used event.target to reference the object which dispatched the event.

For the same reason we mentioned above, arrow functions cannot be used as object methods or prototype methods:

// Arrow functions don't work as object methods...
const object1 = {
  i: 1000,
  foo: () => console.log(`the value of i is ${this.i}`)
}

object1.foo()
the value of i is undefined

// ...and they don't not work as prototype methods.
const Person = function(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.introduce = () => console.log(`My name is ${this.name} and I am ${this.age} years old`);
const jack = new Person('Jack', 100);

jack.name
'Jack'

jack.age
100

jack.introduce()
My name is undefined and I am undefined years old

Conclusions

The arrow function syntax is a very nice feature introduce with ECMAScript6. With this new way of defining functions we can write shorter and cleaner code. We saw how to define an arrow function, and how the new syntax works.

We also saw why arrow functions cannot replace standard functions in all circumstances, because they don’t have their own this, and uses the one of their enclosing scope: this, as we saw in this tutorial, makes them not usable as methods or constructors. If you are interested in other Javascript tutorials stay tuned: in the next tutorial we will talk about the fetch, function. In the meanwhile, you can check our article about promises.



Comments and Discussions
Linux Forum