1 .
El cuento del gato
2 .
El prototipado
El cuento del gato fue originalmente contado por Thomas Brattli en su
tutorial Building a Crossbrowser
DHTML library. Siempre he encontrado en él una manera estupenda de
mostrar cómo se programan objetos en JavaScript y en general cómo funciona la
programación orientada a objetos, y por supuesto, cada vez que lo he contado,
lo he hecho de una manera diferente, he ido añadiendo y quitando cosas del
"cuento" original, y cada vez que lo he contado, me han quedado las ganas de
escribirlo.
Recientemente en el WEB-ES hemos tenido un pequeño jaleo con objetos,
métodos y propiedades, así que pensé que si no lo escribía ahora no lo haría
nunca... Así que allá va. Si a alguien le parece que puede añadirse o quitarse
algo, o que no queda correctamente explicado, ¡escribeme!
El cuento del gato
Para construir un objeto gato en nuestro código javascript sólo tenemos
que hacer una simple función:
function Gato() {
return this;
}
Ya tendríamos un objeto gato en nuestro código, y sólo faltaría
instanciarlo:
var Roger= new Gato();
El gato vacío, sin propiedades de ningún tipo, no nos sirve de mucho. De
modo que vamos a darle una propiedad, que será su número de vidas. Es un gato
a la española, o sea que tendrá siete vidas en vez de nueve. también le daremos
un sonido para cuando maúlle.
function Gato() {
this.vidas=7;
this.sonido="Miau!!!!";
return this;
}
De esta manera, habiendo usado el constructor anterior y teniendo un gato
instanciado llamado Roger, podíamos decir
alert(Roger.sonido);
Y el resultado sería evidentemente
éste.
Esto está muy bien cuando todas las instancias del objeto van a tener una
propiedad con el mismo valor. Pero supongamos que queremos hacer gatos con
distintos colores (lo que sería lo normal). Para ello podríamos pasarle el valor
de una propiedad con un parámetro convencional. Algo como esto:
function Gato(color) {
this.vidas=7;
this.sonido="Miau!!!!";
this.color=color;
return this;
}
var Roger = new Gato("marron");
De esta manera nuestro gato Roger sería marrón.
Y otra vez estamos en las mismas: Tener un objeto que sólo tiene
propiedades, puede llegar a ser útil, pero lo será mucho más si tiene métodos.
Éstos le permitirán hacer cosas. ¿Cómo se hace un método? Un método es una
función que se "endosa" al objeto y que hereda sus propiedades. Vamos a
permitir que Roger maúlle:
function Gato(color) {
this.vidas=7;
this.sonido="Miau!!!!";
this.color=color;
this.maulla=Maulla; //Fíjate en las mayúsculas, y también
//en que Maulla no lleva paréntesis.
return this;
}
function Maulla() {
alert(this.sonido);
}
var Roger = new Gato("marron");
Roger.maulla();
Como luego quiero demostrar otra cosa, y además quiero complicar esto un
poquito, vamos a darle otro método, que será el de arañar. Es más complicado:
este método recibirá un parámetro, el cual será otro gato. Lo que hará este
método es quitar una vida a otro gato, esto es, accederá a la propiedad vidas
del otro gato y restará 1.
function Gato(color) {
//objeto gato.
//propiedades
this.vidas=7;
this.sonido="Miau!!!!";
this.color=color;
//metodos
this.maulla=Maulla;
this.arania=Arania;
return this;
}
function Maulla() {
//Metodo del objeto Gato
alert(this.sonido);
}
function Arania(aQuien) {
//Metodo del objeto Gato
aQuien.vidas-=1;
}
var Roger = new Gato("marron");
var Michael = new Gato("amarillo");
Roger.arania(Michael);
alert(Michael.vidas);
Como habrás imaginado despues de esto, el pobre Michael sólo tendrá 6
vidas. Todavía tiene muchas, pero tampoco es como para alegrarse.
Te habrás fijado en que he colocado comentarios que dícen qué son
métodos, qué propiedades, qué funciones son objetos y cuales métodos. Esto es
así por que nuestro código comienza a hacerse grande. Es muy útil que hagas
esto, cuando tengas un código de cientos de líneas, por mucho que lo conozcas,
te acabarás perdiendo si no lo haces.
Y de nuevo estamos en las mismas. Nuestros gatos son ciegos y sordos.
Quiero decir que, aunque nosotros sepamos que un gato maúlla, porque vemos el
cartelito del alert, los otros gatos nunca sabrán de ninguna manera que
otro gato ha maullado.¿Como hacemos para que cuando el gato maúlle o arañe los
otros se enteren? A través de eventos. Se hacen añadiendo un método distinto...
para el que no hace falta una función aparte. Será mejor si lo ves en el
ejemplo:
function Gato(color) {
//objeto gato.
//propiedades
this.vidas=7;
this.sonido="Miau!!!!";
this.color=color;
//metodos
this.maulla=Maulla;
this.arania=Arania;
//eventos
this.onArania=new Function();
this.onMaulla=new Function();
return this;
}
function Maulla() {
//Metodo del objeto Gato
alert(this.sonido);
this.onMaulla();
}
function Arania(aQuien) {
//Metodo del objeto Gato
aQuien.vidas-=1;
this.onArania();
}
¡Es muy importante que al llamar al método que dispara el evento se llame
al evento! De otra manera no servirá de nada. ¿cómo se usa esto? Bien, vamos a
hacer que Michael tenga muy mala leche, y que cada vez que Roger maúlle, Michael
lo arañe. Claro, el pobre Roger no es de piedra, y el pobre animalito, cuando
es arañado, vuelve a maullar. Y claro, cuando Michael acaba con sus vidas,
Roger muere (el objeto desaparece). Veamoslo, suponiendo el código anterior:
var Roger=new Gato("marron");
var Michael=new Gato("amarillo");
function RogerEsAraniado() {
if (Roger.vidas>0) Roger.maulla();
else Roger=null;
}
Roger.onMaulla=Michael.arania(Roger);
Michael.onArania=RogerEsAraniado();
Roger.maulla();
Verás que hay dos modos de activar el evento: o bien se escribe
directamente el código correspondiente
(Roger.onMaulla) o bien se llama a una función
exterior pensada para manejar el evento
(RogerEsAraniado) Las dos maneras son igual de
válidas, pero deberás decantarte por la segunda si tienes que hacer
condicionales, o, en general, cualquier cosa que exceda de una sola línea.
Finalmente ya has visto como destruir el objeto: basta con decir
objeto=null. También puedes incluir un
condicional dentro de la propia función que hace de objeto, incluyendo la línea:
if (lo que sea) this=null;
Y esa es la triiiiiiste historia del gato.
Tiene muchas aplicaciones, en realidad. Es una manera completamente
distinta a la habitual en la que tienes un montón de funciones separadas que
hacen cosas independientemente. Es una forma organizada y estructurada de
trabajar, que te evitará muchos fallos y vicios de programación. Hay quien dice
que es así como se debería de programar. Yo no creo en que deba hacerse así,
pero siempre que se pueda, para un script complejo, creo que es una manera
clara y útil de hacerlo.
Si quieres ver un ejemplo práctico de cómo llevar a cabo todo esto, puedes
ver cómo he hecho la
librería
y verás que el resultado es mucho más claro que si hubiese habido 200 lineas
de funciones independientes que no se comunicasen entre sí de ninguna manera.
¡Un abrazo!
El prototipado
Amablemente, me ha escrito Jaime Maimó del WEB-ES
para completar en este minitutorial la parte del prototipado, que como vereis,
puede ser muy útil a la hora de modularizar el código. Este es el mensaje que
me ha enviado:
Esto del prototipado tiene la ventaja de que puedes ir definiendo funciones y
añadiendolas como métodos de objetos, ya sean tuyos, o sean objetos "intrínsecos".
Un ejemplillo:
function paralelepipedo (x,y,z) {
this.x=x;
this.y=y;
this.z=z;
}
Esta sería la función que crearía el/los objeto/s "paralelepipedo".
Ahora podremos añadir los métodos que queramos, sin tener que tocar para
nada la función original, creando unas funciones independientes:
function variarEjeX(valor) {
this.x+=valor;
}
function variarEjeY(valor) {
this.y+=valor;
}
function variarEjeZ(valor) {
this.z+=valor;
}
function volumen() {
return this.x*this.y*this.x;
}
Y asociándolas a la función constructora, según:
paralelepipedo.prototype.nuevoX=variarEjeX;
paralelepipedo.prototype.nuevoY=variarEjeY;
paralelepipedo.prototype.nuevoZ=variarEjeZ;
paralelepipedo.prototype.cubicaje=volumen;
De este modo podremos manipular el objeto según:
var miobjeto=new paralelepipedo(2,3,4);
miobjeto.nuevoX(-1);
miobjeto.nuevoZ(2);
alert(miobjeto.cubicaje());
...
Donde más se usa el prototipado es para agregar funcionalidades a los
objetos intrínsecos de JavaScript. Por ejemplo, ahí van unos prototipados para
el objeto String:
String.prototype.left = function(lng) {
return this.substr(0,lng);
}
String.prototype.right = function(lng) {
return this.substr(this.length-lng,lng);
}
String.prototype.trim = function() {
return this.replace(/(^\s*)|(\s*$)/g,"");
}
De modo que si definimos una cadena de carácteres:
var prueba=" hay dos espacios en blanco al principio y uno al final ";
prueba.left(5)
//nos dará " hay";
prueba.right(5)
//nos dará "inal ";
prueba.trim()
//nos dará "hay dos espacios en blanco al principio y uno al final";
Con lo que podemos enriquecer los objetos a voluntad.