El valor del puntero
this en JavaScript es uno de los grandes enigmas con que los que se enfrenta el que empieza a manejar el lenguaje. En principio parece sencillo. Dentro de un objeto, el puntero
this hace referencia al propio objeto ¿o no?:
var myObject = {
message: “Hello!”,
talk: function() {
alert(“I say: ” + this.message);
}
}
cuando hacemos referencia a
this.message estamos apuntando a la propiedad
message en el contexto del objeto
myObject. Por lo tanto
this es una referencia al objeto en el que estamos.
Si hacemos:
myObject.talk();
veremos el mensaje:
"I say: Hello"
Parece fácil, pero se complica y mucho.
El contexto de un callback
Supongamos que queremos utilizar el método
myObject.talk() como
callback o
event handler que se ejecuta al pulsar un botón:
$(".button1").click( myObject.talk );
Al hacer
click en el botón veremos:
"I say: undefined"
¡¿Undefined?!. Cuando se llama al método
myObject.talk al hacer
click en el botón, el valor de
this.message es
undefined. La razón es que
this ya no apunta al objeto
myObject, hace referencia a otro contexto diferente.
En este caso, al ser un
callback de un evento del DOM, el navegador asocia
this al elemento que provoca el evento ( si usamos
addEventListener, que es la opción que utiliza jQuery si el navegador lo permite ). Es decir,
this es el objeto
button. Como dentro de este objeto no hay ninguna propiedad que se llame
message,
this.message es
undefined.
No es el único caso en que se cambia el contexto. Si lo usamos como
callback de una llamada
AJAX o de
setTimeout() tampoco tendrá el valor original.
Y por si las cosas no fueran ya bastante complicadas, resulta que si ponemos el método dentro de una función anónima, el mensaje es correcto:
$(".button1").click(function() {
myObject.talk();
});
El resultado es:
"I say: Hello!"
Es casi igual que lo que teníamos antes, llamámos al método
myObject.talk() pero ahora dentro de una función ¿por qué ahora sí funciona?. Bueno, el valor de
this ahora sí es el objeto
myObject. Lo explicaremos un poco más adelante.
Vamos a ver como se comporta el puntero
this en funciones/métodos y después volveremos sobre este ejemplo para explicar qué es lo que está pasando.
this en funciones y métodos
Cuando tenemos una referencia a this dentro de una función, su valor dependerá siempre de cómo se llama a la función.
Por ejemplo, si hacemos:
var f1 = myObject.talk;
f1(); //this.message is undefined
Estamos llamando a la función
talk() de una forma diferente, desde fuera del objeto. En este caso
this será el contexto desde el que ejecutamos
f1(), que es el objeto global (window).
Sin embargo, si ejecutamos la función como un método de un objeto concreto, el valor de
this será ese objeto:
myObject.talk(); //this.message is "Hello!"
Podríamos incluso llamar a la misma función desde otro objeto diferente:
var Object2 = {
message: "I'm OBJECT2",
}
Object2.talk = myObject.talk;
Object2.talk(); //this.message is "I'm OBJECT2"
En el
alert en pantalla veremos:
"I say: I'm OBJECT2"
Volviendo al ejemplo del callback
Ahora podemos entender qué estaba pasando en nuestros dos ejemplos iniciales. Recordemos que teníamos la función
talk() como
callback de dos formas diferentes:
//Example 1
$(".button1").click( myObject.talk );
//Example 2
$(".button1").click(function() {
myObject.talk();
});
En el primer caso le estamos pasando una referencia directa a nuestra función, para que la ejecute cuando y como quiera. Como hemos visto antes, el valor de
this dependerá de cómo se llame a la función. Nosotros ya no tenemos el control sobre cómo se la llamará, por eso el valor del contexto ya no es nuestro objeto.
El navegador, cuando llame al callback, colocará el contexto del objeto del DOM que ha lanzado el evento.
En el segundo caso se llama a la función anónima también con el contexto del elemento
button, pero dentro de esta función estamos ejecutando explícitamente un método del objeto myObject:
myObject.talk();
Es una llamada directa al método dentro del objeto concreto y, como hemos visto antes, el contexto será el del objeto que se referencia al llamarlo. Por eso aquí el puntero
this es correcto.
Hay varias formas de asegurarnos de que el contexto con el que se llama a una función es el que nosotros queremos, como
Function.apply(),
Function.call() o utilizando una
función proxy, pero esto lo veremos en otro post.
Fuentes:
MDN This