Funciones de flecha en JavaScript

Si es un desarrollador de JavaScript, puede saber que JavaScript cumple con los estándares ECMAScript (ES). Las especificaciones ES6, o ECMAScript 2015, tenían intr...

Introducción

Si es un desarrollador de JavaScript, puede saber que JavaScript cumple con los estándares ECMAScript (ES). Las especificaciones ES6, o ECMAScript 2015, habían introducido algunas de las especificaciones revolucionarias para JavaScript, como funciones de flecha, clases, operadores Rest y Spread, Promises, let y const, etc.

En este tutorial, nos centraremos en las funciones de flecha, que son muy confusas e intimidantes para los principiantes de JavaScript.

Sintaxis de la función de flecha

Como sabemos, una función ES5 tiene la siguiente sintaxis:

1
2
3
function square(a) {
    return a * a;
}

En ES6, podemos escribir la misma función con solo una línea de código:

1
let square = (a) => { return a * a; }

Además, si el cuerpo de la función tiene solo una declaración que devuelve, podemos omitir las llaves {} y la declaración return:

1
let square = (a) => a * a

Además, si la función toma solo un parámetro, incluso podemos omitir las llaves () a su alrededor:

1
let square = a => a * a

Por otro lado, si la función no toma ningún parámetro, podemos escribirla así:

1
let results = () => { /* ...some statements */ };

Tenemos que escribir menos código con esta sintaxis mientras proporcionamos la misma funcionalidad, lo que puede ayudar a ordenar y simplificar su código.

La ventaja de esta sintaxis es más notable cuando se usa en devoluciones de llamada. Entonces, un fragmento de código detallado y difícil de seguir como este:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function getRepos() {
    return fetch('https://api.github.com/users/wikihtp/repos')
      .then((response) => {
          return response.json();
      }).then((response) => {
          return response.data;
      }).then((repos) => {
          return repos.filter((repo) => {
              return repo.created_at > '2019-06-01';
          });
      }).then((repos) => {
          return repos.filter((repo) => {
              return repo.stargazers_count > 1;
          });
      });
}

se puede reducir a lo siguiente, mediante el uso de funciones de flecha:

1
2
3
4
5
6
7
function getRepos() {
    return fetch('https://api.github.com/users/wikihtp/repos')
      .then(response => response.json())
      .then(response => response.data)
      .then(repos => repos.filter(repo => repo.created_at > '2019-06-01'))
      .then(repos => repos.filter(repo => repo.stargazers_count > 1));
}

Beneficios de las funciones de flecha

Hay dos ventajas principales de utilizar las funciones de flecha. Una es que es una sintaxis más corta y, por lo tanto, requiere menos código. El principal beneficio es que elimina varios puntos problemáticos asociados con el operador “esto”.

Sin enlace de 'this' Operador

A diferencia de otros lenguajes de programación orientados a objetos, en JavaScript (antes de las funciones de flecha) cada función definía su referencia de ’esto’ y depende de cómo se llamó a la función. Si tiene experiencia con lenguajes de programación modernos como Java, Python, C#, etc., el operador this o self dentro de un método se refiere al objeto que llamó al método y no a cómo se llama ese método.

Los programadores siempre se quejaron de que usar this es demasiado complicado en JavaScript. Provoca una gran confusión en la comunidad de JavaScript y provoca un comportamiento no deseado del código en algunos casos.

Para comprender mejor el beneficio de las funciones de flecha, primero comprendamos cómo funciona “esto” en ES5.

El operador 'this' en ES5

El valor de this está determinado por el contexto de ejecución de una función, que en términos simples significa cómo se llama una función.

Lo que aumenta aún más la confusión es que cada vez que se llama a la misma función, el contexto de ejecución puede ser diferente.

Tratemos de entenderlo con la ayuda de un ejemplo:

1
2
3
4
function test() {
    console.log(this);
}
test();

La salida de este programa sería la siguiente en la consola de un navegador:

1
Window {...}

Como llamamos test() desde el contexto global, la palabra clave this se refiere al objeto global que es un objeto Ventana en los navegadores. Cada variable global que creamos se adjunta a este objeto global Ventana.

Por ejemplo, si ejecuta el siguiente código en la consola de un navegador:

1
2
3
let greetings = 'Hello World!';
console.log(greetings);
console.log(window.greetings)

será recibido con 'Hello World!' dos veces:

1
2
Hello World!
Hello World!

Como podemos ver, la variable global saludos se adjunta al objeto global ventana.

Tomemos un ejemplo diferente con una función constructora:

1
2
3
4
5
6
function Greetings(msg) {
    this.msg = msg;
};

let greetings = Greetings('Hello World!');
console.log(greetings);

Obtendremos el siguiente mensaje en la consola:

1
undefined

