Creación de objetos y definición de clases en Javascript (Parte I)

En la anterior entrada sobre el identificador «this», ya creamos nuestros primeros objetos con Javascript:

O también podíamos haber escrito:

Este método para crear objetos, podríamos llamarlo el método «literal».

También mencionamos una peculiaridad de Javascript: en cualquier momento, podemos añadir a cualquier objeto nuevas propiedades:

Era inevitable crearlos sin dar muchas explicaciones y lo más simples posible, porque necesitábamos crear objetos para explicar los conceptos que tratamos en la entrada. Pero ya advertí que habíamos creado nuestros objetos de la forma más rudimentaria posible y que me hacía falta una entrada específica para explicar en profundidad la creación de objetos en Javascript. Pues, bien, esta entrada ya ha llegado. Me ha salido tan extensa, que he decidido dividirla en dos partes (o incluso es posible que tres). A ver si os gusta esta primera.

Definición de «clases» en Javascript

Fijaros que he titulado esta entrada «Creación de objetos y definición de clases». El motivo de este título es porque, tal como hemos visto podemos crear objetos sobre la marcha, en cualquier momento. Pero en cuanto nuestra aplicación comience a crecer, si no tenemos mucha disciplina con nuestro código, éste se puede volver inmanejable ¿Qué ocurre si necesitamos crear varios objetos con las mismas características? (lo que comunmente se conoce en otros lenguajes de POO como «clases»).

Bueno, Javascript no tiene clases… pero tiene algo por el estilo. Veamos qué podemos hacer en Javascript (y después lo explico):

¿Qué es lo que hemos hecho?:

  • Primero, hemos definido una función. Pero, dentro de esta función, hemos creado varias propiedades del objeto this (por el momento, conformaros con esta explicación; paciencia y ya insitiré en este punto más adelante).
  • Segundo, hemos definido varias variables. Pero las hemos inicializado usando un nuevo operador: el operador new (pasándole como argumento la función que acababa de definir).
  • Por último, hemos comprobado que los tres objetos definidos tienen el método hello y cuando se invocan responden con su propio valor de name.

No es como en los lenguajes de POO tradicionales, pero en el fondo podemos decir que hemos definido una clase comicCharacter y hemos creado tres objetos calvin, hobbes y dilbert de esta clase.

El operador new

Lo que hemos hecho, no ha sido definir una clase (en el sentido de los lenguajes de POO tradicionales). Por eso digo que hemos definido una «clase». Y conviene que comprendamos los mecanismos que operan internamente cada vez que usamos el operador new. Vamos a estudiar la secuencia de acciones que ocurren cada vez que lo usamos, paso a paso:

  • El operador new espera como argumento un objeto function (que hará el papel de «constructor» para nuestros objetos).
  • Primero, crea un nuevo objeto, absolutamente vacío.
  • A este nuevo objeto, le inicializa su propiedad interna __proto__ al valor de la propiedad prototype del objeto function que le hemos pasado como parámetro.
  • Segundo, ejecuta la función que le hemos pasado como parámetro, dentro del contexto de este nuevo objeto ¿Recordáis la anterior entrada sobre el contexto de ejecución? Si la recordáis, entonces ahora comprendéis que las instrucciones que hemos incluido dentro de nuestra función comicCharacter lo que están haciendo es dotar al nuevo objeto recién creado de las propiedades que queremos que tenga.
  • Una vez ejecutada la función, si esta función devuelve un objeto, éste objeto será lo que devuelve el operador new. Si la función no devuelve explícitamente un objeto, el operador new devolverá el objeto que creó. En nuestro caso, como la función comicCharacter no devuelve nada, el operador new devolverá el objeto que creó (y al que la función comicCharacter le ha añadido dos propiedades name y hello).

Como veis, el operador new hace muchas operaciones, algunas de ellas fácilmente comprensibles. Pero otras… realmente todavía no nos cuadran. Parece que nos faltan piezas del puzzle. A ver si las podemos encontrar.

La propiedad function.prototype

Cualquier objeto function que nos encontremos tiene una propiedad que se llama prototype (hasta ahora no lo había mencionado en ninguna entrada porque hasta ahora no nos había hecho falta):

Como vemos, esta propiedad apunta a un objeto que, a su vez tiene una propiedad llamada constructor que apunta… al mismo objeto function del que es propiedad.

A ver si se comprende mejor con un pequeño diagrama:

Creacion de objetos en javascript parte I - figura 1

Como cualquier otra propiedad de cualquier otro objeto, podemos ampliarla en cualquier momento:

O incluso podemos sustituirla por otro valor:

Pero ¡cuidado! Si sustituimos su valor por defecto por otro perdemos la información que ya contenía (como veis, la propiedad constructor ya no apunta a comicCharacter como ocurría en el objeto original).

“Vale, Jose. Ya veo que cualquier objeto function tiene una propiedad que se llama prototype que a su vez tiene una propiedad que se llama constructor que a su vez apunta al mismo objeto function. Y eso ¿para qué me sirve?”

Tranquilo. Paciencia. Estamos repasando las piezas del puzzle antes de montarlo. De momento, me conformo con que hayas comprendido que existe esta propiedad.

