Файл: ycheb/js_teach/obj2.php
Строк: 798
<?php
include '../../config.php';
$title='Учебник JavaScript';
aut();
head();
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<phpl<TITLE>Объектная Модель. Детали.</TITLE>
<BODY>
<h5><a href="contents.php">Оглавление</a> | <a href="obj.php">Назад</a>
| <a href="partcli.php">Вперёд</a> | <a href="bklast.php">Индекс</a></h5><HR><h1>Глава 8<BR>
<A NAME="1013803"></a>Объектная Модель. Детали.</h1><p><A NAME="1013805"></a>
JavaScript это язык на базе прототипов, а не на базе классов. Из-за этого в JavaScript
менее очевидно, как создаётся иерархия объектов и происходит наследование свойств
и их значений. В данной главе делается попытка прояснит этот вопрос.</p><p><A NAME="1008313"></a>
Предполагается, что Вы уже немного знакомы с JavaScript и что Вы использовали
функции JavaScript для создания простых объектов.</p><p><A NAME="1010311"></a>В главе имеются следующие разделы:</p>
<ul><LI><A NAME="1010312"></a><a href="#1008342">Языки на базе классов и языки на базе прототипов</a></LI><LI><A NAME="1008329"></a>
<A HREF="#1008388">Пример Employee</a></LI><LI><A NAME="1011320"></a><a href="#1008404">Создание иерархии</a></LI><LI><A NAME="1008333"></a>
<a href="#1008465">Свойства объекта</a></LI><LI><A NAME="1008337"></a><a href="#1008499">Более гибкие конструкторы</a></LI><LI><A NAME="1008341"></a>
<a href="#1008567">И снова о наследовании свойств</a></LI></ul><H2><A NAME="Class-Based vs. Prototype-Based Languages"></a>
Я<a name="1008342">зык</a>и на базе классов и языки на базе прототипов</H2><hr><p><A NAME="1011288"></a>
Объектно-ориентированные языки на базе классов, такие как Java и C++, помогут понять две разных сущности: класс и экземпляр.</p>
<ul><LI><A NAME="1010952"></a><b><i>Класс</i></b> определяет все свойства (методы и поля - в Java, члены, то
есть свойства, в C++), характеризующие определённый набор объектов. Класс это
абстракция, в отличие от конкретного члена набора объектов, который он
описывает. Например, класс <CODE>Employee</CODE> может представлять набор всех employees/служащих.</LI><LI><A NAME="1010959"></a>
<b><i>Экземпляр</i></b>, с другой стороны, это инстанциация класса; то есть
это один из членов класса. Например, <CODE>Victoria</CODE> может быть
экземпляром класса <CODE>Employee</CODE>, представляя конкретную персону как employee.
Экземпляр имеет в точности все свойства своего класса-родителя (ни более, ни менее).</LI></ul><p><A NAME="1010590"></a>
В языке на базе прототипов, таком как JavaScript, нет такого отличия: здесь
просто имеются объекты. В языке на базе прототипов имеется понятие <I>
объект-прототипprototypical object</I>, это объект, используемый как шаблон, по
которому получаются начальные свойства объекта. Любой объект может
специфицировать свои собственные свойства либо при создании, либо на этапе
прогона. Кроме того, любой объект может быть ассоциирован как <i>прототип</i>
другого объекта для совместного использования свойств первого объекта.</p><H3><A NAME="Head2;"></a>
<A NAME="1011360"></a>Определение класса</H3><hr><p><A NAME="1010591"></a>
В языках на базе классов Вы определяете класс в отдельном <i>определении классаclass definition</i>.
Здесь Вы можете специфицировать специальные методы, называемые <i>конструкторами</i>,
которые создают экземпляры данного класса. Метод-конструктор может
специфицировать начальные значения свойств экземпляров и выполнять иную работу,
необходимую на этапе создания экземпляров. Вы используете операцию <CODE>new</CODE>
вместе с методом-конструктором для создания экземпляров класса.</p><p><A NAME="1008345"></a>
В JavaScript используется похожая модель, но отсутствует определение класса,
отдельное от конструктора. Вместо этого Вы определяете функцию-конструктор для
создания объектов с определённым набором начальных значений и свойств. Любая
функция JavaScript может использоваться как конструктор. Вы используете операцию <CODE>new</CODE>
вместе с функцией-конструктором для создания нового объекта.</p><H3><A NAME="Head2;"></a>
<A NAME="1011368"></a>Подклассы и наследование</H3><hr><p><A NAME="1008346"></a>
В языках на основе классов Вы создаёте иерархию классов через определения
классов. В определении класса Вы можете специфицировать, что новый класс
является <I>подклассомsubclass</I> уже существующего класса. Подкласс наследует
все свойства своего родителя (суперкласса) и может добавлять новые свойства или
изменять наследуемые. Например, класс <CODE>Employee</CODE> имеет только
свойства <CODE>name</CODE> и <CODE>dept</CODE>, а <CODE>Manager</CODE> является
подклассом класса <CODE>Employee</CODE> и добавляет свойство <CODE>reports</CODE>.
В этом случае экземпляры класса <CODE>Manager</CODE> будут иметь три свойства: <CODE>name</CODE>, <CODE>dept</CODE>
и <CODE>reports</CODE>.</p><p><A NAME="1008347"></a>В JavaScript реализовано наследование, что даёт возможность ассоциировать
объект-прототип с любой функцией-конструктором. Так, Вы можете воспроизвести тот
же пример <CODE>Employee</CODE>-<CODE>Manager</CODE>, но используя несколько
иную терминологию. Сначала Вы определяете функцию-конструктор <CODE>Employee</CODE>,
специфицируя свойства <CODE>name</CODE> и <CODE>dept</CODE>. Затем Вы
определяете функцию-конструктор <CODE>Manager</CODE>, специфицируя свойство <CODE>reports</CODE>.
Наконец, Вы присваиваете новый объект <CODE>Employee</CODE> как <CODE>prototype</CODE>
функции-конструктору <CODE>Manager</CODE>. Затем создаёте новый объект <CODE>Manager</CODE>,
который наследует свойства <CODE>name</CODE> и <CODE>dept</CODE> из объекта <CODE>Employee</CODE>.</p>
<H3><A NAME="Head2;"></a><A NAME="1011428"></a>Добавление и удаление свойств</H3><hr><p><A NAME="1008348"></a>
В языках на основе классов Вы обычно создаёте класс на этапе компиляции, а затем
инстанциируете экземпляры на этапе компиляции или на этапе прогона. Вы не можете
изменить количество или тип свойств класса, после того как Вы его определили. В JavaScript,
однако, Вы можете на этапе прогона добавлять или удалять свойства любого объекта.
Если Вы добавляете свойство объекту, который используется как прототип для
набора объектов, то объекты, для которых данный объект является прототипом, также получают новое свойство.</p>
<H3><A NAME="Head2;"></a><A NAME="1011444"></a>Отличия. Резюме.</H3><hr><p><A NAME="1008352"></a>
В таблице дано краткое резюме по некоторым отличительным особенностям языков.
Остальная часть данной главы посвящена деталям использования конструкторов и
прототипов языка JavaScript для создания иерархии и делаются сравнения с
аналогичными действиями в Java.</p><A NAME="1010209"></a>
<h6 style="text-align: center"><A NAME="1008356"></a>
Таблица 8.1 Сравнение объектных систем языков на базе классов (Java)<br>и языков на базе прототипов (JavaScript)</h6>
<TABLE BORDER="2" CELLPADDING=5><TR><TH><A NAME="1008360"></a>На базе классов (Java)<TH><A NAME="1008362"></a>
На базе прототипов (JavaScript)<TR><TD><P><A NAME="1008364"></a>
Класс и экземпляр это разные сущности.</P><TD><P><A NAME="1008366"></a>Все объекты являются экземплярами.</P>
<TR><TD><P><A NAME="1008368"></a>Класс определяется в определении класса; инстанциация (создание экземпляров)
производится методами-конструкторами.</P><TD><P><A NAME="1008370"></a>
Набор объектов создаётся и определяется функциями-конструкторами.</P><TR><TD><P><A NAME="1008372"></a>
Одиночный объект создаётся операцией <CODE>new</CODE>.</P><TD><P><A NAME="1008374"></a>То же самое.</P>
<TR><TD><P><A NAME="1008376"></a>Иерархия объектов создаётся через использование определения класса для
определения подклассов существующих классов.</P><TD><P><A NAME="1008378"></a>
Иерархия объектов создаётся путём присвоения объекта как прототипа, ассоциированного с функцией-конструктором.</P>
<TR><TD><P><A NAME="1008380"></a>Свойства наследуются по цепочке классов.</P><TD><P><A NAME="1008382"></a>
Свойства наследуются по цепочке прототипов.</P>
<TR><TD><P><A NAME="1008384"></a>Определение класса специфицирует <i>все</i> свойства всех экземпляров данного
класса. Свойства нельзя добавлять динамически на этапе прогона.</P><TD><P><A NAME="1008386"></a>
Функция-конструктор или прототип специфицируют <i>начальный набор</i> свойств.
Свойства могут добавляться динамически отдельному объекту или целому набору объектов.</P></TABLE><H2><A NAME="The Employee Example">
<A NAME="1008388"></a>Пример Employee</H2><hr><p><A NAME="1008389"></a>В остальной части данной главы используется иерархия employee, показанная на
рисунке.</p><h6><A NAME="1008397"></a>Рисунок 8.1 Простая иерархия объектов</h6>
<P><IMG SRC="graphics/hier01.gif" width="282" height="163"></a></P><p><A NAME="1011451"></a>
В этом примере используются следующие объекты:</p><ul><LI><A NAME="1008398"></a>
<CODE>Employee</CODE> имеет свойства <CODE>name</CODE> (значение по умолчанию -
пустая строка) и <CODE>dept</CODE> (значение по умолчанию - "general").</LI><LI><A NAME="1008399"></a>
<CODE>Manager</CODE> базируется на <CODE>Employee</CODE>. Он добавляет свойство <CODE>reports</CODE> (значение
по умолчанию - пустой массив, предназначенный для хранения массива <CODE>Employee</CODE>-объектов как значений).</LI><LI><A NAME="1008400"></a>
<CODE>WorkerBee</CODE> также основан на <CODE>Employee</CODE>. Он добавляет
свойство <CODE>projects</CODE> (значение по умолчанию - пустой массив,
предназначенный для хранения массива строк как значений).</LI><LI><A NAME="1008401"></a>
<CODE>SalesPerson</CODE> базируется на <CODE>WorkerBee</CODE>. Он добавляет
свойство <CODE>quota</CODE> (значение по умолчанию - 100). Также
переопределяет свойство <CODE>dept</CODE> значением "sales", указывая, что все
менеджеры по продажам/salespersons находятся в этом отделе.</LI><LI><A NAME="1008402"></a>
<CODE>Engineer</CODE> базируется на <CODE>WorkerBee</CODE>. Он добавляет
свойство <CODE>machine</CODE> (значение по умолчанию - пустая строка), а также
переопределяет свойство <CODE>dept</CODE> property значением "engineering".</LI></ul><H2><A NAME="Creating the Hierarchy">
<A NAME="1008404"></a>Создание иерархии</H2><hr><p><A NAME="1011527"></a>
Есть несколько способов создания функции-конструктора для реализации иерархии Employee.
Выбор конкретного способа во многом зависит от того, что должно будет делать Ваше приложение.</p><p><A NAME="1011528"></a>
В данном разделе показано, как использовать очень простое (и сравнительно
негибкое) определение для демонстрации создания работающей иерархии. В этих
определениях Вы не можете специфицировать значения свойств, когда создаёте
объект. Вновь создаваемый объект просто получает все значения по умолчанию,
которые Вы можете изменить позднее. <A HREF="#1008418">Рисунок 8.2</a>
показывает иерархию с несколькими простыми определениями.</p><p><A NAME="1008410"></a>
В реальном приложении, Вы, вероятно, определите конструктор, который позволит
задавать начальные значения свойств на этапе создания объекта (см. <A HREF="#1008499">"Более
Гибкие Конструкторы"</a>). Итак, эти простые определения демонстрируют появление иерархии.</p>
<h6><A NAME="1008418"></a>Рисунок 8.2 Определения объекта Employee</h6>
<P><IMG SRC="graphics/hier02.gif" width="493" height="290"></a></P><p><A NAME="1008419"></a>
Следующие определения <CODE>Employee</CODE> в Java и в JavaScript похожи.
Единственное отличие - Вы должны специфицировать тип каждого свойства в Java, но
не в JavaScript, и Вы должны создать конкретный метод-конструктор для Java-класса.</p><A NAME="1010246"></a>
<TABLE BORDER="2" CELLPADDING=5><TR><TH><A NAME="1008422"></a>
JavaScript<TH><A NAME="1008424"></a>
Java<TR><TD><PRE><A NAME="1008426"></a>function Employee () {<br>this.name = "";<br>this.dept = "general";<br>
}</PRE><TD><PRE><A NAME="1008428"></a>public class Employee {<br> public String name;<br>
public String dept;<br> public Employee () {<br>
this.name = "";<br>
this.dept = "general";<br> }<br>}</PRE></TABLE><p><A NAME="1008429"></a>
Определения для <CODE>Manager</CODE> и WorkerBee показывают отличия в
специфицировании объекта, стоящего выше в цепочке иерархии. В JavaScript Вы
добавляете экземпляр-прототип как значение свойства <CODE>prototype</CODE>
функции-конструктора. Вы можете сделать это в любое время после определения
конструктора. В Java Вы специфицируете суперкласс в определении класса. Вы не
можете изменить суперкласс вне определения класса.</p><A NAME="1010266"></a><TABLE BORDER="2" CELLPADDING=5>
<TR><TH><A NAME="1008432"></a>JavaScript<TH><A NAME="1008434"></a>
Java<TR><TD><PRE><A NAME="1008436"></a>function Manager () {<br> this.reports = [];<br>}<br>Manager.prototype = new Employee;</PRE><PRE><A NAME="1008437"></a>function WorkerBee () {<br> this.projects = [];<br>}<br>WorkerBee.prototype = new Employee;</PRE><TD><PRE><A NAME="1008439"></a>public class Manager extends Employee {<br> public Employee[] reports;<br> public Manager () {<br> this.reports = new Employee[0];<br> }<br>}</PRE><PRE><A NAME="1008440"></a>public class WorkerBee extends Employee {<br> public String[] projects;<br> public WorkerBee () {<br> this.projects = new String[0];<br> }<br>}</PRE></TABLE>
<p><A NAME="1008441"></a>Определения <CODE>Engineer</CODE> и <CODE>SalesPerson</CODE> создают объекты,
которые являются потомками <CODE>WorkerBee</CODE> и, следовательно, потомками <CODE>Employee</CODE>.
Объект этих типов имеет свойства всех объектов, стоящих выше него в цепочке
иерархии. Кроме того, эти определения переопределяют наследуемое значение
свойства <CODE>dept</CODE> новыми значениями, специфичными для этих объектов.</p><A NAME="1010284"></a>
<TABLE BORDER="2" CELLPADDING=5><TR><TH><A NAME="1008444"></a>JavaScript<TH><A NAME="1008446"></a>
Java<TR><TD><PRE><A NAME="1008448"></a>function SalesPerson () {<br> this.dept = "sales";<br> this.quota = 100;<br>}<br>SalesPerson.prototype = new WorkerBee;</PRE><PRE><A NAME="1008449"></a>function Engineer () {<br> this.dept = "engineering";<br> this.machine = "";<br>}<br>Engineer.prototype = new WorkerBee;</PRE><TD><PRE><A NAME="1008451"></a>public class SalesPerson extends WorkerBee {<br> public double quota;<br> public SalesPerson () {<br> this.dept = "sales";<br> this.quota = 100.0;<br> }<br>}</PRE><PRE><A NAME="1008452"></a>public class Engineer extends WorkerBee {<br> public String machine;<br> public Engineer () {<br> this.dept = "engineering";<br> this.machine = "";<br> }<br>}</PRE></TABLE>
<p><A NAME="1008453"></a>Используя эти определения, Вы можете создавать экземпляры этих объектов, которые
получают значения по умолчанию своих свойств. <A HREF="#1008463">Рисунок 8.3</a>
иллюстрирует использование этих определений JavaScript для создания новых объектов и показывает значения свойств новых объектов.</p>
<BLOCKQUOTE><p><B>ПРИМЕЧАНИЕ: </B><A NAME="1010709"></a>
Термин <I>экземплярinstance</I> имеет специфическое техническое значение в
языках программирования на базе классов. В них экземпляр это отдельный член
класса, фундаментально отличающийся от класса. В JavaScript "экземпляр/instance"
не имеет этого технического значения, поскольку JavaScript не различает классы
и экземпляры. Однако, говоря о JavaScript, "экземпляр" может использоваться
неформально для обозначения объекта, созданного с использованием конкретной
функции-конструктора. Так, в данном примере, Вы можете неформально сказать,
что <CODE>jane</CODE> это <CODE>Engineer</CODE>-экземпляр. Аналогично, хотя
термины <I>parentродитель</I>, <I>childдочерний</I>, <I>ancestorпредок</I>
и <I>descendantпотомок</I> не имеют формальных значений в JavaScript, Вы можете
использовать их неформально для обращения к объектам выше или ниже по цепочке прототипов.</p></BLOCKQUOTE>
<h6><A NAME="1008463"></a>Рисунок 8.3 Создание объектов с помощью простых определений</h6>
<P><IMG SRC="graphics/hier03.gif" width="490" height="360"></a></P><H2><A NAME="Object Properties"><A NAME="1008465"></a>
Свойства объекта</H2><hr><p><A NAME="1012333"></a>
В этом разделе обсуждается, как объекты наследуют свойства других объектов в
цепочке прототипов и что происходит при добавлении свойства на этапе прогона.</p><H3><A NAME="Head2;"></a>
<A NAME="1008468"></a>Наследование свойств</H3><hr><p><A NAME="1008472"></a>
Предположим, Вы создаёте объект <CODE>mark</CODE> как <CODE>WorkerBee</CODE>,
как показано на <A HREF="#1008463">Рисунке 8.3</a>, следующим оператором:</p>
<PRE><A NAME="1008473"></a>mark = new WorkerBee;</PRE><p><A NAME="1008474"></a>
Когда JavaScript видит операцию <CODE>new</CODE>, он создаёт новый родовой/generic
объект и передаёт этот новый объект как значение ключевого слова <CODE>this</CODE>
функции-конструктору <CODE>WorkerBee</CODE>. Функция-конструктор явным образом
устанавливает свойства <CODE>projects</CODE>. Она также устанавливает в значение
внутреннего свойства <CODE>__proto__</CODE> значение <CODE>WorkerBee.prototype</CODE>. (Имя
этого свойства имеет по два символа подчёркивания в начале и в конце.) Свойство <CODE>__proto__</CODE>
определяет цепочку прототипов, используемую для возвращения значения свойства.
После установки этих свойств JavaScript возвращает новый объект, и операция
присвоения устанавливает переменную <CODE>mark</CODE> в этот объект.</p><p><A NAME="1008475"></a>
Этот процесс не помещает явным образом значения в объект <CODE>mark</CODE> (<i>локальные</i>
значения) для свойств, которые <CODE>mark</CODE> наследует из цепочки прототипов.
Когда Вы запрашиваете значение свойства, JavaScript сначала проверяет,
существует ли значение в данном объекте. Если значение существует, оно
возвращается. Если значение локально отсутствует, JavaScript проверяет цепочку
прототипов (используя свойство <CODE>__proto__</CODE>). Если объект в цепочке
прототипов имеет значение для этого свойства, возвращается это значение. Если
такое свойство не найдено, JavaScript сообщает, что такого свойства у объекта
нет. Отсюда, объект <CODE>mark</CODE> имеет следующие свойства и значения:</p>
<PRE><A NAME="1008476"></a>mark.name = "";<br>mark.dept = "general";<br>mark.projects = [];</PRE>
<p><A NAME="1008477"></a>Объект <CODE>mark</CODE> наследует значения свойств <CODE>name</CODE> и <CODE>dept</CODE>
из объекта-прототипа в <CODE>mark.__proto__</CODE>. Локальное значение свойства <CODE>projects</CODE>
присваивается конструктором <CODE>WorkerBee</CODE>. Это даёт Вам наследование
свойств и их значений в JavaScript. Некоторые тонкости этого процесса
обсуждаются в разделе <A HREF="#1008567">"И Снова о Наследовании Свойств"</a>.</p><p><A NAME="1008481"></a>
Поскольку эти конструкторы не предоставляют поддержки значений, специфичных для
экземпляра, эта информация является общей/generic. Значения свойств являются
значениями по умолчаниями, используемыми всеми новыми объектами, создаваемыми из <CODE>WorkerBee</CODE>. Вы
можете, конечно, изменить значение любого из этих свойств. Так, Вы можете задать
специфическую информацию для <CODE>mark</CODE> таким образом:</p>
<PRE><A NAME="1008482"></a>mark.name = "Doe, Mark";<br>mark.dept = "admin";<br>mark.projects = ["navigator"];</PRE>
<H3><A NAME="Head2;"></a><A NAME="1008483"></a>Добавление свойств</H3><hr><p><A NAME="1012393"></a>
В JavaScript Вы можете добавить свойства любому объекту на этапе прогона. Вы не
ограничены использованием только свойств, предоставленных функцией-конструктором.
Для добавления свойств, специфичных для отдельного объекта, Вы присваиваете значение объекту:</p>
<PRE><A NAME="1008485"></a>mark.bonus = 3000;</PRE><p><A NAME="1008486"></a>
Теперь объект <CODE>mark</CODE> имеет свойство <CODE>bonus</CODE>, но другие <CODE>WorkerBee</CODE> этого свойства не имеют.</p>
<p><A NAME="1008487"></a>Если Вы добавляете новое свойство объекту, который используется как
функция-конструктор, Вы добавляете это свойство всем объекта, наследующим
свойства от данного прототипа. Например, Вы можете добавить свойство <CODE>specialty</CODE> ко всем employees следующим оператором:</p>
<PRE><A NAME="1008488"></a>Employee.prototype.specialty = "none";</PRE><p><A NAME="1008489"></a>
Как только JavaScript выполнит этот оператор, объект <CODE>mark</CODE> также
получит свойство <CODE>specialty</CODE> со значением <CODE>"none"</CODE>. На
рисунке показан эффект от добавления этого свойства прототипу <CODE>Employee</CODE> и
последующее переопределение его для прототипа <CODE>Engineer</CODE>.</p>
<h6><A NAME="1008497"></a>Рисунок 8.4 Добавление свойств</h6>
<P><IMG SRC="graphics/hier04.gif" width="538" height="353"></a></P><H2><A NAME="More Flexible Constructors">
<A NAME="1008499"></a>Более гибкие конструкторы</H2><hr><p><A NAME="1008500"></a>
Показанная функция-конструктор не даёт возможности создавать экземпляры. Как и в Java, Вы
можете предоставлять аргументы конструктору для инициализации значений свойств
экземпляров. На рисунке показан один из способов сделать это.</p><h6><A NAME="1008508"></a>
Рисунок 8.5 Специфицирование свойств в конструкторе, этап 1</h6>
<P><IMG SRC="graphics/hier05.gif" width="628" height="342"></a></P><p><A NAME="1008526"></a>
В таблице показаны определения Java и JavaScript для этих объектов.</p><A NAME="1010297"></a>
<TABLE BORDER="2" CELLPADDING=5><TR><TH><A NAME="1008511"></a>
JavaScript<TH><A NAME="1008513"></a>
Java<TR><TD><PRE><A NAME="1008515"></a>function Employee (name, dept) {<br> this.name = name || "";<br> this.dept = dept || "general";<br>}</PRE><TD><PRE><A NAME="1008517"></a>public class Employee {<br> public String name;<br> public String dept;<br> public Employee () {<br> this("", "general");<br> }<br> public Employee (name) {<br> this(name, "general");<br> }<br> public Employee (name, dept) {<br> this.name = name;<br> this.dept = dept;<br> }<br>}</PRE>
<TR><TD><PRE><A NAME="1008519"></a>function WorkerBee (projs) {<br> this.projects = projs || [];<br>}<br>WorkerBee.prototype = new Employee;</PRE><TD><PRE><A NAME="1008521"></a>public class WorkerBee extends Employee {<br> public String[] projects;<br> public WorkerBee () {<br> this(new String[0]);<br> }<br> public WorkerBee (String[] projs) {<br> this.projects = projs;<br> }<br>}</PRE>
<TR><TD><PRE><A NAME="1008523"></a>function Engineer (mach) {<br> this.dept = "engineering";<br> this.machine = mach || "";<br>}<br>Engineer.prototype = new WorkerBee;</PRE><TD><PRE><A NAME="1008525"></a>public class Engineer extends WorkerBee {<br> public String machine;<br> public WorkerBee () {<br> this.dept = "engineering";<br> this.machine = "";<br> }<br> public WorkerBee (mach) {<br> this.dept = "engineering";<br> this.machine = mach;<br> }<br>}</PRE></TABLE>
<p><A NAME="1008527"></a>Эти определения JavaScript используют специальную идиому для установки значений по умолчанию:</p>
<PRE><A NAME="1008528"></a>this.name = name || "";</PRE><p><A NAME="1008529"></a>
Логическая операция ИЛИ (<CODE>||</CODE>) в JavaScript вычисляет первый аргумент.
Если он конвертируется в true, операция возвращает его. Иначе операция возвращает
значение второго аргумента. Следовательно, эта строка кода проверяет, имеет ли <CODE>name</CODE>
подходящее значение для свойства <CODE>name</CODE>. Если имеет, это значение
устанавливается в <CODE>this.name</CODE>. Иначе - в <CODE>this.name</CODE>
устанавливается пустая строка. Эта идиома используется в данной главе для
краткости; однако она может, на первый взгляд, показаться непонятной.</p><p><A NAME="1008530"></a>
При помощи этих определений Вы можете при создании объекта специфицировать
значения локально определяемых свойств. Как видно на <A HREF="#1008508">Рисунке 8.5</a>, Вы
можете использовать следующий оператор для создания нового <CODE>Engineer</CODE>:</p>
<PRE><A NAME="1008534"></a>jane = new Engineer("belau");</PRE><p><A NAME="1008535"></a>Jane-свойства будут теперь:</p>
<PRE><A NAME="1008536"></a>jane.name == "";<br>jane.dept == "general";<br>jane.projects == [];<br>jane.machine == "belau"</PRE>
<p><A NAME="1008537"></a>
Заметьте, что с помощью этих определений Вы не можете специфицировать начальное
значение наследуемого свойства, такого как <CODE>name</CODE>. Если Вы хотите
специфицировать начальные значения наследуемых свойств в JavaScript, Вы
должны добавить дополнительный код в функцию-конструктор.</p><p><A NAME="1008538"></a>
Итак, функция-конструктор создала общий (родовой/generic) объект и затем
специфицировала локальные свойства и значения для этого нового объекта. Вы
можете заставить конструктор добавить дополнительные свойства,
непосредственно вызвав функцию-конструктор для объекта, стоящего выше в цепочке
прототипов. На рисунке показаны эти новые определения.</p><h6><A NAME="1008546"></a>
Рисунок 8.6 Специфицирование свойств в конструкторе, этап 2</h6>
<P><IMG SRC="graphics/hier06.gif" width="633" height="350"></a></P><p><A NAME="1008547"></a>
Рассмотрим одно из этих определений детально. Вот новое определение для конструктора <CODE>Engineer</CODE>:</p>
<PRE><A NAME="1008548"></a>function Engineer (name, projs, mach) {<br> this.base = WorkerBee;<br> this.base(name, "engineering", projs);<br> this.machine = mach || "";<br>}</PRE>
<p><A NAME="1008549"></a>Предположим, Вы создаёте новый <CODE>Engineer</CODE>-объект:</p>
<PRE><A NAME="1008550"></a>jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");</PRE>
<p><A NAME="1008551"></a>JavaScript выполняет следующее:</p><OL><LI><A NAME="1008552"></a>
Операция <CODE>new</CODE> создаёт общий объект и присваивает его свойство <CODE>__proto__</CODE> <CODE>Engineer.prototype</CODE>.</LI><LI><A NAME="1008553"></a>
Операция <CODE>new</CODE> передаёт этот новый объект <CODE>Engineer</CODE>-конструктору
как значение ключевого слова <CODE>this</CODE>.</LI><LI><A NAME="1008554"></a>
Конструктор создаёт новое свойство <CODE>base</CODE> для этого объекта и
присваивает значение конструктора <CODE>WorkerBee</CODE> свойству <CODE>base</CODE>.
Это делает конструктор <CODE>WorkerBee</CODE> методом <CODE>Engineer</CODE>-объекта.</LI><P><A NAME="1008556"></a>
Имя свойства <CODE>base</CODE> не является специальным. Вы может использовать
любое верное имя свойства; <CODE>base</CODE> просто подходит по смыслу.</P><LI><A NAME="1008557"></a>
Конструктор вызывает метод <CODE>base</CODE>, передавая ему в качестве
аргументов два аргумента из переданных конструктору (<CODE>"Doe, Jane"</CODE> и <CODE>["navigator", "javascript"]</CODE>),
а также строку "engineering". Явное использование "engineering" в конструкторе
указывает, что все <CODE>Engineer</CODE>-объекты имеют одно значение для
наследуемого свойства <CODE>dept</CODE>, и что это значение переопределяет
значение, наследуемое из <CODE>Employee</CODE>.</LI><LI><A NAME="1008558"></a>
Поскольку <CODE>base</CODE> это метод из <CODE>Engineer</CODE>, внутри вызова <CODE>base</CODE> JavaScript
связывает ключевое слово <CODE>this</CODE> с объектом, созданным на
<a href="#1008552">Этапе 1</a>. Таким образом, функция <CODE>WorkerBee</CODE> в
свою очередь передаёт аргументы <CODE>"Doe, Jane"</CODE> и <CODE>["navigator", "javascript"]</CODE>
в функцию-конструктор <CODE>Employee</CODE>. После возвращения из
функции-конструктора <CODE>Employee</CODE>, функция <CODE>WorkerBee</CODE>
использует оставшийся аргумент для установки свойства <CODE>projects</CODE>.</LI><LI><A NAME="1008559"></a>
После возвращения из метода <CODE>base</CODE>, конструктор <CODE>Engineer</CODE>
инициализирует свойство <CODE>machine</CODE> объекта значением <CODE>"belau"</CODE>.</LI><LI><A NAME="1008560"></a>
После возвращения из конструктора, JavaScript присваивает новый объект
переменной <CODE>jane</CODE>.</LI></OL><p><A NAME="1008561"></a>
Вы можете подумать, что, вызвав конструктор <CODE>WorkerBee</CODE> из
конструктора <CODE>Engineer</CODE>, Вы установили соответствующее наследование
для <CODE>Engineer</CODE>-объектов, но это не так. Вызов конструктора <CODE>WorkerBee</CODE>
гарантирует, что объект <CODE>Engineer</CODE> стартует со свойствами,
специфицированными во всех функциях-конструкторах, которые вызываются. Однако,
если Вы позднее добавите свойства в прототипы <CODE>Employee</CODE> или <CODE>WorkerBee</CODE>,
эти свойства не будут наследоваться объектом <CODE>Engineer</CODE>. Например, у Вас имеются операторы:</p>
<PRE><A NAME="1008562"></a>function Engineer (name, projs, mach) {<br> this.base = WorkerBee;<br> this.base(name, "engineering", projs);<br> this.machine = mach || "";<br>}<br>jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");<br>Employee.prototype.specialty = "none";</PRE>
<p><A NAME="1008563"></a>Объект <CODE>jane</CODE> не наследует свойство <CODE>specialty</CODE>. Вам всё
ещё необходимо явным образом изменить прототип для обеспечения динамического
наследования. предположим, что у Вас имеются другие операторы:</p>
<PRE><A NAME="1008564"></a>function Engineer (name, projs, mach) {<br> this.base = WorkerBee;<br> this.base(name, "engineering", projs);<br> this.machine = mach || "";<br>}<br>Engineer.prototype = new WorkerBee;<br>jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");<br>Employee.prototype.specialty = "none";</PRE>
<p><A NAME="1008565"></a>Теперь значение свойства <CODE>specialty</CODE> объекта <CODE>jane</CODE> равно "none".</p>
<H2><A NAME="Property Inheritance Revisited"><A NAME="1008567"></a>И снова о наследовании свойств</H2><hr>
<p><A NAME="1008568"></a>В предыдущем разделе показано, как реализуются иерархия и наследование в
конструкторах и прототипах JavaScript.<br>В данном разделе обсуждаются некоторые тонкости, не очевидные при предыдущем рассмотрении.</p>
<H3><A NAME="Head2;"></a><A NAME="1008570"></a>Локальные и наследуемые значения</H3><hr><p><A NAME="1008571"></a>
Когда Вы выполняете доступ к свойству объекта, JavaScript выполняет следующие шаги, как уже было показано в этой главе:</p>
<OL><LI><A NAME="1008572"></a>Проверяется, существует ли значение локально. Если да, это значение возвращается.</LI><LI><A NAME="1008573"></a>
Если локального значения нет, проверяется цепочка прототипов (через использование свойства <CODE>__proto__</CODE>).</LI><LI><A NAME="1008574"></a>
Если объект в цепочке прототипов имеет значение для специфицированного свойства,
это значение возвращается.</LI><LI><A NAME="1008575"></a>Если такое свойство не найдено, объект не имеет данного свойства.</LI></OL>
<p><A NAME="1008576"></a>Результат выполнения этих шагов зависит от того, каковы были определения.<br>
Оригинальный пример имел такие определения:</p>
<PRE><A NAME="1008577"></a>function Employee () {<br> this.name = "";<br> this.dept = "general";<br>}</PRE><PRE><A NAME="1008578"></a>function WorkerBee () {<br> this.projects = [];<br>}<br>WorkerBee.prototype = new Employee;</PRE>
<p><A NAME="1008579"></a>С помощью этих определений Вы, предположим, создаёте <CODE>amy</CODE> как
экземпляр <CODE>WorkerBee</CODE> таким оператором:</p><PRE><A NAME="1008580"></a>amy = new WorkerBee;</PRE>
<p><A NAME="1008581"></a>Объект <CODE>amy</CODE> имеет одно локальное свойство, <CODE>projects</CODE>.
Значения свойств <CODE>name</CODE> и <CODE>dept</CODE> не являются локальными по
отношению к <CODE>amy</CODE> и поэтому получены из свойства <CODE>__proto__</CODE>
объекта <CODE>amy</CODE>.<br>Таким образом, <CODE>amy</CODE> имеет следующие значения свойств:</p>
<PRE><A NAME="1008582"></a>amy.name == "";<br>amy.dept = "general";<br>amy.projects == [];</PRE>
<p><A NAME="1008583"></a>Теперь предположим, что Вы изменяете значение свойства <CODE>name</CODE> в
прототипе, ассоциированном с <CODE>Employee</CODE>:</p><PRE><A NAME="1008584"></a>Employee.prototype.name = "Unknown"</PRE>
<p><A NAME="1008585"></a>На первый взгляд можно ожидать, что новое значение передаётся всем экземплярам <CODE>Employee</CODE>.
Однако это не так.</p><p><A NAME="1008586"></a>Когда Вы создаёте <i>любой</i> экземпляр объекта <CODE>Employee</CODE>, этот
экземпляр получает локальное значение свойства <CODE>name</CODE> (пустую строку).
Это означает, что, если Вы устанавливаете прототип <CODE>WorkerBee</CODE> через
создание нового объекта <CODE>Employee</CODE>, <CODE>WorkerBee.prototype</CODE>
имеет локальное значение для свойства <CODE>name</CODE>. Следовательно, когда JavaScript
ищет свойство <CODE>name</CODE> объекта <CODE>amy</CODE> (экземпляра <CODE>WorkerBee</CODE>), JavaScript
находит значение для этого свойства в <CODE>WorkerBee.prototype</CODE>. Он,
следовательно, не просматривает далее цепочку до <CODE>Employee.prototype</CODE>.</p><p><A NAME="1008587"></a>
Если Вы хотите изменить значение свойства объекта на этапе прогона и иметь новое
значение наследуемым всеми потомками этого объекта, Вы не определяете это
свойство в функции-конструкторе объекта. Вместо этого Вы добавляете это свойство
в ассоциированный прототип конструктора. Например, Вы изменяете предыдущий код на такой:</p>
<PRE><A NAME="1008588"></a>function Employee () {<br> this.dept = "general";<br>}<br>Employee.prototype.name = "";</PRE><PRE><A NAME="1008589"></a>function WorkerBee () {<br> this.projects = [];<br>}<br>WorkerBee.prototype = new Employee;</PRE><PRE><A NAME="1008590"></a>amy = new WorkerBee;</PRE><PRE><A NAME="1008591"></a>Employee.prototype.name = "Unknown";</PRE>
<p><A NAME="1008592"></a>Теперь свойство <CODE>name</CODE> объекта <CODE>amy</CODE> становится "Unknown".</p>
<p><A NAME="1008593"></a>Как видно из этих примеров, если Вы хотите иметь значения по умолчанию для
свойств объекта и хотите иметь возможность изменять значения по умолчанию на
этапе прогона программы, Вы должны устанавливать свойства в прототипе конструктора, а не в самой функции-конструкторе.</p>
<H3><A NAME="Head2;"></a><A NAME="1008594"></a>Определение взаимоотношений экземпляров</H3><hr><p><A NAME="1008595"></a>
Возможно, Вы захотите узнать, какие объекты находятся в цепочке прототипов данного объекта,
чтобы определить, из какого объекта данный объект наследует свойства. В языках
на базе классов Вы можете использовать для этого операцию <CODE>instanceof</CODE>.
В JavaScript нет <CODE>instanceof</CODE>, но Вы сами можете написать такую функцию.</p><p><A NAME="1008599"></a>
Как сказано в <A HREF="#1008468">"Наследовании Свойств"</a>, если Вы используете
операцию <CODE>new</CODE> с функцией-конструктором для создания новых объектов, JavaScript
устанавливает в свойство <CODE>__proto__</CODE> нового объекта значение свойства <CODE>prototype</CODE>
функции-конструктора. Вы можете использовать это для проверки цепочки прототипов.</p><p><A NAME="1008600"></a>
Например, у Вас есть уже показанный ранее набор определений с соответственно
установленными прототипами. Создайте объект <CODE>__proto__</CODE> так:</p>
<PRE><A NAME="1008601"></a>chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");</PRE>
<p><A NAME="1008602"></a>С эти объектом все следующие операторы true:</p>
<PRE><A NAME="1008603"></a>chris.__proto__ == Engineer.prototype;<br>chris.__proto__.__proto__ == WorkerBee.prototype;<br>chris.__proto__.__proto__.__proto__ == Employee.prototype;<br>chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;<br>chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;</PRE>
<p><A NAME="1008604"></a>Имея это, Вы можете написать функцию <CODE>instanceOf</CODE>:</p>
<PRE><A NAME="1008605"></a>function instanceOf(object, constructor) { <br> while (object != null) { <br> if (object == constructor.prototype) <br> return true; <br> object = object.__proto__; <br> } <br> return false; <br>}</PRE>
<p><A NAME="1008606"></a>При таком определении все следующие выражения true:</p>
<PRE><A NAME="1008607"></a>instanceOf (chris, Engineer)<br>instanceOf (chris, WorkerBee)<br>instanceOf (chris, Employee)<br>instanceOf (chris, Object)</PRE>
<p><A NAME="1008608"></a>Но следующее выражение будет false:</p><PRE><A NAME="1008609"></a>instanceOf (chris, SalesPerson)</PRE>
<H3><A NAME="Head2;"></a><A NAME="1008610"></a>Глобальная информация в конструкторах</H3><hr><p><A NAME="1008611"></a>
При создании конструкторов Вы должны быть осторожны, если устанавливаете
глобальную информацию в конструкторе. Например, у Вас имеется уникальный
идентификатор ID, присваиваемый автоматически каждому новому employee. Вы может
использовать такое определение для <CODE>Employee</CODE>:</p>
<PRE><A NAME="1008612"></a>var idCounter = 1;</PRE><PRE><A NAME="1008613"></a>function Employee (name, dept) {<br> this.name = name || "";<br> this.dept = dept || "general";<br> this.id = idCounter++;<br>}</PRE>
<p><A NAME="1008614"></a>Теперь, ели Вы создаёте новый <CODE>Employee</CODE>, конструктор присваивает ему
следующий ID и выполняет инкремент глобального счётчика ID. Поэтому, если Ваш
следующий оператор будет таким, как ниже приведённый, <CODE>victoria.id</CODE>
будет 1, а <CODE>harry.id</CODE> будет 2:</p>
<PRE><A NAME="1008615"></a>victoria = new Employee("Pigbert, Victoria", "pubs")<br>harry = new Employee("Tschopik, Harry", "sales")</PRE>
<p><A NAME="1008616"></a>На первый взгляд всё отлично. Однако <CODE>idCounter</CODE> увеличивается всякий
раз при создании <CODE>Employee</CODE>-объекта. Если Вы создаёте всю иерархию <CODE>Employee</CODE>,
показанную в данной главе, <CODE>Employee</CODE>-конструктор вызывается при
каждой установке прототипа. Предположим, у Вас имеется такой код:</p>
<PRE><A NAME="1008617"></a>var idCounter = 1;</PRE><PRE><A NAME="1008618"></a>function Employee (name, dept) {<br> this.name = name || "";<br> this.dept = dept || "general";<br> this.id = idCounter++;<br>}</PRE><PRE><A NAME="1008619"></a>function Manager (name, dept, reports) {...}<br>Manager.prototype = new Employee;</PRE><PRE><A NAME="1008620"></a>function WorkerBee (name, dept, projs) {...}<br>WorkerBee.prototype = new Employee;</PRE><PRE><A NAME="1008621"></a>function Engineer (name, projs, mach) {...}<br>Engineer.prototype = new WorkerBee;</PRE><PRE><A NAME="1008622"></a>function SalesPerson (name, projs, quota) {...}<br>SalesPerson.prototype = new WorkerBee;</PRE><PRE><A NAME="1008623"></a>mac = new Engineer("Wood, Mac");</PRE>
<p><A NAME="1008624"></a>Предположим далее, что отсутствующие здесь определения имеют свойство <CODE>base</CODE>
и вызывают конструктор выше себя в цепочке конструкторов. Тогда к моменту
создания объекта <CODE>mac</CODE> <CODE>mac.id</CODE> будет 5.</p><p><A NAME="1008625"></a>
В зависимости от приложения может или может не быть важно, увеличивается ли
счётчик на это дополнительное количество раз. Если Вам необходимо точное
значение счётчика, одним из возможных решений может быть использование следующего конструктора:</p></P>
<PRE><A NAME="1008626"></a>function Employee (name, dept) {<br> this.name = name || "";<br> this.dept = dept || "general";<br> if (name)<br> this.id = idCounter++;<br>}</PRE>
<p><A NAME="1008627"></a>Если Вы создаёте экземпляр <CODE>Employee</CODE> для использования его в
качестве прототипа, Вы не предоставляете аргументы конструктору. Используя
данное определение конструктора и не предоставляя аргументов, конструктор не
присваивает значение id и не обновляет счётчик. Следовательно, чтобы <CODE>Employee</CODE>
получил присвоенный id, Вы обязаны специфицировать имя employee. В данном примере, <CODE>mac.id</CODE> может быть 1.</p>
<H3><A NAME="Head2;"></a><A NAME="1008628"></a>Нет множественного наследования</H3><hr><p><A NAME="1008629"></a>
Некоторые объектно-ориентированные языки допускают множественное наследование.
То есть объект может наследовать свойства и значения из не соотнесённых/unrelated
родительских объектов. JavaScript не поддерживает множественное наследование.</p><p><A NAME="1008630"></a>
Наследование значений свойств возникает на этапе прогона программы, когда JavaScript
ищет значение в цепочке прототипов объектов. Поскольку объект имеет единственный
ассоциированный прототип, JavaScript не может динамически наследовать от более чем одной цепочки прототипов.</p>
<p><A NAME="1008631"></a>В JavaScript Вы можете иметь функцию-конструктор, вызывающую более одной
функции-конструктора внутри себя. Это создаёт иллюзию множественного наследования. Например, рассмотрим такие операторы:</p>
<PRE><A NAME="1008632"></a>function Hobbyist (hobby) {<br> this.hobby = hobby || "scuba";<br>}</PRE><PRE><A NAME="1008633"></a>function Engineer (name, projs, mach, hobby) {<br> this.base1 = WorkerBee;<br> this.base1(name, "engineering", projs);<br> this.base2 = Hobbyist;<br> this.base2(hobby);<br> this.machine = mach || "";<br>}<br>Engineer.prototype = new WorkerBee;</PRE><PRE><A NAME="1008634"></a>dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo")</PRE>
<p><A NAME="1008635"></a>Предположим, что имеется определение <CODE>WorkerBee</CODE>, как ранее в этой
главе. Тогда объект dennis имеет такие свойства:</p>
<PRE><A NAME="1008636"></a>dennis.name == "Doe, Dennis"<br>dennis.dept == "engineering"<br>dennis.projects == ["collabra"]<br>dennis.machine == "hugo"<br>dennis.hobby == "scuba"</PRE>
<p><A NAME="1008637"></a>Итак, <CODE>dennis</CODE> получает свойство <CODE>hobby</CODE> из конструктора <CODE>Hobbyist</CODE>.
Однако, если Вы затем добавите свойство в прототип конструктора <CODE>Hobbyist</CODE>:</p>
<PRE><A NAME="1008638"></a>Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"]</PRE>
<p><A NAME="1008639"></a>объект <CODE>dennis</CODE> не унаследует это новое свойство.</p>
<h5><a href="contents.php">Оглавление</a> | <a href="obj.php">Назад</a>
| <a href="partcli.php">Вперёд</a> | <a href="bklast.php">Индекс</a></h5>
<hr></BODY></HTML>
<?php
echo gb.'<a href="../../ycheb">Учебники</a>'.div;
echo gb.'<a href="'.H.'enter">Прихожая</a>'.div;
foot(); ?>