La palabra clave this de JavaScript es uno de los aspectos más difíciles de comprender del lenguaje. Pero es de vital importancia para escribir código JavaScript más avanzado.

En JavaScript, la palabra clave this nos permite:

  • Reutilizar funciones en diferentes contextos de ejecución. Es decir, una función una vez definida se puede invocar para diferentes objetos usando la palabra clave this.
  • Identificar el objeto en el contexto de ejecución actual cuando invocamos un método.

La palabra clave this estrechamente asociada con las funciones de JavaScript. Cuando se trata de this, lo fundamental es entender dónde se invoca una función. Porque no sabemos qué hay en la palabra clave this hasta que se invoca la función.

El uso de this se puede clasificar en cinco aspectos vinculantes (binding ) diferentes. En este artículo, aprenderemos sobre los cinco aspectos con ejemplos.

Primero, ¿Qué es la vinculación (binding)?

En JavaScript, un Entorno Léxico  (Lexical Environment) es donde se escribe físicamente tu código. En el siguiente ejemplo, el nombre de la variable está léxicamente dentro de la función decirNombre().

function decirNombre() {
  let nombre = 'algunNombre';
  console.log('El nombre es, ', nombre);
 }

Un Contexto de Ejecución (Execution Context) se refiere al código que se está ejecutando actualmente y todo lo demás que ayuda a ejecutarlo. Puede haber muchos entornos léxicos disponibles, pero el contexto de ejecución que se está ejecutando actualmente es manejado por el Contexto de Ejecución.

lexical
Entorno Léxico vs Contexto de Ejecución

Cada uno de los contextos de ejecución contiene un Registro de Entorno (Environment Record). A medida que el motor JavaScript ejecuta el código, las variables y los nombres de las funciones se agregan al registro de entorno.

Este fenómeno se conoce como vinculación en JavaScript.  La vinculación ayuda a asociar los identificadores (variables, nombres de funciones) con la palabra clave this para un contexto de ejecución.

No se preocupe si encuentra esto un poco difícil de entender ahora. Obtendrá una mejor comprensión a medida que avancemos.

Regla #1: Cómo funciona la vinculación implícita de JavaScript

La vinculación implícita cubre la mayoría de los casos de uso para tratar con la palabra clave this.

En la vinculación implícita, debe verificar lo que está a la izquierda del operador de punto (.) adyacente a una función en el momento de la invocación. Esto determina a saber donde la palabra clave this está vinculada.

Veamos un ejemplo para entenderlo mejor.

let usuario = {
    nombre: 'Tapas',
    direccion: 'freecodecamp',
    getName: function() {
        console.log(this.nombre);
    }
};

usuario.getName();

Aquí, this está vinculado al objeto usuario. Sabemos esto porque a la izquierda del operador punto(.) adyacente a la función getName(), vemos el objeto usuario . Entonces this.name va a registrar Tapas en la consola.

Veamos otro ejemplo para comprender mejor este concepto:

function decorateLogName(obj) {
      obj.logName = function() {
          console.log(this.nombre);
      }
  };

  let tom = {
      nombre: 'Tom',
      edad: 7
  };

  let jerry = {
      nombre: 'jerry',
      edad: 3
  };

  decorateLogName(tom);
  decorateLogName(jerry);

  tom.logName();
  jerry.logName();

En este ejemplo, tenemos dos objetos, tom y jerry . Hemos decorado (mejorado) estos objetos adjuntando un método llamado logName().

Observa que cuando invocamos tom.logName(), el objeto tom esta a la izquierda del operador punto(.) adyacente a la función logName(). Entonces this está vinculado al objeto tom y registra el valor de tom ( this.nombre es igual a tom aquí). Lo mismo se aplica cuando se invoca jerry.logName().

Regla #2: Cómo funciona la vinculación explícita de JavaScript (explicit binding)

Hemos visto que JavaScript crea un entorno para ejecutar el código que escribimos. Se encarga de la creación de memoria para variables, funciones, objetos, etc. en la fase de creación. Finalmente ejecuta el código en la fase de ejecución. Este entorno especial se denomina Contexto de ejecución (execution context).

