Arrow and `this` functions in JavaScript: a complete guide

  • The value of this In JavaScript, it depends on how the function is invoked, varying between global context, object methods, and strict mode usage.
  • Arrow functions do not create their own thisInstead, they lexically inherit the terminology of the domain where they were defined, which avoids many problems in callbacks.
  • It is recommended to use arrow functions in callbacks and array methods, but avoid them as object methods, constructors, or DOM event handlers that require this dynamic.

Illustration of arrow and this functions in JavaScript

If you've been programming in JavaScript for a while, you'll surely recognize the keyword This has given you more than one headacheAnd since arrow functions appeared in ES6, things got even more complicated... or simpler, depending on how you look at it.

In this article we'll take a closer look at how it works This applies to traditional functions and arrow functions.Why does it sometimes seem to point to a specific object and other times to the global object, and in what situations does it make sense to use arrow functions and in which situations it is better to avoid them?

Que es exactamente this in JavaScript

the reserved word this It is a reference to the execution context of the function that is currently executing. Unlike other languages, in JavaScript the decision is not based on where the function is defined, but rather on the function's execution. how to invoke.

This means that the same function can be called in different ways, and in each of them, this can point to a different objectIt's not something you can change with a direct assignment (you can't do this = algo), but you can influence it with specific mechanisms such as call, apply y bind.

Furthermore, their behavior varies between strict mode and non-strict modeIn non-strict mode, if you call a function "bare" (without an object in front of it), this It is usually the global object (in the browser, window), while in strict mode it can be undefinedThis distinction is important when comparing code examples from different sources.

This in the global context and in normal functions

In browsers, when you are not inside any module or function, the global context is the object windowand there this point to that objectThat is, if you type the following into the console:

console.log(this === window); // true en un entorno de navegador no estricto

Within a function declared in a “classic” way (normal function), the value of this It depends on what that function is called.If you invoke it without a prior object, in non-strict mode this It is usually the global one, and strictly speaking it will be undefinedThat's why sometimes, when moving code from one site to another, This is no longer what you expected..

This in object methods defined with normal functions

When you define a method on an object using traditional syntax, this within the method, reference to the object itself from which that method has been invoked.

For example, if you have something like:

const obj = {
  speak() {
    console.log(this);
  }
};
obj.speak();

The call obj.speak() makes this within speak be the one objThis is the behavior that people usually expect intuitively: the method speaks “on behalf” of the object.

If you use a classic function instead of the abbreviated syntax, the effect is the same, because The key lies in how the method is invokedIt doesn't matter if you used the method abbreviation or the keyword function inside the object.

This in methods defined with arrow functions

Things change when you define the method with an arrow function. Something like:

const obj2 = {
  speak: () => {
    console.log(this);
  }
};
obj2.speak();

In this case, when executing obj2.speak() you will see that this It is no longer obj2but the external lexical context to that object, which in a classic browser script is usually the global object window.

This is puzzling the first time you see it, because you expect a method of the object to point to the object itself. However, arrow functions do not create their own thisThey inherit the value of this of the scope where they were defined. If that scope is global, they inherit the global scope; if it is another, they will inherit that other scope.

Therefore, a frequently repeated recommendation in modern documentation is: Do not use arrow functions as object methods when you need to this aim at that object.

Lexical scope of this arrow functions

The key difference between normal functions and arrow functions is that the latter have a lexical link for thisSimply put: they don't decide their this not when they call each other, but when they create.

Imagine this example:

const obj3 = {
  speak() {
    (() => {
      console.log(this);
    })();
  }
};
obj3.speak();

Here it might seem that, as within speak we execute an arrow function, This should "reset" to the globalBut the exact opposite happens: the arrow function captures the this of the function that surrounds itwhich in this case is the method speak invoked as obj3.speak()Therefore, the value of this The one shown on the console is the one from obj3.

That is to say, arrow functions do not have their own thisbut rather reuse that of their immediate surroundingsThis is incredibly useful in nested callbacks, timers, promises, and anywhere else where, with classic functions, you had to wrestle with .bind or with tricks like const that = this;.

Practical examples of loss and preservation of this

One of the classic problems in JavaScript is that, when defining a function inside a method, you lose the reference to this that pointed at the object and you end up with the global one or with undefined.

Let's take the typical case of using setTimeout within a method of an object with a traditional function:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(function() {
      console.log(this.nombre);
    }, 3000);
  }
};
persona.decirNombre(); // Muestra undefined

Here this within the function passed to setTimeout It is no longer the object personaThat callback function executes in the global context (in a browser, window), so this.nombre It tries to read a property in the global, which doesn't exist, and ends up being undefined.

Before arrow functions existed, a common solution was to store the value of this in an auxiliary variable to "drag" it into the function:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    let that = this; // aquí this es persona
    setTimeout(function() {
      console.log(that.nombre);
    }, 3000);
  }
};

