remiendos js: apañando métodos

Cómo sobrescribir métodos en javaScript

Florin Ciulache

archivado en: JavaScript / 22 septiembre, 2016

Para Borja y Chele, 301 facts

En general es de rigor programar de la mejor forma posible, atendiendo a criterios de calidad, de forma escalable y mantenible, pero también es cierto que a veces se debe resolver un problema en muy poco tiempo y en condiciones un tanto extremas: son las tantas de la madrugada, en tres horas hay que entregar, no sientes ya ni los dedos de la mano y hay pasajes del código escalofriantes que, por supuesto, nunca ha picado uno mismo. Es entonces cuando surge de entre las tazas de café y los restos de un tele-algo el genio de la programación orientada a entregas para concederte una bula sobre cualquier burrada que hagas hasta que pase el apuro y solo hasta entonces, pues los remiendos de esta naturaleza deben ser provisionales si no queremos que la aplicación termine en desastre.

Un truco habitual en estas situaciones es añadir y quitar métodos al vuelo en objetos que por la razón que sea no podemos tocar directamente. En el caso de los objetos construidos mediante notación de literales, hacer esto es tan sencillo como añadir el método que necesitemos. Por ejemplo:

const awesomePlugin = {

foo: function() {

console.log('Foo');

}

};

awesomePlugin.foobar = function() {

console.log('Bazinga!');

/* El this hace referencia al objeto */

this.foo();

};

awesomePlugin.foobar();

Y si podemos añadir métodos, también podemos modificarlos de manera inmisericorde:

awesomePlugin.foo = function() {

console.log('Uka Lele!');

}

En los objetos con función constructora también podemos añadir métodos con facilidad. Basta con prototiparlo:

const AwesomePlugin = function() {

this.foo = function() {

console.log('Foo');

};

};

AwesomePlugin.prototype.foobar = function() {

console.log('Bazinga!');

this.foo();

};

let awesome = new AwesomePlugin();

awesome.foobar();

Para cambiar el comportamiento hay varias maneras. Lo más sencillo es atacar directamente a la instancia:

let awesome = new AwesomePlugin();

awesome.foo = function() {

console.log('Uka Lele');

};

Claro que todo esto lo podemos hacer si objeto no está protegido por una closure, pues en javaScript las cosas nacen y mueren dentro del par de llaves en el que se encuentran. Por ejemplo, así no habría forma de atacar al objeto (de ahí que sea recomendable en muchas construcciones):

(function() {

const awesomePlugin = {

....

};

})();

/* Uncaught ReferenceError: awesomePlugin is not defined */

