Prototipado y herencia en JavaScript

Prototipado y herencia en JavaScript

Cuando se empieza a programar en un lenguaje como JavaScript (es decir, permisivo hasta no poder más) dar los primeros pasos puede resultar realmente complicado. En mis más tempranas indagaciones ansiaba encontrar la forma de crear clases que pudieran heredar de otras al estilo C++ (que es lo que venía aprendiendo).

Sin embargo, el enfoque que da JavaScript a estos aspectos es ligeramente distinto a lo que cabe esperar, ya que en este lenguaje todo son objetos, y cuando digo todo quiero decir que los números son objetos, las funciones también son objetos y las "clases" que crearán objetos también son objetos. Una vez que se ha digerido esta idea es más fácil entender cosas como las funciones anónimas o el prototipado, así que si ya os habéis empapado de la idea podemos ir al grano.

Creación de clases

function Animal (edad) { this.edad = edad || 0; } var iris = new Animal(4), rufo = new Animal(6); console.log(iris instanceof Animal); // true console.log(rufo.edad); // 6

Para crear una clase lo primero que debemos hacer es definir nuestro constructor, tarea que desempeñará una función que hará uso de la variable this como el objeto creado. Desde el momento en el que se defina la función podemos crear instancias de la clase para obtener nuevos objetos. El siguiente paso sería añadir métodos a nuestra clase que puedan ser utilizados por los objetos creados, y dado que en JavaScript todo es un objeto podríamos hacer algo así:

function Animal (edad) { this.edad = edad; this.crecer = function () { this.edad = this.edad + 1; return this.edad; } } var iris = new Animal(3); console.log(iris.crecer()); // 4

Efectivamente este código funcionaría y nos da una forma de implementar métodos de instancia, pero surge un pequeño problema, y es que nuestro constructor está creando una función crecer para cada una de las instancias que creemos de la clase, por lo que si creamos un array con 1000 instancias de Animal se crearán 1000 funciones distintas (e independientes), algo que en términos de eficiencia es tremendamente indeseable.

Para esto, JavaScript nos provee de un pequeño pero práctico atributo que todos los objetos poseen: prototype. Este atributo a priori es un objeto vacío y corrientucho pero que cuenta con unas características muy interesantes. Cuando una función se utiliza como constructor (que es lo que estábamos haciendo en los ejemplos anteriores) JavaScript establece como atributo prototype del objeto al mismo prototipo del constructor, de forma que los objetos creados pueden acceder a todos los atributos que se encuentren dentro de este objeto sin necesidad de duplicar información. Veamos como queda el código introduciendo este concepto:

function Animal (edad) { this.edad = edad; } Animal.prototype.crecer = function () { this.edad = this.edad + 1; return this.edad; }; var iris = new Animal(3); console.log(iris.crecer()); // 4

Herencia

Ahora que hemos visto como crear la clase animal, ¡vamos a hacer que los animales creados puedan hablar!. El problema es que no todos emiten el mismo sonido, así que deberíamos hacer clases distintas que hereden de la clase animal.

function Felino () { } Felino.prototype = new Animal(); Felino.prototype.constructor = Felino; Felino.prototype.maullar = function () { console.log('meowwwww'); }; var iris = new Felino(); iris.crecer(); iris.maullar();

¡Genial! Ahora iris puede maullar por ser un felino y crecer por ser un animal pero, ¿qué ha ocurrido aquí exactamente?

Cuando creamos nuestra clase Felino establecemos su prototipo como un nuevo objeto Animal de forma que el prototipo contendrá todos los métodos y atributos que tendría un animal corriente. Seguidamente sobrescribimos el constructor de Felino para que los objetos creados pertenezcan a esta clase y ya podemos añadir todos los métodos/atributos que queramos sin ningún problema.

El problema es que con este método iris no puede heredar de otras clases como Cuadrúpedo, la cual le permite usar el método caminar. Para hacer esta doble herencia habrá que crear un método algo más complejo o usar una librería que nos proporcione estas capacidades como Class.js o Ring.js.