Javacript y arrow functions. Todo lo que deberías saber
Resumen de las principales características de las “arrow functions” de Javascript
Las “arrow functions” fueron introducidas en la especificación ES2015 de Javascript proporcionándonos una sintaxis más compacta para declarar funciones que además nos permite olvidarnos de algunos de los quebraderos de cabeza que supone trabajar con la variable this
.
En este artículo quiero hacer un resumen de sus principales características ya que su uso se ha estandarizado y ha pasado a ser muy habitual encontrárnoslas en proyectos por lo que creo muy interesante profundizar en ellas de cara a tener cierto dominio de cómo funcionan.
Así que, sin entretenernos más, ¡vamos a verlas!
¿Cómo se declaran?
La declaración de una arrow function es muy muy sencilla:
const giveMeAMessage = () => 'hi arrow functions';
Hay dos puntos interesantes de esta declaración:
- cuando se componen de una única sentencia no necesitamos emplear la palabra clave
return
, - y el valor que devuelven será aquel empleado como cuerpo de la función.
Por tanto, cuando invoquemos nuestra función giveMeAMessage
, lo que obtendremos será el texto hi arrow functions
:
const giveMeAMessage = () => 'hi arrow functions';const aMessage = giveMeAMessage();console.log(aMessage); // hi arrow functions
Un aspecto interesante de estas funciones compuestas por una única línea es que si queremos devolver un objeto, deberemos emplear los paréntesis para rodear el cuerpo de la función:
❌ const giveMeADeveloper = () => { name: 'Gerardo', age: '33' };✅ const giveMeADeveloper = () => ({ name: 'Gerardo', age: '33' });
Por supuesto, las arrow functions pueden componerse de más de una línea, lo cual ya nos obligará a emplear los paréntesis y a emplear return
para, ahora sí, especificar el valor de retorno:
const giveMeAMessage = () => {
const foo = 'hi arrow functions';
return foo;
}
Argumentos en las arrow functions
Por otra parte, cuando queramos pasar parámetros a este tipo de funciones podremos hacerlo de dos formas.
En el caso de que nuestra función tenga un único argumento no será necesario especificar los paréntesis. Por ejemplo:
const triple = x => x*3;const foo = triple(3); // 9
En el resto de casos sí que necesitaremos especificar los paréntesis:
const sum = (a, b) => a + b;const foo = sum(1, 2); // 3
Por lo demás, esta clase de funciones también nos permite especificar valores por defecto:
const pow = (a, b = 2) => Math.pow(a, b);const foo = pow(2); // 4const bar = pow(3, 3); // 27
🎩 Truco: Hacer un parámetro requerido
Aunque no es específico de las arrow functions, me parece interesante comentaros un “truco” para forzar a que un parámetro sea requerido de una función:

🎩 Truco: Emplear un parámetro anterior
Otra cosa que no es específica de las arrow functions pero que es interesante conocer es la posibilidad de emplear los parámetros a la izquierda de un parámetro para emplearlos en la generación de los valores por defecto de dicho parámetro.

