By Benjamin Semah
In JavaScript, there are two main ways of writing functions. You can create functions using the regular function syntax. Or you can use the arrow function syntax.
In this article, you will learn how to use both options. You'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?
Regular Function Syntax vs Arrow Function Syntax
To understand the difference between the two options, let's begin by looking at their syntax.
Regular function 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 function syntax
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.
How to access arguments with regular functions
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)
Log results of the arguments object
As you can see from the log result, the arguments
object contains the two numbers passed as arguments to the logNumbers
function.
How to access arguments with arrow functions
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)
Log results for the arguments from an arrow function
Using the rest parameter syntax (...
), you get access to all the arguments passed to the logNumbers
function.
Duplicate Named Parameters
Another difference between regular functions and arrow functions is how they handle duplicates in the named parameters.
Duplicate named parameters in regular functions
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")
Log results for named duplicate parameters
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")
Strict mode doesn't allow using a parameter name more than once
Duplicate named parameters in arrow functions
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")
Arrow functions do not allow duplicate parameter names
Function Hoisting
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.
Hosting in regular functions
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.")
}
Log result of calling the regular function before it's declared
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.
Hoisting in arrow functions
Arrow functions, on the other hand, cannot be accessed before they are initialised.
arrowFunction()
const arrowFunction = () => {
console.log("This is an arrow function.")
}
Log result of calling the arrow function before it's declared
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.
How to Handle this
Binding in Functions
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.
1. Setting the this
value during a simple function call
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()
A simple invocation of a regular function sets this
to the window object
"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()
A simple invocation of an arrow function sets this
to the window object
2. When invoking or calling a method on an object
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()
Log result for a method with a regular function and another with an arrow function
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.
How to Use Functions as Constructors
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")
Log results for attempting to use regular and arrow functions as constructors
The RegularFuncBird
constructor works fine with the new
keyword, but the ArrowFuncBird
results in a type error.
So Which One Should You Use?
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.
Thanks for reading and happy coding!
##