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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
|
---
title: Докладно про об'єктну модель
slug: Web/JavaScript/Guide/Dokladno_pro_Objectnu_Model
tags:
- Guide
- Intermediate
- JavaScript
- Object
translation_of: Web/JavaScript/Guide/Details_of_the_Object_Model
---
<div>{{jsSidebar("JavaScript Guide")}} {{PreviousNext("Web/JavaScript/Guide/Working_with_Objects", "Web/JavaScript/Guide/Using_promises")}}</div>
<p class="summary">JavaScript - це об'єктна мова, що заснована на прототипах, а не на класах. У зв'язку з цим може бути менш очевидно, як саме JavaScript дозволяє створювати ієрархії об'єктів із наслідуванням їх властивостей та значень. Цей розділ є спробою дещо прояснити цей механізм.</p>
<p>Вміст цього розділу розрахований на те, що ви вже, принаймні, дещо знайомі з мовою JavaScript, і застосовували його функції для створення простих об'єктів.</p>
<h2 id="Мови_з_класовою_та_прототипною_моделлю">Мови з класовою та прототипною моделлю</h2>
<p>Об'єктно-орієнтовані мови з класовою моделлю, такі як Java і C++, засновані на концепції двох окремих сутностей: класів та екземплярів.</p>
<ul>
<li><em>Клас</em> визначає всі властивості (включаючи поля та методи у Java, чи фукнції-члени у C++, як властивості), що характеризують певний набір об'єктів. Класс - це абстрактна річ, на відміну від будь-якого окремого об'єкту із набору, який він описує. Наприклад, клас <code>Employee</code> може представляти групу всіх робітників.</li>
<li><em>Екземпляр</em>, з іншого боку, є реалізацією класу. Наприклад, <code>Victoria</code> може бути екземпляром класу <code>Employee</code>, представляючи окрему особу в якості працівника. Екземпляр має точно ті самі властивості, що і батьківський клас (не більше, не менше).</li>
</ul>
<p>Мови з прототипною моделлю наслідування, такі як JavaScript, не розділяють ці сутності: у них просто є об'єкти. Такі мови реалізовують поняття <em>об'єкту-прототипу</em> — об'єкту, що використовується як зразок, з якого вибираються початкові властивості для нового об'єкту. Будь-який об'єкт може вказати власні властивості, як в момент створення, так і під час виконання. Ну і на додачу, будь-який об'єкт можна задати в якості прототипу з іншого об'єкту — таким чином перший об'єкт розділить свої властивості з другим.</p>
<h3 id="Задання_і_визначення_класу">Задання і визначення класу</h3>
<p>У мовах із класовою моделлю, класс задається у окремому <em>визначенні класу</em>. У цому визначенні можна вказати особливі методи, що називаються <em>конструкторами</em>, щоб створити екземпляри класу. Метод-конструктор може задати початкові значення властивостей екземпляру, і виконати якісь інші задачі прямо у момент створення. Для створення екземплярів застосовується оператор <code>new</code> у комбінації із методом-конструктором.</p>
<p>JavaScript слідує подібній моделі, проте не має відокремленого від конструктора визначення класу. Натомість, ви одразу задаєте функцію-конструктор, щоб створити об'єкти із відповідним початковим набором властивостей та значень. Будь-яка JavaScript-функція може використовуватись як конструктор. Для створення нового об'єкту так само використовується оператор <code>new</code> із фукнцією-конструктором.</p>
<p> </p>
<div class="blockIndicator note">
<p>Зауважте, що ECMAScript 2015 вводить <a href="/uk/docs/Web/JavaScript/Reference/Classes">визначення класу</a>:</p>
<blockquote>
<p>Класи JavaScript, введені стандартом ECMAScript 2015, є лише синтаксичним цукром поверх уже наявного у JavaScript прототипного наслідування. Тобто ці класи <em>не вводять</em> у JavaScript нової моделі наслідуваня.</p>
</blockquote>
</div>
<p> </p>
<h3 id="Дочірні_класи_і_наслідування">Дочірні класи і наслідування</h3>
<p>У мові з класовою моделлю наслідкування ієрархія класів створюється через визначення класу. У цьому визначенні можна окремо вказати, що новий клас являється <em>дочірнім</em> стосовно уже наявного класу. Дочірній клас отримає всі властивості батьківського і може привнести нові (або ж змінити успадковані). Наприклад, припустимо, що клас <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>JavaScript реалізовує наслідування дещо інакше. Він дозволяє пов'язувати об'єкт-прототип із будь-якою фукнцією-конструктором. Тобто ви можете точнісінько реалізувати приклад <code>Employee</code> — <code>Manager</code>, проте використовуючи дещо інші терміни. Спершу ви визначаєте конструктор <code>Employee</code>, задаючи властивості <code>name</code> та <code>dept</code>. Далі ви визначаєте фукнцію-конструктор <code>Manager</code>, що викликає конструктор <code>Employee</code> та задає властивість <code>reports</code>. Насамкінець, призначаєте новий об'єкт, отриманий з <code>Employee.prototype</code> в якості прототипу конструктора <code>Manager</code>. Надалі, при створенні екземпляра <code>Manager</code> він наслідує властивості <code>name</code> і <code>dept</code> з об'єкту <code>Employee</code>.</p>
<h3 id="Додавання_і_видалення_властивостей">Додавання і видалення властивостей</h3>
<p>Зазвичай у мовах із класовою моделлю наслідування класи створюються під час компіляції, а екземпляри класів - під час компіляції чи виконання програми. Після того, як його було визначено, не можна змінити кількість або тип його властивостей. Однак, у JavaScript можна додавати чи видаляти властивості будь-якого об'єкту безпосередньо під час виконання програми. Якщо додати нову властивість до об'єкту-прототипу певного набору об'єктів, вони всі також отримають цю властивість.</p>
<h3 id="Підсумок_відмінностей_класової_і_прототипної_моделей">Підсумок відмінностей класової і прототипної моделей</h3>
<p>Наступна таблиця надає короткий підсумок цих відмінностей. Решта розділу розкриває деталі застосування JavaScript-конструкторів і прототипів для створення ієрархії об'єктів, та порівнює це із тим, як би це робилось у Java.</p>
<table class="standard-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>Функції-конструктори і описують, і створюють набори об'єктів.</td>
</tr>
<tr>
<td>Окремий об'єкт створюється оператором <code>new</code>.</td>
<td>Так само.</td>
</tr>
<tr>
<td>Ієрархія об'єктів формується при визначенні класів, шляхом задання нащадків для уже наявних класів.</td>
<td>Ієрархія об'єктів формується шляхом призначення об'єкту прототипом функції-конструктора.</td>
</tr>
<tr>
<td>Властивості наслідуються згідно ланцюжка класів.</td>
<td>Властивості наслідуються згідно ланцюжка прототипів.</td>
</tr>
<tr>
<td>Визначення класу задає <em>всі</em> властивості всіх екземплярів класу. Неможливо динамічно додавати властивості під час виконання програми.</td>
<td>Функція-конструктор чи прототип задають лише <em>початковий набір</em> властивостей. Можна додавати чи видаляти властивості як окремого об'єкту, так певного їх набору.</td>
</tr>
</tbody>
</table>
<h2 id="Приклад_із_робітником_Employee">Приклад із робітником "Employee"</h2>
<p>Надалі у розділі ієрархія робітників, що показана на наступному зображенні.</p>
<div style="display: table-row;">
<div style="display: table-cell; width: 350px; text-align: center; vertical-align: middle; padding: 10px;">
<p>Проста ієрархія об'єктів, сформована із наступних елементів:</p>
<p><img alt="" src="https://mdn.mozillademos.org/files/3060/figure8.1.png"></p>
</div>
<div style="display: table-cell; vertical-align: middle; padding: 10px;">
<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" (що означає, що всі <code>SalesPerson</code> відносяться до одного відділу).</li>
<li><code>Engineer</code> заснований на <code>WorkerBee</code>. Він додає властивість <code>machine</code> (значення за замовчуванням — порожній рядок) і перевизначає властивість <code>dept</code>, задаючи їй значення "engineering".</li>
</ul>
</div>
</div>
<h2 id="Створення_ієрархії">Створення ієрархії</h2>
<p>Існує декілька способів задати відповідні функції-конструктори, щоб реалізувати ієрархію робітників. Який спосіб обрати — значною мірою залежить від того, які можливості ви хочете отримати від вашого додатку.</p>
<p>Цей розділ показує, як використовувати дуже прості (і відносно негнучкі) визначення, і таким чином демонструє, як отримати робочий механізм наслідування. У цих визначеннях не можна задати жодного значення при створенні об'єкту — він отримає властивості із значеннями за замовчуванням, які можна буде змінити пізніше.</p>
<p>У реальному додатку ви б, ймовірно, визначали конструктор, що дозволяє задавати значення в момент створення об'єкту (докладніше у <a href="#Більш_гнучкі_конструктори">Більш гнучкі конструктори</a>). А наразі ці прості визначення покажуть, як загалом відбувається наслідування.</p>
<p>Наступні визначення <code>Employee</code> у Java та JavaScript ідентичні. Єдина відмінність — у Java необхідно явно вказувати тип кожної властивості, на відміну від JavaScript (так як Java — <a href="http://en.wikipedia.org/wiki/Strong_and_weak_typing">мова із сильною типізацією</a>, а JavaScript — із слабкою).</p>
<div class="twocolumns">
<h4 id="JavaScript">JavaScript</h4>
<pre class="brush: js">function Employee() {
this.name = '';
this.dept = 'general';
}
</pre>
<h4 id="Java"><br>
Java</h4>
<pre class="brush: java">public class Employee {
public String name = "";
public String dept = "general";
}
</pre>
</div>
<p>Визначення <code>Manager</code> і <code>WorkerBee</code> показують різницю у заданні батьківського об'єкту. У JavaScript ви додаєте екземпляр прототипу в якості значення поля <code>prototype</code> функції-конструктора, а потім перевизначаєте <code>prototype.constructor</code>, щоб це поле вказувало на функцію-конструктор. Ви можете це зробити в будь-якому місці після визначення конструктора. У Java надклас задається всередині визначення класу, і його ніяк не можна змінити зовні визначення класу.</p>
<div class="twocolumns">
<h4 id="JavaScript_2">JavaScript</h4>
<pre class="brush: js">function Manager() {
Employee.call(this);
this.reports = [];
}
Manager.prototype =
Object.create(Employee.prototype);
<span class="diff_add">Manager.prototype.constructor = Manager;</span>
function WorkerBee() {
Employee.call(this);
this.projects = [];
}
WorkerBee.prototype =
Object.create(Employee.prototype);
<span class="diff_add">WorkerBee.prototype.constructor = WorkerBee;</span>
</pre>
<h4 id="Java_2"><br>
Java</h4>
<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>
</div>
<p> </p>
<p>Визначення <code>Engineer</code> та <code>SalesPerson</code> створюють об'єкти, що наслідуються вже від <code>WorkerBee</code>, а, отже, і від <code>Employee</code>. Об'єкти цих типів мають властивості всіх об'єктів вище у ланцюжку наслідування. Надодачу, ці визначення перевизначають успадковані значення поля <code>dept</code>, змінюючи їх відповідно до нового типу.</p>
<div class="twocolumns">
<h4 id="JavaScript_3">JavaScript</h4>
<pre class="brush: js">function SalesPerson() {
WorkerBee.call(this);
this.dept = 'sales';
this.quota = 100;
}
SalesPerson.prototype =
Object.create(WorkerBee.prototype);
<span class="diff_add">SalesPerson.prototype.constructor = SalesPerson;</span>
function Engineer() {
WorkerBee.call(this);
this.dept = 'engineering';
this.machine = '';
}
Engineer.prototype =
Object.create(WorkerBee.prototype);
<span class="diff_add">Engineer.prototype.constructor = Engineer;</span>
</pre>
<h4 id="Java_3"><br>
Java</h4>
<pre class="brush: java">public class SalesPerson extends WorkerBee {
public String dept = "sales";
public double quota = 100.0;
}
public class Engineer extends WorkerBee {
public String dept = "engineering";
public String machine = "";
}
</pre>
</div>
<p>Таким чином, ви можете створювати екземпляри об'єктів із уже заданими значеннями для своїх властивостей. Наступна схема ілюструє застосування цих JavaScript-визначень для створення нових об'єктів, і демонструє значення їх властивостей.</p>
<div class="note">
<p><strong>Зауважте, що:</strong> термін <em><em>екземпляр</em></em> має специфічний технічний зміст у мовах із класовою моделлю. У цих мовах екземпляр являється окремою реалізацією класу і корінним чином відрізняється від його визначення. У JavaScript, "екземпляр" не має такого особливого змісту, бо сам JavaScript не має такої значної відмінності класів від їх реалізацій. Однак, у контексті JavaScript, "екземпляр" може неформально позначати об'єкт, створений певною функцією-конструктором. Тому, згідно наступного прикладу, можна неформально стверджувати, що <code><code>jane</code></code> є екземпляром класу <code><code>Engineer</code></code>. Так само, хоча терміни <em><em>предок</em>, <em>нащадок</em>, дочірній і батьківський класи</em> не мають формального смісту в JavaScript, їх можна застосовувати для позначення об'єктів, що знаходяться вище чи нижче у ланцюжку прототипів.</p>
</div>
<h3 id="Створення_об'єктів_за_допомогою_простих_визначень">Створення об'єктів за допомогою простих визначень</h3>
<div class="twocolumns">
<h4 id="Ієрархія_об'єктів">Ієрархія об'єктів</h4>
<p>Наступна ієрархія створена за допомогою коду у правій частині.</p>
<p><img src="https://mdn.mozillademos.org/files/10412/=figure8.3.png"></p>
<h4 id="Окремі_об'єкти_Jim_Sally_Mark_Fred_Jane_etc._Екземпляри_створені_конструктором">Окремі об'єкти = Jim, Sally, Mark, Fred, Jane, etc.<br>
"Екземпляри", створені конструктором</h4>
<pre class="brush: js">var jim = new Employee;
// Дужки можна опустити, якщо
// конструктор не приймає аргументів.
// jim.name має значення ''
// jim.dept має значення 'general'
var sally = new Manager;
// sally.name має значення ''
// sally.dept має значення 'general'
// sally.reports має значення []
var mark = new WorkerBee;
// mark.name має значення ''
// mark.dept має значення 'general'
// mark.projects має значення []
var fred = new SalesPerson;
// fred.name має значення ''
// fred.dept має значення 'sales'
// fred.projects має значення []
// fred.quota має значення 100
var jane = new Engineer;
// jane.name має значення ''
// jane.dept має значення 'engineering'
// jane.projects має значення []
// jane.machine має значення ''
</pre>
</div>
<h2 id="Властивості_(поля)_об'єкту">Властивості (поля) об'єкту</h2>
<p>Ця секція описує, як об'єкти наслідують властивості інших об'єктів у ланцюжку прототипів, і що відбувається, якщо додати властивість під час виконання програми.</p>
<h3 id="Наслідування_властивостей">Наслідування властивостей</h3>
<p>Припустимо, такою інструкцією ви створили екземпляр <code>WorkerBee</code> — об'єкт <code>mark</code>:</p>
<pre class="brush: js">var mark = new WorkerBee;
</pre>
<p>Коли JavaScript бачить оператор <code>new</code>, він створює загальний об'єкт і неявно встановлює <code>WorkerBee.prototype</code> значенням внутрішньої властивості [[Prototype]], і передає цей новий об'єкт як значення <em><code>this</code></em> до фукнції-конструктора <code>WorkerBee</code>. Внутрішня властивість [[Prototype]] визначає ланцюжок прототипів для виводу значень полів. Коли ці властивості задані, JavaScript повертає новий об'єкт, а інструкція присвоєння задає його в якості значення змінної <code>mark</code>.</p>
<p>Описаний процес явно не встановлює значення об'єкту <code>mark</code> для властивостей (<em>локальні</em> значення), які <code>mark</code> наслідує з ланцюжка прототипів. Коли ви запитуєте значення властивості, JavaScript в першу чергу перевіряє наявність цього значення у об'єкті, і повертає його, якщо знаходить. Якщо ж ні, JavaScript перевіряє весь ланцюжок прототипів (за допомогою властивості [[Prototype]]). Якщо об'єкт у ланцюжку має таку властивість - буде повернуто її значення (або повідомлення, що об'єкт не має такої властивості, якщо її все-таки не було знайдено). Таким чином, об'єкт <code>mark</code> має наступні властивості і значення:</p>
<pre class="brush: js">mark.name = '';
mark.dept = 'general';
mark.projects = [];
</pre>
<p>З конструктора Employee об'єкту <code>mark</code> призначено локальні значення для властивостей <code>name</code> та <code>dept</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>Додавання властивостей</strong></small></p>
<h2 id="Більш_гнучкі_конструктори">Більш гнучкі конструктори</h2>
<p>Наведені раніше функції-конструктори не дозволяють задавати значення під час створенні екземпляру. Як і у Java, можна надати конструктору аргументи для ініціалізації значень властивостей у об'єктів. Наступна схема показує один із способів це зробити.</p>
<p><img alt="" class="internal" id="figure8.5" src="/@api/deki/files/4423/=figure8.5.png" style="height: 481px; width: 1012px;"><br>
<small><strong>Задання властивостей у конструкторі, варіант 1</strong></small></p>
<p>Таблиця далі показує визначення цих об'єктів у Java і JavaScript.</p>
<div class="twocolumns">
<h4 id="JavaScript_4">JavaScript</h4>
<h4 id="Java_4">Java</h4>
</div>
<div class="twocolumns">
<pre class="brush: js">function Employee(name, dept) {
this.name = name || '';
this.dept = dept || 'general';
}
</pre>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<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>
</div>
<div class="twocolumns">
<pre class="brush: js">function WorkerBee(projs) {
this.projects = projs || [];
}
WorkerBee.prototype = new Employee;
</pre>
<p> </p>
<p> </p>
<p> </p>
<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>
</div>
<div class="twocolumns">
<pre class="brush: js">
function Engineer(mach) {
this.dept = 'engineering';
this.machine = mach || '';
}
Engineer.prototype = new WorkerBee;
</pre>
<p> </p>
<p> </p>
<p> </p>
<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>
</div>
<p>Ці JavaScript-визначення застосовують особливу ідіому для задання значення за замовчуванням:</p>
<pre class="brush: js">this.name = name || '';
</pre>
<p>Логічний оператор АБО у JavaScript (<code>||</code>) обчислює перше значення. Якщо результат можна привести до <code>true</code>, оператор повертає його, а інакше - значення другого аргументу. Таким чином, ця стрічка коду перевіряє, чи <code>name</code> має якесь корисне значення для властивості <code>name</code>. Якщо так — <code>this.name</code> отримує її значення, а інакше значенням <code>this.name</code> стає порожній рядок. У розділі ця ідіома застосовується для стислості; однак вона може бути неочевидною на перший погляд.</p>
<div class="note">
<p><strong>Зауважте, що:</strong> це може працювати не так, як очікується, якщо конструктор викликається із аргументами, що приводяться до <code><code>false</code></code> (число <code>0</code> і порожній рядок (<code><code>""</code></code>). У цьому випадку буде обрано значення за замовчуванням.</p>
</div>
<p>Таким чином можливо задати значення для властивостей на місці, безпосередньо під час створення екземпляру об'єкту. Наступною інструкцією ви можете створити новий екземпляр <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>Задання властивостей у конструкторі, варіант 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 виконує наступні кроки:</p>
<ol>
<li>Оператор <code>new</code> створює загальний об'єкт і задає <code>Engineer.prototype</code> значенням його властивості <code>__proto__</code> .</li>
<li>Оператор <code>new</code> передає новий об'єкт у конструктор <code>Engineer</code> в якості значення <code>this</code>.</li>
<li>Конструктор створює нову властивість <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> до об'єкту, створеного на першому етапі. Таким чином функція <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>Іншим способом наслідування являється використання методів <code><a href="/uk/docs/Web/JavaScript/Reference/Global_Objects/Function/call" title="en-US/docs/JavaScript/Reference/Global Objects/Function/call">call()</a></code> чи <a href="/uk/docs/Web/JavaScript/Reference/Global_Objects/Function/apply" title="en-US/docs/JavaScript/Reference/Global Objects/Function/apply"><code>apply()</code></a>. Ось дві еквівалентні ділянки коду:</p>
<div class="twocolumns">
<pre class="brush: js">function Engineer(name, projs, mach) {
this.base = WorkerBee;
this.base(name, 'engineering', projs);
this.machine = mach || '';
}
</pre>
<pre class="brush: js">function Engineer(name, projs, mach) {
WorkerBee.call(this, name, 'engineering', projs);
this.machine = mach || '';
}
</pre>
</div>
<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>Якщо прототип у ланцюжку має значення вказаної властивості — повертає це значення.</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>WorkerBee</code> у змінній <code>amy</code>:</p>
<pre class="brush: js">var amy = new WorkerBee;
</pre>
<p>Об'єкт <code>amy</code> має одну локальну змінну — <code>projects</code>. Значення властивостей <code>name</code> та <code>dept</code> насправді не належать <code>amy</code>, і тому виводяться через поле її поле <code>__proto__</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>При створенні <em>будь-якого</em> екземпляру об'єкту <code>Employee</code>, цей екземпляр отримає <strong>локальне значення</strong> для властивості <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>), він знаходить місцеве значення властивості у <code>WorkerBee.prototype</code>. І тому він не намагається шукати далі в ланцюжку прототипів до <code>Employee.prototype</code>.</p>
<p>Якщо ви хочете змінити значення властивості під час виконання, та ще й так, щоб воно поширилось на всіх нащадків об'єкту, слід визначати це значення не у конструкторі об'єкту, а у прототипі конструктора. Припустимо, наприклад, що ви змінили попередній варіант реалізації на такий:</p>
<pre class="brush: js">function Employee() {
this.dept = 'general'; // Зауважте, що тут немає this.name (локальної змінної)
}
Employee.prototype.name = ''; // Одна копія
function WorkerBee() {
this.projects = [];
}
WorkerBee.prototype = new Employee;
var amy = new WorkerBee;
Employee.prototype.name = 'Unknown';
</pre>
<p>У цьому випадку, властивість <code>amy</code> <code>name</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>__proto__</code> (за винятком <code>Object</code>); кожна функція має властивість <code>prototype</code>. Тобто об'єкти можуть відноситись один до одного згідно "прототипного наслідування". Перевірити наслідування можна, порівнюючи властивість <code>__proto__</code> об'єкту із об'єктом <code>prototype</code> функції. JavaScript надає скорочення: оператор <code>instanceof</code> порівнює об'єкт і функцію, і повертає <font face='consolas,"Liberation Mono",courier,monospace'>true</font>, якщо об'єкт є нащадком прототипу функції. Наприклад:</p>
<pre class="brush: js">var f = new Foo();
var isTrue = (f instanceof Foo);</pre>
<p>Для більш докладного прикладу, припустимо, що у нас є набір визначень із показаних у <a href="#Наслідування_властивостей">Наслідування властивостей</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 class="brush: js">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> так:</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, і потім підвищує на 1 глобальний лічильник ID. Тобто, якщо виконати наступні інструкції, <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>base</code> і викликають констуктор, що знаходиться вище у ланцюжку прототипів. У цьому випадку, у момент створення об'єкту <code>mac</code>, <code>mac.id</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>id</code> та не оновлює лічильник. Таким чином, щоб отримати <code>Employee</code> із призначеним <code>id</code>, необхідно задати ім'я робітника. У цьому випадку <code>mac.id</code> буде дорівнювати 1.</p>
<p>Інший варіант <span style='background-color: transparent; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal;'>—</span> створювати копію об'єкту-прототипу <span style='background-color: #eeeeee; color: #333333; direction: ltr; display: inline !important; float: none; font-family: consolas,monaco,"Andale Mono",monospace; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; line-height: 24px; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; white-space: pre;'>Employee</span>, щоб потім присвоювати її WorkerBee:</p>
<pre class="brush: js">WorkerBee.prototype = Object.create(Employee.prototype);
// instead of WorkerBee.prototype = new Employee
</pre>
<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/Using_promises")}}</div>
|