Continuando os estudos com Javascript, diversas coisas interessantes, que pareciam acessíveis apenas aos programadores de Ruby, Perl e outras linguagens mais flexíveis podem ser feitas também na linguagem. O problema é que, normalmente, é difícil de usar isso em bibliotecas que já existem, principalmente porque a maioria dos códigos em Javascript ou não seguem boas-práticas ou simplesmente são escritos de forma diferente do que vou apresentar aqui, porém vale pela dica. Um exemplo bem simples, pensando na seguinte API:
objeto = { valor: 10, incrementar: function(v) { if(!v) v = 1; valor += v; }, raizQuadrada: function() { Math.sqrt(incrementar(4)); } }
Para implementá-la, em Javascript, há várias formas. Simulando o comportamento que eu fiz no post passado, é possível usar o seguinte formato:
function createObjeto() { var valor = 10; var getValor = function() { return valor; } var incrementar = function(v) { if(!v) v = 1; valor += v; return valor; }; var raizQuadrada = function() { return Math.sqrt(incrementar(6)); }; var O = new Function(); O.prototype.<strong>defineGetter</strong>('valor', getValor); O.prototype.incrementar = incrementar; O.prototype.raizQuadrada = raizQuadrada; return new O(); }
Ok, agora vamos ver uma coisa realmente interessante: digamos que, por algum motivo estranho, alguém reimplemente o método “incrementar”. Monkey-patch total. Em Ruby, por exemplo, se reimplementarmos o atributo “valor” para ele retornar uma string, a função “raizQuadrada” simplesmente deixa de funcionar. Já em Javascript:
var objeto = createObjeto(); objeto.<strong>defineGetter</strong>('valor', function() { return 'algo'; }); objeto.raizQuadrada(); //retorna 4 (sqrt(10 + 6)).
Ok, como ele retornou “4”, sendo que eu mudei o “getter” do object? O segredo está na linha:
incrementar: function(v) { if(!v) v = 1; valor += v; //<-- Aqui é o segredo. },
Por quê? Porque estamos acessando a variável “valor” direto, sem usar o “getter”. Ah, então, se fizermos “monkey-patch” do “incrementar”, as coisas falham?
var objeto = createObjeto(); objeto.incrementar = function(v) { if(!v) v = 1; valor += v; return valor; }; objeto.raizQuadrada(); //retorna 4, de novo.
Por quê? O segredo, novamente, está na linha que chama a função “incrementar”, da função raizQuadrada. Porque, na verdade, estamos chamando a função que está na variável “incrementar” definida no “createObject”, não a função “incrementar” definda dentro do “objeto”. Isto, como é uma closure, mantém os bindings da função “createObjeto”. Logo, é uma maneira de fazer um “monkey-patch” seguro, em Javascript, aproveitando-se do fato de que as funções (function) em Javascript criam, na verdade, closures e dessa forma mantém o binding anterior.