Discovering the World of JavaScript
Chris K. is a software engineer at Sofomo, a proud Codesmith alum, and a believer in continuous...
JavaScript's dynamism has made it a cornerstone of web development, and its evolution never ceases to impress. Among its many innovations, the arrow function—unveiled with ECMAScript 6 (ES6)—stands out. Beyond its sleeker syntax, it brings a fresh perspective to the behavior of the “this” keyword. In this piece, we'll journey through the landscape of arrow functions, understanding their strengths, quirks, and discerning when they shine brightest (or perhaps, when they don't).
Understanding the Allure of Arrow Functions
Arrow functions are no longer the new kids on the block, but their appeal remains undiminished. So, what keeps developers enamored with them over the traditional function declaration? Together, we'll navigate:
Syntax Examples
Arrow functions also allow for an implicit return when the function body is written without curly braces. For instance, number => 2 * number will implicitly return the result of multiplying number by 2 without needing the return keyword.
For functions with a single parameter, parentheses aren't strictly necessary. Omitting parentheses in cases where a function or method takes a single identifier can make the code visually less cluttered.
In scenarios where destructuring values are used, the parameter must be wrapped in parentheses.
For functions that utilize a default parameter value, the parameter must also be enclosed in parentheses:
Let's move on to another fundamental difference that separates arrow functions from traditional function declarations and function expressions. As seen in the examples above, in addition to the mentioned parentheses, we also got rid of the curly braces and the return keyword. Braces can be omitted in definitions whose body takes up one line. The need for explicit use of return depends on the use of braces.
The fewer lines of code, the less chance of an error—undoubtedly one of the main reasons for the phenomenon of arrow functions. In most cases, they allow you to do just as much, writing much less. Let's verify this with an example. Let's say we want to square all the elements of an array that are even (everyday struggle 😊).
In the code above, excluding empty lines, the first solution occupies 7 lines, whereas the second is neatly compressed into just 3. The beauty of arrow functions shines especially with simple expressions—they're so much more readable. This clarity allows us to zero in on the operations we're executing, sidestepping the clutter of extra braces and keywords.
Now, for those curious about more advanced scenarios: have you ever explored functions that employ closures in JavaScript? To illustrate, consider a classic function designed to add a chosen value to any number.
Once again, the arrow function emerged victorious from the duel with the venerable declaration. However, this syntactic brevity doesn't always work in our favor. For more complex functions, it's worth considering which variant is more understandable and readable.
Enough raving about the syntax of arrow functions. Finally, it's worth mentioning a few details to be careful of.
The => marker must be on the same line as the parameter list; otherwise, the engine will throw a SyntaxError.
If our function body contains a statement, regardless of the number of lines of code, we must enclose it in braces.
The second specific case is a function that returns an object literal. To ensure everything goes according to plan, we must enclose it in parentheses. In this situation, they are interpreted as the grouping operator for the returned expression. This allows us to use the previously discussed implicit return mechanism. If we omit the parentheses, the keys of our object will be interpreted as labels and the values as ordinary strings.
Lexical `this`
You're probably wondering why all the fuss about a change that only affects the visual aspect of our code. Fortunately, the main character of today's post brings with it a much more significant change. The TC39 Committee made arrow functions a remedy for the daily suffering of programmers who were tired of invoking their functions with .bind(this) and using tricks like var self = this.
Traditional functions have dynamic binding of 'this'. In short, this means that the value of 'this' depends on the context in which the function is called. This mechanism causes many difficulties, especially when, for some reason, we want to create an object that adds a selected value wherever the language allows (everyday struggle part 2).
Unfortunately, with such code, our ambitious plan will fail. In strict mode, the 'this' pointer inside the map method will take the value undefined. Omitting strict mode, it will refer to the global Window object. In this case, our function will spit out [NaN, NaN], and we'll start wondering if programming in JavaScript was a good idea…
In the vast landscape of coding challenges, the seemingly simple task of adding a favorite number to array elements might not be the first thing developers worldwide lose sleep over. Yet, for those rare moments when sprinkling in that special number becomes the task of the day, there are tried-and-true techniques ready to be employed.
Upon examining the code above, it might strike one as less than straightforward. The intricacies of managing the 'this' context can sometimes lead to convoluted solutions.
In collaborative environments, it's essential to prioritize code elegance and readability. As the developer community continually strives for improvement and efficiency, innovative solutions emerge. Below is a streamlined approach that uses an arrow function as a callback.
Unlike traditional functions, arrow functions in JavaScript have a lexical scope for 'this'. This means that they inherit 'this' from the scope in which they were defined, rather than from the scope in which they are executed. This behavior ensures that 'this' always points to the expected object, which can significantly simplify code, especially in cases involving callbacks or methods in classes. This understanding of lexical scope, where a function looks for variables in its surrounding scope if not found in the current scope, adds clarity and predictability to code. Adopting such practices can offer substantial benefits in collaborative coding environments, promoting more readable and maintainable code across the industry.
When NOT to Use Arrow Functions
Before we head to the station of arrow hype, we should consider whether we are indeed dealing with a foolproof solution. Is the function keyword going to retire? Spoiler alert: far from it.
Constructors
Going back to our Adder, we could try rewriting the constructor function using an arrow function expression.
Are arrow functions less capable in certain scenarios compared to traditional functions? In the context of using the new keyword, indeed they are. Traditional functions have an internal [[Construct]] method and a prototype property, allowing them to be used as constructors. Arrow functions, on the other hand, lack both of these features, leading to the observed error when attempting to use them as constructors.
Methods
Another trick awaits us in the case of methods. Precisely the same mechanism that forced us to use .bind(this) now becomes handy.
The 'this' pointer in the getLikes() method points to the global Window object. This is due to the lack of creating its binding of 'this' and searching in the lexical scope. Nothing is lost - success on social media is still guaranteed. The function setting the number of likes works as it should ;)
You certainly don't want the time spent reading this post to go to waste. The best way to consolidate knowledge is through practice.
https://csx.codesmith.io/units/callbacks
Chris K. is a software engineer at Sofomo, a proud Codesmith alum, and a believer in continuous...
Introduction Pre-trained Large Language Models (LLMs), such as OpenAI’s GPT-4 and Meta’s Llama 2,...
Behind many apps is a geospatial search service. Also known as proximity search, this allows users...