Funções em Diferentes Escopos
Se você programa em JavaScript orientado a objeto (1, 2, 3), provavelmente já teve alguns problemas com funções que deveriam ser executadas em um determinado escopo. Nesse post vou explicar como executar e vincular funções ao escopo de algum objeto.
Executando
Para chamar um método no escopo de outro objeto, utilizamos o método Function.apply. O objeto Function pode ser qualquer função que será chamada no escopo que deve ser passado como primeiro parâmetro. Vamos a um exemplo:
var Class = function() {
this.text = "Escopo da Classe";
}
Class = new Class();
var showText = function() {
alert(this.text);
}
// Escopo Global
showText(); // undefined
// Escopo de Class
showText.apply(Class); // "Escopo da Classe"
O resultado da primeira chamada a função é obvio: não há this.text definido. Quando aplicamos a mesma função no escopo de Class, o retorno sai como esperado.
O método Function.apply também pode receber um segundo parâmetro, contendo os parãmetros que devem ser passados à função. Esse parâmetro deve ser obrigatoriamente um array, e deve ser definido na ordem em que os parâmetros precisam ser recebidos.
var Class = function() {
this.text = "Também podemos passar parâmetros:";
}
var Class = new Class();
var showText = function(text) {
alert(this.text + " " + text);
}
showText(); // undefined undefined
showText.apply(Class, ["Viva!"]); // "Também podemos passar parâmetros: Viva!"
Também há o método Function.call, que tem a mesma função de Function.aplly. A diferença está em como os parâmetros para a função são definidos. Em Function.apply, eles são passados como um array, e em Function.call são passados como vários argumentos, sem a necessidade de estarem dentro de algum array.
var Class = function() {
this.text = "Também podemos passar parâmetros:";
}
var Class = new Class();
var showText = function(text) {
alert(this.text + " " + text);
}
showText(); // undefined undefined
showText.call(Class, "Eba!"); // "Também podemos passar parâmetros: Eba!"
Vinculando
O Function.apply resolve os problemas de muita gente, mas não todos os problemas. Imagine a seguinte situação (pela qual eu também já passei), onde precisamos recuperar uma variável da classe de dentro do objeto XMLHttpRequest:
function() {
this.msgToShow = "Completo!";
XMLHttpRequest.onreadystatechange = show;
}
var show = function() {
alert(this.msgToShow);
}
E agora? Precisamos executar a função show no escopo da classe, e não no escopo do XMLHttpRequest. Poderíamos usar o Function.apply, mas ele executaria a função assim que fosse chamado. A solução é vincular a função ao escopo do objeto XMLHttpRequest. A solução mais comum é o uso de closures:
function() {
var self = this;
this.msgToShow = "Completo!";
XMLHttpRequest.onreadystatechange = function() {
show.apply(self);
}
}
var show = function() {
alert(this.msgToShow);
}
Nos aproveitamos da capacidade do JavaScript de compartilhar variáveis entre funções, e armazenamos o escopo correto na variável self, que utilizamos para aplicar a função.
Reutilização
Tudo funcionando dentro do esperado, mas não estamos pensando na parte mais bela da programação orientada a objetos: a reutilização. Teríamos que criar closures toda vez em que uma execução em outro escopo fosse necessária. Pra isso existe o método Function.bind, criado para vincular a função a qualquer escopo. A função é a seguinte:
Function.prototype.bind = function() {
var self = this, args = $A(arguments), scope = args.shift();
return function() {
return self.apply(scope, args.concat($A(arguments)));
}
}
var $A = function(object) {
var array = [];
for(var i = 0; i < object.length; i++)
array[i] = object[i];
return array;
}
Esse método cria o closure e retorna uma função que executa o que for necessário. A função $A só serve para transformar os argumentos em um array. Modificando nosso script anterior:
function() {
this.msgToShow = "Completo!";
XMLHttpRequest.onreadystatechange = show.bind(this);
}
var show = function() {
alert(this.msgToShow);
}
Quem não trabalha com POO, eventos ou timeouts talvez não tenha se deparado com nenhum problema deste tipo, mas duvido que alguém desse ramo nunca tenha recebido um “undefined” sem querer…

Esse bind eu desconhecia, muito boa dica. XD
Valeu Julio,
Bom retorno
Excelente Julio!
Obrigado, pessoal. Em breve posto mais sobre escopo (eu acho), caso interesse.
Muito bom Júlio, vale lembrar que existe o método call, em que os parâmetros são passados como parâmetros mesmo e não em um Array.
Quem é vivo sempre aparece.. haha excelente post, muito bem explicado, detalhado e objetivo :D
Julio, estou escrevendo também para pedir uma opiniao sua como profissional, pode responder por email se quiser, sobre o seguinte site que estou desenvolvendo com um amigo:
http://www.yoomp.com
Uma frase ou 2 me bastam.. heheh soh um feedback mesmo :D
Se nao for incomodar claro.
Abraço e Feliz Natal
@Bernardo: sim, já estou atualizando o artigo, me passou despercebido. Valeu pelo lembrete.
@Rodrigo: tá certo, te respondo via e-mail.