Una de las caracter铆sticas de JavaScript que m谩s confusi贸n genera es la palabra clave this. M谩s concretamente a qu茅 o qui茅n hace referencia y en qu茅 circunstancias.

Para dejarlo claro desde el principio: this, de ninguna manera, es una referencia a una funci贸n. this tampoco permite acceder o hacer de puente entre 谩mbitos de diferentes funciones. No, nada de eso es this.

Pero entonces, 驴qu茅 es?.

驴Que es this?

this es una referencia que se crea cuando una funci贸n es invocada, no declarada. El valor de esa referencia depende al 100% del lugar en la que esa invocaci贸n se realice, llamado call-site.

Ese lugar de llamada es la invocaci贸n en s铆 a la funci贸n. Es decir, el momento justo en que es llamada (no declarada, no referenciada) esa funci贸n.

Como estamos tratando con JavaScript, vamos a examinar los call-site a funciones en los escenarios m谩s comunes.

this en el contexto global

Fuera de cualquier funci贸n, es decir, en el 谩mbito global, this siempre hace referencia al objeto global window:

// Estamos en el contexto de ejecuci贸n global
console.log(this === window); // true

this en invocaciones de funciones

Dentro de una funci贸n, el valor de this est谩 determinado por el lugar en el que esa funci贸n es invocada.

En una sencilla funci贸n declarada, this hace referencia al objeto global window:

function funcion() {
    console.log(this);
}

funcion(); // window

Sin embargo, ese escenario cambia de forma dr谩stica si utilizamos el modo estricto de JavaScript:

"use strict";

function funcion() {
    console.log(this);
}

funcion(); // undefined

Esta distinci贸n es vital si no queremos ensuciar el 谩mbito global accidentalmente. Por ejemplo, utilizando de forma err贸nea una llamada de construcci贸n a una funci贸n.

function Gato(raza, color) {
    this.raza = raza;
    this.color = color;
}

const nino = Gato('europeo', 'negro');
console.log(nino); // undefined

Como puedes comprobar, con s贸lo olvidar el operador new ya no estar谩s creando una nueva instancia de Gato bajo el objeto nino.

Lo que es peor: sin darte cuenta estar谩s a帽adiendo propiedades al objeto global window, ya que dentro de una funci贸n this hace siempre referencia al objeto global.

/* C贸digo anterior */
console.log(window.raza, window.color); // europeo negro

Basta con usar el modo estricto para evitar estos accidentes:

"use strict"
/* C贸digo anterior */
console.log(window.raza); // Uncaught TypeError: Cannot set property 'raza' of undefined

Este ejemplo me lleva al siguiente escenario de invocaci贸n o call-site: los constructores.

this en invocaciones de construcci贸n

Como sabes, a diferencia de otros lenguajes, JavaScript no dispone de constructores como tal, sino de llamadas de construcci贸n 鈥漜onstruction calls鈥 a una funci贸n.

A trav茅s del operador new se crea un nuevo objeto, se asigna su prototipo a la funci贸n constructora y lo que es m谩s importante: dentro de la funci贸n que hace de constructor, el valor de this hace referencia a ese nuevo objeto que se est谩 creando.

Por eso, cuando una funci贸n es invocada como un constructor, gracias a ese valor espec铆fico de this podemos establecer los par谩metros que recibe la funci贸n como propiedades del nuevo objeto:

function Gato(raza, color) {
    this.raza = raza;
    this.color = color;
    console.log(this);
    // Gato {raza: "europeo", color: "negro"}
}

const guizmo = new Gato('europeo', 'negro');

Sigamos hablando de objetos. En concreto en lo que ocurre con el valor de this cuando invocamos una funci贸n que pertenece a un objeto.

this en llamadas a m茅todos

Cuando una funci贸n es llamada como un m茅todo de un objeto, su contexto de this se asocia al objeto que contiene el m茅todo.

const rouco = {
    nombre: 'Rouco',
    especie: 'gato',
    saludar() {
        console.log('Miauuuuu (Hola me llamo ${this.nombre})');
        console.log(this === rouco);
    }
};

