One of the most notable innovations in modern JavaScript has been the emergence of arrow functions (arrow function), which are sometimes called “fat” arrow functions (fat arrow function). When declaring such functions use a special combination of characters -
=>
.
Switch functions have two main advantages over traditional functions. The first is a very convenient and compact syntax. The second is that the approach to working with the value of
this
in the switch functions looks more intuitive than in normal functions.
Sometimes these and other advantages lead to the fact that the switch syntax is given unconditional preference over other methods of function declaration. For example, the popular
eslint configuration from Airbnb forces that, whenever an anonymous function is created, such a function would be a switch.
')
However, like other concepts and mechanisms used in programming, the switch functions have their pros and cons. Their use can cause negative side effects. In order to use the switch functions correctly, you need to know about possible problems associated with them.
In the material, the translation of which we are publishing today, we will discuss how the switch functions. Here we will consider situations in which their use can improve code, and situations in which they should not be used.
Features of arrow functions in JavaScript
Arrow functions in javascript are something like
lambda functions in python and
blocks in ruby.
These are anonymous functions with a special syntax that take a fixed number of arguments and operate in the context of their scope, that is, in the context of a function or other code in which they are declared.
Let's talk about this in more detail.
The syntax of the switch functions
Arrow functions are built according to a unified scheme, and the structure of functions can be, in special cases, simplified. The basic structure of the arrow function looks like this:
(argument1, argument2, ... argumentN) => {
The list of function arguments is in parentheses, followed by an arrow composed of the characters
=
and
>
, and then the function body is in curly braces.
This is very similar to how ordinary functions are arranged, the main differences are that the
function
keyword is omitted here and an arrow is added after the argument list.
In certain cases, however, simple arrow functions can be declared using much more compact constructions.
Consider a variant of the syntax that is used if the body of the function is represented by a single expression. It allows you to do without curly brackets framing the body of the function, and eliminates the need to explicitly return the results of the evaluation of the expression, since this result will be returned automatically. For example, it might look like this:
const add = (a, b) => a + b;
Here is another version of the abbreviated function write, used when the function has only one argument.
const getFirst = array => array[0];
As you can see, the parentheses framing the argument list are omitted here. In addition, the function body, which in this example is represented by a single command, is also written without brackets. Later we will talk about the advantages of such structures.
▍Return objects and abbreviated recording of pointer functions
When working with the arrow functions, some more complex syntax structures are used, which are useful to know about.
For example, we will try to use a single-line expression to return an object literal from a function. It may seem, given what we already know about the arrow functions, that the function declaration will look like this:
(name, description) => {name: name, description: description};
The problem with this code is its ambiguity. Namely, the curly braces that we want to use to describe the object literal look like we are trying to enclose the function body in them.
In order to indicate to the system that we mean the object literal, we need to enclose it in parentheses:
(name, description) => ({name: name, description: description});
▍Fishers and their execution context
Unlike other functions, the switch functions do not have their own
execution context .
In practice, this means that they inherit the
this
and
arguments
entities from the parent function.
For example, compare the two functions presented in the following code. One of them is normal, the second - arrow.
const test = { name: 'test object', createAnonFunction: function() { return function() { console.log(this.name); console.log(arguments); }; }, createArrowFunction: function() { return () => { console.log(this.name); console.log(arguments); }; } };
There is a
test
object with two methods. Each of them is a function that creates and returns an anonymous function. The difference between these methods lies only in the fact that in the first method the traditional functional expression is used, and in the second - the arrow function.
If you experiment with this code in the console, passing the same arguments to the object's methods, then although the methods look very similar, we will get different results:
> const anon = test.createAnonFunction('hello', 'world'); > const arrow = test.createArrowFunction('hello', 'world'); > anon(); undefined {} > arrow(); test object { '0': 'hello', '1': 'world' }
An anonymous function has its own context, therefore, when it is called, when accessing
test.name
name
property of the object will not be displayed, and when
arguments
are called, the list of arguments of the function that was used to create and return the function being examined will not be displayed.
In the case of the arrow function, it turns out that its context coincides with the context of the function that created it, which gives it access to both the list of arguments passed by this function and the
name
property of the object of which this function is.
Situations in which the switch functions improve code
▍Processing lists of values
Traditional lambda functions, as well as arrow functions, after their appearance in JavaScript, are usually used in the situation when a certain function is applied to each element of a certain list.
For example, if there is an array of values that needs to be converted using the array method
map
, an arrow function is ideal for describing such a conversion:
const words = ['hello', 'WORLD', 'Whatever']; const downcasedWords = words.map(word => word.toLowerCase());
Here is an extremely common example of this use of switch functions, which is to work with the properties of objects:
const names = objects.map(object => object.name);
Similarly, if instead of traditional
for
loops, modern
forEach
loops based on iterators are used, the fact that the switch functions use
this
parent entity makes their use intuitively clear:
this.examples.forEach(example => { this.runExample(example); });
Promises and promise chains
Another situation where the arrow functions allow you to write cleaner and more understandable code is represented by asynchronous software constructs.
So,
promises greatly simplify working with asynchronous code. At the same time, even if you prefer to use the async / await
construction, you cannot do without an
understanding of the promises , since this construction is based on them.
However, when using promises, you need to declare functions that are called after the completion of an asynchronous code or the completion of an asynchronous call to an API.
This is the ideal place to use the switch functions, especially if the resulting function has a state, refers to something in the object. For example, it might look like this:
this.doSomethingAsync().then((result) => { this.storeResult(result); });
Transformation of objects
Another common example of using switch functions is to encapsulate object transformations.
For example, in Vue.js, there is a generally accepted
pattern for including fragments of Vuex storage directly into a Vue component using
mapState
.
This operation includes declarations of a set of "converters" that select from the initial full state exactly what is needed for a particular component.
Such simple transformations are an ideal place for using switch functions. For example:
export default { computed: { ...mapState({ results: state => state.results, users: state => state.users, }); } }
Situations in which you should not use the arrow function
Object methods
There are a number of situations in which the use of switch functions is not the best idea. Arrow functions, if they are used thoughtlessly, not only do not help programmers, but also become a source of problems.
The first such situation is the use of switch functions as methods of objects. Here, the execution context and the
this
, characteristic of traditional functions, are important.
At one time, it was popular to use a combination of class properties and switch functions to create methods with “automatic binding”, that is, those that can be used by event handlers but remain attached to a class. It looked like this:
class Counter { counter = 0; handleClick = () => { this.counter++; } }
When using such a construction, even if the
handleClick
function
handleClick
called by an event handler, and not in the context of an instance of the
Counter
class, this function had access to the data of that instance.
However, this approach has a lot of minuses to which
this material is dedicated.
Although the use of the arrow function here is certainly a convenient-looking way to bind a function, the behavior of this function in many aspects is far from intuitive, interfering with testing and creating problems in situations where, for example, they try to use the corresponding object as a prototype.
In such cases, instead of switch functions, use normal functions, and, if necessary, bind to them an instance of an object in the constructor:
class Counter constructor() }
Long call chains
Arrow functions can become a source of problems if they are planned to be used in many different combinations, in particular, in long chains of function calls.
The main reason for such problems, as with the use of anonymous functions, is that they provide extremely uninformative results of the
call stack trace.
This is not so bad if, for example, there is only one level of nesting of function calls, say, if we are talking about the function used in the iterator. However, if all the functions used are declared as pointer functions, and they call each other functions, then if an error occurs, it will not be easy to understand what is happening. Error messages will look something like this:
{anonymous}() {anonymous}() {anonymous}() {anonymous}() {anonymous}()
▍Functions with dynamic context
The last of the situations we are discussing in which the switch functions can be a source of trouble is to use them where you need this dynamic link.
If pointer functions are used in such situations, then this dynamic link will not work. This unpleasant surprise can make you think over the causes of what is happening to those who will have to work with code in which the pointer functions are used incorrectly.
Here are some things to keep in mind when considering the use of dial functions:
- Event handlers are called with
this
, bound to the currentTarget
event attribute. - If you're still using jQuery, keep in mind that most jQuery methods bind
this
to the selected DOM element. - If you use Vue.js, then methods and computed functions usually bind
this
to a Vue component.
Of course, the switch functions can be used intentionally, in order to change the standard behavior of software mechanisms. But, especially in the cases of jQuery and Vue, this often conflicts with the normal functioning of the system, which leads to the fact that the programmer cannot understand why some code that looks quite normal suddenly refuses to work.
Results
Arrow functions are a great fresh JavaScript feature. They allow, in many situations, to write more convenient code than before. But, as is the case with other features, they have both advantages and disadvantages. Therefore, it is necessary to use the switch functions where they can be useful, without considering them as a complete replacement for ordinary functions.
Dear readers! Have you encountered situations in which the use of switch functions leads to errors, inconveniences or unexpected behavior of programs?
