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