¿Qué hace Object.create()?

La incorporación de Object.create() a JavaScript permite aún mayor flexibilidad para tratar con objetos. La idea básica es crear objetos a partir del prototipo de otro objeto. Aunque la forma clásica de crear objetos usando una función constructora, la palabra reservada this para declarar propiedades y el operador new para las instanciaciones sigue vigente. ¿En qué debemos basarnos para seguir con la forma clásica o usando Object.create? La verdad aún no sabría decir algo sobre esto. Creo que lo primero es intentar entender el prototipo, pieza angular de los objetos en JavaScript. Veámos primero estos cuatro ejemplos que hacen uso de Object.create():

Ejemplo:

En el contenedor anterior hay un elemento <pre id="mensajes1"> que recoge los mensajes de este codigo:

Estas son capturas de pantallas de un Developer Tools después de la ejecucición de cada uno de los ejemplos anteriores:

Objetos con Object.create()
Objetos con Object.create()Imagen no disponible
Un objeto literal con { } se construye tomando como prototipo el objeto íntrinseco Object.

Primero tenemos un objeto literal llamado miObjeto. Cuando JavaScript encuentra un objeto literal entre llaves { } construye un nuevo objeto tomando como prototipo el objeto intrínseco (o built-in) Object. En la primera captura de pantalla podemos ver como __proto__ es este objeto intrínseco. La propiedad __proto__ es el prototipo y es a su vez también un objeto. Por ejemplo, podemos ver accediendo a la propiedad constructor de ese objeto __proto__. La utilidad es que __proto__ es una referencia al verdadero objeto del prototipo, que suele escribirse como [[Prototype]] y al cual no podemos acceder. Aunque podemos leer y modificar __proto__ se aconseja no usarla, pues al no ser estándar no se comporta igual en todos los navegadores y algunos no la soportan. Además tiene problemas acerca del rendimiento. Muchos defienden el uso de __proto__ basándose en la facilidad para crear objetos. Veámos este ejemplo:

Ejemplo:

Este es el código:

Por lo tanto es lo mismo usar Object.create() para el mismo propósito. Además hay que comprobar que el navegador soporta __proto__ como puede observar en el código.

Siguiendo con el primer ejemplo pasamos a la segunda captura de pantalla donde creamos var miInstancia a partir de Object.create(miObjeto). Las propiedades nombre y propiedad son las del prototipo de miObjeto. En la siguiente imagen vemos otra instancia miInstancia2 del mismo prototipo de miObjeto. Ahora modificamos el nombre pero realmente lo que estamos haciendo es agregarlo como una nueva propiedad de la instancia. Cualquier acceso a leer o sobreescribir nombre en miInstancia2 lo hará en esta instancia. A no ser que borremos la propiedad con delete miInstancia2.nombre, en cuyo caso los accesos posteriores utilizarían la propiedad nombre que sigue existiendo en el prototipo miObjeto.

Cuarta imagen de la serie: ¿Y qué pasa si usamos el operador new para construir instancias en funciones?, que no sea por probar. Hacemos var miInstancia3 = new Object(miObjeto). En este caso el prototipo NO es miObjeto, sino que apunta al intrínseco Object. Podemos aplicar el operador new porque Object tiene el método constructor: function Object(). Es decir, Object() es una función y por tanto con new Object(miObjeto) "deberíamos" crear una nueva instancia de Object aplicando como prototipo miObjeto. Pero al final lo que realmente sucede es que estamos creando una referencia a miObjeto. Lo mismo que si hubiésemos hecho var miInstancia3 = miObjeto. Vea que desde miInstancia3 hemos cambiado la propiedad nombre a miObjeto, porque realmente miInstancia3 === miObjeto.

La especificación ECMA-262-5.1 en su apartado 15.2.2.1 new Object([value]) dice que si el argumento, que es opcional, es un objeto de usuario (host object) entonces devuelve algo que depende de la implementación. Pero en los navegadores que he consultado se comporta como si el argumento fuera un objeto intrínseco, donde especifica que no se creará un nuevo objeto sino simplemente devolverá el valor. Vamos, que al final el operador new en estos casos devuelve el mismo objeto. Sólo tiene utilidad cuando el argumento es nulo, en cuyo caso var x = new Object()nos devuelve un Object vacío. Es lo mismo que asignarlo con var x = {}.

En la última imagen vemos una cadena de prototipos. El objeto miInstancia4 se creó con Object.create(miInstancia, ...) a partir de miInstancia. Por lo tanto éste es su prototipo, que a su vez tiene como prototipo miObjeto, que a su vez tiene como prototipo Object. En la construcción de miInstancia4 hemos pasado nuevas propiedades en el segundo argumento del constructor como un objeto literal. Esta es otra ventaja adicional que yo veo con Object.create() pues permite aplicar parámetros writable o enumerable a las propiedades, tal como hacíamos con defineProperties en el primer tema.

Cómo funciona Object.create()

IE8 no soporta Object.create() pero el primer ejemplo funciona para este navegador porque, al igual que en el primer tema hemos acompañado una solución:

if (!Object.create){
    Object.create = function(proto){
        function temp(){};
        temp.prototype = proto;
        return new temp();
    };
}

