We'll look at the four ways of creating a function in JavaScript: as a statement, as an expression, as an arrow function, and using the Function constructor.
“I wish undefined was a function in JavaScript.”
There are four ways a function can be created in JavaScript. They are as follows:
All four ways of function creation have distinct features such as an arrow function that does not have its own this
object, a function statement that is hoisted on the top of the execution context, and a function expression that can be immediately invoked without creating a separate scope. Having a good understanding of various characteristics of different types of JavaScript functions is useful to write better code. This article explains these functions.
A function as a statement can be created as shown the following code example:
function Add(num1,num2){
let sum = num1+ num2;
return sum;
}
let res = Add(7,8);
console.log(res); // 15
A function statement starts with the function keyword. It can return a primitive type value, object, or another function. For example, a function statement can return an object as shown in the following code example:
function getProduct(){
let product = {
Id:1,
Title:'Book',
Price: 30
};
return product;
}
let p1 = getProduct();
console.log(p1); // prints product object
The main characteristic of a function statement is it is hoisted at the top of the execution context. Therefore, you can call a function statement before it is declared, as shown in the following code example:
let res = Add(7,8);
console.log(res); // 15
// .... other codes
function Add(num1,num2){
let sum = num1+ num2;
return sum;
}
As you see, that Add
function is called before it is created, and this is possible because a function statement is hoisted at the top of the execution context.
A function statement is hoisted at the top of the execution context. So, it can be invoked before it is created.
In a function statement, you can pass primitive values as either parameters or non-primitive values, such as an object or array. The primitive parameters are passed as a value, and the non-primitive values are passed as a reference. So, if you pass an object to a function statement and the function changes the object’s properties, that change is visible outside the function, as shown in the following code example:
function updateProduct(product){
product.Title = 'Updated Book';
}
let product = {
Id:1,
Title:'Book',
Price: 30
};
console.log(product.Title); // Book
updateProduct(product);
console.log(product.Title); // Updated Book
In the above code, JavaScript updates the Title
of the product object. However, if you pass a string, number, or Boolean parameters in the function, it won’t be reflected globally.
In a function statement, primitive parameters are passed as a value, and non-primitive parameters such as objects and arrays are passed as a reference.
A function as an expression can be created as shown in the following code example.
let add = function a(num1,num2){
let sum = num1+ num2;
return sum;
}
let res = add(8,9);
console.log(res);// 17
In a function expression, you assign a function to a variable. A function expression can also be created as anonymous without a name, as shown in the following code example:
let add = function (num1,num2){
let sum = num1+ num2;
return sum;
}
let res = add(8,9);
console.log(res);// 17
The name of the function expression is local to the function body and very useful for recursions or any scenario in which you need to refer to the function inside the function body. You can write a factorial function by using function expression name:
var Calculator = {
factoriral : function fact(n){
if(n<=1){
return 1;
}
return n*fact(n-1);
}
}
let result = Calculator.factoriral(7);
console.log(result); // 5040
The main differences between a function statement and a function expression are:
Since a function expression is not hoisted at the top of the execution context, calling the function before it is created throws ReferenceError, as shown in the following code example:
let res = add(8,9);
console.log(res); // ReferenceError : add is not a function
let add = function (num1,num2){
let sum = num1+ num2;
return sum;
}
So, you can summarize the hoisting difference as:
A function expression is not hoisted at the top of the execution context. So, it cannot be invoked before it is created.
One other important feature of a function expression is that it can be immediately invoked as soon as it is defined, which is also known as Immediately Invoked Function Expression:
let add = function (num1,num2){
let sum = num1+ num2;
return sum;
}(8,9);
console.log(add); // 17
On the other hand, a function statement cannot be immediately invoked as soon as it is defined without creating a separate scope, as shown in the following code example.
(function add(num1,num2){
let sum = num1+num2;
console.log(sum); //17
})(8,9);
The arrow functions were introduced in ECMA 2015 with the main purpose of giving a shorter syntax to a function expression. Besides providing shorter syntax, which increases the readability of the code, it does not have its own value of the this
object. The value of this
object inside an arrow function is inherited from the enclosing scope.
You can write an arrow function to add two numbers as shown in the next code example.
var add = (num1, num2)=> num1+num2;
let res = add(5,2);
console.log(res); // 7
Some syntax rules for an arrow function are:
You can apply the above syntax rules to create arrow functions, as shown in the following code example.
// More the one parameters and only one expression
var add = (num1, num2)=> num1+num2;
// No parameters and no return statment
let greet = ()=>console.log('hey');
// More than one paramter and more than one expression in body
var divide = (num1,num2)=>{
if(num2 ==0){
return 'can not divide';
}
else {
return num1 / num2;
}
}
To return an object from an arrow function, you can either use a return statement or a slightly different syntax of enclosing the object in a small bracket, as shown in the following example:
let product = (title,price)=>({
title:title,
price : price
})
let p1 = product('pen',100);
console.log(p1);
Some other important features of an arrow function are:
this
object The value of this
object inside an arrow function is inherited from the enclosing scope. To understand it in a better way, consider the following code example:
let Product = {
Title :'Pen',
Price : 100
}
function foo(){
console.log(this); // Product object
function koo(){
console.log(this); // global object
}
koo();
}
foo.call(Product);
The function foo
is called indirectly using the call()
method to pass the Product
object as the value of this
inside it. And since the function koo
is called using the function invocation pattern, the value of this
object for it is the global object. So, even though koo
is an internal function of foo
, it has its own value of this
object.
But, if you refactor the koo
function as an arrow function, then instead of having its own this
object, it inherits it from the enclosing scope, as shown in the next code example.
let Product = {
Title :'Pen',
Price : 100
}
function foo(){
let that = this;
console.log(this); // Product object
var koo = ()=> console.log(this);
koo();
}
foo.call(Product);
The other restriction on arrow functions is that they cannot be used as constructors, so the following code example throws an exception.
var Product = (Title,Price)=>{
return {
Title:Title,
Price: Price
}
}
let p1 = new Product('Pen',200); // Error Product is not a constructor
A function can be dynamically created using the Function constructor, but it suffers from security and performance issues and is not advisable to use.
You can create a function using the Function constructor as shown in the following example.
var add = Function('num1','num2','return num1+num2');
let res = add (7,8);
console.log(res); // 15
In the Function
constructor, you pass parameters and function body as a string. The function created with the Function constructor is always created in the global scope.
In this article you learned about various ways of creating a function and their characteristics – for example, a function statement is hoisted at the top of the execution context, a function expression is not hoisted and can be created without a name, and an arrow function does not have its own this
object.
I hope you found the article useful. Thanks for reading.
Dhananjay Kumar is an independent trainer and consultant from India. He is a published author, a well-known speaker, a Google Developer Expert, and a 10-time winner of the Microsoft MVP Award. He is the founder of geek97, which trains developers on various technologies so that they can be job-ready, and organizes India's largest Angular Conference, ng-India. He is the author of the best-selling book on Angular, Angular Essential. Find him on Twitter or GitHub.