En JavaScript, cada función tiene una referencia this creada automáticamente cuando la declaramos.

El this de JavaScript es similar a lo que podríamos encontrar en otros lenguajes basados en clases como Java o C# (JavaScript en realidad es un lenguaje basado en prototipos y no tiene un concepto de "clase"): apunta al objeto que está llamando a la función (este objeto a veces se llama contexto). Sin embargo, en JavaScript, las funciones internas de la referencia this pueden vincularse a diferentes objetos dependiendo de dónde se llame a la función.

Aquí hay 5 reglas básicas para enlazar this en JavaScript:

Regla 1

Cuando se llama a una función en el ámbito global, this hace referencia de forma predeterminada al objeto global (window en el navegador, o global en Node.js). Por ejemplo:

function foo() {
  this.a = 2;
}

foo();
console.log(a); // 2

Nota: Si declaramos la función foo() en modo estricto, y luego la llamamos en el ámbito global, this será undefined y la asignación this.a = 2  arrojará una excepción  Uncaught TypeError (error de tipo no detectado).

Regla 2

Veamos el siguiente ejemplo:

function foo() {
  this.a = 2;
}

const obj = {
  foo: foo
};

obj.foo();
console.log(obj.a); // 2

Claramente, en el fragmento anterior, la función foo() se llama en el contexto del objeto obj, por lo tanto this ahora hace referencia a obj. Entonces, cuando se llama a una función con un objeto de contexto, la referencia  this se vincula a dicho objeto.

Regla 3

.call, .apply y .bind pueden utilizarse para enlazar explícitamente this. Usar .bind(this) es algo que podemos ver en muchos componentes de React.

const foo = function() {
  console.log(this.bar)
}

foo.call({ bar: 1 }) // 1

Aquí vemos algunos ejemplos rápidos de como utilizar cada uno para enlazar this:

  • .call(): fn.call(thisObj, fnParam1, fnParam2)
  • .apply(): fn.apply(thisObj, [fnParam1, fnParam2])
  • .bind(): const newFn = fn.bind(thisObj, fnParam1, fnParam2)

Regla 4

function Point2D(x, y) {
  this.x = x;
  this.y = y;
}

const p1 = new Point2D(1, 2);
console.log(p1.x); // 1
console.log(p1.y); // 2

Lo que notamos aquí es que al llamar a la función Point2D utilizando la palabra clave new, la referencia this se vincula al objeto p1. Entonces, cuando llamamos a una función utilizando new, un nuevo objeto es creado y la referenciathis se vincula al mismo.

Nota: A una función que llamamos utilizando la palabra clave new también la conocemos como función constructora.

Regla 5

JavaScript determina el valor de this en tiempo de ejecución, según el contexto actual. Por eso a veces this podría no estar apuntando a dónde esperamos.

Veamos este ejemplo, una clase Gato que tiene un método llamado hacerSonido(), siguiendo lo que vimos en la regla anterior, utilizamos una función constructora y la palabra clave new.

const Gato = function(nombre, sonido) {
  this.nombre = nombre;
  this.sonido = sonido;
  this.hacerSonido = function() {
    console.log( this.nombre + ' decir: ' + this.sonido );
  };
}

const gatito = new Gato('Papá Gordo', 'Mrrooowww');
gatito.haceSonido(); // Papá Gordo dice: Mrrooowww

Ahora vamos a agregarle al gato el método molestar() para que puedan molestar a las personas repitiendo su sonido 100 veces, con un período de repetición de medio segundo.

const Gato = function(nombre, sonido) {
  this.nombre = nombre;
  this.sonido = sonido;
  this.hacerSonido = function() {
    console.log( this.nombre + ' decir: ' + this.sonido );
  };
  this.molestar = function() {
    let contar = 0, max = 100;
    const t = setInterval(function() {
      this.hacerSonido(); // <-- esta línea falla con `this.hacerSonido no es una función` 
      count++;
      if (contar === max) {
        clearTimeout(t);
      }
    }, 500);
  };
}

const gatito = new Gato('Papá Gordo', 'Mrrooowww');
gatito.molestar();

Esto no funcionará porque dentro de setInterval hemos creado un nuevo contexto con alcance global, entonces this ya no apunta a nuestra instancia gatito. En el navegador, this apuntará al objeto Window, que no tiene un método hacerSonido().

Algunas formas de solucionarlo:

1. Antes de crear el nuevo contexto, asignar this a una variable local llamada me, o self, o cualquier nombre que elijamos, y referenciar esa variable dentro del callback.

const Gato = function(nombre, sonido) {
  this.nombre = nombre;
  this.sonido = sonido;
  this.hacerSonido = function() {
    console.log( this.nombre + ' decir: ' + this.sonido );
  };
  this.molestar = function() {
    let contar = 0, max = 100;
    const self = this;
    const t = setInterval(function() {
      self.hacerSonido();
      contar++;
      if (contar === max) {
        clearTimeout(t);
      }
    }, 500);
  };
}

const gatito = new Gato('Papá Gordo', 'Mrrooowww');
gatito.molestar();

2. Con ES6 podemos evitar tener que asignar this a una variable local usando una arrow function, que automáticamente enlaza this al contexto del código donde ha sido definida.

const Gato = function(nombre, sonido) {
  this.nombre = nombre;
  this.sonido = sonido;
  this.hacerSonido = function() {
    console.log( this.nombre + ' decir: ' + this.sonido );
  };
  this.molestar = function() {
    let contar = 0, max = 100;
    const t = setInterval(() => {
      this.hacerSonido();
      contar++;
      if (contar === max) {
        clearTimeout(t);
      }
    }, 500);
  };
}

const gatito = new Gato('Papá Gordo', 'Mrrooowww');
gatito.molestar();

Traducido del artículo - The Complete Guide to this in JavaScript