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 также добавился сеттер: ...А также "узаконено" свойство `__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 термин "метод объекта" был просто альтернативным названием для свойства-функции. -Теперь это уже не так, добавлены именно "методы объекта". Они, по сути, являются "свойствами-функциями, привязанными к объекту". +Теперь это уже не так, добавлены именно "методы объекта", которые, по сути, являются свойствами-функциями, привязанными к объекту. Их особенности:
      -
    1. Более короткий синтаксис.
    2. -
    3. Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше, в разделе про `super`.
    4. +
    5. Более короткий синтаксис объявления.
    6. +
    7. Наличие в методах специального внутреннего свойства `[[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` есть и ряд отличий: Методы, объявленные внутри `class`, также имеют ряд особенностей: ## 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: Кроль ``` -...Однако, здесь есть небольшие ограничения: +Для такого вызова есть небольшие ограничения: