Si llevas un tiempo programando en JavaScript, seguro que la palabra clave this te ha dado más de un quebradero de cabeza. Y desde que aparecieron las funciones flecha (arrow functions) en ES6, la cosa se complicó todavía un poco más… o se simplificó, según cómo se mire.
En este artículo vamos a ver con calma cómo funciona this en funciones tradicionales y en funciones flecha, por qué a veces parece que apunta a un objeto y otras veces al objeto global, y en qué situaciones tiene sentido usar funciones flecha y en cuáles es mejor evitarlas.
Qué es exactamente this en JavaScript
La palabra reservada this es una referencia al contexto de ejecución de la función que se está ejecutando en ese momento. A diferencia de otros lenguajes, en JavaScript no se decide por dónde está definida la función, sino por cómo se invoca.
Eso significa que una misma función puede ser llamada de distintas formas y, en cada una de ellas, this puede apuntar a un objeto diferente. No es algo que puedas cambiar con una asignación directa (no puedes hacer this = algo), pero sí puedes influir en ello con mecanismos específicos como call, apply y bind.
Además, su comportamiento varía entre modo estricto y modo no estricto. En modo no estricto, si llamas a una función “a pelo” (sin objeto delante), this suele ser el objeto global (en el navegador, window), mientras que en modo estricto puede ser undefined. Este matiz es importante cuando compares ejemplos de código de distintas fuentes.
this en el contexto global y en funciones normales
En los navegadores, cuando no estás dentro de ningún módulo ni función, el contexto global es el objeto window, y ahí this apunta a ese objeto. Es decir, si escribes en la consola:
console.log(this === window); // true en un entorno de navegador no estricto
Dentro de una función declarada de forma “clásica” (función normal), el valor de this depende de cómo se llame a esa función. Si la invocas sin un objeto antes, en modo no estricto this suele ser el global, y en modo estricto será undefined. Por eso a veces, al mover código de un sitio a otro, this deja de ser lo que esperabas.
this en métodos de objeto definidos con funciones normales
Cuando defines un método en un objeto usando la sintaxis tradicional, this dentro del método referencia al propio objeto desde el que se ha invocado ese método.
Por ejemplo, si tienes algo como:
const obj = {
speak() {
console.log(this);
}
};
obj.speak();
La llamada obj.speak() hace que this dentro de speak sea el propio obj. Ese comportamiento es el que la gente suele esperar intuitivamente: el método habla “en nombre” del objeto.
Si en lugar de la sintaxis abreviada usas una función clásica, el efecto es el mismo, porque la clave está en cómo se invoca el método, no en si has usado la notación abreviada de métodos o la palabra clave function dentro del objeto.
this en métodos definidos con funciones flecha
La cosa cambia cuando defines el método con una función flecha. Algo como:
const obj2 = {
speak: () => {
console.log(this);
}
};
obj2.speak();
En este caso, al ejecutar obj2.speak() verás que this ya no es obj2, sino el contexto léxico exterior a ese objeto, que en un script clásico de navegador suele ser el objeto global window.
Esto desconcierta la primera vez que lo ves, porque esperas que un método del objeto apunte al propio objeto. Sin embargo, las funciones flecha no crean su propio this: heredan el valor del this del ámbito donde fueron definidas. Si ese ámbito es global, heredan el global; si es otro, heredarán ese otro.
Por eso una recomendación muy repetida en la documentación moderna es: no uses funciones flecha como métodos de objetos cuando necesites que this apunte a ese objeto.
Ámbito léxico de this en funciones flecha
La diferencia clave entre funciones normales y arrow functions es que las segundas tienen un vínculo léxico para this. Dicho rápido: no deciden su this cuando se llaman, sino cuando se crean.
Imagina este ejemplo:
const obj3 = {
speak() {
(() => {
console.log(this);
})();
}
};
obj3.speak();
Aquí podría parecer que como dentro de speak ejecutamos una arrow function, this debería “resetease” al global. Pero ocurre justo lo contrario: la función flecha captura el this de la función que la envuelve, que en este caso es el método speak invocado como obj3.speak(). Por tanto, el valor de this que se muestra en la consola es el de obj3.
Es decir, las funciones flecha no tienen su propio this, sino que reutilizan el de su entorno inmediato. Esto es tremendamente útil en callbacks anidados, temporizadores, promesas y cualquier sitio donde, con funciones clásicas, tenías que pelearte con .bind o con trucos tipo const that = this;.
Ejemplos prácticos de pérdida y conservación de this
Uno de los problemas clásicos en JavaScript es que, al definir una función dentro de un método, pierdes la referencia a this que apuntaba al objeto y acabas con el global o con undefined.
Pongamos el típico caso de usar setTimeout dentro de un método de un objeto con una función tradicional:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
setTimeout(function() {
console.log(this.nombre);
}, 3000);
}
};
persona.decirNombre(); // Muestra undefined
Aquí this dentro de la función pasada a setTimeout ya no es el objeto persona. Esa función callback se ejecuta en el contexto global (en un navegador, window), así que this.nombre intenta leer una propiedad en el global, que no existe, y acaba siendo undefined.
Antes de que existieran las arrow functions, una solución habitual era guardar el valor de this en una variable auxiliar para “arrastrarlo” al interior de la función:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
let that = this; // aquí this es persona
setTimeout(function() {
console.log(that.nombre);
}, 3000);
}
};
Gracias a esa variable, la referencia correcta al objeto se mantiene. Pero es un truco algo feo y repetitivo. Con las funciones flecha, este problema se simplifica mucho:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
setTimeout(() => {
console.log(this.nombre);
}, 3000);
}
};
Aquí la arrow function no crea su propio this, así que hereda el this del método decirNombre, que es el objeto persona. El resultado: se muestra “Agustin” correctamente sin necesidad de variables intermedias ni .bind.
call, apply y bind: controlar el valor de this
Además de la forma “natural” de fijar el contexto con la llamada a un método, JavaScript nos da herramientas para forzar el valor de this en funciones normales: call, apply y bind.
Los métodos call() y apply() invocan la función inmediatamente, permitiéndote pasar el objeto que quieres usar como this. La diferencia es que call recibe los argumentos uno a uno, mientras que apply los recibe en un array. bind(), en cambio, devuelve una nueva función con el this “pegado” al valor que hayas indicado, para que puedas llamarla más adelante cuando te convenga.
Con funciones flecha, en cambio, estos métodos no sirven para cambiar this porque su valor está ligado léxicamente. Puedes usar call, apply o bind para pasar argumentos, pero no para modificar el contexto de las arrow functions, lo que es una diferencia muy importante con las funciones regulares.
Sintaxis básica de las funciones flecha
Más allá del comportamiento de this, las funciones flecha aportan una sintaxis más compacta y expresiva para muchas situaciones. La forma general es:
(arg1, arg2, ..., argN) => expresion
Esta forma devuelve automáticamente el resultado de la expresión situada a la derecha de la flecha, por lo que no hace falta escribir la palabra return cuando solo tienes una única expresión simple.
Algunos puntos comunes de la sintaxis:
- Sin parámetros:
() => 42o incluso_ => 42si te da igual el nombre del argumento. - Con un solo parámetro:
Los paréntesis son opcionales, puedes escribirx => x * 2o(x) => x * 2. - Con múltiples parámetros:
Los paréntesis son obligatorios:(x, y) => x + y.
Cuando necesitas varias sentencias, puedes usar cuerpo de bloque con llaves:
const sumar = (x, y) => {
const resultado = x + y;
return resultado;
};
En este caso, al haber llaves, ya no hay retorno implícito; si no pones return, la función devolverá undefined. Esto se aplica tanto a funciones flecha como a funciones tradicionales.
Devolver objetos literales con funciones flecha
Hay un pequeño detalle sintáctico muy frecuente: cuando una arrow function devuelve un objeto literal directamente, debes envolverlo entre paréntesis para que el intérprete no lo confunda con un bloque.
Por ejemplo:
x => ({ y: x })
Sin esos paréntesis, JavaScript interpretaría las llaves como el inicio del cuerpo de la función, no como un objeto. Es un truco sencillo pero que provoca muchos errores tontos si no lo recuerdas.
Funciones flecha: anónimas y sin prototype
Las funciones flecha son sintácticamente anónimas: no tienen nombre propio, lo que puede complicar algo el debug y los mensajes de error, ya que en la traza no ves el identificador de la función directamente, salvo que la hayas asignado a una constante con nombre reconocible.
Además, las arrow functions no tienen propiedad prototype y no pueden usarse como constructoras. Si intentas invocarlas con new, obtendrás un error. Para crear objetos mediante constructores o clases, sigue siendo necesario usar funciones normales o la sintaxis class.
Otra consecuencia es que no son adecuadas para patrones que requieran autorreferencia interna, como algunas formas de recursión o manejadores de eventos que necesitan desuscribirse a sí mismos usando this o el propio nombre de la función.
Dónde brillan las funciones flecha
El gran punto fuerte de las funciones flecha es precisamente su vinculación léxica de this. Son ideales en situaciones donde quieres que el callback que pasas a otra función mantenga el this del ámbito envolvente.
Por ejemplo, en un objeto con un método que arranca un temporizador y necesita seguir accediendo a propiedades del propio objeto usando this:
const contador = {
id: 42,
iniciar() {
setTimeout(() => {
console.log(this.id); // this es contador
}, 1000);
}
};
En ES5 era habitual tener que poner .bind(this) al callback o guardar this en otra variable. Con las funciones flecha, el código queda más limpio y cercano a la intención real.
También son muy prácticas con métodos de arrays como map, filter, reduce y compañía, porque reducen el ruido sintáctico cuando la lógica de la función es breve:
const numeros = [1, 2, 3];
const dobles = numeros.map(n => n * 2);
Cuando se usan con moderación, estas formas compactas hacen que el flujo de datos sea más fácil de seguir a simple vista.
Cuándo evitar las funciones flecha
Aunque las arrow functions son muy útiles, no son un reemplazo de las funciones normales. Hay varios casos claros donde es mejor no utilizarlas:
- Métodos de objetos que dependan de
this:
Si defines un método comosaltos: () => { this.vidas--; }dentro de un objetogato,thisno apuntará al gato, sino al ámbito exterior, y la propiedad no se actualizará como esperas. - Callbacks de eventos del DOM que necesitan un
thisdinámico:
En un handler comoboton.addEventListener('click', () => { this.classList.toggle('on'); });,thisno será el botón pulsado, sino el contexto superior, lo que probablemente te dará un error de tipo. - Constructores o funciones que necesiten
prototype:
Al no poder usarse connew, las funciones flecha no sirven para crear instancias ni para patrones basados en prototipos.
En todos estos casos, una función normal sigue siendo la herramienta adecuada porque permite que this se ligue dinámicamente a la forma en que invocas la función.
Si te acostumbras a elegir conscientemente entre función normal y función flecha según lo que necesites de this y del contexto, tu código será más predecible y legible para cualquier persona que lo mantenga después.
Al final, entender bien cómo se decide el valor de this en JavaScript, y cómo las funciones flecha heredan ese valor del ámbito léxico donde se crean, es la clave para dejar de pelearte con resultados inesperados, sacar partido al azúcar sintáctico de ES6 y escribir métodos, callbacks y manejadores de eventos que hagan exactamente lo que tenías en mente.