Puede haber muchos entornos de este tipo (contextos de ejecución) en una aplicación JavaScript. Cada contexto de ejecución opera independientemente de los demás.

Pero a veces, es posible que deseemos usar cosas de un contexto de ejecución en otro. Ahí es donde entra en juego la vinculación explícita.

En la vinculación explícita, podemos llamar a una función con un objeto cuando la función está fuera del contexto de ejecución del objeto.

Hay tres métodos muy especiales, call(), apply() y bind() que nos ayudan a lograr una vinculación explícita.

Como funciona el método call() de JavaScript

Con el método call() , el contexto con el que se debe llamar a la función se pasará como parámetro call(). Veamos cómo funciona con un ejemplo:

let getName = function() {
     console.log(this.nombre);
 }
 
let usuario = {
   nombre: 'Tapas',
   direccion: 'freecodecamp'  
 };

getName.call(usuario);

Aquí, el método call() se invoca en una función llamada getName(). La función getName() simplemente registra this.nombre. ¿Pero qué es this aquí? Eso se determina por lo que se ha pasado al método call().

Aquí, this se vinculará al objeto de usuario porque hemos pasado el usuario como parámetro al método call(). Entonces this.nombre debe registrar el valor de la propiedad de nombre del objeto de usuario, es decir, Tapas.

En el ejemplo anterior, hemos pasado solo un argumento para call(). Pero también podemos pasar múltiples argumentos a call(), así:

let getName = function(pasatiempo1, pasatiempo2) {
     console.log(this.nombre + ' le gusta ' + pasatiempo1 + ' , ' + pasatiempo2);
 }

let usuario = {
   nombre: 'Tapas',
   direccion: 'Bangalore'  
 };

let pasatiempos = ['Nadar', 'Escribir'];
 
getName.call(usuario, pasatiempos[0], pasatiempos[1]);

Aquí hemos pasado varios argumentos al método call(). El primer argumento debe ser el contexto del objeto con el que se debe invocar la función. Otros parámetros podrían ser simplemente valores para usar.

Aquí estoy pasando Nadar y Escribir como dos parámetros para la función getName().

¿Notaste un punto crítico aquí? En el caso de una call(), los argumentos deben pasarse uno por uno, lo cual no es una forma inteligente de hacer las cosas. Ahí es donde nuestro método siguiente, apply(), entra en escena.

Cómo funciona el método apply() de JavaScript

Esta forma frenética de pasar argumentos al método call() puede resolverse con otro método alternativo llamado apply(). Es exactamente lo mismo que  call() pero le permite pasar los argumentos de manera más conveniente. Echar un vistazo:

let getName = function(pasatiempo1, pasatiempo2) {
     console.log(this.name + ' le gusta ' + pasatiempo1 + ' , ' + pasatiempo2);
 }
 
let usuario = {
   nombre: 'Tapas',
   direccion: 'Bangalore'  
 };

let pasatiempos = ['Nadar', 'Escribir'];
 
getName.apply(usuario, pasatiempos);

Aquí podemos pasar una serie de argumentos, lo que es mucho más conveniente que pasarlos uno por uno.

Consejo: cuando solo tenga un argumento de valor o ningún argumento de valor para pasar, use call(). Cuando tenga varios argumentos de valor para pasar, use apply().

Cómo funciona el método bind() de JavaScript

El método bind() es similar al método call() pero con una diferencia. A diferencia del método call() de llamar a la función directamente, bind() devuelve una función nueva y podemos invocarla en su lugar.

let getName = function(pasatiempo1, pasatiempo2) {
     console.log(this.name + ' le gusta ' + pasatiempo1 + ' , ' + pasatiempo2);
 }

let usuario = {
   nombre: 'Tapas',
   direccion: 'Bangalore'  
 };

let pasatiempos = ['Nadar', 'Escribir'];
let nuevaFn = getName.bind(usuario, pasatiempos[0], pasatiempos[1]); 

nuevaFn();

Aquí, getName.bind() no invoca la función getName() directamente. Devuelve una nueva función, newFn y podemos invocarla como newFn().

