Arrow functions in JavaScript: why they are needed, how to handle them, when they should be used, and when not

Original author: Kevin Ball
  • Transfer
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 thisin the switch functions looks more intuitive than in the normal functions.

image

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 talk about 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 constructed 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 made up of symbols =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 keyword is omitted here functionand the arrow after the argument list is added.

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 function body 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, 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 more 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, let's 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 entity thisand argumentsfrom 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() {
    returnfunction() {
      console.log(this.name);
      console.log(arguments);
    };
  },
  createArrowFunction: function() {
    return() => {
      console.log(this.name);
      console.log(arguments);
    };
  }
};

There is an object testwith 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 of them 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, the value of the nameobject property argumentswill not be displayed , and when accessing , 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 property of the nameobject whose method 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 a 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 formodern cycles forEachbased on iterators are used instead of traditional cycles , the fact that the switch functions use the thisparent 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 construct, you cannot do without an understanding of the promises , since this construct 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 an 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:

exportdefault {
  computed: {
    ...mapState({
      results: state => state.results,
      users: state => state.users,
    });
  }
}

Situations in which you should not use the arrow functions


Объектов 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 thoughtlessly applied, 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 keyword thischaracteristic 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 the class. It looked like this:

classCounter {
  counter = 0;

  handleClick = () => {
    this.counter++;
  }
}

When using such a construct, even if the function handleClickwas called by an event handler, and not in the context of an instance of a class Counter, 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 the switch functions, use the usual functions, and, if necessary, bind to them an instance of the object in the constructor:

class Counter {
  counter = 0;
  handleClick() {
    this.counter++;
  }
  constructor(){
    this.handleClick = this.handleClick.bind(this);
  }
}

▍Long call chains


Arrow functions can be 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 dynamic linking this.

If pointer functions are used in such situations, then dynamic snapping thiswill not work. This unpleasant surprise can make you think over the causes of what is happening to those who will have to work with a 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 invoked with an thisevent attribute associated with it currentTarget.
  • If you're still using jQuery, keep in mind that most jQuery methods are tied thisto the selected DOM element.
  • If you use Vue.js, then methods and computed functions are usually bound thisto the 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 operation 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 capabilities, 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?


Also popular now: