16 marzo, 2008

Constructores en Javascript considerados ligeramente confusos

Traducido del Original en el blog code.h(oe)kje. Considerando la afirmación de Flanagan 2006, (página 111) que aparece en una pregunta de comp.lang.javascript el mes pasado:

En javascript, cada objeto tiene una propiedad constructor que se refiere a la función constructor que inicializa el objeto.
Suena bien: hace que los constructores parezcan estáticos como las clases en Java. Incluso la sintaxis new Constructor() parece igual:
 function MyConstructor() {}
 var myobject = new MyConstructor();
 myobject.constructor == MyConstructor;     // true


Pero la vida no es tan simple:
 function MyConstructor() {}
 MyConstructor.prototype = {};
 var myobject = new MyConstructor();
 
 myobject.constructor == MyConstructor;  // false

¿Qué pasa? Algunas definiciones

Objetos y métodos

Los objetos en Javascript son simples bolsas de propiedades con nombre que pueden ser leidas y escritas. Para la mayoría de propósitos, javascript no tiene clases. [1]

Las funciones en javascript son objetos de primera clase. Los métodos en javascript son sólo propiedades que son funciones.

Prototipos

El prototipo (prototype) de un objeto es una propiedad interna a la que me referire como "[[Prototype]]" (como en Ecma-262). En otras palabras, obj.prototype en general no es el [[Prototype]] de obj. El estándar no provee una forma de recuperar la propiedad [[Prototype]] de un objeto.

Los objetos javascript pueden delegar propiedades a su [[Prototype]] (y su [[Prototype]] puede hacer lo mismo; y así hasta Object Prototype).

Búsqueda de propiedades

Cuando una propiedad "propname" de un objeto es leida, el sistema comprueba si ese objeto tiene una propiedad llamada "propname". Si esa propiedad no existe, el sistema comprueba el [[Prototype]] del objeto para encontrar la propiedad, y así recursivamente.

Esto significa que los objetos que comparten un mismo [[Prototype]] tambien comparten las propiedades definidas en ese [[Prototype]].

Cuando una propiedad "propname" de un objeto se establece, la propiedad se inserta en ese objeto, ignorando la cadena [[Prototype]] de ese objeto (y ocultando cualquier propiedad del mismo nombre en la cadena de prototipos).

La propiedad [[Prototype]] se inicializa desde la propiedad (pública) "prototype" de la función constructor, cuando ésta es llamada.

¿Qué está pasando? Línea por línea.

Esto es lo que las propiedades prototype y [[Prototype]] parecen. Las elipses son objetos, las flechas son propiedades que referencia a otros objetos. La/s cadena/s [[Prototype]] están en verde.

#1: function MyConstructor() {}



Bastante simple. MyConstructor.prototype es una propiedad que es automáticamente creada, la cual en cambio tiene una propiedad constructor que apunta a MyConstructor. Recuerda eso: los únicos objetos que de hecho tienen una propiedad constructor por defecto son las propiedades de funciones prototype que se crean automáticamente.

El resto no es realmente relevante pero podría confundir e iluminar (con suerte en ese orden):

El [[Prototype]] de MyConstructor es Function.prototype, no MyConstructor.prototype. Nótese también que la cadena [[Prototype]] de cada objeto termina en el Object.prototype.

El [[Prototype]] de Object.prototype es realmente null indicando que es el final de la cadena. Para los siguientes pasos, estoy dejando la cadena [[Prototype]] de MyConstructor por claridad, ya que no cambia y no es relevante.

#2: MyConstructor.prototype = {}



Ahora hemos terminado con el objeto MyConstructor.prototype predefinido y lo hemos sustituido con un objeto anónimo, mostrado aquí como "{}". Este objeto no tiene propiedad constructor,

#3: var myobject = new MyConstructor()



De este grafo, siguiendo las reglas de búsqueda de propiedades, podemos ahora ver que myobject.constructor es delegado a Object.prototype.constructor, el cual apunta a Object. En otras palabras:

 function MyConstructor() {}
 MyConstructor.prototype = {};
 var myobject = new MyConstructor();
 
 myobject.constructor == Object

¿Y qué pasa con instanceof?

