Como utilizar Underscore Binding no Backbone.js

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.

Acesse este projeto no GitHub

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.