diff options
| author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
|---|---|---|
| committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
| commit | 218934fa2ed1c702a6d3923d2aa2cc6b43c48684 (patch) | |
| tree | a9ef8ac1e1b8fe4207b6d64d3841bfb8990b6fd0 /files/uk/web/javascript/guide/dokladno_pro_objectnu_model/index.html | |
| parent | 074785cea106179cb3305637055ab0a009ca74f2 (diff) | |
| download | translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.gz translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.bz2 translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.zip | |
initial commit
Diffstat (limited to 'files/uk/web/javascript/guide/dokladno_pro_objectnu_model/index.html')
| -rw-r--r-- | files/uk/web/javascript/guide/dokladno_pro_objectnu_model/index.html | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/files/uk/web/javascript/guide/dokladno_pro_objectnu_model/index.html b/files/uk/web/javascript/guide/dokladno_pro_objectnu_model/index.html new file mode 100644 index 0000000000..5e756710dd --- /dev/null +++ b/files/uk/web/javascript/guide/dokladno_pro_objectnu_model/index.html @@ -0,0 +1,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> |
