diff options
Diffstat (limited to 'files/ru/web/javascript/guide/details_of_the_object_model')
| -rw-r--r-- | files/ru/web/javascript/guide/details_of_the_object_model/index.html | 723 |
1 files changed, 723 insertions, 0 deletions
diff --git a/files/ru/web/javascript/guide/details_of_the_object_model/index.html b/files/ru/web/javascript/guide/details_of_the_object_model/index.html new file mode 100644 index 0000000000..a5fda463eb --- /dev/null +++ b/files/ru/web/javascript/guide/details_of_the_object_model/index.html @@ -0,0 +1,723 @@ +--- +title: Подробнее об объектной модели +slug: Web/JavaScript/Guide/Details_of_the_Object_Model +translation_of: Web/JavaScript/Guide/Details_of_the_Object_Model +--- +<p>{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Working_with_Objects", "Web/JavaScript/Guide/Ispolzovanie_promisov")}}</p> + +<p class="summary">JavaScript — это объектно-ориентированный язык, основанный на прототипировании, а не на классах. Из-за этого, менее очевидно то, каким образом JavaScript позволяет создавать иерархии объектов и обеспечивает наследование свойств и их значений. Эта глава является скромной попыткой прояснить ситуацию.</p> + +<p>Эта глава предполагает что читатель знаком с основами JavaScript, и имеет опыт использования функций для создания простейших объектов.</p> + +<h2 id="Языки_основанные_на_классах_против_Прототипно-ориентированных_языков">Языки, основанные на классах против Прототипно-ориентированных языков</h2> + +<p>Основанные на классах объектно-ориентированные языки программирования, такие как Java и C++, строятся на концепции двух отдельных сущностей: класс и экземпляр.</p> + +<ul> + <li><em>Класс</em> определяет все свойства (учитывая методы и все поля в Java, или свойства в C++), которые характеризуют группу объектов. Класс это абстрактная вещь, а не какой-либо конкретный член множества объектов, которые он описывает. Например, класс <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code> может описывать множество всех сотрудников.</li> + <li><em>Экземпляр</em>, это воплощение класса в виде конкретного объекта. Например, <font face="Consolas, Liberation Mono, Courier, monospace"><code>Victoria</code> </font>может быть экземпляром класса <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code>, представляющий собой конкретного сотрудника. Экземпляр класса имеет ровно столько свойств, сколько и родительский класс (не больше и не меньше).</li> +</ul> + +<p>Прототипно-ориентированный язык, например JavaScript, не реализует данное различие: он имеет только объекты. Языки, основанные на прототипах, имеют понятие <em>прототипа объекта — </em>это<em> </em>объект, используемый в качестве шаблона, с целью получить изначальные свойства для нового объекта. Любой объект может иметь собственные свойства, присвоенные либо во время создания, либо во время выполнения. В дополнение, любой объект может быть указан в качестве <em>прототипа </em>для другого объекта, это позволит второму объекту использовать свойства первого.</p> + +<h3 id="Определение_класса">Определение класса</h3> + +<p>В классо-ориентированных языках, вы можете <em>определить класс</em>. В этом определении вы можете указать специальные методы, называемые <em>конструкторами</em>, которые позволят создать экземпляр класса. Метод конструктор может задать начальные значения для свойств экземпляра и выполнять другие действия, в момент создания. Вы можете использовать оператор <code>new</code>, совместно с методом конструктора, для создания экземпляров классов.</p> + +<p>JavaScript использует похожую модель, но не имеет определения класса отдельно от конструктора. Вместо этого, вы определяете функцию-конструктор для создания объектов с начальным набором свойств и значений. Любая функция в JavaScript может быть использована, как конструктор. Вы должны использовать оператор <code>new</code> для создания нового объекта.</p> + +<h3 id="Подклассы_и_наследование">Подклассы и наследование</h3> + +<p>В языках, основанных на классах, вы создаете иерархию классов через объявление классов. В объявлении класса вы можете указать, что новый класс является <em>подклассом</em> уже существующего класса. При этом, подкласс унаследует все свойства суперкласса и в дополнение сможет добавить свои свойства или переопределить унаследованные. Например, предположим, что класс <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code> включает два свойства: <code>name</code> и <code>dept</code>, а класс <code><span style="font-family: consolas,monaco,andale mono,monospace;">Manager</span></code> является подклассом <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code> и добавляет свойство <code>reports</code>. В этом случае, экземпляр класса <code><span style="font-family: consolas,monaco,andale mono,monospace;">Manager</span></code> будет иметь три свойства: <code>name</code>, <code>dept</code>, и <code>reports</code>.</p> + +<p>JavaScript реализует наследование, позволяя связать прототипный объект с любой функцией-конструктором. Итак, вы можете создать объект точь-в-точь, как в примере <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code> — <code><span style="font-family: consolas,monaco,andale mono,monospace;">Manager</span></code>, но используя несколько иную технику. Для начала нужно определить функцию-конструктор <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code>, которая определяет свойства <code>name</code> и <code>dept</code>. Затем, определяем функцию-конструктор <code><span style="font-family: consolas,monaco,andale mono,monospace;">Manager</span></code>, в которой в свою очередь, будет явно вызываться конструктор <span style="font-family: consolas,monaco,andale mono,monospace;"><code>Employee</code> и</span> определяться новое свойство <code>reports</code>. Наконец, присваиваем новый экземпляр <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code>, в качестве <code>prototype</code> для функции-конструктора <code><span style="font-family: consolas,monaco,andale mono,monospace;">Manager</span></code>. Теперь, когда вы создадите нового <span style="font-family: consolas,monaco,andale mono,monospace;">Manager</span>, он унаследует свойства <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">name</span></font> и <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">dept</span></font> из объекта <code><span style="font-family: consolas,monaco,andale mono,monospace;">Employee</span></code>.</p> + +<h3 id="Добавление_и_удаление_свойств">Добавление и удаление свойств</h3> + +<p>В языках, основанных на классах, вы, как правило, создаете класс во время компиляции, а затем вы создаёте экземпляры класса либо во время компиляции, либо во время выполнения. Вы не можете изменить количество или тип свойств класса после определения класса. В JavaScript, однако, вы можете добавлять или удалять свойства любого объекта. Если вы добавляете свойство к объекту, который используется в качестве прототипа для множества объектов, то все эти объекты, для которых он является прототипом, также получат это свойство.</p> + +<h3 id="Подытожим_различия">Подытожим различия</h3> + +<p>Следующая таблица дает краткий обзор некоторых из этих различий. А оставшаяся часть этой главы описывает детали использования конструкторов и прототипов JavaScript для создания иерархии объектов и сравнивает это с тем, как вы могли бы сделать это в Java.</p> + +<table class="fullwidth-table"> + <caption>Сравнение языков на основе классов (Java) и на базе прототипов (JavaScript)</caption> + <thead> + <tr> + <th scope="col">Основанные на классах (Java)</th> + <th scope="col">Основанные на базе прототипов (JavaScript)</th> + </tr> + </thead> + <tbody> + <tr> + <td>Класс и экземпляр являются разными сущностями.</td> + <td>Все объекты могут наследовать свойства другого объекта.</td> + </tr> + <tr> + <td>Определяем класс с помощью определения класса; создаем экземпляр класса с помощью метода-конструктора.</td> + <td>Определение и создание объекта происходит с помощью функций-конcтрукторов.</td> + </tr> + <tr> + <td>Создание отдельного объекта с помощью оператора <code>new</code>.</td> + <td>Так же.</td> + </tr> + <tr> + <td>Иерархия объектов строится с помощью определения классов и их подклассов.</td> + <td> + <p>Построение иерархии объектов происходит путем присвоения объекта в качестве прототипа функции-конструктора.</p> + </td> + </tr> + <tr> + <td>Наследование свойств в цепочке классов.</td> + <td>Наследование свойств в цепочке прототипов.</td> + </tr> + <tr> + <td>Определение класса определяет <em>все</em> свойства всех экземпляров класса. Нельзя динамически добавлять свойства во время выполнения.</td> + <td>Функция-конструктор или прототип задает <em>начальный</em> набор свойств. Можно добавить или удалить свойства динамически к отдельным объектам или всей совокупности объектов.</td> + </tr> + </tbody> +</table> + +<h2 id="Пример_Сотрудник">Пример Сотрудник</h2> + +<p>Оставшаяся часть этой главы объясняет иерархию сотрудников, показанную на следующем рисунке:</p> + +<p><img alt="" class="internal" src="/@api/deki/files/4452/=figure8.1.png" style="height: 194px; width: 281px;"></p> + +<p><small><strong>Рисунок 8.1: Простая иерархия объектов</strong></small></p> + +<p>Этот пример использует следующие объекты:</p> + +<ul> + <li><code>Employee</code> имеет свойство <code>name</code> (значение которого по умолчанию пустая строка) и <code>dept</code> (значение которого по умолчанию "general").</li> + <li><code>Manager</code> основывается на <code>Employee</code>. Он добавляет свойство <code>reports</code> (значение которого по умолчанию пустой массив, предназначенный для хранения массива объектов <code>Employee</code>).</li> + <li><code>WorkerBee</code> так же основан на <code>Employee</code>. Он добавляет свойство <code>projects</code> (значение которого по умолчанию пустой массив, предназначенный для хранения строк).</li> + <li><code>SalesPerson</code> основан на <code>WorkerBee</code>. Он добавляет свойство <code>quota </code>(значение которого по умолчанию 100). Он также переопределяет свойство <code>dept</code>, со значением "sales", указывая, что все продавцы находятся в одном отделе.</li> + <li><code>Engineer</code> основан на <code>WorkerBee</code>. Он добавляет свойство <code>machine</code> (значение которого по умолчанию пустая строка), а так же определяет свойство <code>dept</code> значением "engineering".</li> +</ul> + +<h2 id="Создание_иерархии">Создание иерархии</h2> + +<p>Известно несколько способов определить подходящие функции-конструкторы, которые реализуют иерархию <code>Employee</code>. Выбор способа определения в большей степени зависит от того, на что рассчитано ваше приложение.</p> + +<p>В этом разделе приведены очень простые (и сравнительно не гибкие) определения, для демонстрации того, как же работает наследование. В этих определениях, вы не можете указать значения свойствам при создании объекта. Свойства вновь созданного объекта попросту получают значения по умолчанию, которые можно изменить позднее.</p> + +<p>В реальном приложении, вы, вероятно, будете определять конструкторы, которые позволяют устанавливать нужные вам значения свойств во время создания объекта (см <a href="#Более_гибкие_конструкторы">Более гибкие конструкторы</a>). В данном же случае конструкторы упрощены сознательно для того, чтобы сфокусироваться на сути наследования.</p> + +<p>Следующие определения <code>Employee</code> для языков Java и JavaScript довольно похожи. Единственное отличие состоит в том, что вам необходимо указать тип каждого свойства в Java, но не в JavaScript (потому что Java является <a href="https://ru.wikipedia.org/wiki/Сильная_и_слабая_типизация">строго типизированным языком</a>, в то время как JavaScript слабо типизированный).</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">JavaScript</th> + <th scope="col">Java</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <pre class="brush: js"> +function Employee() { + this.name = ''; + this.dept = 'general'; +} +</pre> + </td> + <td> + <pre class="brush: java"> +public class Employee { + public String name = ""; + public String dept = "general"; +} +</pre> + </td> + </tr> + </tbody> +</table> + +<p>Определения классов <code>Manager</code> и <code>WorkerBee</code> показывают разницу в определении вышестоящего объекта в цепочке наследования. В JavaScript вводится связующий объект (прототипный экземпляр), который присваивается в качестве значения свойству <code>prototype </code>функции-конструктора. Вы можете сделать это в любое время после того, как вы создали конструктор. В Java, необходимо указать суперкласс внутри определения класса. Вы не можете изменить суперкласс вне определения класса.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">JavaScript</th> + <th scope="col">Java</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <pre class="brush: js"> +function Manager() { + Employee.call(this); + this.reports = []; +} +//создаем пустой бъект с прототипом от коструктора Employee +//и используем этот объект как прототип для Manager +Manager.prototype = Object.create(Employee.prototype); + +Manager.prototype.constructor = Manager; + +function WorkerBee() { + Employee.call(this); + this.projects = []; +} +WorkerBee.prototype = Object.create(Employee.prototype); +WorkerBee.prototype.constructor = WorkerBee; +</pre> + </td> + <td> + <pre class="brush: java"> +public class Manager extends Employee { + public Employee[] reports = new Employee[0]; +} + +public class WorkerBee extends Employee { + public String[] projects = new String[0]; +} +</pre> + </td> + </tr> + </tbody> +</table> + +<p>Классы <code>Engineer</code> и <code>SalesPerson</code> создают объекты, которые происходят от <code>WorkerBee</code> и, следовательно, от <code>Employee</code>. Объект этих типов имеет свойства всех объектов, расположенных над ним в иерархии. Также, эти классы переопределяют наследуемое значение свойства <code>dept</code> своими значениями, характерными для этих объектов.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">JavaScript</th> + <th scope="col">Java</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <pre class="brush: js"> +function SalesPerson() { + WorkerBee.call(this); + this.dept = 'sales'; + this.quota = 100; +} +SalesPerson.prototype = Object.create(WorkerBee.prototype); +SalesPerson.prototype.constructor = SalesPerson; + +function Engineer() { + WorkerBee.call(this); + this.dept = 'engineering'; + this.machine = ''; +} +Engineer.prototype = Object.create(WorkerBee.prototype); +Engineer.prototype.constructor = Engineer; +</pre> + </td> + <td> + <pre class="brush: java"> +public class SalesPerson extends WorkerBee { + public double quota; + public dept = "sales"; + public quota = 100.0; +} + +public class Engineer extends WorkerBee { + public String machine; + public dept = "engineering"; + public machine = ""; +} +</pre> + </td> + </tr> + <tr> + <td></td> + <td></td> + </tr> + </tbody> +</table> + +<p>Используя эти определения, вы можете создавать экземпляры объектов, которые получат значения по умолчанию для своих свойств. Рисунок 8.3 иллюстрирует использование этих определений и показывает значения свойств у полученных объектов.</p> + +<p>{{ note('Термин <em>экземпляр </em>имеет специфическое значение в языках, основанных на классах. В этих языках экземпляр — это индивидуальная сущность определенного класса и принципиально отличается от класса. В JavaScript «экземпляр» не имеет такого технического значения, потому что JavaScript не делает таких отличий между классами и экземплярами. Однако, в разговоре о JavaScript, термин «экземпляр» может неформально использоваться для обозначения объекта, созданного с использованием конкретной <span>функции конструктора. Так, в этом примере, вы можете неформально сказать, что <code>jane</code> является экземпляром <code>Engineer</code>. Аналогично, хотя термины <em>parent, child, ancestor</em> и <em>descendant</em> (<em>родитель, ребенок, предок</em> и <em>потомок</em>) не имеют формальных значений в JavaScript, вы можете использовать их неформально для ссылки на объекты выше или ниже</span> <span>в</span> <span>цепочке прототипов.') }}</span></p> + +<p><img alt="figure8.3.png" class="default internal" id="figure8.3" src="/@api/deki/files/4403/=figure8.3.png"><br> + <a id="8.3" name="8.3"><small><strong>Рисунок 8.3: Создание объектов с простыми определениями</strong></small></a></p> + +<h2 id="Свойства_объекта">Свойства объекта</h2> + +<p>Этот раздел о том, как объекты наследуют свойства из других объектов в цепочке прототипов, и что происходит, когда вы добавляете свойство во время выполнения.</p> + +<h3 id="Наследование_свойств">Наследование свойств</h3> + +<p>Предположим, вы создаете объект <code>mark</code> в качестве <code>WorkerBee</code> (как показано на <a href="#8.3">Рисунок 8.3</a>) с помощью следующего выражения:</p> + +<pre class="brush: js">var mark = new WorkerBee; +</pre> + +<p>Когда JavaScript видит оператор <code>new</code>, он создает новый обобщенный объект и неявно устанавливает значение внутреннего свойства [[Prototype]] в <code>WorkerkBee.prototype</code>, затем передает этот новый объект в качестве значения <code>this</code> в функцию-конструктор <code>WorkerBee</code>. Внутреннее свойство [[Prototype]] определяет цепочку прототипов, используемых для получения значений свойств. После того, как эти свойства установлены, JavaScript возвращает новый объект, а оператор присваивания устанавливает переменную <code>mark</code> для этого объекта.</p> + +<p>Этот процесс не задает значения свойств (<em>локальных</em> значений), которые унаследованы по цепочке прототипов, объекта <code>mark</code> напрямую. Когда вы запрашиваете значение свойства, JavaScript сначала проверяет, существует ли это значение в данном объекте. Если так и есть, тогда возвращается это значение. Если значение не найдено в самом объекте, JavaScript проверяет цепочку прототипов (используя внутреннее свойство [[Prorotype]]). Если объект в цепочке прототипов имеет значение для искомого свойства, это значение возвращается. Если такое свойство не найдено, JavaScript сообщает, что объект не обладает свойством. Таким образом, объект <code>mark</code> содержит следующие свойства и значения:</p> + +<pre class="brush: js">mark.name = ''; +mark.dept = 'general'; +mark.projects = []; +</pre> + +<p>Значения для свойств <code>name</code> и <code>dept</code> объекту <code>mark</code> присваиваются из конструктора <code>Employee</code>. Также из конструктора <code>WorkerBee</code> присваивается локальное значение для свойства <code>projects</code>. Это дает вам наследование свойств и их значений в JavaScript. Некоторые детали этого процесса обсуждаются в <a href="#Тонкости_наследования_свойств">Тонкости наследования свойств</a>.</p> + +<p>Поскольку эти конструкторы не позволяют вводить значения, специфичные для экземпляра, добавленная информация является общей. Значения свойств устанавливаются по умолчанию одинаковыми для всех объектов, созданных функцией <code>WorkerBee</code>. Конечно, вы можете изменить значения любого из этих свойств. Так, вы можете добавить специфичную информацию для <code>mark</code> следующим образом:</p> + +<pre class="brush: js">mark.name = 'Doe, Mark'; +mark.dept = 'admin'; +mark.projects = ['navigator'];</pre> + +<h3 id="Добавление_свойств">Добавление свойств</h3> + +<p>В JavaScript вы можете добавить свойства для любого объекта в реальном времени. Вы не ограничены только свойствами, установленными функцией-конструктором. Чтобы добавить свойство, специфичное для конкретного объекта, вы присваиваете ему значение в объекте, вот так:</p> + +<pre class="brush: js">mark.bonus = 3000; +</pre> + +<p>Теперь объект <code>mark</code> имеет свойство <code>bonus</code>, но никакой другой <code>WorkerBee</code> не имеет этого свойства.</p> + +<p>Если вы добавляете новое свойство в объект, который используется в качестве прототипа для функции-конструктора, вы добавляете это свойство для всех объектов, наследующих свойства из этого прототипа. Например, вы можете добавить свойство <code>specialty</code> для всех сотрудников с помощью следующего выражения:</p> + +<pre class="brush: js">Employee.prototype.specialty = 'none'; +</pre> + +<p>Как только JavaScript выполняет это выражение, объект <code>mark</code> также получает свойство <code>specialty</code> со значением <code>"none"</code>. Следующий рисунок показывает результат добавления этого свойства в прототип <code>Employee</code> и последующее переопределение его в прототипе <code>Engineer</code>.</p> + +<p><img alt="" class="internal" src="/@api/deki/files/4422/=figure8.4.png" style="height: 519px; width: 833px;"><br> + <small><strong>Рисунок 8.4: Добавление свойств</strong></small></p> + +<h2 id="Более_гибкие_конструкторы">Более гибкие конструкторы</h2> + +<p><span id="result_box" lang="ru"><span>Функции</span>-<span>конструкторы</span><span>, показанные</span> <span>до сих пор,</span> <span>не</span> <span>позволяют задавать</span> <span>значения</span> <span>свойств при создании</span> <span>экземпляра. Как и в</span> <span>Java</span><span>,</span> <span>вы можете передать</span> <span>аргументы в</span> <span>конструкторах</span> <span>для инициализации значений свойств</span><span> экземпляров</span><span>.</span> <span>На следующем рисунке показан</span> <span>один из способов сделать</span> <span>это</span><span>.</span></span></p> + +<p><img alt="" class="internal" id="figure8.5" src="/@api/deki/files/4423/=figure8.5.png" style="height: 481px; width: 1012px;"><br> + <a id="8.5" name="8.5"><small><strong>Рисунок 8.5: Определение свойств в конструкторе, вариант 1</strong></small></a></p> + +<p>Следующая таблица показывает определения для этих объектов в JavaScript и Java.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">JavaScript</th> + <th scope="col">Java</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <pre class="brush: js"> +function Employee (name, dept) { + this.name = name || ''; + this.dept = dept || 'general'; +} +</pre> + </td> + <td> + <pre class="brush: java"> +public class Employee { + public String name; + public String dept; + public Employee () { + this("", "general"); + } + public Employee (String name) { + this(name, "general"); + } + public Employee (String name, String dept) { + this.name = name; + this.dept = dept; + } +} +</pre> + </td> + </tr> + <tr> + <td> + <pre class="brush: js"> +function WorkerBee (projs) { + + this.projects = projs || []; +} +WorkerBee.prototype = new Employee; +</pre> + </td> + <td> + <pre class="brush: java"> +public class WorkerBee extends Employee { + public String[] projects; + public WorkerBee () { + this(new String[0]); + } + public WorkerBee (String[] projs) { + projects = projs; + } +} + +</pre> + </td> + </tr> + <tr> + <td> + <pre class="brush: js"> + +function Engineer (mach) { + this.dept = 'engineering'; + this.machine = mach || ''; +} +Engineer.prototype = new WorkerBee; +</pre> + </td> + <td> + <pre class="brush: java"> +public class Engineer extends WorkerBee { + public String machine; + public Engineer () { + dept = "engineering"; + machine = ""; + } + public Engineer (String mach) { + dept = "engineering"; + machine = mach; + } +} +</pre> + </td> + </tr> + </tbody> +</table> + +<p>В JavaScript эти определения используют специальную идеому для установки значений по умолчанию:</p> + +<pre class="brush: js">this.name = name || ''; +</pre> + +<p>В JavaScript логический оператор ИЛИ (<code>||</code>) оценивает свой первый аргумент. Если этот аргумент преобразуется в true, оператор возвращает его. Иначе, оператор возвращает значение второго аргумента. Следовательно, эта строчка кода проверяет, содержит ли аргумент <code>name</code> значение, пригодное для свойства <code>name</code>. Если так и есть, <code>this.name</code> определяется этим значением. В противном случае, значению <code>this.name</code> присваивается пустая строка. Эта глава использует такую идиому для краткости; тем не менее, с первого взгляда она может озадачить.</p> + +<p>{{ note('Это может работать не так, как ожидается, если функция-конструктор вызывается с аргументами, которые преобразуются в <code><code>false</code></code>, вроде нуля (<code>0</code>) или пустой строки (<code>""</code>). В этом случае будет выбрано значение по умолчанию.') }}</p> + +<p>С помощью таких определений, создавая экземпляр объекта, вы можете указать значения для локально определенных свойств. Как показано на <a href="#8.5">Рисунок 8.5</a>, можно использовать следующее выражение для создания нового <code>Engineer</code>:</p> + +<pre class="brush: js">var jane = new Engineer('belau'); +</pre> + +<p>Свойства созданного объекта <code>jane</code>:</p> + +<pre class="brush: js">jane.name == ''; +jane.dept == 'engineering'; +jane.projects == []; +jane.machine == 'belau' +</pre> + +<p>Обратите внимание, что с таким способом вы не можете указать начальное значение наследуемого свойства, такого как <code>name</code>. Если вы хотите задать начальное значение для наследуемых свойств в JavaScript, вам нужно добавить больше кода в функцию-конструктор.</p> + +<p>До сих пор функция-конструктор создавала обобщенный объект, а затем определяла локальные свойства и значения для нового объекта. Вы можете использовать конструктор, который добавляет дополнительные свойства путем непосредственного вызова функции-конструктора для объекта, расположенного выше в цепочке прототипов. На следующем рисунке показаны эти новые определения.</p> + +<p><img alt="" class="internal" src="/@api/deki/files/4430/=figure8.6.png" style="height: 534px; width: 1063px;"><br> + <small><strong>Рисунок 8.6: Определение свойств в конструкторе, вариант 2</strong></small></p> + +<p>Давайте рассмотрим одно из этих определений детальнее. Вот новое определение функции-конструктора <code>Engineer</code>:</p> + +<pre class="brush: js">function Engineer (name, projs, mach) { + this.base = WorkerBee; + this.base(name, 'engineering', projs); + this.machine = mach || ''; +} +</pre> + +<p>Предположим, вы создаете новый объект, используя <code>Engineer, следующим образом:</code></p> + +<pre class="brush: js">var jane = new Engineer('Doe, Jane', ['navigator', 'javascript'], 'belau'); +</pre> + +<p>JavaScript <span id="result_box" lang="ru"><span>выполняет следующие действия</span><span>:</span></span></p> + +<ol> + <li>Оператор <code>new</code> создает обобщенный объект и устанавливает его свойству <code>__proto__</code> значение <code>Engineer.prototype</code>.</li> + <li>Оператор <code>new</code> передает этот новый объект в конструктор <code>Engineer</code> в качестве значения ключевого слова <code>this</code>.</li> + <li>Конструктор создает новое свойство с именем <code>base</code> для этого объекта и присваивает значение свойства <code>base</code> из конструктора <code>WorkerBee</code>. Это делает конструктор <code>WorkerBee</code> методом объекта, созданного <code>Engineer</code>. Имя свойства <code>base</code> не является специальным словом. Вы можете использовать любое допустимое для свойства имя; <code>base</code> всего-лишь напоминает о предназначении свойства.</li> + <li>Конструктор вызывает метод <code>base</code>, передавая в качестве аргументов два аргумента, переданных конструктору (<code>"Doe, Jane"</code> и <code>["navigator", "javascript"]</code>), а также строку <code>"engineering"</code>. Явное использование <code>"engineering"</code> в конструкторе указывает на то, что все объекты, созданные <code>Engineer</code>, имеют одинаковое значение для наследуемого свойства <code>dept</code>, это значение переопределяет значение, унаследованное из <code>Employee</code>.</li> + <li>Поскольку <code>base</code> является методом <code>Engineer</code>, внутри вызова <code>base</code> JavaScript привязывает ключевое свойство <code>this</code> к объекту, созданному в шаге 1. Таким образом, функция <code>WorkerBee</code> передает поочередно аргументы <code>"Doe, Jane"</code> и <code>"engineering"</code> в функцию-конструктор <code>Employee</code>. Получив результат из <code>Employee</code>, функция <code>WorkerBee</code> использует оставшийся аргумент для установки значения свойства <code>projects</code>.</li> + <li>После возвращения из метода <code>base</code>, конструктор <code>Engineer</code> инициализирует свойство объекта <code>machine</code> со значением <code>"belau"</code>.</li> + <li>После возвращения из конструктора, JavaScript присваивает новый объект переменной <code>jane</code>.</li> +</ol> + +<p>Можно подумать, что вызвав <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 class="brush: js">function Engineer (name, projs, mach) { + this.base = WorkerBee; + this.base(name, 'engineering', projs); + this.machine = mach || ''; +} +var jane = new Engineer('Doe, Jane', ['navigator', 'javascript'], 'belau'); +Employee.prototype.specialty = 'none'; +</pre> + +<p>В примере выше <code>jane</code> не унаследует свойство <code>specialty</code>. Для динамического наследования необходимо явно устанавливать прототип, что и сделано в следующем дополнении:</p> + +<pre class="brush: js">function Engineer (name, projs, mach) { + this.base = WorkerBee; + this.base(name, 'engineering', projs); + this.machine = mach || ''; +} +Engineer.prototype = new WorkerBee; +var jane = new Engineer('Doe, Jane', ['navigator', 'javascript'], 'belau'); +Employee.prototype.specialty = "none"; +</pre> + +<p>Теперь свойство <code>specialty</code> объекта <code>jane</code> имеет значение "none".</p> + +<p>Другой способ вызвать родительский конструктор в контексте создаваемого объекта это использование методов <a href="/en-US/docs/JavaScript/Reference/Global_Objects/Function/call" title="en-US/docs/JavaScript/Reference/Global Objects/Function/call"><code>call()</code></a> / <a href="/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply" title="en-US/docs/JavaScript/Reference/Global Objects/Function/apply"><code>apply()</code></a>. Следующие блоки эквивалентны:</p> + +<table> + <tbody> + <tr> + <td> + <pre class="brush: js"> +function Engineer (name, projs, mach) { + this.base = WorkerBee; + this.base(name, 'engineering', projs); + this.machine = mach || ''; +} +</pre> + </td> + <td> + <pre class="brush: js"> +function Engineer (name, projs, mach) { + WorkerBee.call(this, name, 'engineering', projs); + this.machine = mach || ''; +} +</pre> + </td> + </tr> + </tbody> +</table> + +<p>Использование метода <code>call()</code> является более чистой реализацией наследования, так как он не требует создания дополнителного свойства, именованного в примере как <code>base</code>.</p> + +<h2 id="Тонкости_наследования_свойств">Тонкости наследования свойств</h2> + +<p>В секции выше рассказывалось каким образом конструкторы и прототипы в JavaScript обеспечивают иерархию и наследование. В секции ниже будут затронуты тонкости, которые выше были не так очевидны.</p> + +<h3 id="Локальные_значения_против_унаследованных">Локальные значения против унаследованных</h3> + +<p>Когда вы пытаетесь получить значение некоторого свойства объекта, JavaScript выполняет следующие шаги, которые уже перечислялись ранее в этой главе:</p> + +<ol> + <li>Проверяется, существует ли локальное свойство с запрашиваемым именем. Если да, то возвращается значение этого свойства.</li> + <li>Если локального свойства не существует, проверяется цепочка прототипов (через использование свойства <code>__proto__</code>).</li> + <li>Если один из объектов в цепочке прототипов имеет свойство c запрашиваемым именем, возвращается значение этого свойства.</li> + <li>Если искомое свойство не обнаружено, считается, что объект его не имеет.</li> +</ol> + +<p>Результат выполнения этих шагов будет зависеть от того, в каком порядке вы создаете объекты, прототипы и их свойства. Рассмотрим пример:</p> + +<pre class="brush: js">function Employee () { + this.name = ""; + this.dept = "general"; +} + +function WorkerBee () { + this.projects = []; +} +WorkerBee.prototype = new Employee; +</pre> + +<p>Предположим, на основе конструкции выше, вы создаете объект <code>amy</code> как экземпляр класса <code>WorkerBee</code> следующим выражением:</p> + +<pre class="brush: js">var amy = new WorkerBee; +</pre> + +<p><code><font face="Open Sans, Arial, sans-serif">В результате, объект </font>amy</code> будет иметь одно локальное своство - <code>projects</code>. Свойства <code>name</code> и <code>dept</code> не будут локальными для <code>amy</code> но будут взяты из прототипа (объект на который ссылается свойство <code>__proto__</code> объекта <code>amy)</code>. Таким образом, <code>amy</code> имеет три свойства:</p> + +<pre class="brush: js">amy.name == ""; +amy.dept == "general"; +amy.projects == []; +</pre> + +<p>Теперь предположим, что вы изменили значение свойства <code>name</code> у прототипа <code>Employee</code>:</p> + +<pre class="brush: js">Employee.prototype.name = "Unknown" +</pre> + +<p>На первый взгляд вы можете ожидать, что это изменение распространится на все экземпляры <code>Employee</code>. Однако этого не случится.</p> + +<p>Когда вы устанавливаете прототип для <code>WorkerBee</code> вы создаете новый объект <code>Employee</code>, таким образом <code>WorkerBee.prototype</code> получает свое собственное локальное свойство <code>name</code> (в данном примере пустую строку). Следовательно, когда JavaScript ищет свойство <code>name</code> у объекта <code>amy</code> (экземпляра <code>WorkerBee</code>), он первым делом натыкается на него в прототипе <code>WorkerBee.prototype,</code> и до проверки <code>Employee.prototype</code> дело не доходит.</p> + +<p>Если у вас есть необходимость изменять некоторое свойство объекта во время работы приложения, и применять это изменение на все существующие экземпляры, не нужно создавать это свойство внутри конструктора. Вместо этого добавьте свойство в прототип, принадлежащий конструктору. Для примера, предположим, вы изменили код, который был показан выше, следующим образом:</p> + +<pre class="brush: js">function Employee () { + this.dept = "general"; +} +Employee.prototype.name = ""; + +function WorkerBee () { + this.projects = []; +} +WorkerBee.prototype = new Employee; + +var amy = new WorkerBee; + +Employee.prototype.name = "Unknown"; +</pre> + +<p>в таком случае свойство <code>name</code> у объекта <code>amy</code> примет значение "Unknown".</p> + +<p>Как показано в этом примере, если вы хотите иметь значения свойств по умолчанию, и иметь возможность менять эти значения во время работы приложения, создавайте их в прототипе конструктора, а не в самом конструкторе.</p> + +<h3 id="Разбираемся_во_взаимосвязи_экземпляров">Разбираемся во взаимосвязи экземпляров</h3> + +<p>Поиск свойств в JavaScript начинается с просмотра самого объекта, и если в нем свойство не найдено, поиск переключается на объект, на который указывает ссылка <code>__proto__</code>. Это продолжается рекурсивно и такой процесс поиска называется "поиск в цепочке прототипов".</p> + +<p>Специальное свойство <code>__proto__</code> устанавливается автоматически при создании объекта. Оно принимает значение свойства <code>prototype</code> функции-конструктора. Таким образом, <code>new Foo()</code> создаст объект для которого справедливо выражение <code>__proto__ == <code class="moz-txt-verticalline">Foo.prototype</code></code>. Вследствие этого, любые изменения свойств у <code class="moz-txt-verticalline">Foo.prototype,</code> оказывают эффект на процесс поиска свойств во всех объектах, созданных при помощи <code>new Foo()</code>.</p> + +<p>Все объекты (за исключением глобального объекта <code>Object</code>) имеют свойство <code>__proto__.</code> Все функции имеют свойство <code>prototype</code>. Благодаря этому, могут быть установлены родственные связи в иерархии объектов. Вы можете установить родство и происхождение объекта, сравнив его свойство <code>__proto__</code> со свойством <code>prototype</code> конструктора. Здесь JavaScript представляет оператор <code>instanceof</code> как более простой способ проверки, наследуется ли объект от конкретного конструктора. Для примера:</p> + +<pre class="brush: js">var f = new Foo(); +var isTrue = (f instanceof Foo);</pre> + +<p>Для более детального примера, предположим, у вас имеются те же определения, что приведены в разделе <a href="#Inheriting_properties">Inheriting properties</a>. Создадим экземпляр <code>Engineer</code> как показано здесь:</p> + +<pre class="brush: js">var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji"); +</pre> + +<p>Для полученного объекта будут истинными все из следующих выражений:</p> + +<pre class="brush: js">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; +</pre> + +<p>Зная это, вы можете написать свою функцию <code>instanceOf</code> как показано ниже:</p> + +<pre class="brush: js">function instanceOf(object, constructor) { + object = object.__proto__; + while (object != null) { + if (object == constructor.prototype) + return true; + if (typeof object == 'xml') { + return constructor.prototype == XML.prototype; + } + object = object.__proto__; + } + return false; +} +</pre> + +<div class="note"><strong>Замечание:</strong> Реализация выше особым образом обрабатывает тип "xml". Это сделано для того, чтобы обойти особенность представления XML объектов в последних версиях JavaScript. Смотрите описание ошибки {{ bug(634150) }} если вам интересны детали.</div> + +<p>Следующие вызовы функции instanceOf, заданной выше, вернут истинные значения:</p> + +<pre>instanceOf (chris, Engineer) +instanceOf (chris, WorkerBee) +instanceOf (chris, Employee) +instanceOf (chris, Object) +</pre> + +<p>Но следующее выражение вернет <code>false</code>:</p> + +<pre class="brush: js">instanceOf (chris, SalesPerson) +</pre> + +<h3 id="Глобальные_данные_в_конструкторах">Глобальные данные в конструкторах</h3> + +<p>При написании конструкторов, следует с особым вниманием относиться к изменению глобальных переменных. Например, если вам нужен уникальный ID, который был бы автоматически назначен каждому экземпляру <code>Employee,</code> вы примените следующий подход для определения <code>Employee</code>:</p> + +<pre class="brush: js">var idCounter = 1; + +function Employee (name, dept) { + this.name = name || ""; + this.dept = dept || "general"; + this.id = idCounter++; +} +</pre> + +<p>Здесь, когда вы создаете новый экземпляр <code>Employee</code>, конструктор присваивает ему все новый и новый ID увеличивая значение глобальной переменной <code>idCounter</code>. Следовательно, при выполнении кода ниже, <code>victoria.id</code> станет равным 1 а <code>harry.id</code> — 2:</p> + +<pre class="brush: js">var victoria = new Employee("Pigbert, Victoria", "pubs") +var harry = new Employee("Tschopik, Harry", "sales") +</pre> + +<p>Навскидку, все выглядит предсказуемо. Однако, <code>idCounter</code> увеличивается при создании каждого объекта <code>Employee</code> вне зависимости от цели его создания. Если вы создаете полную иерархию класса <code>Employee,</code> показанную выше в этой главе, конструктор <code>Employee</code> будет так же вызван каждый раз, когда вы устанавливаете прототип для подклассов. Следующий код раскрывает суть возможной проблемы:</p> + +<pre class="brush: js">var idCounter = 1; + +function Employee (name, dept) { + this.name = name || ""; + this.dept = dept || "general"; + this.id = idCounter++; +} + +function Manager (name, dept, reports) {...} +Manager.prototype = new Employee; + +function WorkerBee (name, dept, projs) {...} +WorkerBee.prototype = new Employee; + +function Engineer (name, projs, mach) {...} +Engineer.prototype = new WorkerBee; + +function SalesPerson (name, projs, quota) {...} +SalesPerson.prototype = new WorkerBee; + +var mac = new Engineer("Wood, Mac"); +</pre> + +<p>Предположим, каждый из конструкторов, тело которого опущено для краткости, содержит вызов конструктора прародителя. Это приведет к тому, что <code>id</code> у объекта <code>mac</code> примет значение 5 вместо ожидаемой единицы.</p> + +<p>В зависимости от приложения, лишние увеличения счетчика могут быть не критичны. В случае же, когда точный контроль за значениями счетчика важен, одним из возможных решений станет такой код:</p> + +<pre class="brush: js">function Employee (name, dept) { + this.name = name || ""; + this.dept = dept || "general"; + if (name) + this.id = idCounter++; +} +</pre> + +<p>Когда вы создаете экземпляр <code>Employee</code> в качестве прототипа, вы не предоставляете аргументы в конструктор за ненадобностью. Конструктор выше проверяет наличие аргумента <code>name,</code> и в случае, если значение не указано, идентификатор id объекту не присваивается, а значение глобального счетчика <code>idCounter</code> не увеличивается. Таким образом, для получения уникального <code>id</code> становится обязательным указание параметра <code>name</code> при вызове конструктора <code>Employee</code>. С внесенными в пример выше изменениями, <code>mac.id</code> станет равным долгожданной, заветной единице.</p> + +<h3 id="Никакого_множественного_наследования">Никакого множественного наследования</h3> + +<p>Некоторые из объектно-ориентированных языков предоставляют возможность множественного наследования. Когда один объект может унаследовать свойства и методы множества других, не связанных друг с другом объектов. В JavaScript такого не предусмотрено.</p> + +<p>В JavaScript наследование свойств осуществляется путем поиска в цепочке прототипов. Так как объект может иметь лишь единственный присвоенный ему прототип, JavaScript не может осуществить наследование более чем от одной цепочки прототипов.</p> + +<p>Однако конструктор в JavaScript может вызывать любое количество вложенных конструкторов. Это дает некоторую, хоть и ограниченную (отсутствием прототипной связанности) видимость множественного наследования. Рассмотрим следуюший фрагмент:</p> + +<pre class="brush: js">function Hobbyist (hobby) { + this.hobby = hobby || "scuba"; +} + +function Engineer (name, projs, mach, hobby) { + this.base1 = WorkerBee; + this.base1(name, "engineering", projs); + this.base2 = Hobbyist; + this.base2(hobby); + this.machine = mach || ""; +} +Engineer.prototype = new WorkerBee; + +var dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo") +</pre> + +<p>Предполагается, что определение <code>WorkerBee</code> задано ранее, как уже было показано в этой главе. В таком случае список свойств для объекта <code>dennis</code> примет следующий вид:</p> + +<pre class="brush: js">dennis.name == "Doe, Dennis" +dennis.dept == "engineering" +dennis.projects == ["collabra"] +dennis.machine == "hugo" +dennis.hobby == "scuba" +</pre> + +<p>Мы видим, что <code>dennis</code> получил свойство <code>hobby</code> из конструктора <code>Hobbyist</code>. Однако, если вы добавите любое свойство в прототип конструктора <code>Hobbyist</code>:</p> + +<pre class="brush: js">Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"] +</pre> + +<p>Объект <code>dennis</code> этого свойства не унаследует.</p> + +<div>{{PreviousNext("Web/JavaScript/Guide/Working_with_Objects", "Web/JavaScript/Guide/Iterators_and_Generators")}}</div> |
