ISBN 966-7343-29-5 К.305

УДК 531.0
ББК 22.311
  К.305

Клиентский JavaScript. Руководство по Использованию

Глава 8
Объектная Модель. Детали.

JavaScript это язык на базе прототипов, а не на базе классов. Из-за этого в JavaScript менее очевидно, как создается иерархия объектов и происходит наследование свойств и их значений. В данной главе делается попытка прояснит этот вопрос.

Предполагается, что Вы уже немного знакомы с JavaScript и что Вы использовали функции JavaScript для создания простых объектов.

В главе имеются следующие разделы:

Языки на базе классов и языки на базе прототипов

Объектно-ориентированные языки на базе классов, такие как Java и C++, помогут понять две разных сущности: класс и экземпляр.

  • Класс определяет все свойства (методы и поля - в Java, члены, то есть свойства, в C++), характеризующие определенный набор объектов. Класс это абстракция, в отличие от конкретного члена набора объектов, который он описывает. Например, класс Employee может представлять набор всех employees/служащих.
  • Экземпляр, с другой стороны, это инстанциация класса; то есть это один из членов класса. Например, Victoria может быть экземпляром класса Employee, представляя конкретную персону как employee. Экземпляр имеет в точности все свойства своего класса-родителя (ни более, ни менее).

В языке на базе прототипов, таком как JavaScript, нет такого отличия: здесь просто имеются объекты. В языке на базе прототипов имеется понятие объект-прототип\prototypical object, это объект, используемый как шаблон, по которому получаются начальные свойства объекта. Любой объект может специфицировать свои собственные свойства либо при создании, либо на этапе прогона. Кроме того, любой объект может быть ассоциирован как прототип другого объекта для совместного использования свойств первого объекта.

Определение класса

В языках на базе классов Вы определяете класс в отдельном определении класса\class definition. Здесь Вы можете специфицировать специальные методы, называемые конструкторами, которые создают экземпляры данного класса. Метод-конструктор может специфицировать начальные значения свойств экземпляров и выполнять иную работу, необходимую на этапе создания экземпляров. Вы используете операцию new вместе с методом-конструктором для создания экземпляров класса.

В JavaScript используется похожая модель, но отсутствует определение класса, отдельное от конструктора. Вместо этого Вы определяете функцию-конструктор для создания объектов с определенным набором начальных значений и свойств. Любая функция JavaScript может использоваться как конструктор. Вы используете операцию new вместе с функцией-конструктором для создания нового объекта.

Подклассы и наследование

В языках на основе классов Вы создаете иерархию классов через определения классов. В определении класса Вы можете специфицировать, что новый класс является подклассом\subclass уже существующего класса. Подкласс наследует все свойства своего родителя (суперкласса) и может добавлять новые свойства или изменять наследуемые. Например, класс Employee имеет только свойства name и dept, а Manager является подклассом класса Employee и добавляет свойство reports. В этом случае экземпляры класса Manager будут иметь три свойства: name, dept и reports.

В JavaScript реализовано наследование, что дает возможность ассоциировать объект-прототип с любой функцией-конструктором. Так, Вы можете воспроизвести тот же пример Employee-Manager, но используя несколько иную терминологию. Сначала Вы определяете функцию-конструктор Employee, специфицируя свойства name и dept. Затем Вы определяете функцию-конструктор Manager, специфицируя свойство reports. Наконец, Вы присваиваете новый объект Employee как prototype функции-конструктору Manager. Затем создаете новый объект Manager, который наследует свойства name и dept из объекта Employee.

Добавление и удаление свойств

В языках на основе классов Вы обычно создаете класс на этапе компиляции, а затем инстанциируете экземпляры на этапе компиляции или на этапе прогона. Вы не можете изменить количество или тип свойств класса, после того как Вы его определили. В JavaScript, однако, Вы можете на этапе прогона добавлять или удалять свойства любого объекта. Если Вы добавляете свойство объекту, который используется как прототип для набора объектов, то объекты, для которых данный объект является прототипом, также получают новое свойство.

