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

Continúo profundizando en toda la información que he ido recopilando acerca de la creación de objetos en Javascript:

  • En la primera entrada de esta serie, me concentré en explicar los mecanismos que usaba el operador new y, sobre todo, en comprender la cadena de prototipado.
  • En la segunda entrada, estuvimos repasando toda la casuística que se me ha ocurrido para manipular la cadena de prototipado de los objetos, para así asentar nuestros conocimientos teóricos (y también que nos sirvan de inspiración para diseñar nuestros propios objetos Javascript).

Tenía pensado cerrar esta serie con esta tercera entrada dedicada a las funciones Javascript para interaccionar con objetos: Object.create, instanceof, isPrototypeOf, … Pero cuando me he puesto a explicar la función Object.create, ha necesitado una entrada completa para ella sola. Espero que os resulte de interés.

Repaso de los métodos para crear objetos en Javascript

Ya hemos estudiado dos métodos para crear objetos:

  • El método «literal»:
  • Es el método más directo, permite crear objetos individuales «sobre la marcha».

  • El operador new:
  • Permite usar funciones (me gusta llamarlas «constructoras» pero si prefieres llamarlas «clases», lo dejo a tu criterio) que nos sirven parar crear objetos que todos cumplen el mismo patrón.

Recordemos también el diagrama que detalla la cadena de prototipado de nuestro objeto calvin recién creado:

Creacion de objetos en javascript parte III - figura 1

(La presencia en este diagrama de la función constructora comicCharacter es meramente informativa. Una vez creado el objeto calvin, su verdadera cadena de prototipado está formada por el objeto calvin y el objeto comicCharacter.prototype)

El operador Object.create

Existe un tercer método para crear objetos: la función Object.create. Esta función, es muy parecida al operador new… pero con sus peculiaridades.

Veamos el ejemplo anterior, usando Object.create:

Vamos a repasar qué es lo que hemos hecho:

  • Hemos definido un objeto ComicCharacter. Este objeto (INSISTO: es un objeto, no es una función) lo usaré como patrón para crear todos mis objetos (si quieres, llámalo «clase»). Verás que he escrito su nombre con la primera letra en mayúsculas (es la notación más recomendada, para distinguirlo como objeto-patrón). Este objeto, tiene todas las propiedades y métodos que deben tener los objetos que voy a crear a partir de él.
  • Para crear mi nuevo objeto calvin a partir del objeto patrón ComicCharacter, lo paso éste como argumento a Object.Create.
  • Por fin, puedo operar con el nuevo objeto calvin recién creado.

Arquitectura de los objetos creados con Object.create

«Pero, Jose ¿qué ha pasado con la cadena de prototipado? ¿ya no existe? ¿es que ya no hace falta?»

Por supuesto que el objeto calvin que hemos creado tiene su cadena de prototipado. Y por supuesto que seguimos usándola. Lo que ocurre es que los mecanismos internos que usa Object.create son distintos de los que hemos estudiado que usa new:

  • Primero, crea un objeto absolutamente limpio.
  • A este nuevo objeto, le asigna su propiedad __proto__ apuntando al objeto que se le pasa como parámetro.

El diagrama de este nuevo objeto calvin recién creado sería:

Creacion de objetos en javascript parte III - figura 2

Si comparamos este nuevo diagrama con el diagrama previo (cuando creamos el objeto con el operador new) vemos que no tiene nada que ver. Pero, eso sí: lo importante es que la cadena de prototipado sigue cumpliendo su cometido para dotar a nuestros objetos de un conjunto de propiedades y funciones.

A diferencia de cuando creamos objetos usando new, no tenemos el equivalente a la inicialización del objeto que realizaba la función constructora. Por eso, he añadido a mi nuevo objeto-patrón ComicCharacter un método init. Una vez que invoquemos a este método (calvin.init('Calvin')) el diagrama será:

Creacion de objetos en javascript parte III - figura 3

Como podemos ver, a partir de ahora el objeto calvin tiene su propia propiedad name de modo que si consultamos calvin.name el motor Javascript responde directamente «Calvin» (y ya no recorre la cadena de prototipado).

Examinando la nueva cadena de prototipado

Lo mismo que «jugamos» con la cadena de prototipado de los objetos creados con new, ahora vamos a «jugar» con la cadena de prototipado de nuestros nuevos objetos creados con Object.create:

Vamos a repasar lo que hemos hecho:

  • Hemos definido el objeto ComicCharacter que nos va a servir como modelo.
  • Hemos creado dos objetos calvin y hobbes usando Object.create.
  • Hemos comprobado que, tal como he explicado, la propiedad calvin.__proto__ apunta a ComicCharacter.
  • Para comprobar (más aún) que dominamos esta cadena de prototipado, le hemos añadido un nuevo método shout al objeto comicCharacter e inmediatamente después hemos comprbado que este nuevo método está accesible (a través de sus cadenas de prototipado) desde nuestros dos objetos calvin y hobbes.

Pero ¿quién es el constructor?

En estas reflexiones sobre Object.create he omitido intencionadamente cualquier referencia sobre la propiedad constructor de los nuevos objetos… hasta ahora. Vamos a verlo:

