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.

El model d'objectes en detall

JavaScript és un llenguatge orientat a objectes basat en prototipus en comptes d'estar basat en classes. Degut a aquesta diferència pot resultar menys evident la forma en que JavaScript permet crear jerarquies d'objectes i proporciona herència de propietats i els seus valors. Aquest capítol pretén aclarir aquest tema.

Aquest capítol assumeix que teniu certa familiaritat amb JavaScript i sabeu crear objectes simples mitjançant funcions.

Llenguatges basats en classes vs llenguatges basats en prototipus

Els llenguatges orientats a objectes basats en classes, com ara Java i C++, es basen en el concepte de tenir dues entitats diferents: classes i instàncies.

  • Una classe defineix totes les propietats (considerem que a Java els mètodes i camps, així com els membres a C++, són propietats) que caracteritzen un determinat conjunt d'objectes. Una classe és quelcom abstracte, en contraposició amb un membre específic del grup d'objectes que defineix. Per exemple, la classe Employee podria representar el conjunt de tots els empleats.
  • Una instància, d'altra banda, és la instanciació d'una classe, és a dir, un dels seus membres. Per exemple, Victoria podría ser una instància de la classe Employee, representant un individu concret com a empleat. Una instància te exactament les propietats de la seva classe pare, ni més ni menys.

Un llenguatge basat en prototipus, com ara JavaScript, no fa aquesta distinció: simplement disposa d'objectes. Un llenguatge basat en prototipus, però, té el concepte de objecte prototipus, un objecte que s'utilitza com a motlle del qual obtindre les propietats inicial d'un nou objecte. Qualsevol objecte pot especificar les seves pròpies propietats, ja sigui quan aquest és creat o be en temps d'execució. A més, qualsevol objecte pot ser associat com el prototipus d'un altre objecte, tot permetent al segon objecte compartir les propietats del primer.

Definir una classe

En llenguatge basats en classes hom defineix una classe de forma separada com una definició de classe. En aquesta definició es poden especificar mètodes especials, anomenats constructors, que serveixen per a crear instàncies de la classe a la que pertanyen. Un mètode constructor pot especificar valors inicials per a les propietats d'una instància, així com realitzar altres tasques en temps de creació de l'objecte. Per a crear instàncies d'una classe s'utilitza l'operador new en associació amb un mètode constructor de la classe.

JavaScript empra un model similar tot i que no disposa de definicions de classes separades dels constructors. En comptes d'això, es defineix una funció constructora per a crear objectes amb un conjunt específic de propietats i valors. Qualsevol funció de JavaScript pot ser utilitzada com a constructor. S'utilitza l'operador new amb una funció constructora per a crear un nou objecte.

Subclasses i herència

En els llenguatges basats en classes, es poden crear jerarquies de classes mitjançant la definició de les classes. En una definició de classe es pot especificar que la nova classe és una subclasse d'una classe que ja existeix. La subclasse hereta totes les propietats de la superclasse i pot afegir noves propietats adicionals o bé modificar les heretades. Per exemple, suposem que la classe Employee només inclou les propietats name i department, i la classe Manager és una subclasse de Employee que afegeix la propietat reports. En aquest cas una instància de la classe Manager tindria totes tres propietats: name, department i reports.

JavaScript implementa l'herència permetent associar un objecte prototipus a qualsevol funció constructora. D'aquesta forma es pot crear exactament l'exemple Employee — Manager, tot i que fent servir una terminologia lleugerament diferent. Primerament cal definir la funció constructora de Employee, tot especificant les propietats nom i departament. Seguidament es defineix la funció constructora de Manager, tot cridant el constructor de Employee i especificant la propietat informes. Finalment s'assigna el nou objecte derivat de Employee.prototype com el prototype de la funció constructora de Manager. Llavors quan es crea un nou objecte de tipus Manager aquest hereta les propietats name i department de l'objecte Employee.

Afegir i esborrar propietats

En llenguatges basats en classes les classes són normalment creades en temps de compilació i mentre que les instàncies de la classe poden ser creades tant en temps de compilació com en temps d'execució. No és possible canviar el nombre o el tipus de les propietats d'una classe després d'haver definit la classe. En JavaScript, però, és possible afegir o eliminar propietats de qualsevol objecte en temps d'execució. Si s'afegeix una propietat a un objecte que està sent utilitzat com a prototipus d'un conjunt d'objectes, aquests objectes també rebran la nova propietat.

