Creacion de objetos y definicion de clases en Javascript (Parte II)

En la anterior entrada, expliqué el funcionamiento interno del operador new e introduje el concepto de la cadena de prototipado.

Fue una entrada especialmente teórica, demasiado para mi gusto (personalmente, prefiero un buen ejemplo a una buena explicación). Pero era necesario explicar detalladamente los mecanismos que usaba el operador new para construir un nuevo objeto.

Así, vimos que si creamos el objeto Calvin usando la función constructora comicCharacter:

El diagrama que representa su cadena de prototipado es:

Creacion de objetos en javascript parte II - figura 1

Y también vimos cómo el objeto creado adquiría todos los métodos que estaban definidos no sólo dentro de él, sino también en su cadena de prototipado:

Creacion de objetos en javascript parte I - figura 3

Esta cadena de prototipado, nos permite crear funciones constructoras sofisticadas. Pero personalmente opino que si las llamamos «clases» nos estamos haciendo un flaco favor. El motivo es que nuestra mente intenta encajarlas con los mecanismos de otros lenguajes de POO. Y no es lo mismo. Javascript tiene sus propios mecanismos. Nos gustarán más o menos. Pero son los que son. Y nuestro objetivo es conocerlos lo mejor posible para dominarlos y obtener lo mejor de ellos. Y también para evitar errores por falta de conocimientos.

Para insistir en la peculiaridad de estos mecanismos, en esta entrada vamos a ver unos cuantos trozos de código que fuerzan la cadena de prototipado. De este modo, asentaremos las explicaciones teóricas.

Cómo crear propiedades «privadas»

Veamos un primer ejemplo:

¿Qué es lo que hemos hecho?:

  • Hemos creado una función constructor counter. Dentro de esta función, hemos definido una variable local count y hemos creado una propiedad getCount que consulta y modifica a la variable.
  • Hemos creado dos objetos (counter1 y counter2) con este constructor. Cada uno de estos objetos, cuando invoque a su método getCount, cada uno de los métodos invocará a su propia versión de la variable local count (¿recordáis nuestra entrada sobre las funciones closure?). Por eso, cada invocación de getCount sobre cada objeto actualiza su propia versión de la variable local count.
  • No olvidemos que la variable count es local a la función constructora counter. Esto quiere decir que si intentamos acceder a ella a través del objeto (intentando usar counter1.count) no es posible, porque su visibilidad queda limitada dentro de la función counter y las funciones que hayamos definida dentro de ella.

Si queréis, podríamos decir que hemos definido una propiedad con acceso privado (pero, insisto, no es lo mismo…).

Para insistir en las peculiaridades de Javscript, vamos a intentar hacer esto mismo, ligeramente diferente:

“Jose, no entiendo nada. Hemos definido nuestro método getCount en counter.prototype de modo que counter1.getCounter() debería funcionar correctamente… Pero está dando un error ¿Por qué?

Para comprender el error que estamos cometiendo, debemos insistir en el alcance de la variable local count: está definida dentro de la función counter y sólo es accesible para las funciones definidas dentro de ella. Y nuestra nueva versión de la función getCount está definida fuera de counter. Por eso, no tiene acceso a la variable count y da un error cuando intenta acceder a ella.

Objetos mutantes

Otro ejemplo de las manipulaciones que podemos hacer a la cadena de prototipado:

¿Qué hemos hecho ahora?:

  • Hemos definido nuestra función constructor comicCharacter. Esta vez, sólo tiene un método hello que hemos definido en su propiedad prototype.
  • Los dos objetos que hemos creado calvin y hobbes tienen acceso al método hello a través de la cadena de prototipado y lo ejecutan correctamente («hello, I am Calvin» y «hello, I am Hobbes»).
  • Una vez que hemos comprobado que la ejecución es correcta, hemos redefinido comicCharacter.prototype.hello ¿Y por qué lo hemos hecho? Porque se puede hacer.
  • La próxima vez que invoco a calvin.hello() o hobbes.hello(), usarán la nueva definición de hello («grrr, I am Calvin» y «grrr, I am Hobbes»).

«¿Qué quieres demostrar, Jose?»

Que Javascript es un lenguaje absolutamente dinámico y que cada vez que se ejecuta el método hello se busca en la cadena de prototipado. Y que cualquier elemento de esta cadena se puede variar en cualquier momento, afectando a los resultados posteriores.

«Y, esto ¿para qué sirve?»

Eso, ya depende de ti. Mi objetivo es transmitirte lo mejor que pueda lo que yo sé acerca de la cadena de prototipado cuando definas tus constructores y crees objetos basados en ellos. Y evites errores por mala comprensión de los conceptos.