High Order Functions
Las arrow functions también nos permiten simplificar la forma en que trabajamos con high order functions.
Para aquellos a los que no os suene el concepto, una high order function es una función que devuelve una nueva función. Por ejemplo:
const generateAMultiplier = function(a) {
return function(b) {
return b * a;
}
};const multiplier = generateAMultiplier(5);
const foo = multiplier(10); // 50
Este tipo de declaraciones que empleando la sintaxis “antigua” quedan muy largas, con las arrow functions se simplifican enormemente:
const generateAMultiplier = a => b => a * b;const multiplier = generateAMultiplier(5);
const foo = multiplier(10); // 50
Arrow functions y destructuring
Una característica con la que las arrow functions se llevan genial es con el destructuring assignment. De este modo, cuando pasemos un objeto a una función de este tipo, podremos acceder directamente a sus propiedades de una forma muy visual:
const printAge = ({age}) => console.log(age);printAge({name: 'Gerardo', age: 33});
Así como declarar parámetros por defecto empleando esta característica:
const printAge = ({age} = {age: 20}) => console.log(age);printAge({name: 'Gerardo', age: 33}); // 33
printAge(); // 20
¿Cómo verdad? Pues pasemos a algo aún más interesante.
This y arrow functions
Sin embargo, una de las características por la que más os suenen las arrow functions es por la forma en la que tratan la palabra clave this
. Veámoslo con un ejemplo muy sencillo.
Supongamos que tenemos un objeto con el siguiente aspecto:
const foo = {
log: function() {
return function() {
console.log(this);
}
}
}
Cuando ejecutemos lo siguiente lo que obtendremos en la consola será el objeto Window
(si estamos dentro de un navegador, claro):
foo.log()(); // Window
Ya que la invocación de la función devuelta por el método log
del objeto foo
es realizada por window
, por lo que no se respeta el valor de this
tal y como esperaríamos.
const foo = {
log: function() {
console.log(this); // 1. foo
return function() {
console.log(this); // 2. Window
}
}
}const log = foo.log(); // 1. foo
log(); // 2. Window
Si os interesa profundizar en este comportamiento de la variable this
os sugiero que acudáis al libro You don’t know Javacript o bien a esta respuesta de Stackoverflow:
Sin embargo, cuando trabajamos con arrow functions el comportamiento que obtenemos es esperado, ya que estas funciones tienen un alcance léxico, de modo que el valor de la variable this
dentro de una arrow function viene determinado por el alcance que la rodea. Es decir:
const foo = {
log: function() {
console.log(this); // 1. foo
return () => {
// this is lexically scoped to the surrounding scope,
// ie, the foo object
console.log(this); // 2. foo
}
}
}const log = foo.log(); // 1. foo
log(); // 2. foo
Esta característica de las arrow functions las ha vuelto muy útiles en librerías como React (donde el uso de clases es muy común), permitiéndonos prescindir del uso de bind
a la hora de trabajar con los callbacks de los event listeners del DOM. Os dejo un artículo que escribí hace tiempo explicando los motivos:
Limitaciones de las arrow functions
Sin embargo, las arrow functions tienen algunas limitaciones. Entre ellas me gustaría destacar las siguientes.
No podemos emplearlas para construir objetos.
Por tanto, si intentamos algo de este estilo:
const MyClass = () => {};
const object = new MyClass();
Obtendremos un precioso TypeError
.
No poseen prototype
Es decir, no podemos extender el prototipo de esta clase de objetos.
const MyClass = () => {};
console.log(MyClass.prototype); // undefined
No pueden ser usadas como funciones generadoras
Las arrow functions no admiten la palabra yield
dentro de su cuerpo, por lo que si queremos crear una función generadora deberemos seguir recurriendo a la forma habitual: function*
.
Podéis profundizar más en los motivos de esta limitación en la siguiente respuesta de StackOverflow:
El método call y apply es ignorado
Los métodos call
o apply
nos permiten modificar el valor de this
dentro de una función. Por ejemplo:
const saySomething = function(message) {
console.log(`${this.name} says ${message}`);
}console.log(saySomething.call(
{name: 'Gerardo'}, // value of this inside saySomething function
'hello'
));
Sin embargo, estos métodos no tendrán efecto si los invocamos sobre una arrow function, pues recordad que su alcance sólo se puede definir léxicamente.
Conclusiones
Como veis, las arrow functions nos proporcionan una sintaxis muy corta para definir funciones y que además resuelve algunos de los problemas que podían surgir con la variable this
.
Además, su sintaxis hace que se lleve genial con el estilo de programación funcional, permitiéndonos escribir expresiones como la siguiente:
const anArray = [1, 2, 3];const filtered = anArray.filter(value => value % 2 === 0);
las cuales son muy legibles y que han provocado que el uso de las arrow functions se haya popularizado.
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