Отличия. Резюме.

В таблице дано краткое резюме по некоторым отличительным особенностям языков. Остальная часть данной главы посвящена деталям использования конструкторов и прототипов языка JavaScript для создания иерархии и делаются сравнения с аналогичными действиями в Java.

Таблица 8.1 Сравнение объектных систем языков на базе классов (Java)
и языков на базе прототипов (JavaScript)
На базе классов (Java) На базе прототипов (JavaScript)

Класс и экземпляр это разные сущности.

Все объекты являются экземплярами.

Класс определяется в определении класса; инстанциация (создание экземпляров) производится методами-конструкторами.

Набор объектов создается и определяется функциями-конструкторами.

Одиночный объект создается операцией new.

То же самое.

Иерархия объектов создается через использование определения класса для определения подклассов существующих классов.

Иерархия объектов создается путем присвоения объекта как прототипа, ассоциированного с функцией-конструктором.

Свойства наследуются по цепочке классов.

Свойства наследуются по цепочке прототипов.

Определение класса специфицирует все свойства всех экземпляров данного класса. Свойства нельзя добавлять динамически на этапе прогона.

Функция-конструктор или прототип специфицируют начальный набор свойств. Свойства могут добавляться динамически отдельному объекту или целому набору объектов.

Пример Employee

В остальной части данной главы используется иерархия employee, показанная на рисунке.

Рисунок 8.1   Простая иерархия объектов

В этом примере используются следующие объекты:

  • Employee имеет свойства name (значение по умолчанию - пустая строка) и dept (значение по умолчанию - "general").
  • Manager базируется на Employee. Он добавляет свойство reports (значение по умолчанию - пустой массив, предназначенный для хранения массива Employee-объектов как значений).
  • WorkerBee также основан на Employee. Он добавляет свойство projects (значение по умолчанию - пустой массив, предназначенный для хранения массива строк как значений).
  • SalesPerson базируется на WorkerBee. Он добавляет свойство quota (значение по умолчанию - 100). Также переопределяет свойство dept значением "sales", указывая, что все менеджеры по продажам/salespersons находятся в этом отделе.
  • Engineer базируется на WorkerBee. Он добавляет свойство machine (значение по умолчанию - пустая строка), а также переопределяет свойство dept property значением "engineering".

Создание иерархии

Есть несколько способов создания функции-конструктора для реализации иерархии Employee. Выбор конкретного способа во многом зависит от того, что должно будет делать Ваше приложение.

В данном разделе показано, как использовать очень простое (и сравнительно негибкое) определение для демонстрации создания работающей иерархии. В этих определениях Вы не можете специфицировать значения свойств, когда создаете объект. Вновь создаваемый объект просто получает все значения по умолчанию, которые Вы можете изменить позднее. Рисунок 8.2 показывает иерархию с несколькими простыми определениями.

В реальном приложении, Вы, вероятно, определите конструктор, который позволит задавать начальные значения свойств на этапе создания объекта (см. "Более Гибкие Конструкторы"). Итак, эти простые определения демонстрируют появление иерархии.

Рисунок 8.2   Определения объекта Employee

Следующие определения Employee в Java и в JavaScript похожи. Единственное отличие - Вы должны специфицировать тип каждого свойства в Java, но не в JavaScript, и Вы должны создать конкретный метод-конструктор для Java-класса.

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

Определения для Manager и WorkerBee показывают отличия в специфицировании объекта, стоящего выше в цепочке иерархии. В JavaScript Вы добавляете экземпляр-прототип как значение свойства prototype функции-конструктора. Вы можете сделать это в любое время после определения конструктора. В Java Вы специфицируете суперкласс в определении класса. Вы не можете изменить суперкласс вне определения класса.

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

Определения Engineer и SalesPerson создают объекты, которые являются потомками WorkerBee и, следовательно, потомками Employee. Объект этих типов имеет свойства всех объектов, стоящих выше него в цепочке иерархии. Кроме того, эти определения переопределяют наследуемое значение свойства dept новыми значениями, специфичными для этих объектов.

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