La excepción confirma la regla

Otro ejemplo:

Y, ahora ¿qué hemos hecho?:

  • Hemos vuelto a definir nuestra función constructor comicCharacter con un método hello definido en su propiedad prototype.
  • Después, hemos creado nuestras dos variables calvin y hobbes usando este constructor.
  • Pero, ahora, he realizado una nueva variante: hemos definido el método hobbes.hello.
  • Cuando invocamos calvin.hello() el intérprete de Javascript busca la propiedad hello en calvin. Como no la encuentra, la busca en calvin.__proto__. Y aquí sí que la encuentra y la ejecuta («hello, I am Calvin»).
  • Pero cuando invocamos hobbes.hello(), el intérprete encuentra la propiedad hobbes.hello. Ya no busca nada más, sino que directamente ejecuta esta propiedad («grrr, I am Hobbes»).

Como espero que hayáis podido comprobar, tenemos una cadena de prototipado que en cualquier momento podemos modificar cualquiera de sus eslabones. Insisto: la forma de usar esta capacidad, ya depende de vosotros.

Constructores que se saltan la regla

En nuestra anterior entrada, cuando definí los pasos que daba el operador new cuando construía un nuevo objeto, expliqué:

«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 ese momento, pasé sólo por encima de esta frase y me limité a explicar que el ejemplo práctico que estaba proponiendo seguía el caso más normal (cuando la función constructor no devuelve nada). Pero ¿qué ocurre cuando devuelve un objeto? Ahora, vamos a ver un ejemplo de esta casuística:

¿Qué hemos hecho?:

  • Hemos creado la función constructor comicCharacter y una función comicCharacter.prototype.hello en su prototipo.
  • Hemos creado un objeto calvin y hemos intentado usar en ese objeto el método hello… pero nos ha dado error.

¿Por qué nos ha dado error? Porque la función constructor, esta vez, ha retornado explícitamente un objeto. Este objeto no tiene nada que ver con el objeto que ha creado el operador new. Y el objeto devuelto por new, por tanto, no tiene como constructor a comicCharacter ni tiene a hello en su cadena de prototipado. Por eso:

  • Si intentamos acceder al método hello… no está ni en el objeto devuelto por new ni en su cadena de prototipado.
  • Podemos comprobar explícitamente que este objeto calvin no tiene nada que ver con la función constructor comicCharacter consultando su propiedad calvin.constructor.

Quiero otro ninja

No quiero terminar esta lista de ejemplos sin rendir un homenaje al genial John Resig, citando un ejemplo suyo:

¿Qué hemos hecho?:

  • Hemos creado una función constructor Ninja.
  • Hemos creado un objeto ninja usando ese constructor. Comprobamos que ninja.constructor apunta, efectivamente, a Ninja.
  • Ahora viene lo interesante: para crear el segundo objeto ninjaB hemos usado la función apuntada por ninja.constructor (que acabamos de confirmar que apunta a la función constructor Ninja). Y comprobamos que este segundo objeto es un objeto Ninja totalmente válido.

Para crear este segundo objeto, podríamos haber usado directamente la función constructor Ninja. Pero el ejemplo insiste en el hecho de que objeto.constructor apunta a la función constructora usada (al menos, como hemos visto, en condiciones normales).

¿Conclusiones?

Como habéis podido comprobar, la operativa construcción de objetos en Javascript usando el operador new y funciones constructoras es relativamente simple… pero hace falta comprenderla. Por eso, en la primera entrada me concentré en explicar los mecanismos que usa el operador new mientras que esta segunda entrada por fin he podido concentrarme en ejemplos para comprobar los conceptos explicados.

Tengo previsto dedicar una tercera entrada a los operadores que tiene Javascript para poder explorar las propiedades de cualquier objeto: instanceof, getPrototypeOf, isPrototypeOf. Esta tercera entrada, con estas dos entradas convenientemente digeridas, debe ser mucho más fácil de seguir.

¡Ojo! Los conceptos y la información que he ido exponiendo en estas entradas, son el resultado de mi propia experiencia personal. Y frecuentemente me ha ocurrido que creía dominar un concepto… hasta que me he encontrado una nueva fuente de información que me ha obligado a recapacitar. Cualquier feedback que me deis, será absolutamente bienvenido.

Si hemos comprendido estos conceptos, aparte de que podremos usar el operador new con conocimiento de causa, estamos más cerca de poder dominar los mecanismos que podemos usar en Javascript para crear herencia de clases… pero eso será ya tema para otra entrada.

Deja una respuesta

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

*
*
Sitio Web