Cuando estudiamos cómo new creaba nuevos objetos a partir de una función constructor, ya vimos que la propiedad constructor formaba parte del objeto prototype de la función constructora. Y, por lo tanto, era accesible desde el objeto creado a través de su cadena de prototipado. Es más fácil verlo que explicarlo:

Creacion de objetos en javascript parte III - figura 1

Pero, cuando usamos Object.construct, no hay función constructora. Y la cadena de prototipado del nuevo objeto apunta al objeto que hemos usado como patrón. Entonces ¿qué devuelve nuestro nuevo objeto si intentamos consultar su propiedad constructor?:

A ver qué hemos hecho:

  • Hemos comprobado si calvin.constructor vale ComicCharacter… pero no es así. Intuitivamente, podríamos imaginar que sí (ComicCharacter es el constructor ¿no?). Pero ahora que conocemos la cadena de prototipado, sabemos que la relación entre calvin y ComicCharacter es a través de la propiedad calvin.__proto__.
  • Lo que sí que son iguales son calvin.constructor y ComicCharacter.constructor. Ya comprendemos por qué: porque calvin.constructor busca su valor en su cadena de prototipado… y encuentra el valor de ComicCharacter.constructor.
  • ¿Y cuál es el valor de ComicCharacter.constructor? Pues como es un objeto creado literalmente, su constructor es la función constructora Object().

El diagrama que incluye al constructor sería el siguiente:

Creacion de objetos en javascript parte III - figura 5

Moraleja: si tienes que analizar el origen de un objeto Javascript, puedes fiarte de su cadena de prototipado pero yo no me fiaría de su propiedad constructor.

Creando tu propio Object.create

La función Object.create es un estándar ECMAScript 5.1. Como tal, se puede usar en todos los navegadores modernos… excepto en IE8 y anteriores (podéis consultar esta tabla de compatibilidad).

Lo que sí podemos, es construir nuestra propia versión para usarla en IE8. Vamos a estudiar la versión que han desarrollado en es5-shim:

Vamos a estudiar qué es lo que hace (me voy a concentrar en lo esencial):

  • Define la propiedad Object.create como un objeto función que espera dos parámetros, protype y properties. Ahora, nos vamos a concentrar en el primer parámetro prototype y dejamos el segundo parámetro properties para explicarlo más adelante.
  • En la línea 3 crea una función vacía Type.
  • Después de algunas comprobaciones, en la línea 16 asigna manualmente la propiedad Type.prototype al objeto prototype que se le pasó como parámetro.
  • En la línea 17 crea un objeto object a partir de la función constructora Type.
  • En la línea 31, devuelve este objeto object como resultado.

Veamos el diagrama del nuevo objeto object:

Creación de un objeto simulando Object.create

Vemos que, efectivamente, nuestro nuevo objeto object tiene en su cadena de prototipado al objeto prototype que pasamos como patrón (el resultado es exactamente lo mismo que si hubiéramos usado la función Object.create original).

Veréis que también he dibujado el objeto Type.prototype. Este objeto ha quedado «huérfano» y sin utilidad desde el momento que asignamos manualmente la propiedad Type.prototype para que apuntara a nuestro objeto prototype.

No olvidemos que la función type y el objeto type.prototype son locales a la función y por lo tanto «morirán» cuando la función devuelva el objeto object.

El segundo parámetro de Object.construct

Tal como hemos visto, la función Object.create tiene un segundo parámetro properties que, hasta ahora, no había mencionado.

Este segundo parámetro permite añadir al objeto que se cree nuevas propiedades adicionales a las que aporta el objeto patrón que se pasa como primer parámetro. Además, estas propiedades adicionales se pueden definir con un nivel de precisión de su comportamiento potentísimo (propiedades de sólo lectura, propiedades enumerables, que tienen funciones setters y getters, …).

¿Por qué no lo he mencionado antes? Porque, desafortunadamente, IE8 y anteriores no soportan este segundo parámetro porque se basa en la función Object.defineProperties que no es soportada (podéis consultarlo de nuevo en la tabla de compatibilidad que mencioné antes).

Las posibilidades de configuración que nos permite este segundo parámetro son tan potentes que necesitaría una entrada específica para estudiar a fondo todas sus posibilidades.

¿Conclusiones?

Mi intención era terminar con esta tercera entrada mis reflexiones sobre la creación de objetos y la cadena de prototypado en Javascript. Pero, cuando me encontré todas las cosas que tenía que contar sobre la función Object.create, no he tenido más remedio que dedicarle una entrada exclusivamente para ella.

Lo bueno que ha tenido esta entrada es que nos ha permitido (además de conocer a Object.create) seguir trabajando nuestro dominio de la cadena de prototipado. Así, incluso hemos estudiado un sustituto de Object.create y cómo podemos manipular la cadena de prototipado del objeto que creamos para que la simulación sea correcta.

Insisto que la cadena de prototipado es el concepto fundamental para organizar nuestros objetos en Javascript. Y que potenciaremos todavía más cuando lleguemos a la entrada específica sobre la «herencia» de clases. Y que ya os adelanto que consistirá en aumentar la cadena de prototipado:

  • En los objetos que hemos creado, la cadena de prototipado sólo tenía dos niveles (el propio objeto y el objeto apuntado por su propiedad __proto__).
  • Para simular la «herencia», lo que haremos será aumentar los niveles de la cadena de prototipado.

Pero eso ya es 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