Используя эти определения, Вы можете создавать экземпляры этих объектов, которые получают значения по умолчанию своих свойств. Рисунок 8.3 иллюстрирует использование этих определений JavaScript для создания новых объектов и показывает значения свойств новых объектов.

ПРИМЕЧАНИЕ: Термин экземпляр\instance имеет специфическое техническое значение в языках программирования на базе классов. В них экземпляр это отдельный член класса, фундаментально отличающийся от класса. В JavaScript "экземпляр/instance" не имеет этого технического значения, поскольку JavaScript не различает классы и экземпляры. Однако, говоря о JavaScript, "экземпляр" может использоваться неформально для обозначения объекта, созданного с использованием конкретной функции-конструктора. Так, в данном примере, Вы можете неформально сказать, что jane это Engineer-экземпляр. Аналогично, хотя термины parent\родитель, child\дочерний, ancestor\предок и descendant\потомок не имеют формальных значений в JavaScript, Вы можете использовать их неформально для обращения к объектам выше или ниже по цепочке прототипов.

Рисунок 8.3   Создание объектов с помощью простых определений

Свойства объекта

В этом разделе обсуждается, как объекты наследуют свойства других объектов в цепочке прототипов и что происходит при добавлении свойства на этапе прогона.

Наследование свойств

Предположим, Вы создаете объект mark как WorkerBee, как показано на Рисунке 8.3, следующим оператором:

mark = new WorkerBee;

Когда JavaScript видит операцию new, он создает новый родовой/generic объект и передает этот новый объект как значение ключевого слова this функции-конструктору WorkerBee. Функция-конструктор явным образом устанавливает свойства projects. Она также устанавливает в значение внутреннего свойства __proto__ значение WorkerBee.prototype. (Имя этого свойства имеет по два символа подчеркивания в начале и в конце.) Свойство __proto__ определяет цепочку прототипов, используемую для возвращения значения свойства. После установки этих свойств JavaScript возвращает новый объект, и операция присвоения устанавливает переменную mark в этот объект.

Этот процесс не помещает явным образом значения в объект mark (локальные значения) для свойств, которые mark наследует из цепочки прототипов. Когда Вы запрашиваете значение свойства, JavaScript сначала проверяет, существует ли значение в данном объекте. Если значение существует, оно возвращается. Если значение локально отсутствует, JavaScript проверяет цепочку прототипов (используя свойство __proto__). Если объект в цепочке прототипов имеет значение для этого свойства, возвращается это значение. Если такое свойство не найдено, JavaScript сообщает, что такого свойства у объекта нет. Отсюда, объект mark имеет следующие свойства и значения:

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

Объект mark наследует значения свойств name и dept из объекта-прототипа в mark.__proto__. Локальное значение свойства projects присваивается конструктором WorkerBee. Это дает Вам наследование свойств и их значений в JavaScript. Некоторые тонкости этого процесса обсуждаются в разделе "И Снова о Наследовании Свойств".

Поскольку эти конструкторы не предоставляют поддержки значений, специфичных для экземпляра, эта информация является общей/generic. Значения свойств являются значениями по умолчаниями, используемыми всеми новыми объектами, создаваемыми из WorkerBee. Вы можете, конечно, изменить значение любого из этих свойств. Так, Вы можете задать специфическую информацию для mark таким образом:

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

Добавление свойств

В JavaScript Вы можете добавить свойства любому объекту на этапе прогона. Вы не ограничены использованием только свойств, предоставленных функцией-конструктором. Для добавления свойств, специфичных для отдельного объекта, Вы присваиваете значение объекту:

mark.bonus = 3000;

Теперь объект mark имеет свойство bonus, но другие WorkerBee этого свойства не имеют.

Если Вы добавляете новое свойство объекту, который используется как функция-конструктор, Вы добавляете это свойство всем объекта, наследующим свойства от данного прототипа. Например, Вы можете добавить свойство specialty ко всем employees следующим оператором:

Employee.prototype.specialty = "none";

