Great Confusion About Differences Between Regular and Arrow Functions
In JavaScript, there are two main ways to write functions: regular functions and arrow functions.
While they might look similar, there are some key differences between them that can be tricky to understand. These differences often come up in interviews, where you might face questions that test your knowledge on these details. In this article, we’ll walk through the major differences between regular and arrow functions so you can better understand them:
1. this value
2. constructors
3. Hoisting
4. Arguments objects
5. Return Values
6. Duplicated Named Parameters
7. Methods in classes
1. this
Value
1.1 Regular Function
In a regular JavaScript function, the value of this
(also known as the execution context) is dynamic. This means that this
can change depending on how the function is called. There are four main ways to call a regular function, and each affects the value of this
differently:
- Simple Invocation: If you call a regular function directly,
this
will point to the global object (orundefined
in strict mode).
function myFunction() {
console.log(this);
}
// Simple invocation
myFunction(); // logs global object (window)
- Method Invocation: When a function is called as a method of an object,
this
points to the object that owns the method.
const myObject = {
method() {
console.log(this);
}
};
// Method invocation
myObject.method(); // logs myObject
- Indirect Invocation: If you use
myFunction.call(thisVal)
ormyFunction.apply(thisVal)
,this
will be set to the first argument you pass.
function myFunction() {
console.log(this);
}
const myContext = { value: 'A' };
myFunction.call(myContext); // logs { value: 'A' }
- Constructor Invocation: When you use the
new
keyword,this
refers to the new instance being created.
function MyFunction() {
console.log(this);
}
new MyFunction(); // logs an instance of MyFunction
function MyFunction(name) {
this.name = name;
console.log(this);
}
new MyFunction("Alex"); // logs: MyFunction {name: 'mark'}
1.2 Arrow Function
Arrow functions work differently when it comes to this
. They don’t have their own this
context. Instead, they inherit this
from the surrounding code (the lexical scope).
No matter how or where you call an arrow function, this
inside it will always refer to the this
value from the outer function. This behavior is one of the main differences between regular and arrow functions.
Here’s an example:
const myObject = {
name: "alex",
myMethod(items) {
const callback = () => {
console.log(this);
};
items.forEach(callback);
}
};
myObject.myMethod([1, 2, 3]); // logs myObject 3 times: {name: 'kiro', myMethod: ƒ}
In this example, myMethod()
is the outer function, and callback()
is the arrow function. The this
value inside callback()
is the same as in myMethod()
, which is myObject
. This lexical scoping of this
is a great feature of arrow functions, making them useful in situations where you don’t want to lose the this
context, like in callbacks.
But this can also cause some surprises. Consider this interview question:
let x = {
name: "alex",
arrow: () => {
console.log(this.name);
},
reg: function regular(){
console.log(this.name);
}
};
x.arrow(); // Output?
x.reg(); // Output?
Explanation:
x.arrow()
: Since arrow functions don’t have their ownthis
, it refers to thethis
from the surrounding scope, which is the global object (window
in browsers). So,window.name
isundefined
, and the output isundefined
.x.reg()
: Here,this
points to the objectx
, sothis.name
will output"ahmed"
.
Final Output:
x.arrow()
:undefined
x.reg()
:"ahmed"
Understanding how this
works in regular vs. arrow functions is crucial for writing reliable JavaScript code, especially when you’re dealing with objects and callbacks.
2. Constructors
2.1 Regular Function
Regular functions in JavaScript can be used as constructors. This means they can create instances of objects when used with the new
keyword. For example, consider a function that creates car objects:
function Person(name) {
this.name = name;
}
const alex = new Person('alex');
console.log(alex instanceof Person); // => true
console.log(alex ); // => Person {name: 'alex'}
In this example, Person
is a regular function. When you call it with new Person('alex')
, it creates a new instance of Person
, and this
inside the function refers to that new instance. This is how regular functions can be used to construct objects.
2.2 Arrow Function
Arrow functions, on the other hand, cannot be used as constructors. This is because they don’t have their own this
context—they rely on the surrounding context instead. If you try to use an arrow function as a constructor, JavaScript will throw an error.
const Person = (name) => {
this.name = name;
}
const alex = new Person('alex'); // TypeError: Person is not a constructor
In this case, attempting to create a new Person
instance using an arrow function results in a TypeError
, because arrow functions simply don’t support this kind of operation. This is a key limitation of arrow functions that you need to be aware of.
3. Hoisting
3.1 Hoisting in Regular Functions
In JavaScript, function declarations are hoisted to the top of their containing scope. This means you can call a regular function even before it’s defined in the code.
regularFunction();
function regularFunction() {
console.log("This is a regular function.");
}
// logs "This is a regular function."
In this example, regularFunction
is called before its definition, but it works just fine because of hoisting.
3.2 Hoisting in Arrow Functions
Arrow functions, however, are not hoisted. If you try to call an arrow function before it’s defined, you’ll get a ReferenceError
.
arrowFunction();
const arrowFunction = () => {
console.log("This is an arrow function.");
};
// Uncaught ReferenceError: Cannot access 'arrowFunction' before initialization
In this case, attempting to call arrowFunction
before its declaration results in an error, showing that arrow functions need to be defined before they’re used.
4. Arguments
Object
4.1 Regular Function
In a regular function, there’s a special object called arguments
that contains all the arguments passed to the function. This object is useful when you need to work with a variable number of arguments.
function myFunction() {
console.log(arguments);
}
myFunction('a', 'b'); // logs Arguments(2) ['a', 'b', callee: ƒ, length: 2, Symbol(Symbol.iterator): ƒ]
Here, arguments
is an array-like object that holds all the arguments passed to myFunction
. You can access each argument using an index, just like you would with an array.
Example from official MDN:
function func1(a, b, c) {
console.log(arguments[0]);
// Expected output: 1
console.log(arguments[1]);
// Expected output: 2
console.log(arguments[2]);
// Expected output: 3
}
func1(1, 2, 3);
4.2 Arrow Function
Arrow functions do not have an arguments
object. This can be surprising if you’re used to working with regular functions.
const myArrowFunction = () => {
console.log(arguments);
};
myArrowFunction('c', 'd'); // Uncaught ReferenceError: arguments is not defined
Instead, they inherit arguments
from the surrounding function (if there is one): Just like with this
, the arguments
object in an arrow function is resolved lexically, meaning it grabs the arguments
from the outer function.
Let’s see this in action:
function myRegularFunction() {
const myArrowFunction = () => {
console.log(arguments); //Arguments(2) ['a', 'b', callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
myArrowFunction('c', 'd');
}
myRegularFunction('a', 'b');
In this example, myArrowFunction()
is called with 'c'
and 'd'
, but inside its body, arguments
actually refers to the arguments from myRegularFunction()
—so it logs 'a'
and 'b'
instead.
Since arrow functions don’t have their own arguments
object, trying to access it directly inside an arrow function will result in a ReferenceError
.
If you need to handle arguments in an arrow function, you can use the rest parameter syntax to capture them:
const myArrowFunction = (...args) => {
console.log(args);
};
myArrowFunction('c', 'd'); // Uncaught ReferenceError: arguments is not defined
In this example, the rest parameter ...args
collects all the arguments passed to the arrow function into an array.
5. Return Values
5.1 Regular Function
In regular functions, you can return a value using the return
statement. If no return
statement is present, or if it’s empty, the function returns undefined
by default.
function myFunction() {
return 42;
}
console.log(myFunction()); // => 42
function myEmptyFunction() {
42;
}
console.log(myEmptyFunction()); // => undefined
In the first function, the value 42
is explicitly returned. In the second function, since there’s no return
statement, the function returns undefined
.
5.2 Arrow Function
Arrow functions also use the return
statement, but they offer a shortcut when the function body consists of a single expression. In such cases, you can omit the curly braces and the return
keyword—the value of the expression will be returned automatically.
const increment = (num) => num + 1;
console.log(increment(41)); // => 42
Here, the arrow function increment
returns the result of num + 1
without needing an explicit return
statement, making the code more concise.
6. Duplicate Named Parameters
6.1 Duplicate Parameters in Regular Functions
In regular functions, if you have duplicate parameter names, the last one takes precedence (priority). This behavior changes if you’re in strict mode.
function exampleFunction(a, b, a) {
console.log(a, b);
}
exampleFunction("first", "second", "third"); // logs "third second"
In non-strict mode, the second a
parameter overrides the first, so the output is "third second"
. But in strict mode, this would cause a syntax error:
"use strict";
function exampleFunction(a, b, a) {
console.log(a, b);
}
exampleFunction("first", "second", "third");
// Uncaught SyntaxError: Duplicate parameter name not allowed in this context
6.2 Duplicate Parameters in Arrow Functions
Arrow functions do not allow duplicate parameter names, even in non-strict mode. Attempting to use duplicate names will result in a syntax error.
const exampleFunction = (a, b, a) => {
console.log(a, b);
}
exampleFunction("first", "second", "third");
// Uncaught SyntaxError: Duplicate parameter name not allowed in this context
This strict enforcement helps prevent accidental bugs in your code.
7. Methods in Classes
7.1 Regular Function
Regular functions are typically used to define methods in JavaScript classes. When used this way, the function’s this
value refers to the class instance.
class Person {
constructor(personName) {
this.personName = personName;
}
logName() {
console.log(this.personName);
}
}
const alex = new Person('alex');
console.log(alex.logName()); // alex
In this example, the logName
method is a regular function that prints the hero’s name. The this
value inside logName
refers to the Person
instance.
However, when you pass logName
as a callback (for example, to setTimeout
), you might run into issues with the this
value:
setTimeout(alex.logName, 1000); // after 1 second logs "undefined"
Here, setTimeout
calls logName
with a simple invocation, causing this
to point to the global object, not the alex
instance.
To fix this, you can manually bind this
to the correct context:
setTimeout(alex.logName.bind(alex), 1000); // after 1 second logs "Batman"
While this works, it can be cumbersome if you have many methods to bind.
7.2 Arrow Function
Arrow functions offer a solution to the this
binding problem in classes. When you use an arrow function as a method, it binds this
lexically to the class instance automatically.
class Person {
constructor(personName) {
this.personName = personName;
}
logName = ()=> {
console.log(this.personName);
}
}
const alex = new Person('alex');
setTimeout(alex.logName, 1000);
With the arrow function logName
, you don’t need to worry about manually binding this
. Even when used as a callback, this
remains bound to the alex
instance:
setTimeout(alex.logName, 1000); // after 1 second logs "Batman"
Conclusion
Understanding the differences between regular and arrow functions in JavaScript is crucial for writing efficient, bug-free code. Whether it’s how this
is handled, the ability to use functions as constructors, or how hoisting works, each has its own set of rules that can trip you up if you’re not careful. By mastering these differences, you’ll be better prepared for coding challenges and interviews, and you'll know when to use each type of function in your projects.
Good luck with your coding journey, and happy coding!