Resum de diferències

La taula següent proporciona un breu resum d'algunes d'aquestes diferències. La resta del capítol descriu els detalls d'utilitzar constructors i prototipus a JavaScript per a crear jerarquies d'objectes i ho compara amb com es faria amb Java.

Taula 8.1 Comparació de sistems d'objectes basats en classes (Java) i basats en prototipus (JavaScript)
Basat en classes (Java) Basat en prototipus (JavaScript)
Les classes i les instàncies són entitats diferents. Qualsevol objecte pot heretar d'un altre objecte.
Es defineix una classe mitjançant una definició de classe; Es crea una instància d'una classe mitjançant un dels seus mètodes constructors. Es defineix i es crea un conunt d'objectes mitjançant funcions constructores.
Es crea un sol objecte amb l'operador new. Es crea un sol objecte amb l'operador new.

Les jerarquies d'objectes es construeixen mitjançant definicions de classes per a especificar subclasses de les classes existents.

Les jerarquies d'objectes es construeixen tot assignant un objecte com el prototipus associat amb una funció constructora.

Hereta les propietats tot següient la cadena de classes.

Hereta les propietats tot seguint la cadena de prototipus.

La definició d'una classe especifica totes les propietats per totes les instàncies de la classe. No és possible afegir propietats dinàmicament en temps d'execució.

Una funció constructora o un prototipus especifiquen un conjunt inicial de propietats. És possible afegir o eliminar propietats de forma dinàmica per a objectes individuals o bé de tot el conjunt d'objectes.

 

L'exemple Employee

La resta d'aquest capítol utilitza la jerarquia de Employee que es mostra en la següent figura.

Figura 8.1: Una jerarquia d'objectes simple

Aquest exemple utilitza els objectes següents:

  • Employee té les propietats name (el valor de la qual és una cadena buida per defecte) i dept (el valor de la qual és "general" per defecte).
  • Manager està basada en Employee. Afegeix la propietat reports (el valor de la qual és un array buit per defecte, i destinada a emmagatzemar un array d'objectes de tipus Employee com a valor).
  • WorkerBee també està basada en Employee. afegeix la propietat projects (el valor de la qual és un array buit per defecte, i destinada a emmagatzemar un array de strings com a valor).
  • SalesPerson està basada en WorkerBee. Afegeix la propietat quota (el valor de la qual és 100 per defecte). També sobreescriu la propietat dept amb el valor "sales", tot indicant que tots els objectes del tipus salespersons pertanyen al mateix department.
  • Engineer està basat en WorkerBee. Afegeix la propietat machine (el valor de la qual és una string buida per defecte) i també sobreescriu la propietat dept amb el valor "engineering".

Crear una jerarquia

Hi ha diverses formes de definir les funcions constructores adequades per a implementar una jerarquia de Employee. Quina escollir-ne una depen en gran mesura del que es vol fer en l'aplicació.

Aquesta secció mostra com utilitzar definicions molt simples (i comparativament poc flexibles) per a demostrar com funciona l'herència. En aquestes definicions no es pot especificar cap valor de les propietats al crear un objecte. L'objecte creat recentment senzillament té els valors per defecte, que es poden canviar després.

En una aplicació real probablement es definirien constructors que permetessin especificar valors per les propietats de l'objecte al crear-lo (vegeu Constructors més flexibles per a més informació). De moment aquestes definicions simples demostren com funciona l'herència.

Les següents definicions en Java i JavaScript de Employee són similars. L'única diferència és que a Java s'ha d'especificar el tipus de cada propietat de forma explícita mentre que a JavaScript no (degut a que Java és un llenguatge de tipatge fort mentre que JavaScript és un llenguatge de tipatge dèbil).

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

Les definicions de Manager i WorkerBee mostren com especificar quin és l'objecte pare dins la cadena d'herència. A JavaScript s'afegeix una instància prototipus com a valor de la propietat prototype de la funció constructora. Això es pot fer en qualsevol moment després d'haver definit el constructor. A Java, en canvi, s'ha d'especificar la superclasse dins la definició de la classe, i aquesta, en conseqüència, no pot canviar-se fora de la definició de classe.

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

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

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

Les definicions de Engineer i SalesPerson creen objectes que descendeixen de WorkerBee i, per extensió, de Employee. Un objecte d'algun d'aquests tipus té les propietats de tots els objectes de damunt seu a la cadena d'herència. A més les definicions sobreescriuen els valors heretats de la propietat dept amb els nous valors especificats per a aquests objectes.

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

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

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

A l'utilitzar aquestes definicions les instàncies creades per aquests objectes tenen els valors per defecte apropiats per a les seves propietats. La figura 8.3 mostra com utilitzar aquestes definicions de JavaScript per a crear objectes nous i mostra els valors de les propietats per als nous objectes.

El terme instància té un significat tècnic específic per a llenguatges basats en classes. En aquests llenguatges una instància és una instanciació individual d'una classe i és fundamentalment diferent de la classe. A JavaScript, "instància" no té aquest significat tècnic perquè aquest no diferencia entre classes i instàncies, tot i que parlant informalment de JavaScript es pot utilitzar el terme "instància" per a indicar un objecte creat utilitzant una funció constructora particular. Així, en aquest exemple, hom pot dir informalment que jane és una instància d'Engineer. De la mateixa manera, tot i que els termes fill, predecessor i descendent no tenen cap significat formal a JavaScript, aquests poden emprar-se de forma informal per a fer referència a objectes més amunt o bé més avall de la cadena de prototipus.

 

figure8.3.png
Figura 8.3: Creació d'objectes amb definicions simples

Propietats d'un objecte

Aquesta secció explica com els objectes hereten propietats d'altres objecte mitjançant la cadena de prototipus i què succeeix quan s'afegeix una propietat en temps d'execució.

Heretar propietats

Suposem que creem l'objecte mark a partir de WorkerBee (tal i com es mostra a la Figura 8.3) amb la sentència següent:

var mark = new WorkerBee;

Quan JavaScript es troba amb l'operador new, aquest crea un nou objecte genèric i passa aquest nou objecte com a valor de la paraula clau this a la funció constructora de WorkerBee. La funció constructora assigna el valor de la propietat project de forma explícita, i assigna implícitament el valor WorkerBee.prototype a la propietat interna __proto__ (el nom d'aquesta propietat conté dos guions baixos tant al principi com al final). La propietat __proto__ determina la cadena de prototipus que s'emprarà per a retornar valors de propietats. Un cop assignades aquestes propietats, JavaScript retorna el nou objecte i la sentència d'assignació associa l'objecte a la variable mark.

