aboutsummaryrefslogtreecommitdiff
path: root/files/ru/web/javascript/reference/classes/index.html
blob: 8b64f585f0c889ea757c59cb6862a94efa765839 (plain)
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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
---
title: Классы
slug: Web/JavaScript/Reference/Classes
tags:
  - ECMAScript 2015
  - ECMAScript6
  - JavaScript
  - Reference
  - Классы
  - Наследование
translation_of: Web/JavaScript/Reference/Classes
---
<div>{{JsSidebar("Классы")}}</div>

<p>Классы в JavaScript были введены в ECMAScript 2015 и представляют собой синтаксический сахар над существующим в JavaScript механизмом прототипного наследования. Синтаксис классов <strong>не вводит</strong> новую объектно-ориентированную модель, а предоставляет более простой и понятный способ создания объектов и организации наследования.</p>

<h2 id="Определение_классов">Определение классов</h2>

<p>На самом деле классы — это "специальные <a href="/ru/docs/Web/JavaScript/Reference/Functions">функции</a>", поэтому точно также, как вы определяете функции (<a href="/ru-RU/docs/Web/JavaScript/Reference/Operators/function">function expressions</a> и <a href="/ru-RU/docs/Web/JavaScript/Reference/Statements/function">function declarations</a>), вы можете определять и классы с помощью: <a href="/en-US/docs/Web/JavaScript/Reference/Statements/class">class declarations</a> и <a href="/en-US/docs/Web/JavaScript/Reference/Operators/class">class expressions</a>.</p>

<h3 id="Объявление_класса">Объявление класса</h3>

<p>Первый способ определения класса — <strong>class declaration (</strong><em>объявление класса</em><strong>)</strong>. Для этого необходимо воспользоваться ключевым словом <code>class</code> и указать имя класса (в примере — «Rectangle»).</p>

<pre class="brush: js notranslate"><code>class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}</code></pre>

<h4 id="Подъём_hoisting">Подъём (hoisting)</h4>

<p>Разница между <em>объявлением функции</em> (<em>function declaration</em>) и <em>объявлением класса</em> (<em>class declaration</em>) в том, что <em>объявление функции</em> совершает подъём ({{Glossary("Hoisting", "hoisted")}}), в то время как <em>объявление класса</em> — нет. Поэтому вначале необходимо объявить ваш класс и только затем работать с ним, а код же вроде следующего сгенерирует исключение типа {{jsxref("ReferenceError")}}:</p>

<pre class="brush: js notranslate"><code>var p = new Rectangle(); // ReferenceError

class Rectangle {}</code></pre>

<h3 id="Выражение_класса">Выражение класса</h3>

<p>Второй способ определения класса — <strong>class expression (</strong><em>выражение класса</em><strong>)</strong>. Можно создавать именованные и безымянные выражения. В первом случае имя выражения класса находится в локальной области видимости класса и может быть получено через свойства самого класса, а не его экземпляра.</p>

<pre class="brush: js notranslate"><code>// безымянный
var Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// отобразится: "Rectangle"

// именованный
var Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// отобразится: "Rectangle2" </code>
</pre>

<div class="note">
<p><strong>Обратите внимание: выражения </strong>класса подвержены тем же проблемам с подъёмом (hoisting), что и <strong>объявления</strong> класса.</p>
</div>

<h2 id="Тело_класса_и_задание_методов">Тело класса и задание методов</h2>

<p>Тело класса — это часть кода, заключённая в фигурные скобки <code>{}</code>. Здесь вы можете объявлять члены класса, такие как методы и конструктор.</p>

<h3 id="Строгий_режим">Строгий режим</h3>

<p>Тела <em>объявлений классов</em> и <em>выражений классов</em> выполняются в строгом режиме (<a href="/en-US/docs/Web/JavaScript/Reference/Strict_mode">strict mode</a>).</p>

<h3 id="Constructor">Constructor</h3>

<p>Метод <code><a href="/ru/docs/Web/JavaScript/Reference/Classes/constructor">constructor</a></code> — специальный метод, необходимый для создания и инициализации объектов, созданных, с помощью класса. В классе может быть только один метод с именем <code>constructor</code>. Исключение типа {{jsxref("SyntaxError")}} будет выброшено, если класс содержит более одного вхождения метода <code>constructor</code>.</p>

<p>Ключевое слово <code>super</code> можно использовать в методе <code>constructor</code> для вызова конструктора родительского класса.</p>

<h3 id="Методы_прототипа">Методы прототипа</h3>

<p>См. также <a href="/ru/docs/Web/JavaScript/Reference/Functions/%D0%9E%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B8%D0%BD%D0%B8%D0%B5_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2">определение методов</a>.</p>

<pre class="brush: js notranslate"><code>class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  get area() {
    return this.calcArea();
  }

  calcArea() {
    return this.height * this.width;
  }
}

const square = new Rectangle(10, 10);

console.log(square.area); // 100</code></pre>

<h3 id="Статические_методы_и_свойства">Статические методы  и свойства</h3>

<p>Ключевое слово <code><a href="/en-US/docs/Web/JavaScript/Reference/Classes/static">static</a></code>, определяет статический метод или свойства для класса. Статические методы и свойства вызываются без <a href="/ru/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript#The_Object_.28Class_Instance.29">инстанцирования</a> их класса, и <strong>не могут</strong> быть вызваны у экземпляров (<em>instance</em>) класса. Статические методы, часто используются для создания служебных функций для приложения, в то время как статические свойства полезны для кеширования в рамках класса, фиксированной конфигурации или любых других целей, не связанных с реплецированием данных между экземплярами.</p>

<pre class="brush: js notranslate"><code>class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static displayName = "Точка";
  static distance(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.hypot(dx, dy);
  }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; //undefined
p1.distance;    //undefined
p2.displayName; //undefined
p2.distance;    //undefined

console.log(Point.displayName);      // "Точка"
console.log(Point.distance(p1, p2)); // 7.0710678118654755</code></pre>

<h3 id="Привязка_this_в_прототипных_и_статических_методах">Привязка <code>this</code> в прототипных и статических методах</h3>

<p>Когда статический или прототипный метод вызывается без привязки к <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">this</span></font> объекта (или когда <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">this</span></font> является типом boolean, string, number, undefined, null), тогда <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">this</span></font> будет иметь значение <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">undefined</span></font> внутри вызываемой функции. Автоупаковка не будет произведена. Поведение будет таким же как если бы мы писали код в нестрогом режиме.</p>

<pre class="brush: js notranslate"><code>class Animal {
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // объект Animal
let speak = obj.speak;
speak(); // undefined

Animal.eat() // класс Animal
let eat = Animal.eat;
eat(); // undefined</code></pre>

<p>Если мы напишем этот же код используя классы основанные на функциях, тогда произойдёт автоупаковка основанная на значении <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">this</span></font>, в течение которого функция была вызвана. В строгом режиме автоупаковка не произойдёт - значение <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">this</span></font> останется прежним.</p>

<pre class="brush: js notranslate"><code>function Animal() { }

Animal.prototype.speak = function(){
  return this;
}

Animal.eat = function() {
  return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // глобальный объект (нестрогий режим)

let eat = Animal.eat;
eat(); // глобальный объект (нестрогий режим)</code>
</pre>

<h3 id="Свойства_экземпляра">Свойства экземпляра</h3>

<p>Свойства экземпляра должны быть определены в методе класса:</p>

<pre class="notranslate">class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}</pre>

<p>Статические (class-side) свойства и свойства прототипа должны быть определены за рамками тела класса:</p>

<pre class="notranslate">Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;
</pre>

<h3 id="Определение_полей">Определение полей</h3>

<div class="blockIndicator warning">
<p>Публичные и приватные поля - это <a href="https://github.com/tc39/proposal-class-fields">экспериментальная особенность (stage 3)</a>, предложенная комитетом <a href="https://tc39.es/">TC39</a> по стандартам языка Javascript. Поддержка браузерами ограничена, но это нововведение может быть использовано на моменте сборки, используя к примеру <a href="https://babeljs.io/">Babel</a>.</p>
</div>

<h4 id="Публичные_поля">Публичные поля</h4>

<p>Используя Javascript синтаксис определения полей, приведённый выше пример может быть изменён следующим образом:</p>

<pre class="notranslate">class Rectangle {
  height = 0;
  width;
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}</pre>

<p>Как видно из примера, поля могут быть объявлены как со начальным значением, так и без него.</p>

<p>Более подробно об этом написано в <a href="https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Classes/Class_fields">публичные поля класса</a>.</p>

<h4 id="Приватные_поля">Приватные поля</h4>

<p>Предыдущий пример может быть изменён следующим образом, используя приватные поля:</p>

<pre class="notranslate">class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {
    this.#height = height;
    this.#width = width;
  }
}</pre>

<p>Приватные поля могут быть изменены или прочитаны только в рамках класса и не могут быть вызваны извне. Определяя вещи, которые не видны за пределами класса, вы гарантируете, что пользователи ваших классов не могут зависеть от внутренних компонентов, которые могут изменить версию на версию.</p>

<div class="blockIndicator note">
<p>Приватные поля могут быть объявлены только заранее в объявлении поля.</p>
</div>

<p>Приватные поля не могут быть созданы позже путём присваивания им значения, в отличии от обычных свойств.</p>

<p>Более подробно об этом написано в <a href="/ru/docs/Web/JavaScript/Reference/Classes/%D0%9F%D1%80%D0%B8%D0%B2%D0%B0%D1%82%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%BB%D1%8F_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B0">Приватные поля класса</a>.</p>

<h2 id="Наследование_классов_с_помощью_extends">Наследование классов с помощью <code>extends</code></h2>

<p>Ключевое слово <code><a href="/en-US/docs/Web/JavaScript/Reference/Classes/extends">extends</a></code> используется в <em>объявлениях классов</em> и <em>выражениях классов</em> для создания класса, дочернего относительно другого класса.</p>

<pre class="brush: js notranslate"><code class="language-js">class Animal {
  constructor(name) {
    this.name = name;
  }</code>

  speak() {
    console.log(`${this.name} издаёт звук.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // вызывает конструктор super класса и передаёт параметр name
  }

  speak() {
    console.log(`${this.name} лает.`);
  }
}

<code class="language-js">let d = new Dog('Митци');
d.speak(); // Митци лает</code></pre>

<p>Если в подклассе присутствует конструктор, он должен сначала вызвать <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">super</span></font>, прежде чем использовать <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">this</span></font>.</p>

<p>Аналогичным образом можно расширять традиционные, основанные на функциях "классы":</p>

<pre class="brush: js line-numbers  language-js notranslate"><code class="language-js">function Animal (name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  console.log(</code>`${this.name} издаёт звук.`<code class="language-js">);
}

class Dog extends Animal {
  speak() {
    console.log(</code>`${this.name} лает.`<code class="language-js">);
  }
}

let d = new Dog('Митци');
d.speak(); // Митци лает

// </code>Для аналогичных методов дочерний метод имеет приоритет над родительским.</pre>

<p>Обратите внимание, что классы не могут расширять обычные (non-constructible) объекты. Если вам необходимо создать наследование от обычного объекта, в качестве замены можно использовать {{jsxref("Object.setPrototypeOf()")}}:</p>

<pre class="brush: js line-numbers  language-js notranslate"><code class="language-js">var Animal = {
  speak() {
    console.log(</code>`${this.name} издаёт звук.`<code class="language-js">);
  }
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

// Если вы этого не сделаете, вы получите ошибку TypeError при вызове speak.
Object.setPrototypeOf(Dog.prototype, Animal);

let d = new Dog('Митци');
d.speak(); // Митци издаёт звук.</code></pre>

<h2 id="Species">Species</h2>

<p>Допустим, вам хотелось бы возвращать объекты типа {{jsxref("Array")}} в вашем производном от массива классе <code>MyArray</code>. Паттерн species позволяет вам переопределять конструкторы по умолчанию.</p>

<p>Например, при использовании таких методов, как {{jsxref("Array.map", "map()")}}, который возвращает конструктор по умолчанию, вам хотелось бы, чтобы они возвращали родительский объект <code>Array</code> вместо объекта <code>MyArray</code>. Символ {{jsxref("Symbol.species")}} позволяет это реализовать:</p>

<pre class="brush: js line-numbers  language-js notranslate"><code class="language-js">class MyArray extends Array {
  // Изменить species на родительский конструктор Array
  static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x =&gt; x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true</code></pre>

<h2 id="Обращение_к_родительскому_классу_с_помощью_super">Обращение к родительскому классу с помощью <code>super</code></h2>

<p>Ключевое слово <code><a href="/ru/docs/Web/JavaScript/Reference/Operators/super">super</a></code> используется для вызова функций на родителе объекта.</p>

<pre class="brush: js  language-js notranslate"><code class="language-js">class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(</code>`${this.name} издаёт звук.`<code class="language-js">);
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(</code>`${this.name}<code class="language-js"> рычит.`);
  }
}

let l = new Lion('Фаззи');
l.speak();
// Фаззи издаёт звук.
// Фаззи рычит.</code>
</pre>

<h2 id="Mix-ins">Mix-ins</h2>

<p>Абстрактные подклассы, или mix-ins, — это шаблоны для классов. У класса в ECMAScript может быть только один родительский класс, поэтому множественное наследование (к примеру, от tooling classes) невозможно. Функциональность должен предоставлять родительский класс.</p>

<p>Для реализации mix-ins в ECMAScript можно использовать функцию, которая в качестве аргумента принимает родительский класс, а возвращает подкласс, его расширяющий:</p>

<pre class="brush: js line-numbers  language-js notranslate"><code class="language-js">var calculatorMixin = Base =&gt; class extends Base {
  calc() { }
};

var randomizerMixin = Base =&gt; class extends Base {
  randomize() { }
};</code></pre>

<p>Класс, использующий такие mix-ins, можно описать следующим образом:</p>

<pre class="brush: js line-numbers  language-js notranslate"><code class="language-js">class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }</code></pre>

<h2 id="Спецификации">Спецификации</h2>

<table class="standard-table">
 <tbody>
  <tr>
   <th scope="col">Спецификация</th>
   <th scope="col">Статус</th>
   <th scope="col">Комментарий</th>
  </tr>
  <tr>
   <td>{{SpecName('ES6', '#sec-class-definitions', 'Class definitions')}}</td>
   <td>{{Spec2('ES6')}}</td>
   <td>Изначальное определение.</td>
  </tr>
  <tr>
   <td>{{SpecName('ESDraft', '#sec-class-definitions', 'Class definitions')}}</td>
   <td>{{Spec2('ESDraft')}}</td>
   <td></td>
  </tr>
 </tbody>
</table>

<h2 id="Совместимость_с_браузерами">Совместимость с браузерами</h2>

<p>{{Compat("javascript.classes")}}</p>

<h2 id="Повторное_определение_класа">Повторное определение класа</h2>

<p>Класс не может быть переопределён. Попытка этого приведёт к <code>SyntaxError</code> .</p>

<p>Если мы запускаете код в веб браузере, к примеру в Firefox Web Console (<strong>Tools </strong>&gt;<strong> Web Developer </strong>&gt;<strong> Web Console</strong>) и вы используете ('Run') определение класса с одним и тем же именем дважды, вы получите <code>SyntaxError: redeclaration of let <em>ClassName</em>;</code>. (Обсуждение по ошибке можно посмотреть в {{Bug(1428672)}}.) Chrome Developer Tools возвращает сообщение типа <code>Uncaught SyntaxError: Identifier '<em>ClassName</em>' has already been declared at &lt;anonymous&gt;:1:1</code>.</p>

<h2 id="Смотрите_также">Смотрите также</h2>

<ul>
 <li><a href="/en-US/docs/Web/JavaScript/Reference/Functions">Функции</a></li>
 <li><a href="https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/class">Определение классов</a></li>
 <li><a href="https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/class">Выражение классов</a></li>
 <li><a href="https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Classes/Class_fields">Публичные поля класса</a></li>
 <li><a href="/ru/docs/Web/JavaScript/Reference/Classes/%D0%9F%D1%80%D0%B8%D0%B2%D0%B0%D1%82%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%BB%D1%8F_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B0">Приватные поля класса</a></li>
 <li><a href="/ru/docs/Web/JavaScript/Reference/Operators/super">super</a></li>
 <li><a href="https://hacks.mozilla.org/2015/07/es6-in-depth-classes/">Статья в блоге: "ES6 In Depth: Classes"</a></li>
 <li><a href="https://github.com/tc39/proposal-class-fields">Fields and public/private class properties proposal (stage 3)</a></li>
</ul>