Arrow Functions and Closures
Arrow functions are probably the most used feature in Modern JavaScript.
But JavaScript has SO many ways to define functions. Why Do We Need Another Way?
// The arrow function syntax: const doSomething = () => { // Function Scope };
This new “shorter” syntax to define functions is popular NOT only because it’s shorter but also because it behaves more predictably with closures. This latter point is really what’s important so let’s learn what that’s all about.
Arrow functions give access to their defining environment while regular functions give access to their calling environment. This access is possible through the special this
keyword:
-
The value of the this keyword inside a regular function depends on HOW the function was CALLED.
-
The value of the this keyword inside an arrow function depends on WHERE the function was DEFINED.
Here is a code example to expand on the explanation. Try to figure out what will be printed in Output #1 through #4 (last 4 lines):
this.whoIsThis = 'TOP'; // Identify this scope // 1) Defining const fancyObj { whoIsThis: 'FANCY', // Identify this object regularF: function () { console.log('regularF', this.whoIsThis); }, arrowF: () => { console.log('arrowF', this.whoIsThis); }, }; // 2) Calling console.log('TOP-LEVEL', this.whoIsThis); // It's "TOP" here fancyObj.regularF(); // Output #1 (Fancy) fancyObj.arrowF(); // Output #2 (Top) fancyObj.regularF.call({whoIsThis: 'FAKE'}); // Output #3 (Fake) fancyObj.arrowF.call({whoIsThis: 'FAKE'}); // Output #4 (Top)
You have a regular function (regularF
) and an arrow function (arrowF
) defined in the same environment, and called by the same caller. Here’s the explanation of the outputs in the last 4 lines:
-
The regular function will always use its
this
to represent who called it. In the example above, the caller of both functions was thefancyObj
itself. That’s why Output #1 was "FANCY". -
The arrow function will always print the
this
scope that was available at the time it was defined. That’s why Output #2 was "TOP". -
This holds true even if the caller was changed using
.call
,.apply
, or.bind
(which are functions that can be used to change the calling environment). The first argument to these functions becomes the new "caller". That’s why Output #3 was "FAKE" -
The arrow function does not care about this caller change. It’ll still output "TOP".
You can experiment with this example at jsdrops.com/arrow-functions. |
One other cool thing about arrow functions is that if the function only has a single return line (like square1
below) you can make it even more concise by removing the curly brackets and the return keyword altogether (as in square2
). You can also remove the parentheses around the argument if the function receives a single argument:
const square1 = (a) => { return a * a; }; const square2 = (a) => a * a; const square3 = a => a * a;
The last syntax is usually popular for functions that get passed to array methods like map
, reduce
, filter
, and other functional programming methods:
console.log([1, 2, 3, 4].map(a => a * a));
Note that if you want to use the arrow-function one-liner version to make a function that returns an object, you’ll have to enclose the object in parenthesis because otherwise, the curly brackets you think are for object literals are actually the scope of the function.
// Wrong const objMaker = () => { answer: 42 }; // Right const objMaker = () => ({ answer: 42 });
The above is actually one of the most common mistakes beginners do when working with libraries like React.
Guess what, the confusion about curly brackets are NOT only about scopes and object literals. There are many more things you can do with curly brackes. Keep reading. 😎