Aquest procés no assigna valors de forma explícita a l'objecte mark (valors locals) per a les propietats que mark hereta a través de la cadena de prototipus. Quan s'intenta obtindre el valor d'una propietat JavaScript primer mira si el valor existeix en aquest objecte. Si és així, aquest és el valor retornat. Si el valor no existeix de forma local, JavaScript recorre la cadena prototipus (mitjançant la propietat __proto__). Si un objecte de la cadena prototipus té un valor per assignat a la propietat, aquest és el valor retornat. Si aquesta propietat no es troba a cap objecte de la cadena prototipus JavaScript determina que l'objete no té aquesta propietat. D'aquesta forma, l'objete mark té les següents propietats i valors:

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

L'objecte mark hereta els valors per a les propietats name i dept de l'objecte prototipus a mark.__proto__. La propietat projects rep un valor local designat pel constructor WorkerBee. Així és com funciona l'herència de propietats i els seus valors a JavaScript. Es poden trobar alguns detalls d'aquest procès a Un segon cop d'ull a l'herència de propietats.

Com que aquests constructors no permeten valors per a l'instància aquesta informació és genèrica. Els valors de les propietats són compartits per defecte, compartits per tots els nous objectes creats a partir de WorkerBee. Per suposat, es poden canviar els valors inicials d'aquestes propietats. A continuació es mostra com canviar informació específica:

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

Afegir propietats

A JavaScript, es poden afegir propietats a qualsevol objecte en temps d'execució. No hi ha cap limitació que ens forci a utilitzar només les propietats que ofereix la funció constructora. Per a afegir una propietat específicament a només un objecte, s'assigna un valor a l'objecte de la manera següent:

mark.bonus = 3000;

Ara l'objecte mark té una propietat anomenada bonus, però cap altre objecte de tipus WorkerBee tindrà aquesta propietat.

