Прототипы это объекты (и почему это важно)

от автора

JavaScript – один из главных языков нашего стека в Хекслете. Мы используем ReactJS и NodeJS в интерактивных частях платформы, и сделали вводный курс (более продвинутые – на подходе). Любовь к JS помогла опубликовать этот перевод хорошего эссе «Prototypes are Objects (and why that matters)».

Этот пост рассчитан на тех, кто знаком с объектами в JavaScript и знает, как прототип определяет поведение объекта, что такое функция-конструктор и как свойство .property конструктора относится к объекту, который он конструирует. Общее понимание синтаксиса ECMAScript 2015 тоже не помешает.

Мы всегда могли создать класс в JavaScript таким образом:

function Person (first, last) {   this.rename(first, last); }  Person.prototype.fullName = function fullName () {   return this.firstName + " " + this.lastName; };   Person.prototype.rename = function rename (first, last) {   this.firstName = first;   this.lastName = last;   return this; } 

Person это функция-конструктор, а также класс в JavaScript’овом понимании этого слова. ECMAScript 2015 дает возможность использовать ключевое слово class и т.н. “compact method notation”. Это синтаксический сахар для написания функций и присвоения методов его прототипу (там все чуть сложнее, но сейчас это не важно). Так что мы можем написать класс Person вот так:

class Person {   constructor (first, last) {     this.rename(first, last);   }   fullName () {     return this.firstName + " " + this.lastName;   }   rename (first, last) {     this.firstName = first;     this.lastName = last;     return this;   } }; 

Клево. Но под капотом все равно есть функция-конструктор с привязкой к имени Person, и есть объект Person.prototype, который выглядит так:

{   fullName: function fullName () {     return this.firstName + " " + this.lastName;   },   rename: function rename (first, last) {     this.firstName = first;     this.lastName = last;     return this;   } } 
Прототипы это объекы

Если нужно изменить поведение объекта в JavaScript, можно добавить, удалить или изменить методы объекта через добавление, удаление или изменение функций, привязанных к свойствам этого объекта. В этом отличие от многих “классических” языков, в которых есть специальная форма (например, в Руби есть def) для задания методов.

Прототипы в JavaScript это “всего лишь объекты”, и благодаря этому мы можем добавлять, удалять или изменять методы прототипа через добавление, удаление или изменение функций, привязанных к свойствам этого прототипа.

Именно это и делает ECMAScript 5 код выше, и синтаксис class “рассахаривает” его в эквивалентный код.

Прототипы это “всего лишь объекты”, и это означает, что мы можем использовать любые техники, которые работают на объектах. Например, вместо привязки одной функции к прототипу, мы можем совершать массовую привязку с помощью Object.assign:

function Person (first, last) {   this.rename(first, last); }  Object.assign(Person.prototype, {   fullName: function fullName () {     return this.firstName + " " + this.lastName;   },   rename: function rename (first, last) {     this.firstName = first;     this.lastName = last;     return this;   } }) 

И, конечно, мы можем использовать компактный синтаксис если захотим:

function Person (first, last) {   this.rename(first, last); }  Object.assign(Person.prototype, {   fullName () {     return this.firstName + " " + this.lastName;   },   rename (first, last) {     this.firstName = first;     this.lastName = last;     return this;   } }) 
Mixins (примеси)

Так как class “рассахаривает” код в конструктор-функции и прототипы, мы можем использовать примеси вот так:

class Person {   constructor (first, last) {     this.rename(first, last);   }   fullName () {     return this.firstName + " " + this.lastName;   }   rename (first, last) {     this.firstName = first;     this.lastName = last;     return this;   } };  Object.assign(Person.prototype, {   addToCollection (name) {     this.collection().push(name);     return this;   },   collection () {     return this._collected_books || (this._collected_books = []);   } }) 

Мы только что “вмешали” методы по сбору книг в класс Person. Круто, что можно вот так просто писать код, но можно и давать названия:

const BookCollector = {   addToCollection (name) {     this.collection().push(name);     return this;   },   collection () {     return this._collected_books || (this._collected_books = []);   } };  class Person {   constructor (first, last) {     this.rename(first, last);   }   fullName () {     return this.firstName + " " + this.lastName;   }   rename (first, last) {     this.firstName = first;     this.lastName = last;     return this;   } };  Object.assign(Person.prototype, BookCollector); 

Так можно продолжать сколько захочется:

const BookCollector = {   addToCollection (name) {     this.collection().push(name);     return this;   },   collection () {     return this._collected_books || (this._collected_books = []);   } };  const Author = {   writeBook (name) {     this.books().push(name);     return this;   },   books () {     return this._books_written || (this._books_written = []);   } };  class Person {   constructor (first, last) {     this.rename(first, last);   }   fullName () {     return this.firstName + " " + this.lastName;   }   rename (first, last) {     this.firstName = first;     this.lastName = last;     return this;   } };  Object.assign(Person.prototype, BookCollector, Author); 
Зачем использовать примеси

Сборка классов с помощью базовой функциональности (Person) и миксинов (BookCollector и Author) дает некоторые преимущества. Во-первых, иногда функциональность невозможно хорошо разложить на части в красивой древовидной структуре. Авторы книг могут быть корпорациями, а не людьми. И антикварные книжные лавки собирают книги так же, как книголюбы.

Такие примеси, как BookCollector или Author могут быть вмешаны в несколько разных классов. Попытки композиции функциональности с помощью наследования не всегда удачны.

Еще одно преимущество не так очевидно в простом примере, но в продакшн-системах классы могут разрастаться до нелепых размеров. Даже если примесь не используется в нескольких классах, декомпозиция большого класса с помощью примесей помогает удовлетворить принцип принцип единственной обязанности. Каждый миксин может иметь тольк одну область ответственности. Все это упрощает понимание и тестирование.
почему это важно

Существуют другие способы декомпозиции ответственности в классах (например, делегирование и композиция), но суть в том, что если вы решили использовать mixins, то это очень простой путь, потому что в JavaScript нет большого и сложного механизма ООП, который бы загонял вас в четкие рамки.

Например, в Руби использоват миксины легко, потому что с самого начала там есть специальная фича – модули. В других ОО-языках использовать миксины сложно, потому что система классов не поддерживает их, и они не очень вяжутся с мета-программированием.

ссылка на оригинал статьи http://habrahabr.ru/post/260427/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *