1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
|
---
title: Наследование в JavaScript
slug: Learn/JavaScript/Objects/Inheritance
tags:
- JavaScript
- Наследование
- ООП
translation_of: Learn/JavaScript/Objects/Inheritance
original_slug: Learn/JavaScript/Объекты/Inheritance
---
<p>
<audio class="audio-for-speech"></audio>
</p>
<div class="translate-tooltip-mtz hidden">
<div class="header">
<div class="header-controls"></div>
<div class="translate-icons"><img class="from" src=""> <img class="arrow"> <img class="to" src=""></div>
</div>
<div class="translated-text">
<div class="words"></div>
<div class="sentences"></div>
</div>
</div>
<div>{{LearnSidebar}}</div>
<div>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}</div>
<p class="summary">Теперь, когда объясняется большая часть подробностей OOJS, эта статья показывает, как создавать «дочерние» классы объектов (конструкторы), которые наследуют признаки из своих «родительских» классов. Кроме того, мы дадим некоторые советы о том, когда и где вы можете использовать OOJS , и посмотрим, как классы рассматриваются в современном синтаксисе ECMAScript.</p>
<table class="learn-box standard-table">
<tbody>
<tr>
<th scope="row">Необходимые знания:</th>
<td>
<p>Базовая компьютерная грамотность, понимание основ HTML и CSS, знакомство с основами JavaScript (см. <a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B5_%D1%88%D0%B0%D0%B3%D0%B8">Первые шаги</a> и <a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Building_blocks">Структурные элементы</a>) and основы Объектно-ориентированного JS (см. <a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B/%D0%9E%D1%81%D0%BD%D0%BE%D0%B2%D1%8B">Введение в объекты</a>).</p>
</td>
</tr>
<tr>
<th scope="row">Цель:</th>
<td>Понять, как можно реализовать наследование в JavaScript.</td>
</tr>
</tbody>
</table>
<h2 id="Прототипное_наследование">Прототипное наследование</h2>
<p>До сих пор мы видели некоторое наследование в действии - мы видели, как работают прототипы и как элементы наследуются, поднимаясь по цепочке. Но в основном это связано с встроенными функциями браузера. Как создать объект в JavaScript, который наследует от другого объекта?</p>
<p>Давайте рассмотрим, как это сделать на конкретном примере.</p>
<h2 id="Начало_работы">Начало работы</h2>
<p>Прежде всего сделайте себе локальную копию нашего файла <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/advanced/oojs-class-inheritance-start.html">oojs-class-inheritance-start.html</a> (он также работает <a href="https://mdn.github.io/learning-area/javascript/oojs/advanced/oojs-class-inheritance-start.html">в режиме реального времени</a>). В файле вы найдете тот же пример конструктора <code>Person()</code>, который мы использовали на протяжении всего модуля, с небольшим отличием - мы определили внутри конструктора только лишь свойства:</p>
<pre class="brush: js notranslate">function Person(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
};</pre>
<p><em>Все</em> методы определены в прототипе конструктора. Например:</p>
<pre class="brush: js notranslate">Person.prototype.greeting = function() {
alert('Hi! I\'m ' + this.name.first + '.');
};</pre>
<div class="note">
<p><strong>Примечание</strong>. В исходном коде вы также увидите определенные методы <code>bio()</code> и <code>farewell()</code>. Позже вы увидите, как они могут быть унаследованы другими конструкторами.</p>
</div>
<p>Скажем так, мы хотели создать класс <code>Teacher</code>, подобный тому, который мы описали в нашем первоначальном объектно-ориентированном определении, которое наследует всех членов от <code>Person</code>, но также включает в себя:</p>
<ol>
<li>Новое свойство, <code>subject</code> - оно будет содержать предмет, который преподает учитель.</li>
<li>Обновленный метод <code>greeting()</code>, который звучит немного более формально, чем стандартный метод <code>greeting()</code>— более подходит для учителя, обращающегося к некоторым ученикам в школе.</li>
</ol>
<h2 id="Определение_функции-конструктора_Teacher">Определение функции-конструктора Teacher()</h2>
<p>Первое, что нам нужно сделать, это создать конструктор <code>Teacher()</code> - добавьте ниже следующий код:</p>
<pre class="brush: js notranslate">function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);
this.subject = subject;
}</pre>
<p>Это похоже на конструктор Person во многих отношениях, но здесь есть что-то странное, что мы не видели раньше - функцию <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call">call()</a></code>. Эта функция в основном позволяет вам вызывать функцию, определенную где-то в другом месте, но в текущем контексте. Первый параметр указывает значение <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this">this</a></code>, которое вы хотите использовать при выполнении функции, а остальные параметры - те, которые должны быть переданы функции при ее вызове.</p>
<p>Мы хотим, чтобы конструктор <code>Teacher()</code> принимал те же параметры, что и конструктор <code>Person()</code>, от которго он наследуется, поэтому мы указываем их как параметры в вызове <code>call()</code>.</p>
<p>Последняя строка внутри конструктора просто определяет новое свойство <code>subject</code>, которое будут иметь учителя, и которого нет у Person().</p>
<p>В качестве примечания мы могли бы просто сделать это:</p>
<pre class="brush: js notranslate">function Teacher(first, last, age, gender, interests, subject) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
this.subject = subject;
}</pre>
<p>Но это просто переопределяет свойства заново, а не наследует их от <code>Person()</code>, так что теряется смысл того, что мы пытаемся сделать. Он также занимает больше строк кода.</p>
<h3 id="Наследование_от_конструктора_без_параметров">Наследование от конструктора без параметров</h3>
<p>Обратите внимание, что если конструктор, от которого вы наследуете, не принимает значения своего свойства из параметров, вам не нужно указывать их в качестве дополнительных аргументов в <code>call()</code>. Так, например, если у вас было что-то действительно простое:</p>
<pre class="brush: js notranslate">function Brick() {
this.width = 10;
this.height = 20;
}</pre>
<p>Вы можете наследовать свойства <code>width</code> и <code>height</code>, выполнив это (как и другие шаги, описанные ниже, конечно):</p>
<pre class="brush: js notranslate">function BlueGlassBrick() {
Brick.call(this);
this.opacity = 0.5;
this.color = 'blue';
}</pre>
<p>Обратите внимание, что мы указали только <code>this</code> внутри <code>call()</code> - никаких других параметров не требуется, поскольку мы не наследуем никаких свойств родителя, которые задаются через параметры.</p>
<h2 id="Установка_Teachers_prototype_и_конструктор_ссылок">Установка Teacher()'s prototype и конструктор ссылок</h2>
<p>Пока все хорошо, но у нас есть проблема. Мы определили новый конструктор и у него есть свойство <code>prototype</code>, которое по умолчанию просто содержит ссылку на саму конструкторскую функцию. Он не содержит методов свойства <code>prototype</code> конструктора <code>Person</code>. Чтобы увидеть это, введите <code>Object.getOwnPropertyNames(Teacher.prototype)</code> в поле ввода текста или в вашу консоль JavaScript. Затем введите его снова, заменив <code>Teacher</code> на <code>Person</code>. Новый конструктор <em>не наследует</em> эти методы. Чтобы увидеть это, сравните выводы в консоль <code>Person.prototype.greeting</code> и <code>Teacher.prototype.greeting</code>. Нам нужно заставить <code>Teacher()</code> наследовать методы, определенные на прототипе <code>Person()</code>. Итак, как мы это делаем?</p>
<ol>
<li>Добавьте следующую строку ниже своего предыдущего добавления:
<pre class="brush: js notranslate">Teacher.prototype = Object.create(Person.prototype);</pre>
Здесь наш друг <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create">create()</a></code> снова приходит на помощь. В этом случае мы используем его для создания нового объекта и делаем его значением <code>Teacher.prototype</code>. Новый объект имеет свой прототип <code>Person.prototype</code> и, следовательно, наследует, если и когда это необходимо, все доступные методы <code>Person.prototype</code>.</li>
<li>Нам нужно сделать еще одну вещь, прежде чем двигаться дальше. После добавления последней строки, <code>Teacher.prototype.constructor</code> стало равным <code>Person()</code>, потому что мы просто устанавливаем <code>Teacher.prototype</code> для ссылки на объект, который наследует его свойства от <code>Person.prototype</code>! Попробуйте сохранить код, загрузите страницу в браузере и введите <code>Teacher.prototype.constructor</code> в консоль для проверки.</li>
<li>Это может стать проблемой, поэтому нам нужно сделать это правильно. Вы можете сделать это, вернувшись к исходному коду и добавив следующие строки внизу:
<pre class="notranslate"><code>Object.defineProperty(Teacher.prototype, 'constructor', {
value: Teacher,
enumerable: false, // false, чтобы данное свойство не появлялось в цикле for in
writable: true });</code></pre>
</li>
<li>Теперь, если вы сохраните и обновите, введите <code>Teacher.prototype.constructor</code>, чтобы вернуть <code>Teacher()</code>, плюс мы теперь наследуем <code>Person()</code>!</li>
</ol>
<h2 id="Предоставление_Teacher_новой_функции_greeting">Предоставление Teacher() новой функции greeting()</h2>
<p>Чтобы завершить наш код, нам нужно определить новую функцию <code>greeting()</code> в конструкторе <code>Teacher()</code>.</p>
<p>Самый простой способ сделать это - определить его на прототипе <code>Teacher()</code> - добавить в нижнюю часть кода следующее:</p>
<pre class="brush: js notranslate">Teacher.prototype.greeting = function() {
var prefix;
if (this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
prefix = 'Mr.';
} else if (this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
prefix = 'Mrs.';
} else {
prefix = 'Mx.';
}
alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};</pre>
<p>Это выводит на экран приветствие учителя, в котором используется соответствующий префикс имени для своего пола, разработанный с использованием условного оператора.</p>
<h2 id="Попробуйте_пример">Попробуйте пример</h2>
<p>Теперь, когда вы ввели весь код, попробуйте создать экземпляр объекта из <code>Teacher()</code>, поставив ниже вашего JavaScript кода (или что-то похожее по вашему выбору):</p>
<pre class="brush: js notranslate">var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');</pre>
<p>Теперь сохраните, обновите, и попробуйте получить доступ к свойствам и методам вашего нового объекта <code>teacher1</code>, например:</p>
<pre class="brush: js notranslate">teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();
teacher1.farewell();</pre>
<p>Все должно работать нормально. Запросы в строках 1, 2, 3 и 6 унаследованны от общего конструктора <code>Person()</code> (класса). Запрос в строке 4 обращается к <code>subject</code>, доступному только для более специализированного конструктора (класса) <code>Teacher()</code>. Запрос в строке 5 получил бы доступ к методу <code>greeting()</code>, унаследованному от <code>Person()</code>, но <code>Teacher()</code> имеет свой собственный метод <code>greeting()</code> с тем же именем, поэтому запрос обращается к этому методу.</p>
<div class="note">
<p><strong>Примечание</strong>. Если вам не удается заставить это работать, сравните свой код с нашей <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/advanced/oojs-class-inheritance-finished.html">готовой версией</a> (см. также <a href="http://mdn.github.io/learning-area/javascript/oojs/advanced/oojs-class-inheritance-student.html">рабочее демо</a>).</p>
</div>
<p>Методика, которую мы здесь рассмотрили, - это не единственный способ создания наследующих классов в JavaScript, но он работает нормально и это дает вам представление о том, как реализовать наследование в JavaScript.</p>
<p>Вам также может быть интересно узнать некоторые из новых функций {{glossary("ECMAScript")}}, которые позволяют нам делать наследование более чисто в JavaScript (см. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes">Classes</a>). Мы не рассматривали их здесь, поскольку они пока не поддерживаются очень широко в браузерах. Все остальные конструкторы кода, которые мы обсуждали в этом наборе статей, поддерживаются еще в IE9 или ранее и есть способы добиться более ранней поддержки, чем это.</p>
<p>Обычный способ - использовать библиотеку JavaScript - большинство популярных опций имеют простой набор функций, доступных для выполнения наследования более легко и быстро. <a href="http://coffeescript.org/#classes">CoffeeScript</a> , например, предоставляет класс, расширяет и т.д.</p>
<h2 id="Дальнейшее_упражнение">Дальнейшее упражнение</h2>
<p>В нашем <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS#Object-oriented_programming_from_10000_meters">руководстве по Объектно-ориентированному JavaScript для начинающих</a> мы также включили класс <code>Student</code> как концепцию, которая наследует все особенности <code>Person</code>, а также имеет другой метод <code>greeting()</code> от <code>Person</code>, который гораздо более неформален, чем приветствие <code>Teacher</code>. Посмотрите, как выглядит приветствие ученика в этом разделе, и попробуйте реализовать собственный конструктор <code>Student()</code>, который наследует все функции <code>Person()</code> и реализует другую функцию <code>greeting()</code>.</p>
<div class="note">
<p><strong>Примечание</strong>. Если вам не удается заставить это работать, сравните свой код с нашей <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/advanced/oojs-class-inheritance-student.html">готовой версией</a> (см. также <a href="http://mdn.github.io/learning-area/javascript/oojs/advanced/oojs-class-inheritance-student.html">рабочее демо</a>).</p>
</div>
<h2 id="Object_member_summary">Object member summary</h2>
<p>Подводя итог, вы в основном получили три типа свойств / методов, о которых нужно беспокоиться:</p>
<ol>
<li>Те, которые определены внутри функции-конструктора, которые присваиваются экземплярам объекта. Их довольно легко заметить - в вашем собственном коде они представляют собой элементы, определенные внутри конструктора, используя строки <code>this.x = x</code>; в встроенном коде браузера они являются членами, доступными только для экземпляров объектов (обычно создаются путем вызова конструктора с использованием ключевого слова <code>new</code>, например <code>var myInstance = new myConstructor ()</code>.</li>
<li>Те, которые определяются непосредственно самим конструктором, которые доступны только для конструктора. Они обычно доступны только для встроенных объектов браузера и распознаются путем непосредственной привязки к конструктору, а не к экземпляру. Например, <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys">Object.keys()</a></code>.</li>
<li>Те, которые определены в прототипе конструктора, которые наследуются всеми экземплярами и наследуют классы объектов. К ним относятся любой член, определенный в свойстве прототипа конструктора, например. <code>myConstructor.prototype.x()</code>.</li>
</ol>
<p>Если вы не уверены, что это такое, не беспокойтесь об этом, пока вы еще учитесь и знание придет с практикой.</p>
<h2 id="Когда_вы_используете_наследование_в_JavaScript">Когда вы используете наследование в JavaScript?</h2>
<p>В частности, после этой последней статьи вы можете подумать: «У-у-у, это сложно». Ну, ты прав. Прототипы и наследование представляют собой некоторые из самых сложных аспектов JavaScript, но многие возможности и гибкость JavaScript вытекают из его структуры объектов и наследования и стоит понять, как это работает.</p>
<p>В некотором смысле вы используете наследование все время. Всякий раз, когда вы используете различные функции веб-API или методы/свойства, определенные во встроенном объекте браузера, который вы вызываете в своих строках, массивах и т.д., вы неявно используете наследование.</p>
<p>Что касается использования наследования в вашем собственном коде, вы, вероятно, не будете часто его использовать, особенно для начала и в небольших проектах. Это пустая трата времени на использование объектов и наследование только ради этого, когда они вам не нужны. Но по мере того, как ваши базы кода становятся больше, вы с большей вероятностью найдете необходимость в этом. Если вы начинаете создавать несколько объектов с подобными функциями, то создание универсального типа объекта, содержащего все общие функции и наследование этих функций в более специализированных типах объектов, может быть удобным и полезным.</p>
<div class="note">
<p><strong>Примечание</strong>. Из-за того, как работает JavaScript, с цепочкой прототипов и т.д., совместное использование функций между объектами часто называется <strong>делегированием</strong>. Специализированные объекты делегируют функциональность универсальному типу объекта.</p>
</div>
<p>При использовании наследования вам рекомендуется не иметь слишком много уровней наследования и тщательно отслеживать, где вы определяете свои методы и свойства. Можно начать писать код, который временно изменяет прототипы встроенных объектов браузера, но вы не должны этого делать, если у вас нет действительно веской причины. Слишком много наследования могут привести к бесконечной путанице и бесконечной боли при попытке отладки такого кода.</p>
<p>В конечном счете, объекты - это еще одна форма повторного использования кода, например функций или циклов, со своими конкретными ролями и преимуществами. Если вы обнаруживаете, что создаете кучу связанных переменных и функций и хотите отслеживать их все вместе и аккуратно их упаковывать, объект является хорошей идеей. Объекты также очень полезны, когда вы хотите передать коллекцию данных из одного места в другое. Обе эти вещи могут быть достигнуты без использования конструкторов или наследования. Если вам нужен только один экземпляр объекта, вам лучше всего использовать литерал объекта и вам, разумеется, не нужно наследование.</p>
<h2 id="Резюме">Резюме</h2>
<p>В этой статье мы рассмотрели оставшуюся часть основной теории и синтаксиса OOJS, которые, как мы думаем, вам следует знать сейчас. На этом этапе вы должны понимать основы JavaScript, ООП, прототипы и прототипное наследование, как создавать классы (конструкторы) и экземпляры объектов, добавлять функции в классы и создавать подклассы, которые наследуются от других классов.</p>
<p>В следующей статье мы рассмотрим, как работать с JavaScript Object Notation (JSON), общим форматом обмена данными, написанным с использованием объектов JavaScript.</p>
<h2 id="See_also">See also</h2>
<ul>
<li><a href="http://www.objectplayground.com/">ObjectPlayground.com</a> — A really useful interactive learning site for learning about objects.</li>
<li><a href="https://www.amazon.com/gp/product/193398869X/">Secrets of the JavaScript Ninja</a>, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.</li>
<li><a href="https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/README.md#you-dont-know-js-this--object-prototypes">You Don't Know JS: this & Object Prototypes</a> — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.</li>
</ul>
<p>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}</p>
<h2 id="В_этом_модуле">В этом модуле</h2>
<ul>
<li><a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Объекты/Основы">Основы объекта</a></li>
<li><a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Объекты/Object-oriented_JS">Объектно-ориентированный JavaScript для начинающих</a></li>
<li><a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Объекты/Object_prototypes">Прототипы объектов</a></li>
<li><a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Объекты/Inheritance">Наследование в JavaScript</a></li>
<li><a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Объекты/JSON">Работа с данными JSON</a></li>
<li><a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Объекты/Object_building_practice">Практика построения объектов</a></li>
<li><a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Объекты/Adding_bouncing_balls_features">Добавление функций в нашу демонстрацию прыгающих шаров</a></li>
</ul>
|