Please note, this is a STATIC archive of website developer.mozilla.org from November 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

Detalles del modelo de objetos

JavaScript es un lenguaje basado en objetos que en lugar de estar basado en clases, se basa en prototipos. Debido a esta diferencia, puede resultar menos evidente que JavaScript te permite crear jerarquías de objetos y herencia de propiedades y de sus valores. Este capítulo intenta clarificar estos puntos.

Este capítulo asume que tienes alguna familiaridad con JavaScript y que has usado funciones de JavaScript para crear objetos sencillos.

Lenguajes basados en clases vs. basados en prototipos

Los lenguajes orientados a objetos basados en clases, como Java y C++, se basan en la existencia de dos entidades distintas: clases e instancias.

  • Una clase define todas las propiedades (considerando como propiedades los métodos y campos de Java, o los miembros de C++) que caracterizan un determinado conjunto de objetos. Una clase es una entidad abstracta, y no un ejemplar del conjunto de objetos que describe. Por ejemplo, la clase  Employee puede representar el conjunto de todos los empleados.
  • Una instancia, sin embargo, es la instanciación de una clase; es decir, uno de sus miembros. Por ejemplo, Victoria podría ser una instancia de la clase Employeerepresentando a un individuo particular como un empleado. Una instancia tiene exactamente las propiedades de su clase padre (ni más, ni menos).

Un lenguaje basado en prototipos, como JavaScript, no hace esta distincion: simplemente tiene objetos. Un lenguaje basado en prototipos tiene la nocion del objeto prototipico, un objeto que se utiliza como una plantilla a partir de la cual se obtiene el conjunto inicial de propiedades de un objeto. Cualquier objeto puede especificar sus propias propiedades, ya sea cuando es creado inicialmente o en tiempo de ejecucion. Ademas, cualquier objeto puede ser utilizado como el prototipo de otro objeto, permitiendo al segundo objeto compartir las propiedades del primero.

Definicion de una clase

En los lenguajes basados en clases defines una clase en una definicion de clase separada. En esa definicion puedes especificar metodos especiales, llamados constructores, para crear instancias de la clase. Un metodo constructor puede especificar valores iniciales para las propiedades de la instancia y realizar otro procesamiento de inicializacion apropiado en el momento de la creacion. Puedes utilizar el operador new junto al constructor para crear instancias de clase.

JavaScript sigue un modelo similar, pero sin tener la definicion de clase separada del constructor. En su lugar, se define una funcion constructor para crear objetos con un conjunto inicial de propiedades y valores. Cualquier funcion JavaScript puede utilizarse como constructor. Se utiliza el operador new con una funcion constructor para crear un nuevo objeto.

Subclases y herencia

En un lenguaje basado en clases se crea una jerarquía de clases a través de la jerarquía de clases. En una definición de clase se puede especificar que la nueva clase es una subclase de alguna clase ya existente. Esta subclase hereda todas las propiedades de la superclase y adicionalmente puede añadir nuevas propiedades o modificar las que hereda. Por ejemplo, supongamos que la clase Employee tiene sólo las propiedades name y dept, y que Manager es una subclase de Employee que añade la propiedad reports. En este caso, una instancia de la clase Manager tendría las tres propiedades: name, dept, y reports.

JavaScript implementa la herencia permitiendo al programador asociar un objeto prototípico con cualquier función constructor. De esta forma puedes crear una relación entre Employee y Manager, pero usando una terminología diferente, sin clases. En primer lugar, se define la función constructor  Employee, especificando las propiedades name y dept. Después hay que definir la función constructor  Manager, especificando la propiedad  reports. Por último, hay que asignar un nuevo objeto Employee como el prototype de la función constructor Manager. De esta forma, cuando se crea un nuevo Manager, hereda las propiedades name y dept del objeto Employee.

Añadir y quitar propiedades