Si s'afegeix una nova propietat a un objecte que és emprat com a prototipus per una funció constructora, la propietat s'afegeix a tots els objectes que hereten propietats del prototipus. Per exemple, podem afegir la propietat specialty a tots els empleats amb la sentència següent:

Employee.prototype.specialty = "none";

Un cop la sentència s'ha executat l'objecte mark també disposa de la propietat specialty, amb el valor "none". La figura següent mostra l'efecte d'afegir aquesta propietat al prototipus Employee i tot seguit sobreescriure-la per al prototipus Engineer.


Figura 8.4: Afegir propietats

Constructors més flexibles

Les funcions constructores emprades fins ara no permeten especificar els valors de les propietats al crear una instància. Tal i com succeeix amb Java, es poden passar arguments als constructors per a inicialitzar els valors de les propietats de les instàncies a crear. La figura següent mostra una forma d'aconseguir-ho.


Figura 8.5: Determinar propietats en un constructor, primera aproximació

La taula següent mostra les definicions tant en Java com en JavaScript d'aquests objectes.

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;
   }
}

Aquestes definicions de JavaScript utilitzen un modisme especial per a assignar els valors per defecte:

this.name = name || "";

L'operador lògic OR de JavaScript (||) avalua el primer argument. Si aquest argument esdevé cert, l'operador el retorna. En cas constrari l'operador retorna el valor del segon argument. Així, aquesta línia de codi comprova que la propietat name tingui un valor útil. Si és així, assigna aquest valor a this.name. En cas contrari, assigna una string buida a this.name. Aquest capítol utilitza aquest modisme per a abreujar tot i que pot resultar desconcertant a primera vista.

Això pot no tindre el comportament esperat si la funció constructora es crida amb arguments que s'avaluen a fals (com 0 (zero) o la cadena buida (""). En aquest cas el valor per defecte serà l'escollit.

Amb aquestes definicions, al crear una instància d'un objecte, podem especificar valors per a les propietats locals definides. Tal i com es mostra a la Figura 8.5, es pot utilitzar la sentència següent per a crear un now Engineer:

var jane = new Engineer("belau");

Les propietats de Jane ara són:

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

Fixeu-vos que amb aquestes definicions no és possible especificar un valor inicial per a propietats heretades, com ara name. Si es vol especificar un valor inicial per a propietats heretades a JavaScript és necesari afegir més codi a la funció constructora.

Fins ara les funcions constructores han creat objectes genèrics i han pogut assignar valors a les propietats locals del nou objecte. El constructor mateix també pot afegir més propietats mitjançant la crida a la funció constructora d'un objecte més adalt en la cadena de propotitpus. La figura següent mostra aquestes noves definicions.


Figura 8.6 Especificar propietats al consctructor, segona aproximació

Fem un cop d'ull a una d'aquestes definicions en detall. Aquesta és la nova definició del constructor de Engineer:

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

Suposem que creem un nou objecte de tipus Engineer de la forma següent:

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

JavaScript realitza els següents passos:

  1. L'operador new crea un objecte genèric i assigna Engineer.prototype a la propietat __proto__ d'aquest nou objecte.
  2. L'operador new passa el nou objecte al constructor de Engineer com a valor de la paraula clau this.
  3. El constructor crea una nova propietat anomenada base per a aquest objecte i assigna el valor del constructor de WorkerBee a aquesta propietat. Això fa que el constructor WorkerBee pugui ser emprat com un mètode de l'objecte Engineer. El nom de la propietat base no és especial. Es pot emprar qualsevol nom de propietat que sigui vàlid; base ha estat escollit perquè simplement és adient per al seu propòsit.
  4. El constructor crida el mètode base, tot passant-li com a arguments dos dels arguments passats al constructor ("Doe, Jane" and ["navigator", "javascript"]) i també la string "engineering". Al passar "engineering" explícitament al constructor tots els objectes de tipus Engineer tindran el mateix valor per a la propietat hertada dept, i aquest valor sobreescriurà el valor heretat de Employee.
  5. Com que base és un mètode de Engineer, durant la crida a base JavaScript assigna a la paraula clau this l'objecte creat al pas 1. D'aquesta forma, la funció WorkerBee passa els arguments "Doe, Jane" i "engineering" a la funció constructora Employee. Un cop la funció constructora Employee ha retornat, la funció WorkerBee utilitza l'argument restant per a assignar un valor a la propietat projects.

  6. Un cop el mètode base ha retornat, el constructor Engineer initialitza la propietat machine de l'objecte al valor "belau".
  7. Un cop el constructor ha retornat, JavaScript assigna el nou objecte a la variable jane.

