[Перевод] Прототипы это объекты (и почему это важно)
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 нет большого и сложного механизма ООП, который бы загонял вас в четкие рамки.
Например, в Руби использоват миксины легко, потому что с самого начала там есть специальная фича — модули. В других ОО-языках использовать миксины сложно, потому что система классов не поддерживает их, и они не очень вяжутся с мета-программированием.