You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/8-oop/1-about-oop/article.md
+8-2Lines changed: 8 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,7 +34,13 @@ var vasya = new User("Вася"); // создали пользователя
34
34
vasya.sayHi(); // пользователь умеет говорить "Привет"
35
35
```
36
36
37
-
Здесь мы видим ярко выраженную сущность -- `User` (посетитель).
37
+
Здесь мы видим ярко выраженную сущность -- `User` (посетитель). Используя терминологию ООП, такие конструкторы часто называют *классами*, то есть можно сказать "класс `User`".
38
+
39
+
[smart header="Класс в ООП"]
40
+
[Классом]("https://en.wikipedia.org/wiki/Class_(computer_programming)") в объектно-ориентированной разработке называют шаблон/программный код, предназначенный для создания объектов и методов.
41
+
42
+
В JavaScript классы можно организовать по-разному. Говорят, что класс `User` написан в "функциональном" стиле. Далее мы также увидим "прототипный" стиль.
43
+
[/smart]
38
44
39
45
ООП -- это наука о том, как делать правильную архитектуру. У неё есть свои принципы, например [SOLID](https://ru.wikipedia.org/wiki/SOLID_%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29).
<i>Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес.</i></li>
48
54
</ul>
49
55
50
-
Здесь мы не имеем возможности углубиться в теорию ООП, но основных "китов", на которых оно стоит, потрогаем за усы вдумчиво и конкретно.
56
+
Здесь мы не имеем возможности углубиться в теорию ООП, поэтому чтение таких книг рекомендуется. Хотя основные принципы, как использовать ООП правильно, мы, всё же, затронем.
Copy file name to clipboardExpand all lines: 1-js/8-oop/5-functional-inheritance/article.md
+59-57Lines changed: 59 additions & 57 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,17 +17,17 @@
17
17
18
18
Именно поэтому, увидев новую технику, мы уже можем что-то с ней сделать, даже не читая инструкцию.
19
19
20
-
**Механизм наследования позволяет определить базовый класс `Машина`, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: `Кофеварка`, `Холодильник` и т.п.**
20
+
Механизм наследования позволяет определить базовый класс `Машина`, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: `Кофеварка`, `Холодильник` и т.п.
21
21
22
22
[smart header="В веб-разработке всё так же"]
23
-
В веб-разработке нам могут понадобиться классы `Меню`, `Табы`, `Диалог` и другие компоненты интерфейса.
23
+
В веб-разработке нам могут понадобиться классы `Меню`, `Табы`, `Диалог` и другие компоненты интерфейса. В них всех обычно есть что-то общее.
24
24
25
-
Можно выделить полезный общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код. Это обычная практика, принятая во множестве библиотек.
25
+
Можно выделить такой общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код.
26
26
[/smart]
27
27
28
28
## Наследование от Machine
29
29
30
-
Например, у нас есть класс `Machine`, который реализует методы "включить" `enable()` и "выключить" `disable()`:
30
+
Базовый класс "машина" `Machine` будет реализовывать общего вида методы "включить" `enable()` и "выключить" `disable()`:
31
31
32
32
```js
33
33
functionMachine() {
@@ -48,33 +48,33 @@ function Machine() {
48
48
```js
49
49
functionCoffeeMachine(power) {
50
50
*!*
51
-
Machine.call(this);
51
+
Machine.call(this);// отнаследовать
52
52
*/!*
53
+
53
54
var waterAmount =0;
54
55
55
56
this.setWaterAmount=function(amount) {
56
57
waterAmount = amount;
57
58
};
58
59
59
-
functiononReady() {
60
-
alert('Кофе готово!');
61
-
}
62
-
63
-
this.run=function() {
64
-
setTimeout(onReady, 1000);
65
-
};
66
-
67
60
}
68
61
69
62
var coffeeMachine =newCoffeeMachine(10000);
63
+
64
+
*!*
70
65
coffeeMachine.enable();
66
+
coffeeMachine.setWaterAmount(100);
67
+
coffeeMachine.disable();
68
+
*/!*
71
69
```
72
70
73
-
Наследование реализовано вызовом `Machine.call(this)` в начале `CoffeeMachine`.
71
+
Наследование реализовано вызовом `Machine.call(this)` в начале конструктора `CoffeeMachine`.
74
72
75
73
Он вызывает функцию `Machine`, передавая ей в качестве контекста `this` текущий объект. `Machine`, в процессе выполнения, записывает в `this` различные полезные свойства и методы, в нашем случае `this.enable` и `this.disable`.
76
74
77
-
Далее `CoffeeMachine` продолжает выполнение и может добавить свои свойства и методы, а также пользоваться унаследованными.
75
+
Далее конструктор `CoffeeMachine` продолжает выполнение и может добавить свои свойства и методы.
76
+
77
+
В результате мы получаем объект `coffeeMachine`, который включает в себя методы из `Machine` и `CoffeeMachine`.
78
78
79
79
## Защищённые свойства
80
80
@@ -116,13 +116,13 @@ var coffeeMachine = new CoffeeMachine(10000);
116
116
117
117
**Чтобы наследник имел доступ к свойству, оно должно быть записано в `this`.**
118
118
119
-
**При этом, чтобы обозначить, что свойство является внутренним, его имя начинают с подчёркивания `_`.**
119
+
При этом, чтобы обозначить, что свойство является внутренним, его имя начинают с подчёркивания `_`.
120
120
121
121
```js
122
122
//+ run
123
123
functionMachine() {
124
124
*!*
125
-
this._enabled=false;
125
+
this._enabled=false;// вместо var enabled
126
126
*/!*
127
127
128
128
this.enable=function() {
@@ -147,30 +147,19 @@ function CoffeeMachine(power) {
147
147
var coffeeMachine =newCoffeeMachine(10000);
148
148
```
149
149
150
-
**Подчёркивание в начале свойства -- общепринятый знак, что свойство является внутренним, предназначенным лишь для доступа из самого объекта и его наследников. Такие свойства называют *защищёнными*.**
150
+
Подчёркивание в начале свойства -- общепринятый знак, что свойство является внутренним, предназначенным лишь для доступа из самого объекта и его наследников. Такие свойства называют *защищёнными*.
151
151
152
-
Технически это, конечно, возможно, но приличный программист снаружи в такое свойство не полезет.
153
-
154
-
**Вообще, это стандартная практика: конструктор сохраняет свои параметры в свойствах объекта. Иначе наследники не будут иметь к ним доступ.**
152
+
Технически, залезть в него из внешнего кода, конечно, возможно, но приличный программист так делать не будет.
155
153
156
154
## Перенос свойства в защищённые
157
155
158
-
В коде выше есть свойство `power`. Сейчас мы его тоже сделаем защищённым и перенесём в `Machine`, поскольку "мощность" свойственна всем машинам, а не только кофеварке:
156
+
У `CoffeeMachine`есть приватное свойство `power`. Сейчас мы его тоже сделаем защищённым и перенесём в `Machine`, поскольку "мощность" свойственна всем машинам, а не только кофеварке.
159
157
160
158
```js
161
159
//+ run
162
-
functionCoffeeMachine(power) {
163
-
*!*
164
-
Machine.apply(this, arguments); // (1)
165
-
*/!*
166
-
167
-
alert(this._enabled); // false
168
-
alert(this._power); // 10000
169
-
}
170
-
171
160
functionMachine(power) {
172
161
*!*
173
-
this._power= power; // (2)
162
+
this._power= power; // (1)
174
163
*/!*
175
164
176
165
this._enabled=false;
@@ -184,34 +173,45 @@ function Machine(power) {
184
173
};
185
174
}
186
175
176
+
functionCoffeeMachine(power) {
177
+
*!*
178
+
Machine.apply(this, arguments); // (2)
179
+
*/!*
180
+
181
+
alert(this._enabled); // false
182
+
alert(this._power); // 10000
183
+
}
184
+
187
185
var coffeeMachine =newCoffeeMachine(10000);
188
186
```
189
187
190
-
В коде выше при вызове `new CoffeeMachine(10000)` в строке `(1)` кофеварка передаёт аргументы и контекст родителю вызовом `Machine.apply(this, arguments)`.
188
+
Теперь все машины `Machine` имеют мощность `power`. Обратим внимание, что мы из параметра конструктора сразу скопировали её в объект в строке `(1)`. Иначе она была бы недоступна из наследников.
191
189
192
-
Можно было бы использовать `Machine.call(this, power)`, но использование `apply` гарантирует передачу всех аргументов, мало ли, вдруг мы в будущем захотим их добавить.
190
+
В строке `(2)` мы теперь вызываем не просто `Machine.call(this)`, а расширенный вариант: `Machine.apply(this, arguments)`, который вызывает `Machine` в текущем контексте вместе с передачей текущих аргументов.
193
191
194
-
Далее конструктор `Machine` в строке `(2)` сохраняет `power` в свойстве объекта `this._power`, благодаря этому кофеварка, когда наследование перейдёт обратно к `CoffeeMachine`, сможет сразу обращаться к нему.
192
+
Можно было бы использовать и более простой вызов `Machine.call(this, power)`, но использование `apply` гарантирует передачу всех аргументов, вдруг их количество увеличится -- не надо будет переписывать.
195
193
196
194
## Переопределение методов
197
195
198
196
Итак, мы получили класс `CoffeeMachine`, который наследует от `Machine`.
199
197
200
-
Аналогичным образом мы можем унаследовать от `Machine` холодильник `Fridge`, микроволновку `MicroOven` и другие классы, которые разделяют общий "машинный" функционал.
198
+
Аналогичным образом мы можем унаследовать от `Machine` холодильник `Fridge`, микроволновку `MicroOven` и другие классы, которые разделяют общий "машинный" функционал, то есть имеют мощность и их можно включать/выключать.
201
199
202
200
Для этого достаточно вызвать `Machine` текущем контексте, а затем добавить свои методы.
203
201
204
202
```js
205
203
// Fridge может добавить и свои аргументы,
206
204
// которые в Machine не будут использованы
207
205
functionFridge(power, temperature) {
208
-
Machine.call(this, arguments);
206
+
Machine.apply(this, arguments);
209
207
210
208
// ...
211
209
}
212
210
```
213
211
214
-
Кроме создания новых методов, можно заменить унаследованные на свои:
212
+
Бывает так, что реализация конкретного метода машины в наследнике имеет свои особенности.
213
+
214
+
Можно, конечно, объявить в `CoffeeMachine` свой `enable`:
215
215
216
216
```js
217
217
functionCoffeeMachine(power, capacity) {
@@ -224,7 +224,7 @@ function CoffeeMachine(power, capacity) {
224
224
}
225
225
```
226
226
227
-
...Однако, как правило, мы хотим не заменить, а *расширить* метод родителя. Например, сделать так, чтобы при включении кофеварка тут же запускалась.
227
+
...Однако, как правило, мы хотим не заменить, а *расширить* метод родителя, добавить к нему что-то. Например, сделать так, чтобы при включении кофеварка тут же запускалась.
228
228
229
229
Для этого метод родителя предварительно копируют в переменную, и затем вызывают внутри нового `enable` -- там, где считают нужным:
230
230
@@ -247,10 +247,10 @@ function CoffeeMachine(power) {
247
247
**Общая схема переопределения метода (по строкам выделенного фрагмента кода):**
248
248
249
249
<ol>
250
-
<li>Мы скопировали доставшийся от родителя метод `enable` в переменную, например `parentEnable`.</li>
251
-
<li>Заменили метод `this.enable()` на свою функцию...</li>
252
-
<li>Которая по-прежнему реализует старый функционал через вызов `parentEnable`...</li>
253
-
<li>И в дополнение к нему делает что-то своё, например запускает приготовление кофе.</li>
250
+
<li>Копируем доставшийся от родителя метод `this.enable` в переменную, например `parentEnable`.</li>
251
+
<li>Заменяем `this.enable` на свою функцию...</li>
252
+
<li>...Которая по-прежнему реализует старый функционал через вызов `parentEnable`.</li>
253
+
<li>...И в дополнение к нему делает что-то своё, например запускает приготовление кофе.</li>
254
254
</ol>
255
255
256
256
Обратим внимание на строку `(3)`.
@@ -266,15 +266,17 @@ function Machine(power) {
266
266
267
267
*!*
268
268
varself=this;
269
+
*/!*
269
270
270
271
this.enable=function() {
272
+
*!*
271
273
// используем внешнюю переменную вместо this
272
274
self._enabled=true;
273
-
};
274
275
*/!*
276
+
};
275
277
276
278
this.disable=function() {
277
-
this._enabled=false;
279
+
self._enabled=false;
278
280
};
279
281
280
282
}
@@ -325,14 +327,14 @@ coffeeMachine.enable();
325
327
```js
326
328
functionMachine(params) {
327
329
// локальные переменные и функции доступны только внутри Machine
328
-
varprivate;
330
+
varprivateProperty;
329
331
330
332
// публичные доступны снаружи
331
-
this.public=...;
333
+
this.publicProperty=...;
332
334
333
335
// защищённые доступны внутри Machine и для потомков
334
336
// мы договариваемся не трогать их снаружи
335
-
this._protected=...
337
+
this._protectedProperty=...
336
338
}
337
339
338
340
var machine =newMachine(...)
@@ -349,12 +351,12 @@ function CoffeeMachine(params) {
349
351
Machine.apply(this, arguments);
350
352
*/!*
351
353
352
-
this.coffeePublic=...
354
+
this.coffeePublicProperty=...
353
355
}
354
356
355
357
var coffeeMachine =newCoffeeMachine(...);
356
-
coffeeMachine.public();
357
-
coffeeMachine.coffeePublic();
358
+
coffeeMachine.publicProperty();
359
+
coffeeMachine.coffeePublicProperty();
358
360
```
359
361
360
362
</li>
@@ -365,9 +367,9 @@ function CoffeeMachine(params) {
365
367
Machine.apply(this, arguments);
366
368
367
369
*!*
368
-
var parentProtected =this._protected;
369
-
this._protected=function(args) {
370
-
parentProtected.call(this, args); // (*)
370
+
var parentProtected =this._protectedProperty;
371
+
this._protectedProperty=function(args) {
372
+
parentProtected.apply(this, args); // (*)
371
373
// ...
372
374
};
373
375
*/!*
@@ -389,11 +391,11 @@ function Machine(params) {
389
391
</li>
390
392
</ol>
391
393
392
-
**В следующих главах мы будем изучать прототипный подход, который обладаем рядом преимуществ, по сравнению с функциональным.**
394
+
Надо сказать, что способ наследования, описанный в этой главе, используется нечасто.
393
395
394
-
Но функциональный тоже бывает полезен.
396
+
В следующих главах мы будем изучать прототипный подход, который обладаем рядом преимуществ.
395
397
396
-
В своей практике разработки я обычно наследую функционально в тех случаях, когда *уже* есть какой-то код, который на нём построен. К примеру, уже существуют классы, написанные сторонними разработчиками, которые можно доопределить или расширить только так.
398
+
Но знать и понимать его необходимо, поскольку во многих существующих библиотеках классы написаны в функциональном стиле, и расширять/наследовать от них можно только так.
Copy file name to clipboardExpand all lines: 1-js/9-prototypes/1-prototype/article.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,7 @@
9
9
10
10
Если один объект имеет специальную ссылку `__proto__` на другой объект, то при чтении свойства из него, если свойство отсутствует в самом объекте, оно ищется в объекте `__proto__`.
11
11
12
-
Свойство доступно во всех браузерах, кроме IE10-. Впрочем, в старых IE оно, на самом деле, тоже есть, но требуются чуть более сложные способы для работы с ним, которые мы рассмотрим позднее.
12
+
Свойство `__proto__`доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.
0 commit comments