Introduction
In this article, we will look at one of the most popular features introduced with ES6, the arrow functions. Often, our minds are trained to learn a concept, and when you want to apply the concept via code, we look up for the syntax.
But, what happens if there are multiple syntaxes that exist for a single function? The mind just goes into a loop and that's precisely what happened to me when I started learning arrow functions.
Hence, I'll try to introduce the various syntax that exists with arrow functions with examples, and with practice, it really gets easier.
Arrow Function ⇒ The Basic flavor
/* A simple function that returns a greeting */
const greeting1 = function() {
console.log('Greeting1: Hi! How are you?');
}
/* An Arrow function that returns a greeting */
//In the syntax notice that the word 'function' is removed and after the brackets the '=>' symbol is added
const greeting2 = () => {
console.log('Greeting2: Hi! How are you?');
}
greeting1(); //output -> Greeting1: Hi! How are you?
greeting2(); //Output -> Greeting2: Hi! How are you?
Things to note:
- We removed the function keyword and immediately added an arrow after the angular brackets.
Arrow Function ⇒ No Params, Single line function
When the function has a single line statement within it's body, you can further shorten the arrow function by removing the curly braces {} as shown below.
//Basic Arrow Function as shown in the above example
const greeting2 = () => {
console.log('Greeting2: Hi! How are you?');
}
//Shorter syntax
const greeting3 = () => console.log('Greeting3: Hi! How are you?');
greeting2(); //Greeting2: Hi! How are you?
greeting3(); //Greeting3: Hi! How are you?
Arrow Function ⇒ Single Param, Single line function
When you have only one parameter passed into the function, then you can remove the angular brackets around the param name as shown below.
//Basic Arrow Function as shown in the above example
const greeting2 = (name) => {
console.log(`Greeting2: Hi ${name}, How are you?`);
}
//Shorter syntax
const greeting3 = name => console.log(`Greeting3: Hi ${name}, How are you?`);
greeting2('Skay'); //Greeting2: Hi Skay, How are you?
greeting3('Skay'); //Greeting3: Hi Skay, How are you?
Arrow Function ⇒ Single Param, Multiple lines function
If the function contains multiple lines, then the curly braces are compulsory.
//Single Param with multiple lines in the function
const greeting2 = name => {
const greeting = `Greeting2: Hi ${name}, How are you?`;
console.log(greeting);
}
greeting2('Skay'); //Greeting2: Hi Skay, How are you?
Arrow Function ⇒ Multiple Params, Single & Multiple lines function
Multiple Parameters/Single line function
When there are multiple parameters passed to a function, the angular brackets is compulsory as shown below.
//Multiple Params with single lines
const greeting = (name, membershipType) =>
console.log(`Greeting: Hi ${name}, Are you ${membershipType} member?`);
greeting('Skay', 'Special Edition'); //Greeting: Hi Skay, Are you a Special Edition member?
Multiple Parameters/Multiple lines function
The angular brackets around the parameters are compulsory when there are multiple parameters. Likewise, the curly brackets are also compulsory when you have multiple lines.
//Multiple Params with multiple lines
const greeting = (name, membershipType) => {
const memberGreeting = `Greeting: Hi ${name}, Are you ${membershipType} member?`;
console.log(memberGreeting);
}
greeting('Skay', 'Special Edition'); //Greeting: Hi Skay, Are you a Special Edition member?
So, far we've seen the various syntaxes under different combinations of parameters vs the statements inside the function body. While they are fancy, we still haven't seen the use-cases when arrow functions are truly powerful.
Implicit Return
Let us first look at a function returning a value and how we can use the arrow function's syntax to use its implicit return feature.
Non-Arrow Function:
//Function Expression returning the name of the fruit
const getFruitName = function() {
return 'Mango';
}
//Display the name of the fruit on the console
console.log(getFruitName());
Arrow Function:
//When we convert the above function into an Arrow function
const getFruitName = () => 'Mango';
console.log(getFruitName());
Things to note:
- Notice that the 'return' statement is completely omitted and the string value 'Mango' is returned when the function getFruitName() is invoked. This is known as an implicit return.
- This is very powerful since it significantly improves readability especially while chaining promise functions.
Let us look at a real example, where the code readability improves drastically when implicit return syntax is used.
Non-Arrow Function:
The below code fetches a list of Github users using the Fetch API. The comments within the code provide the explanation in-line.
/*
Function getGithubUsers returns a list of 30 users by default
The function returns a promise with the GitHub users array.
*/
function getGithubUsers() {
//Using Fetch API make a call to the github's get Users API
return fetch('https://api.github.com/users')
.then(function(response) {
//If the call to the API is successful, then it returns the response object
//Returning the JSON object within the response object which contains the actual users data
return response.json();
}).then(function(data) {
//The response.data() returned from the previous function is resolved into the data object
//This data object is an array containing the gitHub users
return data;
});
}
//Call the getGithubUsers() function
//If the response is successful, then the data object is returned which contains the github users
getGithubUsers()
.then(function(data) {
console.log(data);
});
Arrow Function:
With the use of arrow functions and through it's implicit return syntax, the code is much easier to write as well as read.
/*
Function getGithubUsers returns a list of 30 users by default
The function returns a promise with the GitHub users array.
*/
function getGithubUsers() {
//Using Fetch API make a call to the github's get Users API
return fetch('https://api.github.com/users')
.then(response => response.json())
.then(data => data);
}
//Call the getGithubUsers() function
//If the response is successful, then the data object is returned which contains the github users
getGithubUsers()
.then(data => console.log(data));
Things to note:
- We have removed the 'function' keyword & added the ⇒ symbol to make it into an arrow function.
- We have then removed the 'angular' brackets since it has only one parameter, i.e., response.
- We have then removed the 'return' keyword since arrow functions have an implicit return.
We've just combined all of the things you've learned thus far from the above examples. But, when they are combined, the code is much less and it's much more cleaner. It might be a little overwhelming at first, but you'll get used to it once you start using it.
Arrow Functions solve "this" keyword lookup
Context
Whenever a JavaScript code runs, it runs within the context either at a global scope (window) or function scope or block scope.
Within such a context, we can use the 'this' keyword to reference the object. The reference of the object changes based on where you are using the 'this' keyword.
Let us look at the code sample below:
//Global Function 'bark' displays the value of 'this' on the console
function bark() {
//In this case, 'this' refers to the global object which is the 'window'
console.log(this); //Output -> Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
}
//An object declaration 'dog'
const dog = {
name: 'Pluto',
breed: 'Doberman',
bark: function() {
//In this case, 'this' refers to the dog object
console.log(this); //output -> {name: 'Pluto', breed: 'Doberman'}
return "Woof Woof";
}
}
//Invoke the bark and dog.bark functions
bark();
dog.bark();
Things to note:
- When the function 'bark' is invoked, the console.log(this) statement outputs the window object. The reason is whenever a call is made to 'this' keyword, by default a lookup is done to the parent object of the function and in this case, it's the window object.
- When the function 'dog.bark()' is invoked, the console.log(this) statement outputs the dog object. Again, the reason is the lookup done from the bark function which is inside the 'dog' object and hence the reference to 'this' is the dog object itself.
Hope the above statements made sense.
This Keyword with an anonymous function
Now, let's look at another example of using the 'this' keyword with an anonymous function.
//Office Constructor that accepts employees as an object array
const Office = function(employees) {
this.employees = employees;
this.department = 'Marketing'
this.greeting = function() {
this.employees.forEach(function(employee) {
console.log(`Hello ${employee.name}. Welcome to our ${this.department} department.`);
//Output -> Hello John. Welcome to our undefined department.
//Output -> Hello Sarah. Welcome to our undefined department.
//If you console.log(this) over here, it'll reference the window object.
})
}
}
//Creating an employees array with 2 employees, John & Sarah
const employees = [{
name: 'John',
experience: '10 yrs'
},
{
name: 'Sarah',
experience: '20 yrs'
}
];
//Creating myOffice object using the constructor 'Office' and passing the 'employees' as a parameter
const myOffice = new Office(employees);
//Invoke the greeting() method of myOffice object created
myOffice.greeting();
Things to note:
- const Office is a constructor that accepts employees as a parameter.
- const myOffice is the object created by passing in the employees array consisting of John & Sarah.
- When myOffice.greeting() method is invoked, it runs the forEach loop on this.employees array. Here the 'this' keyword refers to the 'myOffice' object.
- An anonymous function is created within the forEach block and within the forEach block, when the 'this' keyword is referenced in the console.log statement for 'this.department', undefined is output.
- So, what happened here? We know that based on previous examples that whenever a 'this' keyword is referenced, the JavaScript compiler references the parent object and should have referenced the 'myOffice' object.
- However, with Anonymous functions, it creates a new scope and within the new scope, the parent object becomes the window object, and hence when 'this' keyword is referenced within the anonymous function, it references the window object.
I hope that made sense. And if we change the above example to arrow functions, it no longer creates a local scope and references the parent object 'myOffice' as it should have and this is what arrow function precisely addresses.
//Office Constructor that accepts employees as an object array
const Office = function(employees) {
this.employees = employees;
this.department = 'Marketing'
this.greeting = function() {
this.employees.forEach(employee => {
console.log(`Hello ${employee.name}. Welcome to our ${this.department} department.`);
//Output -> Hello John. Welcome to our Marketing department.
//Output -> Hello Sarah. Welcome to our Marketing department.
})
}
}
//Creating an employees array with 2 employees, John & Sarah
const employees = [{
name: 'John',
experience: '10 yrs'
},
{
name: 'Sarah',
experience: '20 yrs'
}
];
//Creating myOffice object using the constructor 'Office' and passing the 'employees' as a parameter
const myOffice = new Office(employees);
//Invoke the greeting() method of myOffice object created
myOffice.greeting();
In the above example, we've changed the anonymous function into an arrow function by removing the function keyword and including the ⇒ symbol. It can further be shortened by removing the curly braces as well since there's only one statement within the function.
Conclusion
So to recap, arrow functions are one of the most important and powerful features introduced with ES6 and it primarily addresses two aspects:
- Improves readability by having shorter syntax.
- Arrow functions do not have their own 'this', instead, they look up to their parent scope to determine what the 'this' is referencing.
Hope you enjoyed this article. Please let me know your comments, and do not forget to share it with others.
You can find on Twitter at @skay_tech