En lenguajes basados en clases se crea típicamente una clase en tiempo de compilación y entonces se instancian instancias de la clase, ya sea en tiempo de compilación o en tiempo de ejecución. No se puede cambiar el número o el tipo de propiedades de una clase una vez que ha sido definida. En JavaScript sin embargo, en tiempo de ejecución se pueden añadir y quitar propiedades a un objeto. Si se añade una propiedad a un objeto que está siendo utilizado como el prototipo de otros objetos, los objetos para los que es un prototipo también tienen la nueva propiedad añadida.

Resumen de las diferencias

La siguiente tabla muestra  un pequeño resumen de algunas de estas diferencias. El resto de este capítulo describe los detalles del uso de los constructores JavaScript y los prototipos para crear una jerarquía de objetos, y compara esta forma de herencia no basada en clases con la basada en clases que utiliza Java.

Tabla 8.1 Comparación de los sistemas de objetos basados en clases  (Java) y basados en prototipos (JavaScript)
Basado en clases (Java) Basado en prototipos (JavaScript)
La clase y la instancia son entidades distintas Todos los objetos son instancias
Define una clase en la definición de clase; se instancia una clase con los métodos constructores. Define y crea un conjunto de objetos con funciones constructoras.
Se crea un objeto con el operador new. Igual.
Se construye una jerarquía de objetos utilizando la definición de las clases para definir subclases de clases existentes..

Se construye una jerarquía de objetos mediante la asignación de un objeto como el prototipo asociado a una función constructor.

Se heredan propiedades siguiendo la cadena de clases. Se heredan propiedades siguiendo la cadena de prototipos.
La definición de una clase especifica  todas las propiedades de todas las instnacias de esa clase. No se pueden añadir propiedades dinámicamente en tiempo de ejecución.

El conjunto inicial de propiedades lo determina la función constructor y el prototipo. Se pueden añadir y quitar propiedades dinámicamente a objetos específicos o a un conjunto de objetos.

Ejemplo: employee

El resto de este capitulo utiliza la jerarquia employee que se muestra en la siguiente figura.

Figura 8.1: Una jerarquía de objetos sencilla

Este ejemplo utiliza los siguientes objetos:

  • Employee tiene las propiedades name (cuyo valor por defecto es un string vacío) y dept (cuyo valor por defecto es "general").
  • Manager está basado en Employee. Añade la propiedad reports (cuyo valor por defecto es un array vacío, en la que se pretende almacenar un array de objetos Employee como su valor).
  • WorkerBee también está basado en Employee. Añade la propiedad projects (cuyo valor por defecto es un array vacío en el que se pretende almacenar un array de strings como su valor).
  • SalesPerson está basado en WorkerBee. Añade la propiedad quota (cuyo valor por defecto es 100). También reemplaza la propiedad dept con el valor "sales", indicando que todas las salespersons están en el mismo departamento.
  • Engineer se basa en WorkerBee. Añade la propiedad machine (cuyo valor por defecto es un string vacío) y también reemplaza la propiedad  dept con el valor "engineering".

Creacion de la jerarquia

Hay varias formas de definir funciones constructor para implementar la jerarquia Employee. Elegir una u otra forma depende sobre todo de lo que quieras poder hacer con tu aplicacion.

Esta seccion muestra como utilizar definiciones muy sencillas (y comparativamente inflexibles) para mostrar como hacer funcionar la herencia. En estas definiciones no se pueden especificar valores de propiedades cuando se crea un objeto. El nuevo objeto que se crea simplemente obtiene valores por defecto, que pueden cambiarse posteriormente. La figura 8.2 muestra la jerarquia con estas definiciones sencillas.

En una aplicacion real probablemente definiriamos constructores que permitirian proporcionar valores a las propiedades en el momento de la creacion del objeto (para mas informacion ver  More Flexible Constructors ). Por ahora, estas definiciones sencillas nos sirven para mostrar como funciona la herencia.

figure8.2.png
Figura 8.2: Definiciones de los objetos de la jerarquia Employee

