Tengo una función constructora que registra un manejador de eventos:
-- Comienza el snippet: js hide: false -->
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', function () {
alert(this.data);
});
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
Sin embargo, no puedo acceder a la propiedad data
del objeto creado dentro del callback. Parece que this
no se refiere al objeto creado sino a otro.
También he intentado utilizar un método de objeto en lugar de una función anónima:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', this.alert);
}
MyConstructor.prototype.alert = function() {
alert(this.name);
};
pero presenta los mismos problemas.
Cómo puedo acceder al objeto correcto?
esto
this
(también conocido como "el contexto") es una palabra clave especial dentro de cada función y su valor sólo depende de cómo se llamó a la función, no de cómo/cuándo/dónde se definió. No se ve afectada por los ámbitos léxicos como otras variables (excepto para las funciones de flecha, ver más abajo). He aquí algunos ejemplos:
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
Para aprender más sobre this
, echa un vistazo a la [documentación MDN][this].
this
correctothis
En realidad no quieres acceder a this
en particular, sino al objeto al que se refiere. Por eso una solución fácil es simplemente crear una nueva variable que también haga referencia a ese objeto. La variable puede tener cualquier nombre, pero los más comunes son self
y that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Como self
es una variable normal, obedece a las reglas de alcance léxico y es accesible dentro de la llamada de retorno. Esto también tiene la ventaja de que puedes acceder al valor de this
de la propia llamada de retorno.
this
de la llamada de retorno - parte 1Podría parecer que no tienes control sobre el valor de this
porque su valor se establece automáticamente, pero en realidad no es así.
Cada función tiene el método .bind
[docs], que devuelve una nueva función con this
ligado a un valor. La función tiene exactamente el mismo comportamiento que aquella a la que llamaste .bind
, sólo que this
fue establecido por ti. No importa cómo o cuándo se llame a esa función, this
siempre se referirá al valor pasado.
function MyConstructor(data, transport) {
this.data = data;
var boundFunction = (function() { // parenthesis are not necessary
alert(this.data); // but might improve readability
}).bind(this); // <- here we are calling `.bind()`
transport.on('data', boundFunction);
}
En este caso, estamos vinculando el this
de la llamada de retorno con el valor de this
de MyConstructor
.
Nota: Cuando se vincula el contexto para jQuery, utilice jQuery.proxy
[docs] en su lugar. La razón para hacer esto es que no necesitas almacenar la referencia a la función cuando desvinculas un callback de evento. jQuery maneja eso internamente.
ECMAScript 6 introduce las funciones flecha, que pueden ser consideradas como funciones lambda. No tienen su propio enlace this
. En su lugar, this
se busca en el ámbito como una variable normal. Esto significa que no tienes que llamar a .bind
. Este no es el único comportamiento especial que tienen, por favor consulta la documentación del MDN para más información.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
this
del callback - parte 2Algunas funciones/métodos que aceptan callbacks también aceptan un valor al que debe referirse el this
del callback. Esto es básicamente lo mismo que vincularlo tú mismo, pero la función/método lo hace por ti. [Array#map
[docs]][map] es un método de este tipo. Su firma es:
array.map(callback[, thisArg])
El primer argumento es la llamada de retorno y el segundo argumento es el valor al que debe referirse this
. Este es un ejemplo inventado:
var arr = [1, 2, 3];
var obj = {multiplier: 42};
var new_arr = arr.map(function(v) {
return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument
Nota: El hecho de que se pueda o no pasar un valor para this
se menciona normalmente en la documentación de esa función/método. Por ejemplo, el método $.ajax
de jQuery [docs] describe una opción llamada context
:
Este objeto se convertirá en el contexto de todas las devoluciones de llamada relacionadas con Ajax.
Otra manifestación común de este problema es cuando se utiliza un método de objeto como callback/manejador de eventos. Las funciones son ciudadanos de primera clase en JavaScript y el término "método" es sólo un término coloquial para una función que es un valor de una propiedad del objeto. Pero esa función no tiene un vínculo específico con su objeto "contenedor".
Considere el siguiente ejemplo:
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
La función this.method
se asigna como manejador de eventos de clic, pero si se hace clic en el document.body
, el valor registrado será undefined
, porque dentro del manejador de eventos, this
se refiere al document.body
, no a la instancia de Foo
.
Como ya se mencionó al principio, a qué se refiere this
depende de cómo se llame a la función, no de cómo se defina.
Si el código fuera como el siguiente, podría ser más obvio que la función no tiene una referencia implícita al objeto:
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
La solución es la misma que la mencionada anteriormente: Si está disponible, utiliza .bind
para vincular explícitamente this
a un valor específico
document.body.onclick = this.method.bind(this);
o llamar explícitamente a la función como un "método" del objeto, utilizando una función anónima como callback / manejador de eventos y asignar el objeto (this
) a otra variable:
var self = this;
document.body.onclick = function() {
self.method();
};
o utilizar una función de flecha:
document.body.onclick = () => this.method();
Todo está en la sintaxis "mágica" de llamar a un método:
object.property();
Si obtienes la propiedad del objeto y lo llamas de una sola vez, el objeto será el contexto del método. Si llamas al mismo método, pero en pasos separados, el contexto es el ámbito global (ventana) en su lugar:
var f = object.property;
f();
Cuando obtienes la referencia de un método, ya no está unido al objeto, es sólo una referencia a una función simple. Lo mismo ocurre cuando se obtiene la referencia para usarla como callback:
this.saveNextLevelData(this.setAll);
Ahí es donde se vincularía el contexto a la función:
this.saveNextLevelData(this.setAll.bind(this));
Si estás usando jQuery deberías usar el método $.proxy
en su lugar, ya que bind
no está soportado en todos los navegadores:
this.saveNextLevelData($.proxy(this.setAll, this));
El término "contexto" se utiliza a veces para referirse al objeto referenciado por this. Su uso es inapropiado porque no encaja ni semántica ni técnicamente con ECMAScript's this.
"Contexto" significa las circunstancias que rodean algo que añade significado, o alguna información anterior y posterior que da un significado extra. El término "contexto" se utiliza en ECMAScript para referirse al contexto de ejecución, que son todos los parámetros, el alcance y esto dentro del ámbito de algún código en ejecución.
Esto se muestra en ECMA-262 sección 10.4.2:
Establecer el ThisBinding al mismo valor que el ThisBinding del contexto de ejecución que llama
lo que indica claramente que esto es parte de un contexto de ejecución.
Un contexto de ejecución proporciona la información circundante que añade significado al código que se está ejecutando. Incluye mucha más información que sólo el thisBinding.
Así que el valor de this no es "contexto", es sólo una parte de un contexto de ejecución. Es esencialmente una variable local que puede ser establecida por la llamada a cualquier objeto y en modo estricto, a cualquier valor.