rouco.saludar(); 
// Miauuuuu (Hola me llamo Rouco) 
// true

Sin embargo es relativamente sencillo perder el contexto de this si por ejemplo guardamos el m茅todo en una funci贸n declarada para ejecutarla como tal y no como un m茅todo (con su referencia al objeto):

const rouco = {
    /* C贸digo anterior */
};

let saludar = rouco.saludar;
saludar(); // <鈥 Este es el "call-site"
// Miauuuuu (Hola me llamo undefined) 
// false

F铆jate que aunque nos estamos refiriendo al m茅todo de un objeto, la llamada (call-site) ocurre bajo la forma de una funci贸n declarada en el 谩mbito global.

Otra forma muy com煤n de perder la referencia de this al objeto que contiene el m茅todo es al pasarlo 鈥攅l m茅todo鈥 como un callback a otra funci贸n:

function funcion() {
    console.log(this.lugar);
}

function ejecutar(funcion) {
    funcion(); // <鈥 Este es el "call-site"
}

var objeto = {
    lugar: 'objeto',
    funcion: funcion,
};

var lugar = 'global';

ejecutar(objeto.funcion); // 'global'

El lugar donde se invoca la funci贸n es lo 煤nico importante. En los dos 煤ltimos casos se trata de una funci贸n declarada. Como ya sabes this, en ambos casos, har谩 referencia a window o undefined, dependiendo si estamos en modo estricto o no.

this en arrow functions

Las funciones flecha o “arrow functions” se comportan de una forma muy particular en lo que se refiere a this.

Cuando creamos una arrow function, su valor de this queda asociado permanentemente a al valor de this de su 谩mbito externo inmediato. window en el caso del 谩mbito global:

const funcion = () => {
    console.log(this === window);
};

funcion(); // true

O, si se encuentra dentro un m茅todo, el valor de this del m茅todo que la contiene. Esto hace que utilizar arrow functions como callbacks sea mucho m谩s sencillo. Considera este ejemplo:

const contador = {
    cantidad: 0,
    incremetar() {
        setInterval(function() {
            console.log(++this.cantidad);
        }, 1000);
    }
};

contador.incremetar(); // NaN NaN NaN 鈥

Aunque not a number (NaN) no es el resultado esperado, tiene sentido encontrarlo ya que dentro de setInterval() el valor de this, que dentro del m茅todo incrementar() hac铆a referencia al propio objeto contador, se ha perdido.

Estamos buscando la propiedad cantidad en el objeto global, window.cantidad en lugar de contador.cantidad.

Una soluci贸n muy com煤n hasta la aparici贸n de las funciones flecha es hacer una copia del valor de this para utilizarla en otros 谩mbitos:

const contador = {
    cantidad: 0,
    incremetar() {
            const that = this; // <鈥 Hacemos una copia de this
        setInterval(function() {
            console.log(++that.cantidad); // La usamos鈥
        }, 1000);
    }
};

contador.incremetar(); // 1 2 3 鈥

Funciona, pero es una soluci贸n sucia y requiere crear una variable que s贸lo se utiliza para no perder el contexto de this. Afortunadamente, como las arrow functions tienen su valor de this asociado al 谩mbito exterior, resolver el problema del contador es sencillo:

const contador = {
    cantidad: 0,
    incremetar() {
        setInterval(() => {
            console.log(++this.cantidad);
        }, 1000);
    }
};

contador.incremetar(); // 1 2 3 鈥

Dentro de la funci贸n flecha el valor de this es el mismo que en el m茅todo incrementar, permitiendo entonces acceder a la propiedad cantidad sin problemas.

驴Qu茅 hacer con this entonces?

Habiendo examinado las situaciones m谩s comunes en las que invocar una funci贸n y que ocurre con el valor de this en cada una de ellas, en el pr贸ximo art铆culo veremos qu茅 herramientas pone JavaScript a nuestra disposici贸n para controlar e incluso forzar (hard binding) un valor de this determinado en cada una de nuestras llamadas a funciones.

Como teaser te dir茅 que se trata de los m茅todos pertenecientes a todas las funciones: call(), apply() y bind().

Referencias