Thanks to that variable, the correct reference to the object is maintained. But it's a somewhat ugly and repetitive trick. With arrow functions, this problem is greatly simplified:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(() => {
      console.log(this.nombre);
    }, 3000);
  }
};

Here the arrow function does not create its own this, so inherits the this of the method decirNombrewhich is the object personaThe result: “Agustin” is displayed correctly without the need for intermediate variables or .bind.

call, apply and bind: controlling the value of this

In addition to the "natural" way of setting the context with a method call, JavaScript gives us tools to force the value of this in normal functions: call, apply y bind.

Methods call() y apply() They invoke the function immediately, allowing you to pass the object you want to use as this. The difference is that call receives the arguments one by one, while apply They are received in an array. bind(), instead, it returns a new function with the this “attached” to the value you have indicatedso you can call her later when it suits you.

With arrow functions, however, these methods are not useful for changing this because its value is lexically linked. You can use call, apply o bind to pass arguments, but not to modify the context of arrow functions, which is a very important difference with regular functions.

Basic syntax of arrow functions

Beyond the behavior of thisThe arrow functions provide a more compact and expressive syntax for many situations. The general form is:

(arg1, arg2, ..., argN) => expresion

This form automatically returns the result of the expression to the right of the arrow, so There is no need to write the word return when you only have a single simple expression.

Some common points of syntax:

  • Without parameters:
    () => 42 or even _ => 42 if you don't care about the argument name.
  • With a single parameter:
    The parentheses are optional; you can write x => x * 2 o (x) => x * 2.
  • With multiple parameters:
    Parentheses are required: (x, y) => x + y.

When you need multiple statements, you can use block body with keys:

const sumar = (x, y) => {
  const resultado = x + y;
  return resultado;
};

In this case, since there are keys, There is no longer any implicit return; if you don't put returnthe function will return undefinedThis applies to both arrow functions and traditional functions.

Return literal objects with arrow functions

There is a small but very common syntactic detail: when an arrow function returns a literal object directlyYou must enclose it in parentheses so that the interpreter does not mistake it for a block.

For example:

x => ({ y: x })

Without those parentheses, JavaScript would interpret the curly braces as the beginning of the function body, not as an object. It's a simple trick, but it causes a lot of silly mistakes if you forget it.

Arrow functions: anonymous and without a prototype

Arrow functions are syntactically anonymousThey don't have proper names, which can complicate things somewhat. debug and error messagesbecause in the trace you don't see the function identifier directly, unless you have assigned it to a constant with a recognizable name.

Additionally, the arrow functions They do not own property prototype and they cannot be used as construction companiesIf you try to summon them with newYou will get an error. To create objects using constructors or classes, you still need to use normal functions or syntax. class.

Another consequence is that They are not suitable for patterns that require internal self-referencing, such as some forms of recursion or event handlers that need to unsubscribe themselves using this or the function's own name.

Where arrow functions shine

The great strength of arrow functions is precisely their lexical linking of thisThey are ideal in situations where you want the callback you pass to another function to maintain the this of the surrounding area.

For example, in an object with a method that starts a timer and needs to keep accessing properties of the object itself using this:

const contador = {
  id: 42,
  iniciar() {
    setTimeout(() => {
      console.log(this.id); // this es contador
    }, 1000);
  }
};

In ES5 it was common to have to put .bind(this) to the callback or save this in another variable. With arrow functions, The code becomes cleaner and closer to the actual intention..

They are also very practical with array methods such as map, filter, reduce and company, because reduce syntactic noise when the function logic is brief:

const numeros = [1, 2, 3];
const dobles = numeros.map(n => n * 2);

When used sparingly, these compact shapes make the flow of data easier to follow at a glance.

When to avoid arrow functions

Although arrow functions are very useful, they are not a replacement for regular functions. There are several clear cases where It's best not to use them.:

  • Object methods that depend on this:
    If you define a method as saltos: () => { this.vidas--; } inside an object gato, this It won't point to the cat, but to the outside environment, and the property won't update as you expect.
  • DOM event callbacks that need a this dynamic:
    In a handler like boton.addEventListener('click', () => { this.classList.toggle('on'); });, this It won't be the button pressed, but the higher context, that will probably give you a type error.
  • Builders or functions that need prototype:
    Since it cannot be used with newArrow functions are not suitable for creating instances or for prototype-based patterns.

In all these cases, a Normal function remains the appropriate tool because it allows that this It is dynamically linked to the way you invoke the function.

If you get used to consciously choosing between normal function and arrow function depending on what you need from this and from the context, Your code will be more predictable and readable. for anyone who keeps it afterwards.

Ultimately, understanding how the value of this in JavaScript, and how arrow functions inherit that value The key to stopping struggling with unexpected results, taking advantage of ES6's syntactic sugar, and writing methods, callbacks, and event handlers that do exactly what you had in mind is understanding the lexical scope where they are created.