lo cual tiene sentido porque estamos llamando a Greetings() en el contexto de ventana y, como en el ejemplo anterior, this se refiere al objeto global window y this.msg ha agregado la propiedad msg a window objeto.

Podemos comprobarlo si ejecutamos:

1
window.msg

Seremos recibidos con:

1
Hello World!

Pero si usamos el operador nuevo mientras creamos el objeto Saludos:

1
2
let greetings = new Greetings('Hello World!');
console.log(greetings.msg);

Seremos recibidos con:

1
Hello World!

Podemos ver que el operador this no se refiere al objeto global window esta vez. Es el operador nuevo el que hace esta magia. El operador nuevo crea un objeto vacío y hace que este se refiera a ese objeto vacío.

Espero que ahora tenga una idea de nuestra declaración anterior.

esto depende de cómo se llama a una función.

Tomemos otro ejemplo:

1
2
3
4
5
6
7
8
let greetings = {
    msg: 'Hello World!',
    greet: function(){
        console.log(this.msg);
    }
}

greetings.greet();

Si ejecutamos este código en la consola de un navegador, nuevamente seremos recibidos con:

1
Hello World!

¿Por qué crees que this no se refería al objeto window esta vez?

Vinculación implícita: la palabra clave this está vinculada al objeto antes del punto.

En nuestro ejemplo, se refiere al objeto saludos.

Pero si asignamos la función referencia saludos.saludo a una variable:

1
2
let greetRef = greetings.greet;
greetRef();

Seremos recibidos con:

1
undefined
  • ¿Eso explica algo? ¿Recuerda llamar a una función en un contexto de ventana del ejemplo anterior?*

Como llamamos a greetRef() en un contexto de ventana, en este caso this se refiere al objeto ventana y sabemos que no tiene ninguna propiedad msg.

Tomemos un escenario más complicado de usar una función anónima:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let factory = {
    items: [5, 1, 12],
    double: function(){
        return this.items.map(function(item, index){
            let value = item*2;
            console.log(`${value} is the double of ${this.items[index]}`);
            return value;
        });
    }
};

Si llamamos a factory.double() obtendremos el siguiente error:

1
2
3
Uncaught TypeError: Cannot read property '0' of undefined
    at <anonymous>
    at Array.map (<anonymous>)

El error indica que this.items no está definido, lo que significa que this dentro de la función anónima map() no se refiere al objeto factory.

Estas son las razones por las que llamamos que el valor de this está determinado por el contexto de ejecución de una función. Hay ejemplos más complicados sobre este punto de dolor que está más allá del alcance de este tutorial.

Hay varias formas de solucionar este problema, como pasar el contexto this o usar bind(). Pero el uso de estas soluciones hace que el código sea complicado e innecesariamente inflado.

Afortunadamente, en ES6 las funciones de flecha son más predecibles en términos de referencia a la palabra clave this.

El operador 'this' y las funciones de flecha en ES6

Primero, convirtamos la función anónima dentro de map() en una función de flecha:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let factory = {
    items: [5, 1, 12],
    double: function(){
        return this.items.map((item, index) => {
            let value = item*2;
            console.log(`${value} is the double of ${this.items[index]}`);
            return value;
        });
    }
};

Si llamamos a factory.double() seremos recibidos con:

1
2
3
4
10 is the double of 5
2 is the double of 1
24 is the double of 12
[10, 2, 24]

Como podemos ver, el comportamiento de this dentro de una función de flecha es bastante predecible. En las funciones de flecha this siempre tomará su valor desde el exterior. De hecho, la función flecha ni siquiera tiene esto.

Si nos referimos a ’esto’ en algún lugar de la función de flecha, la búsqueda se realiza exactamente de la misma manera que si fuera una variable regular: en el ámbito externo. También lo llamamos Ámbito léxico.

En nuestro ejemplo, ’esto’ dentro de la función de flecha tiene el mismo valor que ’esto’ fuera, es decir, en el método ‘doble()’. Entonces, this.items en la función de flecha es lo mismo que this.items en el método double() y es factory.items.

Las funciones de flecha no se pueden llamar con el operador nuevo

Como la función de flecha no tiene la palabra clave this, es obvio que no pueden admitir el operador new.

Conclusión

Las funciones de flecha pueden ser confusas al principio, pero son muy útiles para hacer que el código se comporte de manera más predecible con el ámbito léxico de la palabra clave this. También es fácil para los dedos, ya que le permite escribir menos código.

Como desarrollador de JavaScript, debería sentirse cómodo al usarlo, ya que se usa ampliamente con diferentes marcos/bibliotecas frontend como React, Angular, etc. Espero que este tutorial pueda ayudarlo a decidir cuándo y cómo implementar funciones de flecha en su proyectos futuros.