Es pot pensar que, debug a que s'ha cridat al constructor WorkerBee des de dins el constructor Engineer, s'ha inicialitzat la herència de forma adequada per als objectes Engineer. No és el cas. Cridar el constructor WorkerBee ens asegura que l'objecte Engineer comença amb les propietats especificades per a totes les funcions constructores que es criden. Però, si més tard afegim propietats als prototipus Employee o WorkerBee, aquestes propietats no són heretades per l'objecte Engineer. Per exemple, suposem que tenim les sentències següents:

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";

L'objecte jane no hereta la propietat specialty. Es fa necesari inicialitzar el prototipus per a assegurar que hi haurà herència de forma dinàmica. Suposem que tenim les sentències següents:

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";

Ara el valor de la propietat specialty de l'objecte  jane és "none".

Una altra forma d'heretar és mitjançant els mètodes call() / apply(). Les funcions següents són equivalents:

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 || "";
}

Utilitzar el mètode de JavaScript call() esdevé en una implementació més neta perquè ja no es requereix la propietat base.

Un segon cop d'ull a la herència de propietats

Les seccions anteriors han descrit com utilitzar els constructors de JavaScript i els prototipus per crear jerarquies i proporcionar herència. Aquesta secció explica alguns detalls que poden no haver estat obvis en les seccions anteriors.

Valors locals versus valors heretats

A l'accedir a la propietat d'un objecte, JavaScritp segueix els passos següents, tal i com s'ha descrit abans en aquest mateix capítol:

  1. Es comprova si el valor existeix de forma local. En cas afirmatiu es retorna aquest valor.
  2. Si ho hi ha valor local, es cerca a la cadena de prototipus (mitjançant la propietat __proto__).
  3. Si un objecte de la cadena de prototipus té un valor per la propietat especificada, es retorna aquest valor.
  4. Si no es troba aquesta propietat, s'infereix que l'objecte no té aquesta propietat.

El resultat d'aquests passos depèn de com s'hagin definit les coses. L'exemple original disposaba de les següents definicions:

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

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

Amb aquestes definicions, suposem que creem amy com una instància de WorkerBee amb la sentència següent:

var amy = new WorkerBee;

L'objecte amy té una propietat local, projects. Els valors per a les propietats name i dept no són locals per a amy i en conseqüència s'obtenen a partir de la propietat __proto__ de amy. Així, amy té els següents valors a les seves propietats:

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

Ara suposem que canviem el valor de la propietat name en el prototipus associat a Employee:

Employee.prototype.name = "Unknown"

A primer cop d'ull podeu pensar que el nou valor es propagarà afectant a totes les instàncies de Employee. Tanmateix això no succeeix.

Quan es crea qualsevol instància de l'objecte Employee, aquesta instància un valor local per a la propietat name (la cadena buida). Això vol dir que quan s'assigna el prototipus WorkerBee al crear un nou objecte Employee, WorkerBee.propotype té un valor local per a la propietat name. Així, quan JavaScript cerca la propietat name per a l'objecte amy (una instància de WorkerBee), JavaScript trova la variable local per a aquesta propietat a WorkerBee.prototype. Degut a això no cerca la propietat més enllà dins la cadena de prototipus, cap a Employee.prototype.

Si es vol canviar el valor de la propietat d'un objecte en temps d'execució i que el nou valor sigui heretat per tots els descendents d'un objecte, no es pot definir la propietat dins la funció constructora de l'objecte. En comptes d'això, s'afegeix al constructor del protipus associat. Per exemple, suposem que canviem el codi anterir pel següent:

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 aquest cas, la propietat name de amy esdevé "Unknown".

Tal i com mostren els exemples, si es vol tenir un valor predefinit per a les propietats d'un objecte i es vol poder canviar aquest valor predefinit en temps d'execució, les propietats s'han d'assignar al constructor del prototipus i no a la funció constructora de l'objecte mateix.

Determining instance relationships