Javascript facilita el operador instanceof que pretende comprobar la cadena de prototipos del objeto con el que estás tratando. A partir de lo de antes, podrías pensar que lo siguiente devolvería false:

 function MyConstructor() {}
 MyConstructor.prototype = {};
 var myobject = new MyConstructor();
 
 myobject instanceof MyConstructor  // true

Pero el hecho es que funciona (pulsa el botón). También nota que myobject delega en Object.prototype:

 function MyConstructor() {}
 MyConstructor.prototype = {};
 var myobject = new MyConstructor();
 
 myobject instanceof Object

Cuando se llama a instanceof, comprueba la propiedad prototype del constructor dado y comprueba la cadena [[Prototype]] del objeto dado. En otras palabras, no depende de la propiedad constructor.

Todo muy bonito, pero tu puedes aún romperlo si lo intentas con ganas:

 function MyConstructor() {}
 var myobject = new MyConstructor();
 MyConstructor.prototype = {};
 
 [ myobject instanceof MyConstructor,     // false !
   myobject.constructor == MyConstructor, // true !
   myobject instanceof Object ]           // true

Así quedan las cadenas de prototipos tras ejecutar eso:



Los constructores no son clases

En un sistema de objetos basado en clases, las clases típicamente heredan de otras, y los objetos son instancias de esas clases. Los métodos y propiedades que son compartidos entre instancias son (al menos conceptualmente) propiedades de clases. Las propiedades (y para algunos lenguajes, métodos) que no deben ser compartidos son propiedades de los mismos objetos.

Los constructores de javascript no hacen nada parecido: de hecho los constructores tienen su propia cadena [[Prototype]] completamente separada de la cadena [[Prototype]] de objetos que ellos inicializan.

Los constructores no funcionan como inicializadores basados en clases

Una llamada a constructor asocia un nuevo objeto con un [[Prototype]]. La función constructor podría establecer propiedades adicionales sobre el objeto. Las llamadas a constructor no llaman constructores "heredados", y no deberían porque el [[Prototype]] del objeto (el prototype del constructor) se supone que está compartido y (probablemente) ya inicializado.

Los constructores sólo son funciones

Cualquier función definida por el usuario en javascript automáticamente obtiene una propiedad prototype que, en cambio, tiene una propiedad constructor que se refiere (de vuelta) a la función.

Cualquier función definida por el usuario puede ser llamada como constructor anteponiéndole new a la llamada. Esto pasará un nuevo objeto this a la funcion, y su propiedad [[Prototype]] se inicializará a la propiedad prototype de la función.

Notas al pie

[1] Hay clases definidas por el sistema: Function, Object, Array, Class, RegExp, Boolean, Number, Math, Date, Error y String. Un usuario no puede añadir una nueva clase, aunque el sistema podría definir más. Nótese que estas clases no son las misma que los objetos predefinidos (constructores) con el mismo nombre, y no pueden ser directamente accedidas de ninguna forma.

Un constructor definido por el usuario que no devuelve explícitamente algo más siempre devuelve un objeto de la clase Object.

Referencias

A comp.lang.javascript question

Subject: "x.constructor == Foo" vs "x instanceof Foo". Message-ID: <fniu6a$2cn$1@reader2.panix.com> http://groups.google.com/group/comp.lang.javascript/msg/102ab20c68aa738f

Ecma-262

Standard ECMA-262. ECMAScript Language Specification 3rd edition (December 1999) http://www.ecma-international.org/publications/standards/Ecma-262.htm

Flanagan 2006

JavaScript: The Definitive Guide, Fifth Edition. ISBN 10: 0-596-10199-6 | ISBN 13:9780596101992

Author & copyright

(c)2008 Joost Diepenmaat, Zeekat Softwareontwikkeling.

2 comentarios:

Anónimo dijo...

Quiero iniciarme a fondo en javascript, pero al tratar de programar con objetos, no puedo comprender cual es la importacion de la palabra prototype...
He buscado por la red y solo encuentro la libreria Prototype, pero nada qeu me saque de mis dudas....
Si tiene algun documento donde se explica con mas detalle el uso de la palabra prototype, estaria agradecido me lo hciera llegar...
Mi correo es r_quinteroa@hotmail.com..

Saludos..

Anónimo dijo...

gustirijillo ^^

Publicar un comentario en la entrada

Últimos links en indiza.com