Eu tenho uma função de construtor que regista um manipulador de eventos:
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);
No entanto, I'não consigo acessar a propriedade dados
do objeto criado dentro do callback. Parece que isso
não se refere ao objeto que foi criado, mas a um outro.
Eu também tentei usar um método de objeto em vez de uma função anônima:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', this.alert);
}
MyConstructor.prototype.alert = function() {
alert(this.name);
};
mas apresenta os mesmos problemas.
Como posso aceder ao objecto correcto?
Esta (aka "o contexto") é uma palavra-chave especial dentro de cada função e seu valor depende apenas de como a função foi chamada, não de como/quando/onde ela foi definida. Ela não é afetada por escopos léxicos como outras variáveis (exceto para funções de seta, veja abaixo). Aqui estão alguns exemplos:
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 saber mais sobre 'isto', dê uma olhada na documentação MDN.
Na verdade, você não quer acessar "isto" em particular, mas o objeto a que ele se refere. É por isso que uma solução fácil é simplesmente criar uma nova variável que também se refira a esse objeto. A variável pode ter qualquer nome, mas as mais comuns são a si mesmo
e aquele
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Como self
é uma variável normal, obedece a regras de escopo léxico e é acessível dentro do callback. Isto também tem a vantagem de que você pode acessar o valor "este" da própria callback.
Pode parecer que você não tem controle sobre o valor do "isto" porque seu valor é definido automaticamente, mas na verdade não é esse o caso.
Toda função tem o método [.bind
[docs]][bind], que retorna uma nova função com isso
vinculado a um valor. A função tem exatamente o mesmo comportamento que a que você chamou de .bind', apenas que
isto' foi definido por você. Não importa como ou quando essa função é chamada, `this' sempre se referirá ao valor passado.
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);
}
Neste caso, estamos vinculando a chamada de retorno ao valor do "Meu Construtor".
Nota: Ao ligar o contexto para jQuery, utilize jQuery.proxy
[docs] em vez disso. A razão para fazer isso é para que você não precise armazenar a referência para a função ao desvincular uma chamada de retorno do evento. jQuery lida com isso internamente.
ECMAScript 6 introduz funções de seta, que podem ser pensadas como funções lambda. Eles não têm a sua própria "ligação". Em vez disso, "isto" é visto no escopo como uma variável normal. Isso significa que você não tem que chamar `.bind'. Esse não é o único comportamento especial que eles têm, por favor consulte a documentação MDN para mais informações.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
isto
da chamada de retorno - parte 2Algumas funções/métodos que aceitam callbacks também aceitam um valor para o qual o isto
deve se referir. Isto é basicamente o mesmo que ligá-lo você mesmo, mas a função/método faz isso por você. [Array#map
[docs]][map] é um método desse tipo. A sua assinatura é:
array.map(callback[, thisArg])
O primeiro argumento é a chamada de retorno e o segundo argumento é o valor a que "este" deve se referir. Aqui está um exemplo:
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: Se você pode ou não passar um valor para "isto" é normalmente mencionado na documentação dessa função/método. Por exemplo, jQuery's $.ajax
method [docs] descreve uma opção chamada context
:
Este objeto será feito o contexto de todos os callbacks relacionados ao Ajax.
Outra manifestação comum deste problema é quando um método de objeto é usado como um método de callback/event handler. Funções são cidadãos de primeira classe em JavaScript e o termo "método" é apenas um termo coloquial para uma função que é um valor de propriedade de um objeto. Mas essa função não tem uma ligação específica com seu objeto "contendo".
Considere o seguinte exemplo:
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
A função 'this.method' é atribuída como manipulador de eventos de clique, mas se o document.body' for clicado, o valor registrado será definido, porque dentro do manipulador de eventos,
this' refere-se ao document.body', não à instância do
Foo'.
Como já mencionado no início, o que "isto" se refere depende de como a função é chamada, não como é **definida***.
Se o código fosse como o seguinte, poderia ser mais óbvio que a função não tem uma referência implícita ao objeto:
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
A solução é a mesma que a acima mencionada: Se disponível, utilize .bind' para vincular explicitamente
isto' a um valor específico
document.body.onclick = this.method.bind(this);
ou chamar explicitamente a função como um "método" do objeto, utilizando uma função anônima como um "callback / event handler" e atribuir o objeto (isto) a outra variável:
var self = this;
document.body.onclick = function() {
self.method();
};
ou use uma função de seta:
document.body.onclick = () => this.method();
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Está tudo na sintaxe "mágica" de chamar um método:
object.property();
Quando você pegar a propriedade do objeto e chamá-la de uma só vez, o objeto será o contexto para o método. Se você chamar o mesmo método, mas em etapas separadas, o contexto é o escopo global (janela):
var f = object.property;
f();
Quando você obtém a referência de um método, ele não está mais preso ao objeto, é apenas uma referência a uma função simples. O mesmo acontece quando você obtém a referência para usar como uma chamada de retorno:
this.saveNextLevelData(this.setAll);
É aí que você ligaria o contexto à função:
this.saveNextLevelData(this.setAll.bind(this));
Se você estiver utilizando jQuery você deve utilizar o método $.proxy
, pois o bind
não é suportado em todos os navegadores:
this.saveNextLevelData($.proxy(this.setAll, this));
O termo "contexto" é às vezes usado para se referir ao objeto referenciado por isto. O seu uso é inapropriado porque não se encaixa nem semanticamente nem tecnicamente com ECMAScript's this.
"Contexto" significa as circunstâncias que envolvem algo que acrescenta significado, ou alguma informação anterior e posterior que dá significado extra. O termo "contexto" é usado no ECMAScript para se referir a contexto de execução, que são todos os parâmetros, escopo e isto dentro do escopo de algum código de execução.
Isto é mostrado em ECMA-262 seção 10.4.2:
Ajuste o ThisBinding para o mesmo valor que o ThisBinding do contexto de execução de chamadas
o que indica claramente que isso faz parte de um contexto de execução.
Um contexto de execução fornece a informação envolvente que acrescenta significado ao código que está sendo executado. Ele inclui muito mais informações do que apenas thisBinding.
Então o valor de isso não é "contexto", é apenas uma parte de um contexto de execução. É essencialmente uma variável local que pode ser definida pela chamada para qualquer objeto e em modo estrito, para qualquer valor.