Creación de objetos (clases) en Javascript WEB
A la hora de crear clases en javascript para WEB, siempre me surgen las mismas preguntas.
- ¿Cual es la mejor manera de crearlas?
- ¿Hacemos clases abstractas u objetos?.
Pero antes de nada, ¿Que entiendo como clase en javascript para WEB?
Es bién sabido que una clase es una colección de variables y métodos (funciones y procedimientos) que puede ser instanciada en objetos. Estas variables o métodos disponen de características de visibilidad hacia afuera (públicas), propias a la clase (protegidas) o a su jerarquía (privadas). En términos prácticos de desarrollo, las clases nos permiten un modelado de código más simple, además de aportar flexibilidad a la hora de readaptar o ampliar funcionalidades.
En Javascript existe la posibilidad de crear clases, aunque con la limitación de que en entornos WEB no todos los navegadores soportan esta funcionalidad. Y hasta que esto sea posible deberemos optar por la creación de estructuras que simulan este comportamiento.
Por suerte, cuando creamos una variable en Javascript mediante el comando “var”, esta variable permite la adición de variables y métodos, dotándola de un comportamiento similar a un objeto. Es más, podemos crear una variable de tipo objeto mediante la sintaxis “{}” a la cual podemos incluir métodos y propiedades.
Pero si somos serios y profesionales ;), lo normal será implementar nuestra clase de tal forma que sea sencillo la creación de nuevas instancias, para lo cual existe el comando “new”.
El comando “new”
Este comando, que está incluido desde la versión 1 del ECMAScript, permite crear instancias de un objeto concreto. Pero para que funcione es necesario implementar el constructor de nuestro nuevo elemento mediante una función que devuelva precisamente nuestro objeto. Pero antes de ponernos a crear código sin más, tenemos que tener en cuenta cómo se comporta el constructor para no llevarnos una mala experiencia.
Si pretendemos ahorrar bytes, cosa que por otro lado es recomendable a la hora de que nuestro código sea rápido, lo rápido y sencillo es añadir funciones directamente a nuestros objetos sin utilizar un prototipo. Esto, que es válido para la mayoría de navegadores, no sería lo más adecuado si queremos construir clases.
La razón es que nuestro constructor crea una instancia del objeto que estamos desarrollando, pero solo de la información base. En el siguiente ejemplo se detalla el comportamiento:
function ClaseBase(){ this.n=”Soy Base”; } ClaseBase.t=function(){ console.log(this.n); } var c1=ClaseBase(); ClaseBase.t(); c1.t();
El resultado de esta ejecución es:
undefined SCRIPT438: El objeto no acepta la propiedad o el método 't' clases.js, Línea 9 Carácter 1
Como se puede ver, por un lado el método “t” existe para ClaseBase porque devuelve undefined (la variable n no está definida), pero esta no existe para la instancia c1.
La solución para esto será añadir “prototype” al método, dejando la sentencia como:
ClaseBase.prototype.t=function(){ …
Herencia
Una vez que ya tenemos definida nuestra clase base, es interesante la idea de crear clases que deriven de esta, ya sabéis, el típico ejemplo de clase Figura que tiene clases como Círculo, Cuadrado, …
Para hacer esto en Javascript tenemos que tener presente el prototipado de la clase, ya que de lo contrario volveremos a caer en el problema de la perdida de métodos.
Al igual que en la clase base, tendremos que implementar nuestro constructor mediante la creación de un método, pero con la particularidad de que deberemos llamar a la clase padre de la que queremos heredar sus propiedades. En otras palabras, invocaremos al constructor de la clase padre y el resultado lo almacenaremos en el this (nosotros).
function ClaseA(){ ClaseBase.call(this); this.n="soy clase A"; }
Y con esto tenemos la mitad del trabajo, la otra será la de cargar los métodos o prototipos de la clase padre. Esta tarea la podemos hacer (por temas de eficiencia), mediante el comando “create” tal y como os muestro en el ejemplo:
ClaseA.prototype=Object.create(ClaseBase.prototype);
Ojo, el método “create” de la clase “Object” se definió en el ECMAScript 5.1 allá por el 2011, lo que implica, entre otras, que solo está disponible a partir de la versión 9 del Internet Explorer (los usuarios de Windows XP suelen tener la versión 8 de este navegador).
Pero tranquilos, ante esto hay 2 alternativas:
- [a1] Implementar el método create
- [a2] Utiliza una librería de terceros que nos implemente esta funcionalidad
Para el segundo caso, podemos utilizar la librería es7-shim, que implementa la versión 7 de ECMAScript para navegadores “antiguos” [https://github.com/es-shims/es7-shim].
Y, aunque la tentación es grande, ¿es necesario que nuestro usuarios descarguen esa librería para un simple “create”?, hombre, quizás la medida sea desproporcionada, por lo que nuevamente hay 2 alternativas:
- [a2.1] Comprobar previamente la versión del navegador y en caso de ser muy antiguo utilizar la librería (descargarla).
- [a2.2] Implementar directamente el método.
Sí, soy consciente que he repetido lo de implementar dos veces, en la acción 1 [a1] y en la 2.2 [a2.2], pero si lo piensas bien, la propia implementación debería ser solo realizada si ésta es necesaria. Además, si algo está bien, para qué vamos a cambiarlo, podemos utilizar el código de esa librería que es muy sencillo.
if (!Object.create) { Object.create = function(o, properties) { if (typeof o !== 'object' && typeof o !== 'function') throw new TypeError('Object prototype may only be an Object: ' + o); else if (o === null) throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument."); if (typeof properties != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument."); function F() {} F.prototype = o; return new F(); }; }
Aún no hemos terminado puesto que al crear nuestro prototipo a partir de la clase base, se ha copiado el método “constructor”, lo que en algunos casos puede venir en nuestro perjuicio. Por esta razón deberemos sobreescribirlo para que nuestra nueva clase sea correcta 100%.
ClaseA.prototype.constructor=ClaseA;
Pero no es oro todo lo que reluce
Como comentaba al comienzo del post, cuando desarrollo una nueva “librería”, siempre me saltan las dudas de si utilizar una “clase” o directamente implementar nuestras instancias. Obviamente la segunda opción siempre es más rápida, aunque la primera da más juego.
Independientemente de esto, se nos suma un nuevo obstáculo a la hora de desarrollar nuestro código en el IDE que escojamos, en mi caso el “eclipse”. Y es la falta, en la mayoría de los casos, del autocompletado (intellisense).
Veréis, en el caso del ejemplo implementado más arriba, nuestro navegador reconoce la clase “ClaseBase”, todos sus método y sus propiedades. Sin embargo, no ocurre lo mismo para la clase “ClaseA”. En esta solo se reconoce la propiedad “n” ya que está utilizada en el propio constructor, y entiendo que es complejo que el eclipse interprete el Javascript de tal forma que se de cuenta de que existe una jerarquía y por tanto pueda autocompletar los métodos y propiedades heredados, al menos sin utilizar la sintaxis “class” que fue definida en el ECMAScript 2015 de Junio del 2015.
Otro handicap menor, lo tenemos en la necesidad de escribir continuamente nuestro nombre de clase más el literal “.prototype.” y finalmente nuestro nuevo método, lo que suma al menos 11 caracteres (11 bytes) a nuestro código. Sé que esto es despreciable, pero si podemos reducirlo, pues mejor que mejor. La cuestión es que si por ejemplo creamos una variable con el prototipo, podemos reducir (si creamos más de un método 😉 nuestro código:
var p=ClaseBase.prototype;
el problema es que si lo hacemos de esta forma, la clase “ClaseBase” no dispondrá de autocompletar para los métodos creados…
La solución para este caso es no preocuparnos por el tamaño en exceso, y utilizar un proceso posterior para reducir el peso de nuestro código. Pero esto, lo veremos en próximos posts….