Regla #3: La vinculación new de JavaScript

La palabra clave new se utiliza para crear un objeto a partir de la función constructora.

let Caricatura = function(nombre, personaje) {
     this.nombre = nombre;
     this.personaje = personaje;
     this.log = function() {
         console.log(this.nombre +  ' is a ' + this.personaje);
     }
 };

Puede crear objetos usando la palabra clave new como esta:

 let tom = new Caricatura('Tom', 'Gato');
 let jerry = new Caricatura('Jerry', 'Raton');

Cuando se invoca una función con la palabra clave new , JavaScript un objeto interno this (como, this = {}) dentro de la función. El recién creado  this se una al objeto que se está creando utilizando la palabra clave new .

¿Suena complejo? Ok, analicémoslo. Toma esta línea,

let tom = new Caricatura('Tom', 'Gato');

Aquí se invoca la función Caricatura con la palabra clave new . Entonces, el this creado internamente estará vinculado al nuevo objeto que se está creando aquí, el cual es tom.

Regla #4: Vinculación de Objetos Globales de JavaScript

¿Cuál crees que será el resultado del siguiente código? ¿A qué se vincula this aquí?

let sayName = function(nombre) {
    console.log(this.nombre);
};

window.nombre = 'Tapas';
sayName();

Si la palabra clave this no se resuelve con ninguno de las vinculaciones, implicit (implícito), explicit (explícito) o new, la palabra clave this se enlaza con el objeto window(global) .

Sin embargo, hay une excepción. El modo estricto (strict mode) de JavaScript no permite esta vinculación predeterminada.

"use strict";
function myFunction() {
  return this;
}

En el caso anterior, la palabra clave this no está definida (undefined.)

Regla #5: Vinculación de elemento de evento HTML en JavaScript (event element)

En los controladores de eventos de HTML, la palabra clave this se vincula a los elementos HTML que reciben el evento.

<button onclick="console.log(this)">¡Haz clic aquí!</button>

El es el registro de salida (output log) en la consola cuando hace clic en el botón:

"<button onclick='console.log(this)'>¡Haz clic aquí!</button>"

Puedes cambiar el estilo del botón usando la palabra clave this , así:

<button onclick="this.style.color='teal'">¡Haz clic aquí!</button>

Pero tenga cuidado cuando llame a una función en el botón clic y use la palabra clave this dentro de esa función.

<button onclick="cambiarColor()">¡Haz clic aquí!</button>

y en JavaScript:

function cambiarColor() {
  this.style.color='teal';
}

El código anterior no funcionará como se esperaba. Como hemos visto en la Regla 4, aquí esto estará ligado al objeto global (en el modo 'no estricto') donde no hay un objeto de estilo para establecer el color.

En Resumen

Para resumir,

  • En el caso de la vinculación implícita, la palabra clave this se vincula a la izquierda del operador punto (.).
  • En el caso de una vinculación explícita, podemos llamar a una función con un objeto cuando la función está fuera del contexto de ejecución del objeto. Los métodos call(), apply(), and bind() juegan un papel importante aquí.
  • Cuando se invoca una función con la palabra clave new, la palabra clave this dentro de la función se une al nuevo objeto que se está construyendo.
  • Cuando la palabra clave this no se resuelve con ninguno de las vinculaciones, implicit(implícito), explicit(explícito) o new, se enlaza con el objeto window(global). En el modo estricto (strict mode) de JavaScript, la palabra clave this no estará definida (undefined).
  • En los controladores de eventos HTML, la palabra clave this se vincula a los elementos HTML que reciben el evento.

Hay un caso más en el que la palabra clave this se comporta de manera diferente, como con las funciones de flecha de ES6 (ES6 arrow functions). Veremos eso en un artículo futuro.

Espero que este artículo le haya resultado útil.

Si este artículo fue útil, compártelo para que otros también puedan leerlo. Puedes @ me on Twitter (@tapasadhikary) con comentarios, o no dudes en seguirme.

Traducido del artículo de Tapas Adhikary - The JavaScript `this` Keyword + 5 Key Binding Rules Explained for JS Beginners