Property lookup in JavaScript looks within an object's own properties and, if the property name is not found, it looks within the special object property __proto__. This continues recursively; the process is called "lookup in the prototype chain".

The special property __proto__ is set when an object is constructed; it is set to the value of the constructor's prototype property. So the expression new Foo() creates an object with __proto__ == Foo.prototype. Consequently, changes to the properties of Foo.prototype alters the property lookup for all objects that were created by new Foo().

Every object has a __proto__ object property (except Object); every function has a prototype object property. So objects can be related by 'prototype inheritance' to other objects. You can test for inheritance by comparing an object's __proto__ to a function's prototype object. JavaScript provides a shortcut: the instanceof operator tests an object against a function and returns true if the object inherits from the function prototype. For example,

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

For a more detailed example, suppose you have the same set of definitions shown in Inheriting properties. Create an Engineer object as follows:

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

With this object, the following statements are all 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;

Given this, you could write an instanceOf function as follows:

function instanceOf(object, constructor) {
   object = object.__proto__;
   while (object != null) {
      if (object == constructor.prototype)
         return true;
      if (typeof object == 'xml') {
        return constructor.prototype == XML.prototype;
      }
      object = object.__proto__;
   }
   return false;
}
Note: The implementation above checks the type of the object against "xml" in order to work around a quirk of how XML objects are represented in recent versions of JavaScript. See errada 634150 if you want the nitty-gritty details.

Using the instanceOf function defined above, these expressions are true:

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

But the following expression is false:

instanceOf (chris, SalesPerson)

Informació global als constructors

A l'hora de crear constructors cal anar amb compte si es manega informació global dins el constructor. Per exemple, suposem que volem crear un identificador (ID) únic que serà assignat automàticament per a cada nou Employee. Podríem utilitzar la definició següent per a Employee:

var idCounter = 1;

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

Amb aquesta definició quan es crea un nou Employee, el constructor assigna la següent ID seqüencialment i llavors incrementa el valor del contador global de ID. Així, suposant el codi següent, tenim que victoria.id val 1 i harry.id val 2:

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

 

At first glance that seems fine. However, idCounter gets incremented every time an Employee object is created, for whatever purpose. If you create the entire Employee hierarchy shown in this chapter, the Employee constructor is called every time you set up a prototype. Suppose you have the following code:

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");

Ara suposem que les definicions omeses aquí tenen la propietat base i criden el constructor que tenen damunt de la cadena de prototipus. En aquest cas, quan es crea l'objecte mac, mac.id rep el valor de 5.

Depenent de l'aplicació, el fet que el contador s'incrementi aquests cops adicionals pot tenir o no conseqüències. Si el valor exacte d'aquest contador és important una possible solució pot ser el constructor següent:

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

Al crear una instància de Employee per a ser emprada com a prototipus, no es passen paràmetres al constructor. Aquesta definició del constructor no assigna un valor a la id i no actualitza el contador quan el constructor no rep paràmetres. Així, per a que un Employee rebi una id, requerim que rebi un nom. Executar l'exemple anterior amb el nou constructor esdevindrà en que mac.id rebi el valor 1.

No hi ha herència múltiple

Alguns llenguatges orientats a objectes permeten l'herència múltiple, és a dir, que un objecte pugui heretar propietats i valors de pares que no tenen res a veure entre ells. JavaScript no suporta l'herència múltiple.

L'herència de valors de propietats succeeix en temps d'execució i és proporcionada per fet que JavaScript cerqui un valor dins la cadena de prototipus de l'objecte. Com que un objecte disposa d'un sol prototipus associat a ell JavaScript no pot heretar dinàmicament de més d'una cadena de prototipus.

A JavaScript, però, es pot fer que una funció constructora cridi a més d'una funció constructora dins d'ella. Això crea la ilusió d'herència múltiple. Per exemple, suposem les sentències següents:

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")

Ara suposem que la definició de WorkerBee és l'emprada abans en aquest capítol. En aquest case l'objecte dennis rep les propietats següents:

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

Així tenim que dennis rep la propietat hobby del constructor Hobbyist. Tot i així, suposem llavors que afegim una propietat al constructor del prototipus Hobbyist:

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

L'objecte dennis no hereta aquesta nova propietat.

Document Tags and Contributors

 Contributors to this page: fscholz, enTropy
 Last updated by: fscholz,