En el fondo esto explica lo que hace JavaScript con Object.create(). Al hacer algo como var miInstancia = Object.create(miObjeto) toma miObjeto como un prototipo, pues el prototipo no es otra cosa que un objeto. Crea una función temporal para aplicar ese prototipo a la propiedad prototype y luego instancia una nueva función devolviéndola. Aquí nos encontramos con algo nuevo: la propiedad prototype en objetos Function. ¿Tiene algo que ver con __proto__?, es decir, ¿Es prototype una referencia al interno y no accesible [[Prototype]]?. ¿Qué es prototype?

Hagamos un ejemplo

Ejemplo:

Este es el código en forma resumida:

He tomado capturas de pantalla en cada una de las acciones ejecutadas en el código anterior:

Cómo funciona Object.create()
Cómo funciona Object.create()Imagen no disponible
Un objeto literal tiene un prototipo __proto__ que es una referencia al interno [[Prototype]]
  1. Primero declaramos un objeto literal con var unObjeto = {...} que, como vimos en el primer apartado, obtiene su prototipo desde [[Prototype]] que vemos externamente referenciado en la propiedad __proto__. Le hemos puestos tres propiedades a,b,c y un método ver().
  2. El siguiente paso lo hacemos con function unaFuncion(){} generando una función vacía. Tiene también un __proto__ y la propiedad prototype apuntando a la misma función. ¿Entonces qué es prototype? ¿Una función?
  3. Si desplegamos estas dos cosas veremos que __proto__ apunta a function Empty(){}, una función vacía que le sirve de prototipo. Y ésta a su vez tiene otro __proto__ que apunta a Object. Por otro lado prototype parece apuntar a sí misma, es decir, a unaFuncion, con el __proto__ apuntando a Object. Y aunque unaFuncion es del tipo Function sucede que unaFuncion.prototype es del tipo Object.
  4. Luego con unaFuncion.prototype = unObjeto asignamos nuestro objeto al prototype de unaFuncion sobrescribiendo el que tenía al ser construida. Esto lo podemos ver al consultar de nuevo unaFuncion donde ahora prototype es nuestro objeto particular con las propiedades a,b,c y el método ver(). El prototipo interno __proto__ sigue igual que antes apuntando a la función vacía.
  5. En el último paso hacemos var unObjetoFuncion = new unaFuncion() y observamos que el __proto__ de esta instancia fue tomado desde prototype de unaFunción y no desde su __proto__. Ahora unObjetoFuncion es un objeto copiado o clonado a partir del prototype de la función.

En resumen, Object.create() viene a condensar las tres sentencias que vimos en el código que sirve para hacerlo funcionar en navegadores que no soporten este método.

Creando objetos con funciones constructoras

En los ejemplos anteriores partíamos de un objeto literal que nos servía como "plantilla" o prototipo para crear nuevos objetos. Pero ya sabemos que además podemos crearlos usando una función constructora. Como hicimos en ejemplos del tema anterior, usamos la palabra reservada this para especificar que la declaración de una variable dentro de una función se convertirá en una propiedad del nuevo objeto instanciado con new. Un ejemplo y unas capturas de pantalla nos ayudará a verlo mejor:

Ejemplo:

Este es el código:

Funciones constructoras
Funciones constructorasImagen no disponible
Una función actuando como constructor dispone de un __proto__ que apunta a una función vacía y de un prototype que apunta a la misma función. Este prototype, que es un objeto, tiene una propiedad constructor que es el encargado de construir objetos.

Dentro de la función que hemos llamado unConstructor hemos declarado una propiedad usando this.propiedad = "A". Esta sentencia no es una declaración de variable y por tanto no aparece en la imagen primera. Pero está almacenada dentro de la función y servirá para crear las nuevas propiedades del objeto cuando se construya. El método ver() también lo podíamos haber puesto ahí con this.ver = function(){...}, pero como vimos en un tema anterior es más eficiente incorporarlo en el prototipo de la función. La función tiene __proto__ que apunta a una función vacía. Y además tiene una propiedad prototype que apunta a sí misma. En este objeto prototype encontramos la propiedad constructor que también está apuntando a sí misma. Este constructor será el encargado de construir el objeto cuando lo instanciemos aplicando el operador new.

En la segunda imagen ya tenemos la instanciación realizada con var instancia = new unConstructor(). Como los objetos que instanciábamos en ejemplos anteriores, tiene su __proto__ que apunta al [[Prototype]] interno. Ahí es donde se pusieron los métodos como ver() asignado con prototype. Este __proto__ heredó también la propiedad constructor. Por último se nos podría ocurrir instanciar nuevos objetos a partir del constructor de una instancia, pero usando __proto__, con algo como var instancia2 = new instancia.__proto__.constructor(). Podría ser necesario si desconocemos cual es la función que construyó el objeto y deseáramos por ejemplo crear una nueva instancia.


En resumen, varias posibilidades para lograr el mismo propósito de crear objetos en JavaScript. Algunas consideraciones sobre el rendimiento de una u otra opción podrían ser tenidas en cuenta si interviene un proceso complejo con una elevada creación de objetos. Pero sea de una forma u otra lo que es evidente es que hemos de tratar de entender como funciona el prototipo.