Электронная библиотека
Программисту веб-дизайнеру
Другие материалы
Клиентский JavaScript. Руководство по Использованию
Глава 8
Объектная Модель. Детали.
JavaScript это язык на базе прототипов, а не на базе классов. Из-за этого в JavaScript менее очевидно, как создается иерархия объектов и происходит наследование свойств и их значений. В данной главе делается попытка прояснит этот вопрос.
Предполагается, что Вы уже немного знакомы с JavaScript и что Вы использовали функции JavaScript для создания простых объектов.
В главе имеются следующие разделы:
Языки на базе классов и языки на базе прототипов
Объектно-ориентированные языки на базе классов, такие как 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)
Класс и экземпляр это разные сущности.
Все объекты являются экземплярами.
Класс определяется в определении класса; инстанциация (создание экземпляров) производится методами-конструкторами.
Набор объектов создается и определяется функциями-конструкторами.
Одиночный объект создается операцией new
.
Иерархия объектов создается через использование определения класса для определения подклассов существующих классов.
Иерархия объектов создается путем присвоения объекта как прототипа, ассоциированного с функцией-конструктором.
Свойства наследуются по цепочке классов.
Свойства наследуются по цепочке прототипов.
Определение класса специфицирует все свойства всех экземпляров данного класса. Свойства нельзя добавлять динамически на этапе прогона.
Функция-конструктор или прототип специфицируют начальный набор свойств. Свойства могут добавляться динамически отдельному объекту или целому набору объектов.
В остальной части данной главы используется иерархия 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-класса.
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 Вы специфицируете суперкласс в определении класса. Вы не
можете изменить суперкласс вне определения класса.
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
новыми значениями, специфичными для этих объектов.
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 для этих объектов.
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.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 выполняет следующее:
new
создает общий объект и присваивает его свойство __proto__
Engineer.prototype
.new
передает этот новый объект Engineer
-конструктору
как значение ключевого слова this
.base
для этого объекта и
присваивает значение конструктора WorkerBee
свойству base
.
Это делает конструктор WorkerBee
методом Engineer
-объекта.
Имя свойства base
не является специальным. Вы может использовать
любое верное имя свойства; base
просто подходит по смыслу.
base
, передавая ему в качестве
аргументов два аргумента из переданных конструктору ("Doe, Jane"
и ["navigator", "javascript"]
),
а также строку "engineering". Явное использование "engineering" в конструкторе
указывает, что все Engineer
-объекты имеют одно значение для
наследуемого свойства dept
, и что это значение переопределяет
значение, наследуемое из Employee
.base
это метод из Engineer
, внутри вызова base
JavaScript
связывает ключевое слово this
с объектом, созданным на
Этапе 1. Таким образом, функция WorkerBee
в
свою очередь передает аргументы "Doe, Jane"
и ["navigator", "javascript"]
в функцию-конструктор Employee
. После возвращения из
функции-конструктора Employee
, функция WorkerBee
использует оставшийся аргумент для установки свойства projects
.base
, конструктор Engineer
инициализирует свойство machine
объекта значением "belau"
.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 выполняет следующие шаги, как уже было показано в этой главе:
__proto__
).Результат выполнения этих шагов зависит от того, каковы были определения.
Оригинальный пример имел такие определения:
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
не унаследует это новое свойство.
Оглавление | Назад | Вперед | Индекс