In the realm of JavaScript programming, distinguishing between arrow functions and regular functions is crucial for making informed coding decisions and steering clear of potential pitfalls. This article serves as a concise overview, elucidating the key disparities between these two constructs.
Arrow functions do not have their own bindings to this
When we call a regular function, the execution context has access to the this
binding, which serves as a reference to the context in which the function is executed/called. That is not the case, however, when executing an arrow function. If we refer to this
within the body of an arrow function, the interpreter simply looks for the binding in its containing lexical scope.
const family = {
name: "foo",
members: ["a", "b"],
printAll() {
this.members.forEach(
member => console.log(`${member} ${this.name}`)
)
}
}
family.printAll();
// a foo
// b foo
In the example above, the arrow function within forEach
didn't have its own binding for this
, and was able to reference the this
within the execution context of printAll
. Let's look at the same example implemented using a regular function:
const family = {
name: "foo",
members: ["a", "b"],
printAll() {
this.members.forEach(
function(member) {
console.log(`${member} ${this.name}`)
}
)
}
}
family.printAll();
// a
// b
The function within forEach
has its own binding for this
which resolves to the window
object. We can fix it by explicitly binding the context to the function.
const family = {
name: "foo",
members: ["a", "b"],
printAll() {
const ctx = this;
this.members.forEach(
function(member) {
console.log(this);
}.bind(ctx)
)
}
}
bind
has no effect on an arrow function.
(() => console.log(this)).bind(1)();
// logs the window object.
Arrow functions do not have their own binding to arguments
Quite similarly, arrow functions do not have their own bindings to arguments
in their execution context, as opposed to regular functions.
function arg(a) {
console.log(arguments);
};
arg(1); // Arguments [1, ...]
This can come in handy when we want to run our function at a later time but want it to retain the execution context.
// Returns a function that runs f after the specified duration.
const defer = (f, duration) => function() {
setTimeout(() => f.apply(this, arguments), duration);
};
const deferredLog = deferred(function(x){
console.log(x);
}, 1000);
deferredLog(5);
// 5 (after 1 second);
Arrow functions cannot be used as constructors
Since arrow functions do not have their own bindings for this
, they cannot be used as constructors using the new
keyword. Here is an example of how we can use we can use a regular function as constructors:
function Person(name, age){
this.name = name;
this.age = age;
}
const foo = new Person("foo", 12);
Using new
with an arrow function will cause a syntax error.
const Person = (name) => {
this.name = name;
}
const foo = new Person("foo");
// Uncaught TypeError: personArrow is not a constructor
Arrow functions are not hoisted
Unlike regular functions, arrow functions are not hoisted during the creation phase of the execution context and hence, cannot be invoked before their definition.
hoisted();
hoistedArrow();
function hoisted(){
console.log("All is fine.")
}
const hoistedArrow = () => {};
// All is fine.
// Uncaught ReferenceError: hoistedArrow is not defined
In conclusion, understanding the differences between regular functions and arrow functions empowers JavaScript developers to choose the right tool for the job. While both serve similar purposes, their nuances in syntax and behavior can significantly impact code clarity and functionality.
Resources
-
https://javascript.info/arrow-functions
-
https://www.freecodecamp.org/news/regular-vs-arrow-functions-javascript
-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
-
https://www.freecodecamp.org/news/how-javascript-works-behind-the-scene-javascript-execution-context/