Uma prática muito comum no desenvolvimento de componentes usando Backbone.js é o uso de uma função chamada _.bindAll
. Esta função faz parte da biblioteca Underscore.js que é uma dependência obrigatória do Backbone.js assim como o jQuery também é.
Quero inicialmente demonstrar com exemplos as dificuldades que existem de se utilizar a palavra reservada this
em um componente JavaScript. Em seguida mostrarei soluções e gradativamente iremos evoluindo até você chegar em um cenário onde usamos o this
e _.bindAll
em um componente Backbone.
O uso do this e seus problemas
Na maioria das linguagens que suportam orientação a objetos é comum o uso do this
para referenciar o próprio objeto. No caso do JavaScript também é possível usar o this
, no entanto é necessário um cuidado extra por que seu comportamento é diferente se comparado com as demais linguagens. Para ficar mais simples prefiro demonstrar no código os desafios de se utilizar o this
em um projeto JavaScript.
(function () {
const hello = {
goodMorning: function () {
console.log('Sample #1: Hello, good morning!');
console.log(this); // Window
},
};
setTimeout(hello.goodMorning);
})();
Se você executar o código acima no seu navegador vai constatar que o this
é o objeto Window e não o objeto hello como você estava esperando. Agora quero demonstrar um segundo exemplo onde vamos escrever nosso código de uma forma que seja possível imprimir o this
corretamente, ou seja, deve imprimir algo próximo à Object { goodMorningWrapper }
.
(function () {
const hello = {
goodMorningWrapper: function () {
const self = this;
const goodMorning = function () {
console.log('Sample #2: Hello, good morning!');
console.log(this); // Object { goodMorningWrapper: ... }
};
return function () {
goodMorning.apply(self);
};
},
};
setTimeout(hello.goodMorningWrapper());
})();
Se você executar esta nova versão do objeto hello vai verificar que desta vez o this
está apontando corretamente para o próprio objeto ao invés de apontar para o objeto Window. Sucesso!
Só me deixe fazer uma pergunta. Você gostou deste código? É fácil de entender? Eu não gostei!
Antes de falarmos sobre como o underscores pode ser útil para resolver este problema quero mostrar um terceiro exemplo. Nem sempre trabalho em projetos que utilizam alguma biblioteca como Underscores e Backbone. Nestes casos pode acontecer de eu precisar escrever código JavaScript puro e usando objetos. Para meus objetos não ficarem complexos como no exemplo anterior procuro criar objetos seguindo uma organização parecida com a deste próximo exemplo.
(function () {
const Hello = function () {
const self = this;
self.goodMorning = function () {
console.log('Sample #3: Hello, good morning!');
console.log(self); // Object { goodMorning: ... }
};
};
const hello = new Hello();
setTimeout(hello.goodMorning);
})();
Nada muito complicado. No momento da criação do objeto a primeira ação é criar uma propriedade chamada self
para armazenar o valor do this
. Daí em diante eu esqueço que o this
existe para preferir usar sempre o self
quando preciso referenciar o próprio objeto. Até hoje essa solução tem funcionado pra mim.
Agora que você entendeu as dificuldades de se trabalhar com o this
nos nossos objetos em JavaScript bora dar um próximo passo. A biblioteca underscore possui algumas funções úteis que nos darão mais opções de usar o this
de uma forma facil e agradável.
Function _.bind()
O Underscore.js é uma das dependências obrigatórias para se utilizar Backbone. É exatamente esta a biblioteca que vamos explorar para encontrar uma solução para tornar mais simples o uso do this
em nossos objetos JavaScript.
A primeira função útil que vou apresentar é o _.bind()
. Basicamente você deve passar primeiro como parâmetro uma função e no segundo parâmetro um objeto que deverá atuar como sendo o this
da funçao. O retorno é uma função que caso seja executada vai preservar corretamente o this
e evitar hacks como aqueles que criamos anteriormente.
(function () {
const Hello = function () {
this.goodMorning = function () {
console.log('Sample #4: Hello, good morning!');
console.log(this); // Object { goodMorning: ... }
};
};
const hello = new Hello();
hello.goodMorning = _.bind(hello.goodMorning, hello);
setTimeout(hello.goodMorning);
})();
Com o uso do _.bind
a classe Hello
ficou menor e não precisamos criar nenhum hack. Só não podemos esquecer que após criar o objeto será necessário chamar a função _.bind
para cada método existente em nossa classe. Mas ainda tem como melhorar – o próximo passo agora é entendermos um pouco sobre a função _.bindAll
na próxima seção.
Function _.bindAll()
O uso do _.bindAll
é na minha opinião a alternativa que parece mais interessante de se utilizar junto com o Backbone. A função recebe por parâmetro um objeto e em seguida os próximos parâmetros são strings contendo o nome dos métodos onde você deseja aplicar o bind de forma permanente. Vamos para mais um exemplo.
(function () {
const Hello = function () {
this.goodMorning = function () {
console.log('Sample #5: Hello, good morning!');
console.log(this); // Object { goodMorning: ... }
};
};
const hello = new Hello();
_.bindAll(hello, 'goodMorning');
setTimeout(hello.goodMorning);
})();
Se por um acaso você adicionasse o método goodNight
na classe Hello
bastaria editar o _.bindAll
adicionando um terceiro parâmetro com o nome do novo método.
_.bindAll(hello, 'goodMorning', 'goodNight');
Agora que você me acompanhou em todos estes exemplos de código resolvendo o uso do this
manualmente e também utilizando o Underscore.js é hora de ver como seria uma forma comum de se resolver isso no dia a dia programando com o Backbone.
Exemplo #1: Usando _.bindAll no Backbone.Model
Finalmente agora vou mostrar como usualmente utilizam o this
e o bind
em um componente criado utilizando Backbone. Abaixo vou demonstrar uma classe similar aos exemplos anteriores, no entanto desta vez a classe Hello
extende Backbone.Model
.
Um ponto importante para observar é que no Backbone.js o método initialize
atua como um construtor, ou seja, será executado sempre que um novo objeto for criado à partir desta classe.
(function () {
const Hello = Backbone.Model.extend({
initialize: function () {
_.bindAll(this, 'goodMorning');
},
goodMorning: function () {
console.log('Backbone.Model Sample #1: Hello, good morning!');
console.log(this); // Object { goodMorning: ... }
},
});
const hello = new Hello();
setTimeout(hello.goodMorning);
})();
O método initialize
em nosso caso é o local ideal para você chamar a função _.bindAll
. Lembrese que a função suporta realizar o bind de muitos métodos em uma única chamada. No entanto evite abusar deste recurso, caso contrário seu objeto poderá sentir algum impacto na performance e consumo de memória.
Faça download dos exemplos no GitHub
Se você acompanhou este artigo até aqui e gostaria de acessar o código fonte apresentado, tenho boas notícias. Disponibilizei no GitHub um projeto para você acessar e testar o conteúdo apresentado neste artigo.
Conclusão
Procurei demonstrar neste artigo alguns cenários onde o this
não funciona conforme o esperado. Aos poucos evolui os exemplos para mostrar possíveis soluções para fazer com que o this
fosse resolvido corretamente.
Descobrimos neste artigo como o Underscore.js pode ser utilizado para fazer os binds utilizando as funções _.bind
e _.bindAll
.
Como gosto de escrever sobre Backbone.js neste blog aproveitei para mostrar como combinar o _.bindAll
com um objeto criado usando Backbone. Talvez este foi o ponto mais importante do artigo, pois no dia a dia de um projeto Backbone.js essa é a solução mais utilizadas pelos programadores.