diff --git a/1-js/10-es-modern/6-es-object/article.md b/1-js/10-es-modern/6-es-object/article.md
index 40772e76..d298c03b 100644
--- a/1-js/10-es-modern/6-es-object/article.md
+++ b/1-js/10-es-modern/6-es-object/article.md
@@ -76,26 +76,23 @@ alert( user["мой зелёный крокодил"] ); // Вася
`Object.getPrototypeOf(obj)`
-В современной JavaScript также добавился сеттер:
+В ES-2015 также добавился сеттер:
- `Object.setPrototypeOf(obj, newProto)`
...А также "узаконено" свойство `__proto__`, которое даёт прямой доступ к прототипу. Его, в качестве "нестандартного", но удобного способа работы с прототипом реализовали почти все браузеры (кроме IE10-), так что было принято решение добавить его в стандарт.
-По стандарту оно реализовано через геттеры-сеттеры `Object.getPrototypeOf/setPrototypeOf`.
-
-
## Object.assign
+Функция `Object.assign` получает список объектов и копирует в первый `target` свойства из остальных.
+
Синтаксис:
```js
Object.assign(target, src1, src2...)
```
-Функция `Object.assign` получает список объектов и копирует в первый `target` свойства из остальных.
-
-Последующие свойства перезаписывают предыдущие.
+При этом последующие свойства перезаписывают предыдущие.
Например:
@@ -109,14 +106,31 @@ let admin = { isAdmin: true };
Object.assign(user, visitor, admin);
+// user <- visitor <- admin
alert( JSON.stringify(user) ); // user: Вася, visits: true, isAdmin: true
```
+Его также можно использовать для 1-уровневого клонирования объекта:
+
+```js
+'use strict';
+
+let user = { name: "Вася", isAdmin: false };
+
+*!*
+// clone = пустой объект + все свойства user
+let clone = Object.assign({}, user);
+*/!*
+```
+
+
## Object.is(value1, value2)
-Возвращает `true`, если `value1 === value2`, иначе `false`.
+Новая функция для проверки равенства значений.
-Есть, однако, два отличия от обычного `===`, а именно:
+Возвращает `true`, если значения `value1` и `value2` равны, иначе `false`.
+
+Она похожа на обычное строгое равенство `===`, но есть отличия:
```js
//+ run
@@ -130,20 +144,20 @@ alert( Object.is(NaN, NaN) ); // true
alert( NaN === NaN ); // false
```
-При сравнении объектов через `Object.is` успользуется алгоритм [SameValue](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevalue), который неявно применяется во многих других местах современного стандарта.
+Отличия эти в большинстве ситуаций некритичны, так что непохоже, чтобы эта функция вытеснила обычную проверку `===`. Что интересно -- этот алгоритм сравнения, который называется [SameValue](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevalue), применяется во внутренних реализациях различных методов современного стандарта.
## Методы объекта
Долгое время в JavaScript термин "метод объекта" был просто альтернативным названием для свойства-функции.
-Теперь это уже не так, добавлены именно "методы объекта". Они, по сути, являются "свойствами-функциями, привязанными к объекту".
+Теперь это уже не так, добавлены именно "методы объекта", которые, по сути, являются свойствами-функциями, привязанными к объекту.
Их особенности:
-- Более короткий синтаксис.
-- Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше, в разделе про `super`.
+- Более короткий синтаксис объявления.
+- Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше.
Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`.
@@ -158,7 +172,7 @@ let name = "Вася";
let user = {
name,
*!*
- /* вместо sayHi: function() { */
+ // вместо "sayHi: function() {" пишем "sayHi() {"
sayHi() {
alert(this.name);
}
@@ -168,7 +182,7 @@ let user = {
user.sayHi(); // Вася
```
-Как видно, создание такого метода -- чуть короче, а обращение -- не отличается от обычной функции.
+Как видно, для создания метода нужно писать меньше букв. Что же касается вызова -- он ничем не отличается от обычной функции. На данном этапе можно считать, что "метод" -- это просто сокращённый синтаксис для свойства-функции. Дополнительные возможности, которые даёт такое объявление, мы рассмотрим позже.
Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`:
@@ -207,11 +221,17 @@ let user = {
alert( user.getFirstName() ); // Вася
```
+Итак, мы рассмотрели синтаксические улучшения. Если коротко, то не надо писать слово "function". Теперь перейдём к другим отличиям.
+
## super
+В ES-2015 появилось новое ключевое слово `super`. Оно предназначено только для использования в методах объекта.
+
Вызов `super.parentProperty` позволяет из метода объекта получить свойство его прототипа.
-Например, в коде ниже `rabbit` наследует от `animal`. Вызов `super.walk` из метода объекта `rabbit` обращается к `animal.walk`:
+Например, в коде ниже `rabbit` наследует от `animal`.
+
+Вызов `super.walk()` из метода объекта `rabbit` обращается к `animal.walk()`:
```js
//+ run
@@ -236,9 +256,11 @@ let rabbit = {
rabbit.walk();
```
+Как правило, это используется в [классах](/class), которые мы рассмотрим в следующем разделе, но важно понимать, что "классы" здесь на самом деле не при чём. Свойство `super` работает через прототип, на уровне методов объекта.
+
При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов.
-Например, если переписать этот код, оформив `rabbit.walk` как обычное свойство-функцию, то будет ошибка:
+В частности, если переписать этот код, оформив `rabbit.walk` как обычное свойство-функцию, то будет ошибка:
```js
//+ run
@@ -253,7 +275,7 @@ let animal = {
let rabbit = {
__proto__: animal,
*!*
- walk: function() {
+ walk: function() { // Надо: walk() {
super.walk(); // Будет ошибка!
}
*/!*
@@ -289,9 +311,11 @@ let rabbit = {
rabbit.walk();
```
+Ранее мы говорили о том, что у функций-стрелок нет своего `this`, `arguments`: они используют те, которые во внешней функции. Теперь к этому списку добавился ещё и `super`.
+
[smart header="Свойство `[[HomeObject]]` -- не изменяемое"]
-При создании метода -- он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить независимо:
+При создании метода -- он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить отдельно, и `super` продолжит работать:
```js
//+ run
@@ -310,14 +334,14 @@ let rabbit = {
let walk = rabbit.walk; // скопируем метод в переменную
*!*
-walk();
+walk(); // вызовет animal.walk()
// I'm walking
*/!*
```
В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`.
-Это относится именно к `super`. Правила `this` для методов те же, что и для обычных функций. В примере выше при вызове `walk()` без объекта `this` будет `undefined`.
+Это -- скорее технический момент, так как методы объекта, всё же, предназначены для вызова в контексте этого объекта. В частности, правила для `this` в методах -- те же, что и для обычных функций. В примере выше при вызове `walk()` без объекта `this` будет `undefined`.
[/smart]
## Итого
diff --git a/1-js/10-es-modern/7-es-class/article.md b/1-js/10-es-modern/7-es-class/article.md
index 1ba08f25..8ce0f71b 100644
--- a/1-js/10-es-modern/7-es-class/article.md
+++ b/1-js/10-es-modern/7-es-class/article.md
@@ -1,11 +1,20 @@
# Классы
-В современном JavaScript появился новый, "более красивый" синтаксис для классов. Он естественным образом продолжает синтаксис для объектов и методов, который мы рассмотрели раньше.
+В современном JavaScript появился новый, "более красивый" синтаксис для классов.
+
+Новая конструкция `class` -- удобный "синтаксический сахар" для задания конструктора вместе с прототипом.
## Class
-Новая конструкция `class` -- удобный "синтаксический сахар" для задания конструктора вместе с прототипом.
+Синтаксис для классов выглядит так:
+
+```js
+class Название [extends Родитель] {
+ constructor
+ методы
+}
+```
Например:
@@ -29,6 +38,8 @@ let user = new User("Вася");
user.sayHi(); // Вася
```
+Функция `constructor` запускается при создании `new User`, остальные методы -- записываются в `User.prototype`.
+
Это объявление примерно аналогично такому:
```js
@@ -43,24 +54,24 @@ User.prototype.sayHi = function() {
В обоих случаях `new User` будет создавать объекты. Метод `sayHi` -- также в обоих случаях находится в прототипе.
-Но есть и отличия при объявлении через `class`:
+Но при объявлении через `class` есть и ряд отличий:
-- `User`, объявленный как класс, нельзя вызывать без `new`, будет ошибка.
-- Объявление класса с точки зрения области видимости ведёт себя как `let`.
+- `User` нельзя вызывать без `new`, будет ошибка.
+- Объявление класса с точки зрения области видимости ведёт себя как `let`. В частности, оно видно только текущем в блоке и только в коде, который находится ниже объявления (Function Declaration видно и до объявления).
Методы, объявленные внутри `class`, также имеют ряд особенностей:
- Метод `sayHi` является именно методом, то есть имеет доступ к `super`.
-- Все методы класса работают в `use strict`, даже если он не указан.
-- Все методы класса не перечислимы, то есть в `for..in` по объекту их не будет.
+- Все методы класса работают в режиме `use strict`, даже если он не указан.
+- Все методы класса не перечислимы. То есть в цикле `for..in` по объекту их не будет.
## Class Expression
-Так же, как и Function Expression, классы можно задавать "инлайн" в выражении.
+Так же, как и Function Expression, классы можно задавать "инлайн", в любом выражении и внутри вызова функции.
Это называется Class Expression:
@@ -94,6 +105,39 @@ new User(); // ошибка
В примере выше имя `User` будет доступно только внутри класса и может быть использовано, например для создания новых объектов данного типа.
+Наиболее очевидная область применения этой возможности -- создание вспомогательного класса прямо при вызове функции.
+
+Например, функция `createModel` в примере ниже создаёт объект по классу и данным, добавляет ему `_id` и пишет в "реестр" `allModels`:
+
+```js
+//+ run
+'use strict';
+
+let allModels = {};
+
+function createModel(Model, ...args) {
+ let model = new Model(...args);
+
+ model._id = Math.random().toString(36).slice(2);
+ allModels[model._id] = model;
+
+ return model;
+}
+
+let user = createModel(class User {
+ constructor(name) {
+ this.name = name;
+ }
+ sayHi() {
+ alert(this.name);
+ }
+}, "Вася");
+
+user.sayHi(); // Вася
+
+alert( allModels[user._id].name ); // Вася
+```
+
## Геттеры, сеттеры и вычисляемые свойства
В классах, как и в обычных объектах, можно объявлять геттеры и сеттеры через `get/set`, а также использовать `[…]` для свойств с вычисляемыми именами:
@@ -108,20 +152,23 @@ class User {
this.lastName = lastName;
}
- // геттер
*!*
+ // геттер
+*/!*
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
-*/!*
- // сеттер
*!*
+ // сеттер
+*/!*
set fullName(newValue) {
[this.firstName, this.lastName] = newValue.split(' ');
}
-*/!*
+*!*
+ // вычисляемое название метода
+*/!*
["test".toUpperCase()]: true
};
@@ -135,9 +182,23 @@ alert( user.TEST ); // true
При чтении `fullName` будет вызван метод `get fullName()`, при присвоении -- метод `set fullName` с новым значением.
+[warn header="`class` не позволяет задавать свойства-значения"]
+
+В синтаксисе классов, как мы видели выше, можно создавать методы. Они будут записаны в прототип, как например `User.prototype.sayHi`.
+
+Однако, нет возможности задать в прототипе обычное значение (не функцию), такое как `User.prototype.key = "value"`.
+
+Конечно, никто не мешает после объявления класса в прототип дописать подобные свойства, однако предполагается, что в прототипе должны быть только методы.
+
+Если свойство-значение, всё же, необходимо, то, можно создать геттер, который будет нужное значение возвращать.
+[/warn]
+
+
## Статические свойства
-Статические свойства класса -- это свойства непосредственно класса `User`.
+Класс, как и функция, является объектом. Статические свойства класса `User` -- это свойства непосредственно `User`, то есть доступные из него "через точку".
+
+Для их объявления используется ключевое слово `static`.
Например:
@@ -165,9 +226,22 @@ alert( user.firstName ); // Гость
alert( User.createGuest ); // createGuest ... (функция)
```
-Как правило, они используются для операций, не требующих наличия объекта, например -- для фабричных, как в примере выше, то есть как альтернативные варианты конструктора.
+Как правило, они используются для операций, не требующих наличия объекта, например -- для фабричных, как в примере выше, то есть как альтернативные варианты конструктора. Или же, можно добавить метод `User.compare`, который будет сравнивать двух пользователей для целей сортировки.
-Или же, можно добавить метод `User.compare`, который будет сравнивать двух пользователей для целей сортировки.
+Также статическими удобно делать константы:
+
+```js
+//+ run
+'use strict';
+
+class Menu {
+ static get elemClass() {
+ return "menu"
+ }
+}
+
+alert( Menu.elemClass ); // menu
+```
## Наследование
@@ -178,7 +252,7 @@ class Child extends Parent {
}
```
-В примере ниже объявлено два класса: `Animal` и наследующий от него `Rabbit`:
+Посмотрим, как это выглядит на практике. В примере ниже объявлено два класса: `Animal` и наследующий от него `Rabbit`:
```js
//+ run
@@ -208,8 +282,9 @@ new Rabbit("Вася").walk();
// and jump!
```
-[smart header="Стандартная цепочка прототипов"]
-При наследовании формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`:
+Как видим, в `new Rabbit` доступны как свои методы, так и (через `super`) методы родителя.
+
+Это потому, что при наследовании через `extends` формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`:
```js
//+ run
@@ -220,15 +295,14 @@ class Rabbit extends Animal { }
alert( Rabbit.prototype.__proto__ == Animal.prototype ); // true
```
-[/smart]
-Как видно из примера выше, методы родителя можно переопределить в наследнике. При этом для обращения к родительскому методу используют `super.method()`.
+Как видно из примера выше, методы родителя (`walk`) можно переопределить в наследнике. При этом для обращения к родительскому методу используют `super.walk()`.
Немного особая история -- с конструктором.
Конструктор `constructor` родителя наследуется автоматически. То есть, если в потомке не указан свой `constructor`, то используется родительский. В примере выше `Rabbit`, таким образом, использует `constructor` от `Animal`.
-Если `constructor` переопределить, то чтобы в нём вызвать конструктор родителя -- используется синтаксис `super()` с аргументами для родителя.
+Если же у потомка свой `constructor`, то чтобы в нём вызвать конструктор родителя -- используется синтаксис `super()` с аргументами для родителя.
Например, вызовем конструктор `Animal` в `Rabbit`:
@@ -258,7 +332,7 @@ class Rabbit extends Animal {
new Rabbit().walk(); // I walk: Кроль
```
-...Однако, здесь есть небольшие ограничения:
+Для такого вызова есть небольшие ограничения:
- Вызвать конструктор родителя можно только изнутри конструктора потомка. В частности, `super()` нельзя вызвать из произвольного метода.
- В конструкторе потомка мы обязаны вызвать `super()` до обращения к `this`. До вызова `super` не существует `this`, так как по спецификации в этом случае именно `super` инициализует `this`.
@@ -281,6 +355,8 @@ class Rabbit extends Animal {
constructor() {
alert(this); // ошибка, this не определён!
// обязаны вызвать super() до обращения к this
+ super();
+ // а вот здесь уже можно использовать this
}
*/!*
}