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
|
---
title: Вступление в Объектно-ориентированный JavaScript
slug: Web/JavaScript/Introduction_to_Object-Oriented_JavaScript
translation_of: Learn/JavaScript/Objects
translation_of_original: Web/JavaScript/Introduction_to_Object-Oriented_JavaScript
---
<p>Объектно-ориентированный до основания, JavaScript предоставляет мощные и гибкие {{Glossary("OOP")}} возможности. Эта статья начинается с введения в объектно-ориентированное программирование, затем рассматривает модель объекта JavaScript и, наконец, демонстрирует концепции объектно-ориентированного программирования в JavaScript.</p>
<h2 id="JavaScript_Review" name="JavaScript_Review">Обзор JavaScript</h2>
<p>Если вы неуверенно владеете такими концепциями JavaScript, как переменные, типы, функции и области видимости, вы можете прочитать об этих темах в <a href="/ru/docs/Web/JavaScript/A_re-introduction_to_JavaScript">Повторное вступление в JavaScript</a>. Вы также можете обратиться к <a href="/ru/docs/Web/JavaScript/Guide">JavaScript Guide</a>.</p>
<h2 id="Object-oriented_programming" name="Object-oriented_programming">Объектно-ориентированное программирование</h2>
<p>Объектно-ориентированное программирование (ООП) — это парадигма программирования, которая использует {{glossary("абстракции")}}, чтобы создавать модели, основанные на объектах реального мира. ООП использует несколько техник из ранее признанных парадигм, включая {{glossary("модульность")}}, {{glossary("полиморфизм")}} и {{glossary("инкапсуляция")}}. На сегодняшний день многие популярные языки программирования (такие как Java, JavaScript, C#, C++, Python, PHP, Ruby и Objective-C) поддерживают ООП.</p>
<p>ООП представляет программное обеспечение как совокупность взаимодействующих объектов, а не набор функций или просто список команд (как в традиционном представлении). В ООП, каждый объект может получать сообщения, обрабатывать данные, и отправлять сообщения другим объектам. Каждый объект может быть представлен как маленькая независимая машина с отдельной ролью или ответственностью.</p>
<p>ООП способствует большей гибкости и поддерживаемости в программировании, и широко распространена в крупномасштабном программном инжиниринге. Так как ООП настоятельно подчеркивает модульность, объектно-ориентированный код проще в разработке и проще для понимания впоследствии. Объектно-ориентированный код способствует более точному анализу, кодированию и пониманию сложных ситуаций и процедур, чем методы программирования с меньшей модульностью.<a href="#cite-1"><sup>1</sup></a></p>
<h2 id="Terminology" name="Terminology">Терминология</h2>
<dl>
<dt>{{Glossary("Пространство имён")}}</dt>
<dd>Контейнер, который позволяет разработчикам связать весь функционал под уникальным, специфичным для приложения именем.</dd>
<dt>{{Glossary("Класс")}}</dt>
<dd>Определяет характеристики объекта. Класс является описанием шаблона свойств и методов объекта.</dd>
<dt>{{Glossary("Объект")}}</dt>
<dd>Экземпляр класса.</dd>
<dt>{{Glossary("Свойство")}}</dt>
<dd>Характеристика объекта, например, цвет.</dd>
<dt>{{Glossary("Метод")}}</dt>
<dd>Возможности объекта, такие как ходьба. Это подпрограммы или функции, связанные с классом.</dd>
<dt>{{Glossary("Конструктор")}}</dt>
<dd>Метод, вызываемый в момент создания экземпляра объекта. Он, как правило, имеет то же имя, что и класс, содержащий его.</dd>
<dt>{{Glossary("Наследование")}}</dt>
<dd>Класс может наследовать характеристики от другого класса.</dd>
<dt>{{Glossary("Инкапсуляция")}}</dt>
<dd>Способ комплектации данных и методов, которые используют данные.</dd>
<dt>{{Glossary("Абстракция")}}</dt>
<dd>Совокупность комплексных наследований, методов и свойств объекта должны адекватно отражать модель реальности.</dd>
<dt>{{Glossary("Полиморфизм")}}</dt>
<dd>Поли означает "<em>много</em>", а морфизм "<em>формы</em>". Различные классы могут объявить один и тот же метод или свойство.</dd>
</dl>
<p>Для более обширного описания объектно-ориентированного программирования, см {{interwiki("wikipedia", "Объектно-ориентированное_программирование")}} в Wikipedia.</p>
<h2 id="Прототипное_программирование">Прототипное программирование</h2>
<p>Прототипное программирование — это модель ООП которая не использует классы, а вместо этого сначала выполняет поведение класса и затем использует его повторно (эквивалент наследования в языках на базе классов), декорируя (или расширяя) существующие<em> </em>объекты <em>прототипы</em>. (Также называемое бесклассовое, прототипно-ориентированное, или экземплярно-ориентированное программирование.)</p>
<p>Оригинальный (и наиболее каноничный) пример прототипно-ориентированного языка это {{interwiki("wikipedia", "Self (programming language)", "Self")}} разработанный Дэвидом Ангаром и Ренделлом Смитом. Однако бесклассовый стиль программирования стал набирать популярность позднее, и был принят для таких языков программирования, как JavaScript, Cecil, NewtonScript, Io, MOO, REBOL, Kevo, Squeak (при использовании фреймворка Viewer для манипуляции компонентами Morphic) и некоторых других.<a href="#cite-1"><sup>1</sup></a></p>
<h2 id="JavaScript_Object_Oriented_Programming" name="JavaScript_Object_Oriented_Programming">Объектно-ориентированное программирование в JavaScript</h2>
<h3 id="Пространство_имён">Пространство имён</h3>
<p>Пространство имён — это контейнер, который позволяет разработчикам собрать функциональность под уникальным именем приложения. <strong>Пространство имён в JavaScript — это объект, содержащий методы, свойства и другие объекты.</strong></p>
<div class="note">
<p>Важно отметить, что на уровне языка в JavaScript нет разницы между пространством имён и любым другим объектом. Это отличает JS от множества других объектно-ориентированных языков и может стать причиной путаницы у начинающих JS программистов.</p>
</div>
<p>Принцип работы пространства имён в JS прост: создать один глобальный объект и все переменные, методы и функции объявлять как свойства этого объекта. Также использование пространств имён снижает вероятность возникновения конфликтов имён в приложении так как каждый объект приложения является свойством глобального объекта.</p>
<p>Давайте создадим глобальный объект MYAPP:</p>
<pre class="brush: js">// Глобальное пространство имён
var MYAPP = MYAPP || {};</pre>
<p>Во фрагменте кода выше мы сначала проверяем определён ли объект MYAPP (в текущем файле или другом файле). Если да, то используем существующий глобальный объект MYAPP, иначе создаём пустой объект MYAPP, в котором мы инкапсулируем все методы, функции, переменные и объекты.</p>
<p>Также мы можем создать подпространство имён (учтите, что сначала нужно объявить глобальный объект):</p>
<pre class="brush: js">// Подпространство имён
MYAPP.event = {};</pre>
<p>Далее следует пример синтаксиса создания пространства имён и добавления переменных, функций и методов:</p>
<pre class="brush: js">// Создаём контейнер MYAPP.commonMethod для общих методов и свойств
MYAPP.commonMethod = {
regExForName: "", // определяет регулярное выражение для валидации имени
regExForPhone: "", // определяет регулярное выражение для валидации телефона
validateName: function(name){
// Сделать что-то с name, вы можете получить доступ к переменной regExForName
// используя "this.regExForName"
},
validatePhoneNo: function(phoneNo){
// Сделать что-то с номером телефона
}
}
// Объект вместе с объявлением методов
MYAPP.event = {
addListener: function(el, type, fn) {
// код
},
removeListener: function(el, type, fn) {
// код
},
getEvent: function(e) {
// код
}
// Можно добавить другие свойства и методы
}
// Синтаксис использования метода addListener:
MYAPP.event.addListener("yourel", "type", callback);</pre>
<h3 id="Core_Objects" name="Core_Objects">Стандартные встроенные объекты</h3>
<p>В JavaScript есть несколько объектов, встроенных в ядро, например {{jsxref("Math")}}, {{jsxref("Object")}}, {{jsxref("Array")}} и {{jsxref("String")}}. Пример ниже показывает как использовать объект Math, чтобы получить случайное число, используя его метод random().</p>
<pre class="brush: js">console.log(Math.random());
</pre>
<div class="note"><strong>Примечание:</strong> В данном примере и далее мы будем использовать глобальную функцию {{domxref("console.log()")}}. Если точнее, то функция <code>console.log()</code> не является частью JavaScript, но она поддерживается многими браузерами для облегчения отладки.</div>
<p>Смотрите <a href="/ru/docs/Web/JavaScript/Reference/Global_Objects" title="en-US/docs/Web/JavaScript/Reference/Global_Objects">JavaScript Reference: Standard built-in objects</a>, чтобы ознакомиться со списком всех встроенных объектов JavaScript.</p>
<p>Каждый объект в JavaScript является экземпляром объекта <code><a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a></code>, следовательно наследует все его свойства и методы.</p>
<h3 id="Custom_Objects" name="Custom_Objects">Объекты, создаваемые пользователем</h3>
<h4 id="The_Class" name="The_Class">Класс</h4>
<p>JavaScript — это прототипно-ориентированный язык, и в нём нет оператора <code>class</code>, который имеет место в C++ или Java. Иногда это сбивает с толку программистов, привыкших к языкам с оператором <code>class</code>. Вместо этого JavaScript использует функции как конструкторы классов. Объявить класс так же просто как объявить функцию. В примере ниже мы объявляем новый класс Person с пустым конструктором:</p>
<pre class="brush: js">var Person = function () {};
</pre>
<h4 id="The_Object_.28Class_Instance.29" name="The_Object_.28Class_Instance.29">Объект (экземпляр класса)</h4>
<p>Для создания нового экзмепляра объекта <code>obj</code> мы используем оператор <code>new obj</code>, присваивая результат (который имеет тип <code>obj</code>) в переменную.</p>
<p>В примере выше мы определили класс <code>Person</code>. В примере ниже мы создаём два его экземпляра (<code>person1</code> и <code>person2</code>).</p>
<pre class="brush: js">var person1 = new Person();
var person2 = new Person();
</pre>
<div class="note">Ознакомьтесь с {{jsxref("Object.create()")}}, новым, дополнительным методом инстанцирования, который создаёт неинициализированный экземпляр.</div>
<h4 id="The_Constructor" name="The_Constructor">Конструктор</h4>
<p>Конструктор вызывается в момент создания экземпляра класса (в тот самый момент, когда создается объект). Конструктор является методом класса. В JavaScript функция служит конструктором объекта, поэтому нет необходимости явно определять метод конструктор. Любое действие определенное в конструкторе будет выполненно в момент создания экземпляра класса.</p>
<p>Конструктор используется для задания свойств объекта или для вызова методов, которые подготовят объект к использованию. Добавление методов и их описаний производится с использованием другого синтаксиса, описанного далее в этой статье.</p>
<p>В примере ниже, конструктор класса <code>Person</code> выводит в консоль сообщение в момент создания нового экземпляра <code>Person</code>.</p>
<pre class="brush: js">var Person = function () {
console.log('instance created');
};
var person1 = new Person();
var person2 = new Person();
</pre>
<h4 id="The_Property_.28object_attribute.29" name="The_Property_.28object_attribute.29">Свойство (аттрибут объекта)</h4>
<p>Свойства — это переменные, содержащиеся в классе; каждый экземпляр объекта имеет эти свойства. Свойства устанавливаются в конструкторе (функции) класса, таким образом они создаются для каждого экземпляра.</p>
<p>Ключевое слово <code>this</code>, которое ссылается на текущий объект, позволяет вам работать со свойствами класса. Доступ (чтение и запись) к свойствам снаружи класса осуществляется синтаксисом <code>InstanceName.Property,</code> так же как в C++, Java и некоторых других языках. (Внутри класса для получения и изменения значений свойств используется синтаксис <code>this.Property</code>)</p>
<p>В примере ниже, мы определяем свойство <code>firstName</code> для класса <code>Person</code> при создании экземпляра:</p>
<pre class="brush: js">var Person = function (firstName) {
this.firstName = firstName;
console.log('Person instantiated');
};
var person1 = new Person('Alice');
var person2 = new Person('Bob');
// Выводит свойство firstName в консоль
console.log('person1 is ' + person1.firstName); // выведет "person1 is Alice"
console.log('person2 is ' + person2.firstName); // выведет "person2 is Bob"
</pre>
<h4 id="The_methods" name="The_methods">Методы</h4>
<p>Методы — это функции (и определяются как функции), но с другой стороны следуют той же логике, что и свойства. Вызов метода похож на доступ к свойству, но вы добавляете () на конце имени метода, возможно, с аргументами. Чтобы объявить метод, присвойте функцию в именованное свойство свойства <code>prototype</code> класса. Потом вы сможете вызвать метод объекта под тем именем, которое вы присвоили функции.</p>
<p>В примере ниже мы определяем и используем метод <code>sayHello()</code> для класса <code>Person</code>.</p>
<pre class="brush: js">var Person = function (firstName) {
this.firstName = firstName;
};
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
// вызываем метод sayHello() класса Person
person1.sayHello(); // выведет "Hello, I'm Alice"
person2.sayHello(); // выведет "Hello, I'm Bob"
</pre>
<p>В JavaScript методы это — обычные объекты функций, связанные с объектом как свойства: это означает, что вы можете вызывать методы "вне контекста". Рассмотрим следующий пример:</p>
<pre class="brush: js">var Person = function (firstName) {
this.firstName = firstName;
};
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
var helloFunction = person1.sayHello;
// выведет "Hello, I'm Alice"
person1.sayHello();
// выведет "Hello, I'm Bob"
person2.sayHello();
// выведет "Hello, I'm undefined" (or fails
// with a TypeError in strict mode)
helloFunction();
// выведет true
console.log(helloFunction === person1.sayHello);
// выведет true
console.log(helloFunction === Person.prototype.sayHello);
// выведет "Hello, I'm Alice"
helloFunction.call(person1);</pre>
<p>Как показывает пример, все ссылки, которые мы имеем на функцию <code>sayHello</code> — <code>person1</code>, <code>Person.prototype</code>, переменная <code>helloFunction</code> и т.д. — ссылаются на одну и ту же функцию. Значение <code>this</code> в момент вызова функции зависит от того, как мы её вызываем. Наиболее часто мы обращаемся к <code>this</code> в выражениях, где мы получаем функцию из свойства объекта — <code>person1.sayHello()</code> — <code>this</code> устанавливается на объект, из которого мы получили функцию (<code>person1</code>), вот почему <code>person1.sayHello()</code> использует имя "Alice", а <code>person2.sayHello()</code> использует имя "Bob". Но если вызов будет совершён иначе, то <code>this</code> будет иным: вызов <code>this</code> из переменной — <code>helloFunction()</code> — установит <code>this</code> на глобальный объект (<code>window</code> в браузерах). Так как этот объект (вероятно) не имеет свойства <code>firstName</code>, функция выведет "Hello, I'm undefined" (так произойдёт в нестрогом режиме; в <a href="https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Strict_mode">strict mode</a> всё будет иначе (ошибка), не будем сейчас вдаваться в подробности, чтобы избежать путаницы). Или мы можем указать <code>this</code> явно с помощью <code>Function#call</code> (или <code>Function#apply</code>) как показано в конце примера.</p>
<div class="note"><strong>Примечание:</strong> Смотрите подробнее о <code>this</code> в <a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/call" title="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call">Function#call</a> и <a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/apply" title="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply">Function#apply</a></div>
<h4 id="Inheritance" name="Inheritance">Наследование</h4>
<p>Наследование — это способ создать класс как специализированную версию одного или нескольких классов (JavaScript поддерживает только одиночное наследование). Специализированный класс, как правило, называют потомком, а другой класс родителем. В JavaScript наследование осуществляется присвоением экземпляра класса родителя классу потомку. В современных браузерах вы можете реализовать наследование с помощью <a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Classical_inheritance_with_Object.create" title="/en-US/docs/JavaScript/Reference/Global_Objects/Object/create#Classical_inheritance_with_Object.create">Object.create</a>.</p>
<div class="note"><strong>Примечание:</strong> JavaScript не обнаружит <code>prototype.constructor</code> класса потомка (смотрите <a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype">Object.prototype</a>) так что мы должны указать его вручную. Смотрите вопрос "<a href="http://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor">Why is it necessary to set the prototype constructor?</a>" на Stackoverflow.</div>
<p>В примере ниже мы определяем класс <code>Student</code> как потомка класса <code>Person</code>. Потом мы переопределяем метод <code>sayHello()</code> и добавляем метод <code>addGoodBye()</code>.</p>
<pre class="brush: js">// Определяем конструктор Person
var Person = function(firstName) {
this.firstName = firstName;
};
// Добавляем пару методов в Person.prototype
Person.prototype.walk = function(){
console.log("I am walking!");
};
Person.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName);
};
// Определяем конструктор Student
function Student(firstName, subject) {
// Вызываем конструктор родителя, убедившись (используя Function#call)
// что "this" в момент вызова установлен корректно
Person.call(this, firstName);
// Инициируем свойства класса Student
this.subject = subject;
};
// Создаём объект Student.prototype, который наследуется от Person.prototype.
// Примечание: Рспространённая ошибка здесь, это использование "new Person()", чтобы создать
// Student.prototype. Это неверно по нескольким причинам, не в последнюю очередь
// потому, что нам нечего передать в Person в качестве аргумента "firstName"
// Правильное место для вызова Person показано выше, где мы вызываем
// его в конструкторе Student.
Student.prototype = Object.create(Person.prototype); // Смотрите примечание выше
// Устанавливаем свойство "constructor" для ссылки на класс Student
Student.prototype.constructor = Student;
// Заменяем метод "sayHello"
Student.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName + ". I'm studying "
+ this.subject + ".");
};
// Добавляем метод "sayGoodBye"
Student.prototype.sayGoodBye = function(){
console.log("Goodbye!");
};
// Пример использования:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk(); // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"
// Проверяем, что instanceof работает корректно
console.log(student1 instanceof Person); // true
console.log(student1 instanceof Student); // true
</pre>
<p>Относительно строки <code>Student.prototype = Object.create(Person.prototype);</code>: В старых движках JavaScript, в которых нет <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create" title="Object.create">Object.create</a></code> можно использовать полифилл (ещё известный как "shim") или функцию которая достигает тех же результатов, такую как:</p>
<pre class="brush: js">function createObject(proto) {
function ctor() { }
ctor.prototype = proto;
return new ctor();
}
// Пример использования:
Student.prototype = createObject(Person.prototype);
</pre>
<div class="note"><strong>Примечание:</strong> Смотрите <a href="/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/create" title="Object.create">Object.create</a> для более подробной информации, и shim для реализации на старых движках.</div>
<h4 id="Encapsulation" name="Encapsulation">Инкапсуляция</h4>
<p>В примере выше классу <code>Student</code> нет необходимости знать о реализации метода <code>walk()</code> класса <code>Person</code>, но он может его использовать; Класс <code>Student</code> не должен явно определять этот метод, пока мы не хотим его изменить. Это называется <strong>инкапсуляция</strong>, благодаря чему каждый класс собирает данные и методы в одном блоке.</p>
<p>Сокрытие информации распространённая особенность, часто реализуемая в других языках программирования как приватные и защищённые методы/свойства. Однако в JavaScript можно лишь имитировать нечто подобное, это не является необходимым требованием объектно-ориентированного программирования.<a href="#cite-2"><sup>2</sup></a></p>
<h4 id="Abstraction" name="Abstraction">Абстракция</h4>
<p>Абстракция это механизм который позволяет смоделировать текущий фрагмент рабочей проблемы, с помощью наследования (специализации) или композиции. JavaScript достигает специализации наследованием, а композиции возможностью экземплярам класса быть значениями атрибутов других объектов.</p>
<p>В JavaScript класс <code>Function</code> наследуется от класса <code>Object</code> (это демонстрирует специализацию), а свойство <code>Function.prototype</code> это экземпляр класса <code>Object</code> (это демонстрирует композицию).</p>
<pre class="brush: js">var foo = function () {};
// выведет "foo is a Function: true"
console.log('foo is a Function: ' + (foo instanceof Function));
// выведет "foo.prototype is an Object: true"
console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object));</pre>
<h4 id="Polymorphism" name="Polymorphism">Полиморфизм</h4>
<p>Так как все методы и свойства определяются внутри свойства <code>prototype</code>, различные классы могут определять методы с одинаковыми именами; методы находятся в области видимости класса в котором они определены, пока два класса не имеют связи родитель-потомок (например, один наследуется от другого в цепочке наследований).</p>
<h2 id="Notes" name="Notes">Примечания</h2>
<p>Это не все способы которыми можно реализовать объектно-ориентированное программирование в JavaScript, который очень гибок в этом отношении. Также способы рассмотренные здесь не отражают всех возможностей JavaScript и не подражают реализации теории объектов в других языках.</p>
<p>Существуют другие способы, которые реализуют ещё более продвинутое объектно-ориентированное программирование на JavaScript, но они выходят за рамки этой вводной статьи.</p>
<h2 id="References" name="References">Ссылки</h2>
<ol>
<li><a name="cite-1"></a>Wikipedia. "<a href="http://en.wikipedia.org/wiki/Object-oriented_programming">Object-oriented programming</a>"</li>
<li><a name="cite-2"></a>Wikipedia. "<a href="http://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29">Encapsulation (object-oriented programming)</a>"</li>
</ol>
|