Как только JavaScript выполнит этот оператор, объект mark также получит свойство specialty со значением "none". На рисунке показан эффект от добавления этого свойства прототипу Employee и последующее переопределение его для прототипа Engineer.

Рисунок 8.4   Добавление свойств

Более гибкие конструкторы

Показанная функция-конструктор не дает возможности создавать экземпляры. Как и в Java, Вы можете предоставлять аргументы конструктору для инициализации значений свойств экземпляров. На рисунке показан один из способов сделать это.

Рисунок 8.5   Специфицирование свойств в конструкторе, этап 1

В таблице показаны определения Java и JavaScript для этих объектов.

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 (name) {
      this(name, "general");
   }
   public Employee (name, 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) {
      this.projects = projs;
   }
}
function Engineer (mach) {
   this.dept = "engineering";
   this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
public class Engineer extends WorkerBee {
   public String machine;
   public WorkerBee () {
      this.dept = "engineering";
      this.machine = "";
   }
   public WorkerBee (mach) {
      this.dept = "engineering";
      this.machine = mach;
   }
}

Эти определения JavaScript используют специальную идиому для установки значений по умолчанию:

this.name = name || "";

Логическая операция ИЛИ (||) в JavaScript вычисляет первый аргумент. Если он конвертируется в true, операция возвращает его. Иначе операция возвращает значение второго аргумента. Следовательно, эта строка кода проверяет, имеет ли name подходящее значение для свойства name. Если имеет, это значение устанавливается в this.name. Иначе - в this.name устанавливается пустая строка. Эта идиома используется в данной главе для краткости; однако она может, на первый взгляд, показаться непонятной.

При помощи этих определений Вы можете при создании объекта специфицировать значения локально определяемых свойств. Как видно на Рисунке 8.5, Вы можете использовать следующий оператор для создания нового Engineer:

jane = new Engineer("belau");

Jane-свойства будут теперь:

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

Заметьте, что с помощью этих определений Вы не можете специфицировать начальное значение наследуемого свойства, такого как name. Если Вы хотите специфицировать начальные значения наследуемых свойств в JavaScript, Вы должны добавить дополнительный код в функцию-конструктор.

Итак, функция-конструктор создала общий (родовой/generic) объект и затем специфицировала локальные свойства и значения для этого нового объекта. Вы можете заставить конструктор добавить дополнительные свойства, непосредственно вызвав функцию-конструктор для объекта, стоящего выше в цепочке прототипов. На рисунке показаны эти новые определения.

Рисунок 8.6   Специфицирование свойств в конструкторе, этап 2

Рассмотрим одно из этих определений детально. Вот новое определение для конструктора Engineer:

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

Предположим, Вы создаете новый Engineer-объект:

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

JavaScript выполняет следующее:

  1. Операция new создает общий объект и присваивает его свойство __proto__ Engineer.prototype.
  2. Операция new передает этот новый объект Engineer-конструктору как значение ключевого слова this.
  3. Конструктор создает новое свойство base для этого объекта и присваивает значение конструктора WorkerBee свойству base. Это делает конструктор WorkerBee методом Engineer-объекта.
  4. Имя свойства base не является специальным. Вы может использовать любое верное имя свойства; base просто подходит по смыслу.

  5. Конструктор вызывает метод base, передавая ему в качестве аргументов два аргумента из переданных конструктору ("Doe, Jane" и ["navigator", "javascript"]), а также строку "engineering". Явное использование "engineering" в конструкторе указывает, что все Engineer-объекты имеют одно значение для наследуемого свойства dept, и что это значение переопределяет значение, наследуемое из Employee.
  6. Поскольку base это метод из Engineer, внутри вызова base JavaScript связывает ключевое слово this с объектом, созданным на Этапе 1. Таким образом, функция WorkerBee в свою очередь передает аргументы "Doe, Jane" и ["navigator", "javascript"] в функцию-конструктор Employee. После возвращения из функции-конструктора Employee, функция WorkerBee использует оставшийся аргумент для установки свойства projects.
  7. После возвращения из метода base, конструктор Engineer инициализирует свойство machine объекта значением "belau".
  8. После возвращения из конструктора, JavaScript присваивает новый объект переменной jane.

Вы можете подумать, что, вызвав конструктор WorkerBee из конструктора Engineer, Вы установили соответствующее наследование для Engineer-объектов, но это не так. Вызов конструктора WorkerBee гарантирует, что объект Engineer стартует со свойствами, специфицированными во всех функциях-конструкторах, которые вызываются. Однако, если Вы позднее добавите свойства в прототипы Employee или WorkerBee, эти свойства не будут наследоваться объектом Engineer. Например, у Вас имеются операторы:

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

Объект jane не наследует свойство specialty. Вам все еще необходимо явным образом изменить прототип для обеспечения динамического наследования. предположим, что у Вас имеются другие операторы:

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

Теперь значение свойства specialty объекта jane равно "none".

И снова о наследовании свойств

В предыдущем разделе показано, как реализуются иерархия и наследование в конструкторах и прототипах JavaScript.
В данном разделе обсуждаются некоторые тонкости, не очевидные при предыдущем рассмотрении.

Локальные и наследуемые значения

Когда Вы выполняете доступ к свойству объекта, JavaScript выполняет следующие шаги, как уже было показано в этой главе:

  1. Проверяется, существует ли значение локально. Если да, это значение возвращается.
  2. Если локального значения нет, проверяется цепочка прототипов (через использование свойства __proto__).
  3. Если объект в цепочке прототипов имеет значение для специфицированного свойства, это значение возвращается.
  4. Если такое свойство не найдено, объект не имеет данного свойства.

Результат выполнения этих шагов зависит от того, каковы были определения.
Оригинальный пример имел такие определения:

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

С помощью этих определений Вы, предположим, создаете amy как экземпляр WorkerBee таким оператором:

amy = new WorkerBee;

Объект amy имеет одно локальное свойство, projects. Значения свойств name и dept не являются локальными по отношению к amy и поэтому получены из свойства __proto__ объекта amy.
Таким образом, amy имеет следующие значения свойств:

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

Теперь предположим, что Вы изменяете значение свойства name в прототипе, ассоциированном с Employee:

Employee.prototype.name = "Unknown"

На первый взгляд можно ожидать, что новое значение передается всем экземплярам Employee. Однако это не так.

Когда Вы создаете любой экземпляр объекта Employee, этот экземпляр получает локальное значение свойства name (пустую строку). Это означает, что, если Вы устанавливаете прототип WorkerBee через создание нового объекта Employee, WorkerBee.prototype имеет локальное значение для свойства name. Следовательно, когда JavaScript ищет свойство name объекта amy (экземпляра WorkerBee), JavaScript находит значение для этого свойства в WorkerBee.prototype. Он, следовательно, не просматривает далее цепочку до Employee.prototype.

Если Вы хотите изменить значение свойства объекта на этапе прогона и иметь новое значение наследуемым всеми потомками этого объекта, Вы не определяете это свойство в функции-конструкторе объекта. Вместо этого Вы добавляете это свойство в ассоциированный прототип конструктора. Например, Вы изменяете предыдущий код на такой:

function Employee () {
   this.dept = "general";
}
Employee.prototype.name = "";
function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;
amy = new WorkerBee;
Employee.prototype.name = "Unknown";

Теперь свойство name объекта amy становится "Unknown".

Как видно из этих примеров, если Вы хотите иметь значения по умолчанию для свойств объекта и хотите иметь возможность изменять значения по умолчанию на этапе прогона программы, Вы должны устанавливать свойства в прототипе конструктора, а не в самой функции-конструкторе.

Определение взаимоотношений экземпляров

Возможно, Вы захотите узнать, какие объекты находятся в цепочке прототипов данного объекта, чтобы определить, из какого объекта данный объект наследует свойства. В языках на базе классов Вы можете использовать для этого операцию instanceof. В JavaScript нет instanceof, но Вы сами можете написать такую функцию.

Как сказано в "Наследовании Свойств", если Вы используете операцию new с функцией-конструктором для создания новых объектов, JavaScript устанавливает в свойство __proto__ нового объекта значение свойства prototype функции-конструктора. Вы можете использовать это для проверки цепочки прототипов.

Например, у Вас есть уже показанный ранее набор определений с соответственно установленными прототипами. Создайте объект __proto__ так:

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

С эти объектом все следующие операторы 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;

Имея это, Вы можете написать функцию instanceOf:

function instanceOf(object, constructor) { 
   while (object != null) {
      if (object == constructor.prototype)
         return true;
      object = object.__proto__;
   }
   return false;
}

При таком определении все следующие выражения true:

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

Но следующее выражение будет false:

instanceOf (chris, SalesPerson)

Глобальная информация в конструкторах

При создании конструкторов Вы должны быть осторожны, если устанавливаете глобальную информацию в конструкторе. Например, у Вас имеется уникальный идентификатор ID, присваиваемый автоматически каждому новому employee. Вы может использовать такое определение для Employee:

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

Теперь, ели Вы создаете новый Employee, конструктор присваивает ему следующий ID и выполняет инкремент глобального счетчика ID. Поэтому, если Ваш следующий оператор будет таким, как ниже приведенный, victoria.id будет 1, а harry.id будет 2:

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

На первый взгляд все отлично. Однако idCounter увеличивается всякий раз при создании Employee-объекта. Если Вы создаете всю иерархию Employee, показанную в данной главе, Employee-конструктор вызывается при каждой установке прототипа. Предположим, у Вас имеется такой код:

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;
mac = new Engineer("Wood, Mac");

Предположим далее, что отсутствующие здесь определения имеют свойство base и вызывают конструктор выше себя в цепочке конструкторов. Тогда к моменту создания объекта mac  mac.id будет  5.

В зависимости от приложения может или может не быть важно, увеличивается ли счетчик на это дополнительное количество раз. Если Вам необходимо точное значение счетчика, одним из возможных решений может быть использование следующего конструктора:

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

Если Вы создаете экземпляр Employee для использования его в качестве прототипа, Вы не предоставляете аргументы конструктору. Используя данное определение конструктора и не предоставляя аргументов, конструктор не присваивает значение id и не обновляет счетчик. Следовательно, чтобы Employee получил присвоенный id, Вы обязаны специфицировать имя employee. В данном примере, mac.id может быть 1.

Нет множественного наследования

Некоторые объектно-ориентированные языки допускают множественное наследование. То есть объект может наследовать свойства и значения из не соотнесенных/unrelated родительских объектов. JavaScript не поддерживает множественное наследование.

Наследование значений свойств возникает на этапе прогона программы, когда JavaScript ищет значение в цепочке прототипов объектов. Поскольку объект имеет единственный ассоциированный прототип, JavaScript не может динамически наследовать от более чем одной цепочки прототипов.

В JavaScript Вы можете иметь функцию-конструктор, вызывающую более одной функции-конструктора внутри себя. Это создает иллюзию множественного наследования. Например, рассмотрим такие операторы:

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;
dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo")

Предположим, что имеется определение WorkerBee, как ранее в этой главе. Тогда объект dennis имеет такие свойства:

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

Итак, dennis получает свойство hobby из конструктора Hobbyist. Однако, если Вы затем добавите свойство в прототип конструктора Hobbyist:

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

объект dennis не унаследует это новое свойство.

Оглавление | Назад | Вперед | Индекс



 

 

Наши ссылки на веб-страницы, можно скопировать html-код ссылки


Книги по математике и физике, программы HTML, компьютерные технологии

Документация HTML, XML, JavaScript на русском языке для разработчиков W3C

   Примечание. Удобная текстовая ссылка для форумов, блогов, цитирования материалов веб-сайта, код html можно скопировать и просто вставить в Ваши веб-страницы при цитировании материалов нашего веб-сайта. Материал носит неофициальный характер и приведен для ознакомления.