awesomePlugin.foobar = function() {

Si podemos tocar el código, para solucionar esto no queda otra que romper la closure. Por ejemplo:

hackingAwesome = (function() {

const awesomePlugin = {

foo: function() {

console.log('Foo');

}

};

return awesomePlugin;

})();

hackingAwesome.foobar = function() {

/// ...

}

hackingAwesome.foobar();

La forma chula: override

Hay formas más elegantes de sobrescribir métodos sin alterar el objeto original, una técnica que en otros lenguajes de programación se denomina override, que es la correcta.

En los objetos mediante notación de literales, en teoría, debería bastar con copiar el objeto y manipular solo la copia. Algo así:

const awesomePlugin = {

foo: function() {

console.log('Foo');

}

};

hackingAwesome = awesomePlugin;

hackingAwesome.foo = function() {

console.log('Bar');

};

/* Bar oO! */

awesomePlugin.foo();

Sin embargo esto no funcionaría por cómo gestiona js los punteros en arrays y objetos, que provoca que cualquier cambio en la copia afecte al original, así que debemos realizar lo que se conoce como copia profunda (copy deep). La manera más sencilla que conozco, si el objeto a copiar no tiene métodos, es convertirlo  en una cadena y luego reparsearla, una operación que sirve para romper el «enlace» entre ambos.

hackingAwesome = JSON.parse (JSON.stringify(awesomePlugin) );

Si el objeto a copiar tiene métodos, entonces hay que hacer algo así:

function clone(obj) {

let objCopy = {};

for (let key in obj) {

if ( obj.hasOwnProperty(key) ) {

objCopy[key] = obj[key];

}

}

return objCopy;

}

const hackingAwesome = clone(awesomePlugin);

Para funciones u objetos con función constructora, la operación es más elegante, pues basta con cambiar el método en la clase que hereda:

const AwesomePlugin = function() {};

AwesomePlugin.prototype.foo = function() {

console.log('Foo');

};

AwesomePlugin.prototype.bar = function() {

console.log('Bar');

};

/* Creamos una clase que herede de la que queremos hackear */

const HackingAwesome = function() {};

/* Establecemos su prototipo en la clase madre */

HackingAwesome.prototype = new AwesomePlugin();

/* Sobrescribimos foo (ojo al uso de prototype. Esto no funcionaría:  HackingAwesome.foo */

HackingAwesome.prototype.foo = function() {

console.log('Uka Lele');

}

let hackingAwesome = new HackingAwesome();

hackingAwesome.foo(); // Uka Lele

hackingAwesome.bar(); // Bar

let awesomePlugin = new AwesomePlugin();

awesomePlugin.foo(); // Foo

Vale, pero ¿qué ocurre si necesitamos conservar la funcionalidad original? Es decir, ¿cómo hacemos que un método haga lo de antes, pero también más cosas?

Si el método sobrescrito no hace nada internamente, basta con cachearlo y lanzarlo después de que hagamos lo nuestro.

HackingAwesome.prototype.foo = function() {

/* Sacamos una instancia de la clase madre */

let awesomePluginTemp = new AwesomePlugin();

/* Hacemos nuestra jugada */

console.log('Uka Lele');

/* Lanzamos el método original */

awesomePluginTemp.foo();

};

call() y apply()

La cosa se complica una miaja si el metodo sobrescrito interactúa con el resto de los miembros de la clase, como en este caso:

var AwesomePlugin = function() {

this.fooProperty = '';

};

AwesomePlugin.prototype.foo = function(_fooProperty_) {

this.fooProperty = _fooProperty_ + ' original Foo';

console.log( this.fooProperty);

};

En teoría, se podría repetir la jugada anterior.

HackingAwesome.prototype.foo = function(_fooProperty_) {

let awesomePluginTemp = new AwesomePlugin();

this.fooProperty = _fooProperty_ + ' new Foo';

console.log( this.fooProperty);

awesomePluginTemp.foo(_fooProperty_);

}

Pero el problema es que estaríamos duplicando código, que en este caso da igual porque solo es una línea, pero si el método fuera más complejo entraríamos en terreno pantanoso.

Por fortuna, en estos casos, para extender la funcionalidad de los métodos heredados podemos jugar con el método call() -con el que se cambia el valor de this, que pasa ser el que indiquemos en su primer parámetro- y el método apply(), que es muy parecido, pero además permite pasar un array de argumentos.

HackingAwesome.prototype.foo = function(_fooProperty_) {

let awesomePluginTemp = new AwesomePlugin();

awesomePluginTemp.foo.apply(this, arguments);

this.fooProperty = _fooProperty_ + ' new Foo';

console.log( this.fooProperty);

}

// bar original Foo

// bar new Foo

Otro ejemplo para terminar de ver las posibilidades de call() y apply(), imaginemos un caso donde se devuelve un valor transformado en función de otro miembro de la clase. Algo así:

var AwesomePlugin = function() {

this.fooProperty = 10;

};

AwesomePlugin.prototype.foo = function(_fooProperty_) {

let value = this.fooProperty + _fooProperty_;

return value;

};

Queremos que foo siga haciendo lo mismo, peeeeero en lugar de sumar los 10 que vale la propiedad fooProperty original, queremos que sume la propiedad de nuestro objeto nuevo.

const HackingAwesome = function() {

this.fooProperty = 5;

};

HackingAwesome.prototype = new AwesomePlugin();

HackingAwesome.prototype.foo = function(_fooProperty_) {

let awesomePluginTemp = new AwesomePlugin();

let originalFoo = awesomePluginTemp.foo.apply(this, arguments);

return originalFoo;

}

let hackingAwesome = new HackingAwesome();

console.log(hackingAwesome.foo(5)); // 10 (no 15)

Se pueden hacer más jugadas de este tipo para sobrescribir métodos en javaScript, pero espero que con lo expuesto valga como introducción.

|| Tags: ,

valoración de los lectores sobre remiendos js: apañando métodos

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 5 sobre 5 (1 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

Aportar un comentario


*