In JavaScript, there are two main ways of writing functions. I can create functions using the regular function syntax. Or I can use the arrow function syntax.
I'll also learn about the differences between the two and when it's appropriate to use each of them.
Table of Contents Regular Function Syntax vs Arrow Function Syntax How to Access the Arguments Passed to Functions Duplicate Named Parameters Function Hoisting How to Handle "this" Binding in Functions How to Use Functions as Constructors So Which One Should You Use?
To understand the difference between the two options, let's begin by looking at their syntax.
The ordinary way of declaring functions in JavaScript is to use the function keyword. Here is an example:
function sayHello(name) {
return 'Hello ' + name
}
To return a value from a regular function, you have to explicitly use the return keyword. Otherwise the function will return undefined.
Arrow functions were introduced with ECMAScript 6 (ES6). They give you a more concside way of defining functions in JavaScript.
Using the same sayHello function from the previous example, let's see how to create it with the arrow function syntax.
const sayHello = (name) => {
return 'Hello ' + name
}
Unlike regular functions, you don't have to use an explicit return if there's only one statement like the above example. You can write the function on a single line like so.
const sayHello = (name) => 'Hello ' + name
Note how the curly braces are also removed to implicitly return the result of the expression. You can even remove the parenthesis too if there is only one argument. See the example below:
const sayHello = name => 'Hello ' + name
The name is the only argument the function takes. And this means you can remove the parenthesis from the argument and it will still work fine.
How to Access the Arguments Passed to Functions JavaScript provides a way to access all arguments passed to a function. But the way you acess these arguments depends on the type of function you are working with.
You can access all the arguments passed to a regular function using the arguments object. The arguments object is an array-like object that holds all the arguments passed to the function.
Example:
function logNumbers(num1, num2) {
console.log(arguments)
}
logNumbers(8, 24)
Output :
0: 8
1: 24
As you can see from the log result, the arguments object contains the two numbers passed as arguments to the logNumbers function.
The arguments object is not available in arrow functions. If you try to access it in arrow functions, JavaScript will throw a reference error.
const logNumbers = (num1, num2) => {
console.log(arguments)
}
logNumbers(8, 24) // Uncaught ReferenceError: arguments is not defined
To access the arguments passed to an arrow function, you can use the rest parameter syntax (...).
Example:
const logNumbers = (...args) => {
console.log(args)
}
logNumbers(8, 24)
Output :
[8, 24]
Using the rest parameter syntax (...), you get access to all the arguments passed to the logNumbers function.
Another difference between regular functions and arrow functions is how they handle duplicates in the named parameters.
When a regular function has duplicate names in the parameters, the last parameter with the duplicate name will take precedence. Let's see an example:
function exampleFunction(a, b, a) {
console.log(a, b)
}
exampleFunction("first", "second", "third")
Output :
third second
In the example above, the third argument overrides the value of the first argument because the last duplicate parameter is the one that takes precedence.
But in "strict mode", using a duplicate named parameter will result in a syntax error.
"use strict"
function exampleFunction(a, b, a) {
console.log(a, b)
}
exampleFunction("first", "second", "third")
Output :
Uncaught SyntexError: Duplicate parameter name not allowed in this context
Arrow functions don't allow for the same parameter name to be used more than once in the parameter list. Doing so will result in a syntax error.
Example:
const exampleFunction = (a, b, a) => {
console.log(a, b)
}
exampleFunction("first", "second", "third")
Output :
Uncaught SyntexError: Duplicate parameter name not allowed in this context
Hoisting in JavaScript is a behaviour where variables and functions are moved to the top of their containing scope during compilation, before the code is executed.
Regular functions are hoisted to the top. And you can access and call them even before they are declared.
regularFunction()
function regularFunction() {
console.log("This is a regular function.")
}
Output :
This is a regular function.
The above is an example of calling a regular function before it is declared. And it works fine without throwing any error because of function hoisting.
Arrow functions, on the other hand, cannot be accessed before they are initialised.
arrowFunction()
const arrowFunction = () => {
console.log("This is an arrow function.")
}
Output:
Uncaught ReferenceError: Cannot assess 'arrowFuction' before initialization.
Unlike regular functions, attempting to call an arrow function before it's declared will result in a reference error, as you can see from the output above.
Regular functions have their own this context. And this is determined dynamically depending on how you call or execute the function.
Arrow functions, on the other hand, do not have their own this context. Instead, they capture the this value from the surrounding lexical context in which the arrow function was created.
The following are two different scenarios using both regular functions and arrow functions. You'll see how the this context is determined.
For regular functions, a simple function call sets the this value to the window object (or to undefined if you're using the "strict mode"):
function myRegularFunction() {
console.log(this)
}
myRegularFunction()
Output :
Window
"use strict"
function myFunction() {
console.log(this)
}
myFunction() // udefined
With arrow functions, a simple function call sets the this value to the surrounding context whether you're using strict mode or not. In the example below, the surrounding context is the global window object.
const myArrowFunction = () => {
console.log(this);
};
myArrowFunction()
Output :
Window
When you invoke a method whose value is a regular function, the this value is set to the object on which the method is called. But when the value of the method is an arrow function, the this value is set to the global window object.
const myObject = {
regularExample: function() {
console.log("REGULAR: ", this)
},
arrowExample: () => {
console.log("ARROW: ", this)
}
}
myObject.regularExample()
myObject.arrowExample()
Output :
REGULAR: {regularExample: f, arrawFunction: f}
ARROW: Window {window: Window, self: Window, document: document, name: '', location, Location, ...}
While the method with the regular function logs the object to the console, the one with the arrow function logs the global window object instead.
For regular functions, you can create a new instance using the new keyword. And this sets the this value to the new instance you've created.
For arrow functions, you cannot use them as constructors. This is because the value of this in arrow functions is lexically scoped – that is, determined by the surrounding execution context. This behaviour does not make them suitable to be used as constructors.
Here's an example:
function RegularFuncBird(name, color) {
this.name = name
this.species = color
console.log(this)
}
const ArrowFuncBird = (name, color) => {
this.name = name
this.color = color
console.log(this)
}
new RegularFuncBird("Parrot", "blue")
new ArrowFuncBird("Parrot", "blue")
Output:
RegularfuncBird {nam: 'Parrot', species: 'blue'}
Uncaught TypeError: ArrowFuncBird is not a constructor
at functions.html:73:1
The RegularFuncBird constructor works fine with the new keyword, but the ArrowFuncBird results in a type error.
There is no straightforward answer to this. Whether you use a regular function or arrow function depends on the specific use case.
It's recommended to use regular function in any of the following cases:
- when you need to use a constructor with the new keyword
- when you need the this binding to be dynamically scoped
- when you want to use the arguments object
And you can use arrow functions in any of the following cases:
- when you want a more concise syntax for the function
- when you need to maintain the lexical scope of this
- for non-method functions (in most cases)
As you've learned from this article, both are valid ways of defining functions in JavaScript. Remember that choosing between them is not always a matter of personal preference. Rather, it's dependent on the kind of behaviour you expect from the function.