Las definiciones Java and JavaScript de Employee que se muestran a continuacion son similares. Las unicas diferencias son que tienes que especificar el tipo de cada propiedad en Java, mientras que en JavaScript no, y que en Java tienes que crear un metodo constructor esplicitamente en la clase.

JavaScript Java
function Employee () {
  this.name = "";
  this.dept = "general";
}
public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this.name = "";
      this.dept = "general";
   }
}

Las definiciones de Manager y WorkerBee ilustran la diferencia a la hora de especificar el siguiente objeto en la jerarquía de herencia. En JavaScript se añade una instancia prototípica como el valor de la propiedad prototype en la función constructor. Puede hacerse en cualquier momento una vez definido el constructor. En Java se especifica la superclase en la definición de la clase. No se puede cambiar la superclase fuera de la definición de la clase.

JavaScript Java
function Manager () {
  this.reports = [];
}
Manager.prototype = new Employee;

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee;
public class Manager extends Employee {
   public Employee[] reports;
   public Manager () {
      this.reports = new Employee[0];
   }
}

public class WorkerBee extends Employee {
   public String[] projects;
   public WorkerBee () {
      this.projects = new String[0];
   }
}

Las definiciones de Engineer y SalesPerson crean objetos que descienden de WorkerBee y por tanto de Employee. Un objeto de estos tipos tiene propiedades de todos los objetos por encima de él en la caena. Además, estas definiciones reemplazan los valores heredados de la propiedad dept con nuevos valores específicos de estos objetos.

JavaScript Java
function SalesPerson () {
   this.dept = "sales";
   this.quota = 100;
}
SalesPerson.prototype = new WorkerBee;

function Engineer () {
   this.dept = "engineering";
   this.machine = "";
}
Engineer.prototype = new WorkerBee;
public class SalesPerson extends WorkerBee {
   public double quota;
   public SalesPerson () {
      this.dept = "sales";
      this.quota = 100.0;
   }
}

public class Engineer extends WorkerBee {
   public String machine;
   public Engineer () {
      this.dept = "engineering";
      this.machine = "";
   }
}

Mediante estas definiciones se pueden crear instancias de estos objetos, que adquieren los valores por defecto de sus propiedades. La figura 8.3 muestra el uso de estas definiciones JavaScript para crear nuevos objetos y muestra los valores de las propiedades de estos nuevos objetos.

Nota: El término instancia tiene un significado técnico preciso en los lenguajes basados en clases. En estos lenguajes una instancia es una instanciación individual de una clase y es fundamentalmente distinto a una clase. En JavaScript, una "instancia" no tiene este significado técnico porque JavaScript no diferencia entre clases e instancias. Sin embago, cuando hablemos de JavaScript, "instancia" puede utilizarse informalmente para significar un objeto creado usando una función constructor particular. En este ejemplo, podríamos decir informalmente que jane es una instancia de  Engineer. Del mismo modo, aunque los términos padre, hijo, ancestro y descendiente no tienen significados formales en JavaScript, se usan informalmente para referirse a objetos por encima o por debajo en la cadena de prototipos.

figure8.3.png
Figura 8.3: Creación de objetos mediante definiciones simples

Propiedades de objetos

Esta sección describe cómo heredan los objetos sus propiedades de otros objetos en la cadena de prototipos y qué ocurre cuando se añade una propiedad en tiempo de ejecución.

Herencia de propiedades

Supongamos que hemos creado el objeto mark como un WorkerBee (como se muestra en la Figura 8.3) con la siguiente sentencia:

var mark = new WorkerBee;