La propiedad interna object.__proto__

Cualquier objeto Javascript que nos encontremos tiene una propiedad interna que se llama __proto__. Hasta donde yo sé, no debería ser asequible desde el código. Pero algunos navegadores permiten acceder a ella.

Como he dicho antes, cuando el operador new crea el nuevo objeto, hace que esta propiedad __proto__ de este nuevo objeto apunte a la propiedad prototype de la función que se le pasó como parámetro.

El diagrama cuando creamos el objeto calvin sería:

Creacion de objetos en javascript parte I - figura 2

“Jose, se me está agotando la paciencia. Primero, te sacas de la manga una propiedad function.prototype. Ahora, te vuelves a sacar una propiedad object.__proto__ que hasta ahora no habías mencionado. Pero no veo para qué sirven ninguna de ellas”

La cadena de prototipado

Tranquilo, porque ya tenemos todas las piezas. Vamos a montar el puzzle. Veamos un nuevo trozo de código (es el mismo que hemos estudiado antes, pero ligeramente modificado):

Vamos a estudiar lo que hemos hecho:

  • Hemos definido, como antes, la función comicCharacter y las tres objetos calvin, hobbes y dilbert usando el operador new.
  • Hemos comprobado que el método hello que definimos dentro del constructor sigue funcionando sobre mis tres objetos.
  • Pero, esta vez, hemos aumentado al objeto comicCharacter.prototype y le hemos añadido un nuevo método shout (ya os advertí antes que podíamos hacerlo ¿lo recordáis?).
  • Y, por fin, viene la magia: si invocamos el método shout sobre mis tres objetos… funcionan correctamente.

¿Qué es lo que ha ocurrido? Pues lo que ha ocurrido es que se ha activado la cadena de prototipado («prototype chain», por si no os gusta mi traducción). Y qué es eso de la «cadena de prototipado»?:

  • Cuando invocamos una propiedad sobre un objeto (por ejemplo calvin.hello() o calvin.shout()), el intérprete de Javascript busca dicha propiedad en ese objeto. Y, si la encuentra, la ejecuta (como ocurre con calvin.hello()).
  • Sin embargo, si la propiedad no existe en el objeto (como ocurre con calvin.shout()) contrariamente a nuestra primera intución no da error sino que decide darle una segunda oportunidad.
  • Para esta segunda oportunidad, consulta al objeto apuntado por la propiedad calvin.__proto__. Y comprueba si encuentra ahí o no a la propiedad invocada.
  • En nuestro caso, calvin.__proto__ apunta al objeto comicCharacter.prototype (esto es así, como he explicado antes, por cortesía de nuestro estimado operador new). Y da la casualidad de que comicCharacter.prototype.shout sí que existe. Por lo que la invoca (y gracias a eso obtenemos el resultado deseado).
  • En el caso de que no hubiera encontrado la propiedad invocada en esta segunda oportunidad, no olvidemos que el objeto donde la ha buscado también es un objeto. Esto quiere decir que también tiene su propiedad __proto__ que apunta a un tercer objeto. Por lo que volvería a buscar la propiedad en el tercer objeto.
  • Esta búsqueda encadenada terminará cuando encuentre en la cadena un objeto que tenga la propiedad buscada o hasta que __proto__ valga null (lo que significa que hemos llegado al fin de la cadena). Esta búsqueda de la propiedad en la cadena de objetos es lo que he llamado «la cadena de prototipado».
Creacion de objetos en javascript parte I - figura 3

En nuestro, caso, el recorrido de la cadena de prototipado que nos interesa llega exclusivamente hasta lo que he llamado «segunda oportunidad» (cuando comprueba calvin.__proto__). Pero el recorrido recursivo será muy importante cuando diseñemos lo que en POO tradicional se llamaría «herencia de clases».

¿Conclusiones?

En esta entrada, hemos pasado de crear objetos manualmente (usando el método literal) a crear objetos usando el operador new.

Si creamos objetos usando el operador new podemos usar objetos function que nos sirven como constructores para nuestros objetos y que nos dan más versatilidad a la hora de definirlos.

Una de las ventajas que nos da usar funciones constructores es que podemos definir parte del código en su propiedad prototype. Este código, será accesible desde todos los objetos que construyamos gracias a la cadena de prototipado.

He intentado explicar de la forma más simple posible en qué consiste esa cadena de prototipado y cómo podemos usarla para la creación de «clases». Pero, sinceramente, es uno de los conceptos de Javascript que requiere más esfuerzo. Sobre todo, porque los que estamos acostumbrados a otros lenguajes más convencionales, no nos damos cuenta de las posibilidades de programación que tiene.

La próxima entrada, la dedicaré a insistir en esta cadena de prototipado, proponiendo unos cuantos ejemplos que he ido recopilando. Estos ejemplos, deben servir tanto para comprender sus sutilidades así como para ayudarnos a madurar la compresión de este concepto.

Y cuando por fin comprendamos la cadena de prototipado (simplemente aplicada a la creación de «clases») por fin podremos ampliar estos conocimientos para poder construir una jerarquía de «clases» que hereden entre sí… pero eso tendrá su propia entrada.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*
*
Sitio Web