Нужны ли классы в JavaScript?
Хочется нам того или нет, но классы в ECMAScript 6 всё-таки будут. Мнения по поводу самой концепции классов в JavaScript всегда были весьма противоречивыми. Одним бесклассовая природа JavaScript нравится — это делает язык отличным от других. Другие же просто ненавидят JavaScript по тем же причинам. Отсутствие классов является одной из основных психологических сложностей, с которыми сталкиваются разработчики при переходе с C++ или Java на JavaScript. Некоторые разработчики говорили мне, что именно по причине отсутствия классов они либо не любят JavaScript, либо вообще отказались его изучать.
В JavaScript изначально не было формального определения классов, что и внесло путаницу с самого начала его использования. Во многих книгах и статьях, посвящённых JavaScript, о классах говорится так, как если бы они реально существовали. Но то, что они называют классами, по сути своей является всего лишь пользовательскими конструкторами, которые используются для определения пользовательских типов данных. Такой способ – это наиболее близкое описание классов в JavaScript. Общий формат выглядит достаточно знакомым большинству разработчиков, вот, например, так:
function MyCustomType(value) {
this.property = value;
}
MyCustomType.prototype.method = function() {
return this.property;
};
Довольно часто такой код называется объявлением класса MyCustomType
. На самом же деле здесь всего лишь объявляется функция MyCustomType
, которая в дальнейшем будет вызываться с помощью new
для создания экземпляра MyCustomType
. Но в этой функции нет ничего особенного, равно как и нет в ней ничего, что указывало бы на её отличие от какой-либо другой функции, не используемой для создания нового объекта. Именно такой способ использования функции и делает её конструктором.
Этот код даже не похож на объявление какого-либо класса. По сути, сложно уловить какую-либо взаимосвязь между объявлением конструктора и методом в прототипе. Начинающим разработчикам JavaScript этот код вообще может показаться невзаимосвязанным. Однако же связь между ними существует, хотя она и близко не похожа на объявление класса, принятого в других языках.
Ещё более непонятным выглядит обсуждение вопроса наследования. Разработчики смело бросаются такими понятиями как «подклассы» и «суперклассы», хотя эти термины имеют смысл лишь при условии реального существования классов. И, конечно же, синтаксис для наследования выглядит довольно путанным и громоздким:
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
console.log(this.name);
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = new Animal(null);
Dog.prototype.bark = function() {
console.log('Woof!');
};
Чтобы осуществить наследование нужно сначала определить конструктор, а затем переопределить прототип. Этот двухшаговый приём невероятно запутан.
В первом издании книги «Professional JavaScript for Web Developers» я использовал только понятие «класс». Из полученных отзывов я понял, что очень многих это сбивало с толку, и поэтому во втором издании я заменил «класс» на «тип». С тех пор я пользуюсь только этой терминологией, что часто помогает мне избежать непонимания.
Но проблема всё равно остаётся довольно острой. Синтаксис для определения пользовательских типов данных действительно путанный и громоздкий. Наследование между двумя типами – процесс многошаговый. Не существует простого способа вызвать метод родительского класса. Общий итог таков: создание и управление пользовательскими типами данных в JavaScript – это одна сплошная головная боль. Если не верится в то, что проблема действительно серьёзная, то достаточно взглянуть на количество библиотек, предлагающих свой способ объявления пользовательских типов данных или наследования, или того и другого вместе взятых.
- YUI – предлагает
Y.extend()
для осуществления наследования. Также добавляет свойствоsuperclass
при использовании этого метода. - Prototype – предлагает
Class.create()
иObject.extend()
для работы с объектами и «классами». - Dojo – предлагает
dojo.declare()
иdojo.extend()
. - MooTools – имеет собственный тип данных под названием
Class
для определения и расширения классов.
Сам факт того, что так много библиотек JavaScript предлагают какие-то свои варианты решения, свидетельствует о том, что проблема действительно существует. Определение собственных типов данных – дело непростое и далеко не интуитивное. Разработчикам JavaScript нужно нечто более удобное, чем существующий синтаксис.
По своей сути классы в ECMAScript 6 – это ни что иное, как синтаксический сахар для использования уже знакомых паттернов. Рассмотрим этот пример:
class MyCustomType {
constructor(value) {
this.property = value;
}
method() {
return this.property;
}
}
Это объявление класса в ECMAScript 6 легко сводится к предыдущему примеру. Объект, созданный с помощью такого определения класса, работает точно так же, как и объект, созданный с помощью определения конструктора в примере выше. Единственное отличие заключается в более компактном синтаксисе. А как на счёт наследования?
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
bark() {
console.log('Woof!');
}
}
Этот пример легко сводится к предыдущему примеру наследования. В данном случае объявление класса выглядит более компактно, а тяжеловесный многошаговый паттерн наследования теперь заменяется простым ключевым словом extends
. Ещё одним преимуществом является возможность использовать метод super()
внутри определения класса, чтобы обращаться к классу родителю.
С точки зрения классов текущая версия ECMAScript 6 не предлагает ничего большего, чем новый синтаксис для уже знакомых паттернов из JavaScript. Наследование работает так же, как и раньше (связь прототипов плюс вызов конструктора у родителя), к прототипам добавляются методы, а свойства объявляются в конструкторе. Единственное реальное преимущество для разработчиков – меньше писанины. Определение классов по сути своей является определением типов, но с помощью другого синтаксиса.
И пока кто-то возмущается по поводу введения классов в ECMAScript 6, помните, что сама идея классов в данном случае весьма абстрактна. На самом деле ничего кардинально не меняется, и ничего нового не вводится. Классы – это лишь синтаксический сахар для пользовательских типов, которые и так уже давно существуют в этом языке. В данном случае просто решается давняя проблема JavaScript — путаницы при определении пользовательских типов. Лично мне очень бы хотелось использовать ключевое слово type
вместо слова class
, но, в конце концов, это всего лишь вопрос семантики.
Так нужны ли классы в JavaScript? Нет. Но в JavaScript определённо нужен более простой и понятный способ определения пользовательских классов. Просто случайно получилось так, что способ сделать это в ECMAScript 6 назвали «классом». И если это помогает разработчикам других языков проще переключиться на JavaScript, то почему бы и нет.
Это перевод статьи Nicholas C. Zakas — «Does JavaScript need classes?». Николас — фронтенд-консультант и автор хороших книг о JavaScript — «Maintainable JavaScript», «Professional JavaScript for Web Developers», «High Performance JavaScript» и «Professional Ajax».