Cuando el intérprete de JavaScript encuentra el operador new crea un nuevo objeto genérico accesible a través del palabra this en la función constructor de WorkerBee. La función constructor asigna explícitamente el valor de la propiedad  projects property, e implícitamente le asigna al valor de la propiedad interna __proto__ el valor de WorkerBee.prototype. (Este nombre de propiedad tiene dos guiones bajos al principio y al final). La propiedad  __proto__ determina la cadena de prototipos que se usará para devolver valores de las propiedades cuando se acceda a ellas. Una vez que estas propiedades tienen sus valores, JavaScript devuelve el nuevo objeto y la sentencia de asignación asigna el nuevo objeto ya inicializado a la variable mark.

Este proceso no asigna explicitamente valores al objeto mark (valores locales) para las propiedades que mark hereda de la cadena de prototipos. Cuando se pregunta por el valor de una propiedad, JavaScript primero comprueba si existe un valor para esa propiedad en objeto. Si existe, se devuelve ese valor. Si el valor no esta ahi localmente, JavaScript comprueba la cadena de prototipos (usando la propiedad  __proto__). Si un objeto en la cadena de prototipos tiene un valor para esa propiedad, se devuelve ese valor. Si no existe en ningun objeto de la cadena de prototipos un valor para esa propiedad, JavaScript dice que el objeto no tiene esa propiedad. De esta forma, el objeto mark tiene las siguientes propiedades y valores:

mark.name = "";
mark.dept = "general";
mark.projects = [];

El objeto mark hereda valores para las propiedades name y dept su objeto prototipico que enlaza en mark.__proto__. Se le asigna un valor local la propiedad projects a traves del constructor WorkerBee.  De esta forma se heredan propiedades y sus valores en JavaScript. En la seccion Property Inheritance Revisited se discuten algunos detalles de este proceso.

Debido a que estos constructores no permiten especificar valores especificos de instancia, esta informacion es generica. Los valores de las propiedades son los valores por omision, compartidos por todos los objetos nuevos creados a partir de WorkerBee. Por supuesto se pueden cambiar despues los valores de estas propiedades. Por ejemplo podriamos dar valores con informacion especifica a mark de la siguiente forma:

mark.name = "Doe, Mark";
mark.dept = "admin";
mark.projects = ["navigator"];

Añadir propiedades

En JavaScript se pueden añadir propiedades a los objetos en tiempo de ejecucion. No se esta limitado a utilizar solo las  propiedades que proporciona la funcion constructor. Para añadir una propiedad que es especifica para un objeto determinado, se le asigna un valor a la propiedad del objeto de la siguiente forma:

mark.bonus = 3000;

Ahora el objeto mark tiene una propiedad bonus, pero ningun otro objeto creado con la funcion constructor WorkerBee tiene esta propiedad.

Si se añade una nueva propiedad a un objeto que se esta utilizando como el prototipo de una funcion constructor, se provoca que dicha propiedad se añada a todos los objetos que hereden propiedades de dicho prototipo. Por ejemplo, puede añadirse una propiedad specialty a todos los empleados con la siguientes sentencia:

Employee.prototype.specialty = "none";

Cuando JavaScript ejecuta esta sentencia, los objetos preexistentes como mark tambien pasan a tener la propiedad specialty con el valor "none". La siguiente figura muestra el efecto de añadir esta propiedad al prototipo  Employee y despues reemplazarlo por el prototipo Engineer.


Figura 8.4: Añadir propiedades

Constructores mas flexibles

Las funciones constructor que se han mostrado hasta ahora no permiten especificar valores a las propiedades cuando se crea una instancia. Al igual que en Java, se pueden proporcionar argumentos a los constructores para inicializar los valores de las propiedades de las instancias. La siguiente figura muestra una forma de hacerlo.


Figura 8.5: Especificacion de propiedades en un construccion, toma 1

La siguiente tabla muestra las definiciones Java y JavaScript para estos objetos.

JavaScript Java
function Employee (name, dept) {
  this.name = name || "";
  this.dept = dept || "general";
}
public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this("", "general");
   }
   public Employee (String name) {
      this(name, "general");
   }
   public Employee (String name, String dept) {
      this.name = name;
      this.dept = dept;
   }
}
function WorkerBee (projs) {
  this.projects = projs || [];
}
WorkerBee.prototype = new Employee;
public class WorkerBee extends Employee {
   public String[] projects;
   public WorkerBee () {
      this(new String[0]);
   }
   public WorkerBee (String[] projs) {
      projects = projs;
   }
}

 
function Engineer (mach) {
   this.dept = "engineering";
   this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
public class Engineer extends WorkerBee {
   public String machine;
   public Engineer () {
      dept = "engineering";
      machine = "";
   }
   public Engineer (String mach) {
      dept = "engineering";
      machine = mach;
   }
}

Estas definiciones JavaScript realizan un uso idiomatico especial para asignar valores por defecto:

this.name = name || "";

El operador logico OR de JavaScript (||) evalua su primer argumento. Si dicho argumento se convierte a true, el operador lo devuelve. Si no, el operador devuelve el valor del segundo argumento. Por tanto, esta linea de codigo compureba si name tiene un valor util para la propiedad name, en cuyo caso asigna a this.name este valor. En caso contrario asigna a  this.name el string vacio. Este capitulo emplea este uso idiomatico por concision. Sin embargo puede resultar chocante las primeras veces que se lee un codigo asi.

Nota: Esto puede no funcionar como se espera si la funcion constructor es llamada con argumentos que se convierten a false (como 0 (cero) y el string vacio (""). En este caso el valor proporcionado en la llamada al constructor no resulta el elegido
.

Con estas definiciones, cuando se crea una instancia de un objeto, se pueden especificar valores para las propiedades definidas localmente. Tal como se muestra en Figura 8.5, se puede utilizar la siguiente sentencia para crear un nuevo Engineer:

var jane = new Engineer("belau");

Ahora las propiedades de jane son:

jane.name == "";
jane.dept == "engineering";
jane.projects == [];
jane.machine == "belau"

Nótese que con estas definiciones no se puede dar un valor inicial a las propiedades heredadas como name. Si se quiere especificar un valor inicial para las propiedades heredadas en  JavaScript hay que añadir más código a la función constructora.

Hasta ahora, la función constructora ha creado un objeto genérico y ha especificado propiedades y valores locales para el nuevo objeto. Se puede hacer que el constructor añada más propiedades llamando directamente a la función constructor de un objeto que esté más arriba en la cadena de prototipos. La siguiente figura muestra estas definiciones.


Figura 8.6 Especificación de propiedades en un constructor, toma 2

Veamos los detalles de una de estas definiciones. A continuación se muestra la definición del constructor Engineer:

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}

Supongamos que se crea un nuevo Engineer de esta forma:

var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");

JavaScript sigue los siguientes pasos:

  1. El operador new crea un nuevo objeto genérico y le asigna su propiedad __proto__ a Engineer.prototype.
  2. El operador new pasa el nuevo objeto al constructor Engineer como el valor de la palabra reservada this.
  3. El constructor crea una nueva propiedad llamada base para ese objeto y le asigna el valor del constructor WorkerBee. Esto hace que el constructor WorkerBee pase a ser un método del objeto Engineer. El nombre de esta propiedad (base) no es especial. Puede usarse cualquier nombre de propiedad, si bien base evoca el uso que se le va a dar.
  4. El constructor llama al método base, pasándole como argumentos dos de los argumentos que se le han pasado al constructor ("Doe, Jane" y ["navigator", "javascript"]) y también el string "engineering". Usar explícitamente "engineering" en el constructor indica que todos los objetos Engineer tienen el mismo valor para la propiedad heredada dept, y este valor reemplaza el valor heredado de Employee.

  5. Como base es un método de Engineer, en la llamada a base, JavaScript liga la palabra this al objeto creado en el paso 1. De esta forma, la función WorkerBee a su vez pasa los argumentos "Doe, Jane" y "engineering" a la función constructor Employee. Cuando retorna la llamada de la función constructor Employee, la función WorkerBee utiliza el resto de argumentos para asignarle un valor a la propiedad projects.
  6. Cuando retorna de la llamada al método base, el constructor Engineer inicializa la propiedad machine del objeto con el valor"belau".
  7. Cuando retorna del constructor, JavaScript asigna el nuevo objeto a la variable jane.

Podria pensarse que por haber llamado al constructor  WorkerBee desde el  constructor Engineer ya se ha establecido la herencia para los objetos  Engineer. Pero no es asi. Llamar al constructor WorkerBee hace que un  objeto Engineer comience con las propiedades especificadas en los constructores. Pero si luego se añaden propiedades a los prototipos de Employee o de WorkerBee, estas propiedades no se heredan por los objetos Engineer. Por ejemplo, veamos las siguientes sentencias:

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

El objeto jane no hereda la propiedad specialty añadida al prototipo de Employee. Sigue siendo necesario dar valor al prototipo de Employee para que la herencia buscada se establezca. Veamos las siguientes sentencias:

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

Ahora el valor de la propiedad specialty del objeto jane si es "none".

Otra forma de llamar al constructor es mediante el uso de los metodos call() / apply():

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
function Engineer (name, projs, mach) {
  WorkerBee.call(this, name, "engineering", projs);
  this.machine = mach || "";
}

Usando el metodo JavaScript call() se llama a la funcion constructor  WorkBee como un metodo, pasandole explicitamente this. El efecto es el mismo que el producido al llamar al constructor a traves de la propiedad  base: en la llamada a WorkBee, this está ligado al objeto que se está creando en Engineer.

Mas sobre herencia de propiedades

En las anteriores secciones se ha descrito como los constructores de JavaScript y los prototipos proporcionan jerarquias de herencia. Esta seccion muestra algunos detalles que no aparecieron antes.

Valores locales frente a valores heredados

Cuando se accede a una propiedad de un objeto, JavaScript realiza estos pasos, tal como se describio mas arriba en este capitulo:

When you access an object property, JavaScript performs these steps, as described earlier in this chapter:

  1. Se comprueba si el valor existe localmente. Si existe, se devuelve ese valor.
  2. Si no hay un valor local, se comprueba la cadena de prototipos (usando la propiedad __proto__).
  3. Si algun objeto en la cadena de prototipos tiene un valor para la propiedad especificada, se devuelve ese valor.
  4. Si no se encuentra la propiedad en la cadena de prototipos, el objeto no tiene la propiedad.

El resultado de estos pasos depende de las definiciones realizadas.:

function Employee () {
  this.name = "";
  this.dept = "general";
}

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee;

Con estas definiciones, supongamos que se crea amy como una instancia de WorkerBee con la siguiente sentencia:

var amy = new WorkerBee;

El objeto amy tiene una propiedad local, projects. Los valores de las propiedades name y dept no son locales a amy y por eso se obtienen de la propiedad __proto__ del objeto. Por ello, amy tiene estos valores en sus propiedades:

amy.name == "";
amy.dept == "general";
amy.projects == [];

Supongamos que ahora cambiamos el valor de la propiedad name en el prototipo de Employee:

Employee.prototype.name = "Unknown"

A primera vista cabria esperar que el nuevo valor se propagase hacia abajo a todas las instancias de Employee. Pero no es eso lo que ocurre.

Cuando se crea cualquier instancia del objeto Employee object, esa instancia obtiene un valor local para la propiedad name (la cadena vacia). Esto significa que cuando se da valor al prototipo de WorkerBee mediante la creacion de un nuevo objeto Employee, WorkerBee.prototype tiene un valor local para la propiedad name. Por tanto, cuando JavaScript busca la propiedad name del objeto amy (una instancia de WorkerBee), JavaScript encuentra el valor local de esa propiedad en WorkerBee.prototype. Por tanto no busca mas arriba en la cadena hasta Employee.prototype.

Si quieres cambiar el valor de una propiedad de un objeto en tiempo de ejecucion y conseguir que el nuevo valor sea heredado por todos los descendientes del objeto, no puedes definir la propiedad en la funcion constructor del objeto. En su lugar, la tienes que añadir al prototipo asociado al constructor. Por ejemplo, supongamos que cambiamos el codigo anterior por este otro:

function Employee () {
  this.dept = "general";
}
Employee.prototype.name = "";

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee;

var amy = new WorkerBee;

Employee.prototype.name = "Unknown";

En este caso, la propiedad name de amy si pasa a ser "Unknown" tras la ultima sentencia.

Tal como muestran estos ejemplos, si lo que se quiere es tener valores por defecto para propiedades de objetos, y se quiere poder cambiar los valores por defecto en tiempo de ejecucion, hay que asignar las propiedades al prototipo del constructor, y no asignarlas dentro de la funcion constructor.

Como determinar las relaciones entre instancias

La busqueda de propiedades en la cadena de prototipos comienza en las propiedades locales del objeto y si no se encuentran localmente, se busca a traves de la propiedad  __proto__ del objeto. La busqueda continua recursivamente, conociendose como "busqueda en la cadena de prototipos".

Esta propiedad especial __proto__ que tienen los objetos recibe su valor en el momento en el que el obejto es construido; se le asigna el valor de la propiedad prototype de la funcion constructor usada para crear el objeto. Por ello, la expresion new Foo() crea un objeto con __proto__ == Foo.prototype. Por tanto, los cambios que se realicen en las propiedades de Foo.prototype alteraran la busqueda de propiedades  de todos los objetos que se crearon mediante new Foo().

Todo objeto tiene una propiedad __proto__ (salvo Object); toda funcion tiene una propiedad prototype. Es asi como los objetos pueden relacionarse mediante 'herencia de prototipos' con otros objetos. Se puede comprobar la herencia comparando el valor de la propiedad __proto__ con el valor de prototype de una funcion constructor. JavaScript proporciona un atajo: el operador instanceof compara un objeto con una funcion constructor y devuelve true si el objeto hereda del prototipo de la funcion. Por ejemplo,

var f = new Foo();
var isTrue = (f instanceof Foo);

Para ver un ejemplo mas detallado, supongamos que tenemos el conjunto de definiciones mostrado en Inheriting Properties. Creamos un objeto Engineer somo sigue:

var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");

En este objeto, las siguientes sentencias son todas true:

chris.__proto__ == Engineer.prototype;
chris.__proto__.__proto__ == WorkerBee.prototype;
chris.__proto__.__proto__.__proto__ == Employee.prototype;
chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;
chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;

Por tanto podria escribirse una funcion instanceOf asi:

function instanceOf(object, constructor) {
   while (object != null) {
      if (object == constructor.prototype)
         return true;
      if (typeof object == 'xml') {
        return constructor.prototype == XML.prototype;
      }
      object = object.__proto__;
   }
   return false;
}
Nota: La implementacion anterior compara el tipo del objeto con  "xml" para soslayar un pequeño problema sobre como se representan los objetos XML en las versiones recientes de JavaScript. Ver bug 634150 para entender los detalles.

Usando esta funcion instanceOf estas expresiones son todas true:

instanceOf (chris, Engineer)
instanceOf (chris, WorkerBee)
instanceOf (chris, Employee)
instanceOf (chris, Object)

Pero la siguiente expresion es false:

instanceOf (chris, SalesPerson)

Informacion global en los constructores

Cuando se crean constructores hay que tener especial cuidado si se asigna informacion global en el constructor. Por ejemplo, supongamos que se quiere tener un ID unico que se asigne automaticamente a cada nuevo empleado. Se podria utilizar la siguiente definicion de Employee:

var idCounter = 1;

function Employee (name, dept) {
   this.name = name || "";
   this.dept = dept || "general";
   this.id = idCounter++;
}

Con esta definicion, cuando se crea un nuevo Employee, el constructor le asigna el siguiente ID y luego incrementa el contador global ID. Por tanto, tras ejecutar el siguiente codigo, victoria.id sera 1 y harry.id sera 2:

var victoria = new Employee("Pigbert, Victoria", "pubs")
var harry = new Employee("Tschopik, Harry", "sales")

A primera vista puede parecer razonable. Sin embargo, idCounter se incrementa siempre que se crea un nuevo objeto Employee, cualquiera que sea su proposito. Si se crea toda la jerarquia Employee mostrada en este capitulo, el constructor Employee es llamado cada vez que se asigna valor a un prototipo. Supongamos que tenemos el siguiente codigo:

var idCounter = 1;

function Employee (name, dept) {
   this.name = name || "";
   this.dept = dept || "general";
   this.id = idCounter++;
}

function Manager (name, dept, reports) {...}
Manager.prototype = new Employee;

function WorkerBee (name, dept, projs) {...}
WorkerBee.prototype = new Employee;

function Engineer (name, projs, mach) {...}
Engineer.prototype = new WorkerBee;

function SalesPerson (name, projs, quota) {...}
SalesPerson.prototype = new WorkerBee;

var mac = new Engineer("Wood, Mac");

Supongamos ademas que las definiciones que se omiten tienen la propiedad base y se llama al constructor que tienen encima en la cadena de prototipos. En este caso, cuando se llega a crear el objeto mac, mac.id es 5.

Dependiendo de la aplicacion, puede o no importar que el contador se haya incrementado esas veces extra. En caso de que importe, una solucion es utilizar este constructor:

function Employee (name, dept) {
   this.name = name || "";
   this.dept = dept || "general";
   if (name)
      this.id = idCounter++;
}

Cuando se crea una instancia de Employee para usarla como prototipo no se especifican argumentos en el constructor. Mediante esta definicion del constructor, cuando no se proporcionan argumentos el constructor no asigna un valor al id y no actualiza el contador. Por tanto,  para que se asigne a un Employee un id, hay que especificar un name al employee. En este caso mac.id seria 1.

No existe herencia multiple

Algunos lenguajes orientados a objetos tienen herencia multiple. Es decir, un objeto puede heredar las propiedades y valores de varios objetos padre distintos. JavaScript no proporciona herencia multiple.

La herencia de valores de propiedades se produce en tiempo de ejecucion por JavaScript buscando en la cadena de prototipos de un objeto para encontrar un valor. Debido a que un objeto tiene un solo prototipo asociado, JavaScript no puede heredar dinamicamente de mas de una cadena de prototipos.

En JavaScript se puede hacer que desde una funcion constructor llame a mas de una funcion constructor. Esto ofrece algo parecido a la herencia multiple. Veamos un ejemplo:

function Hobbyist (hobby) {
   this.hobby = hobby || "scuba";
}

function Engineer (name, projs, mach, hobby) {
   this.base1 = WorkerBee;
   this.base1(name, "engineering", projs);
   this.base2 = Hobbyist;
   this.base2(hobby);
   this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;

var dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo")

Ademas, supongamos que la definicion de WorkerBee que se uso antes en este capitulo. En este caso, el objeto dennis tiene estas propiedades:

dennis.name == "Doe, Dennis"
dennis.dept == "engineering"
dennis.projects == ["collabra"]
dennis.machine == "hugo"
dennis.hobby == "scuba"

Por tanto dennis obtiene la propiedad hobby del constructor Hobbyist constructor. Sin embargo, supongamos que se añade despues una propiedad al prototipo del constructor de Hobbyist:

Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"]

El objeto dennis no hereda esta nueva propiedad porque no esta en su cadena de prototipos.

g

Etiquetas y colaboradores del documento

Etiquetas: 
 Colaboradores en esta página: montogeek, fscholz, teoli, pheras
 Última actualización por: montogeek,