В объектно-ориентированном программировании класс — это расширяемый шаблон программного кода для создания объектов, предоставляющий начальные значения состояния (переменные-члены) и реализации поведения (функции-члены или методы).
На практике нам часто приходится создавать множество объектов одного типа, например, пользователей, товаров или чего-то еще.
Как мы уже знаем из главы Конструктор, в этом может помочь оператор new, new function
.
Но в современном JavaScript есть более продвинутая конструкция «класса», которая предоставляет новые замечательные возможности, полезные для объектно-ориентированного программирования.
Основной синтаксис:
класс MyClass { // методы класса конструктор() { ... } метод1() { ... } метод2() { ... } метод3() { ... } ... }
Затем используйте new MyClass()
, чтобы создать новый объект со всеми перечисленными методами.
constructor()
вызывается автоматически из new
, поэтому мы можем инициализировать объект там.
Например:
класс Пользователь { конструктор(имя) { это.имя = имя; } сказатьПривет() { оповещение(это.имя); } } // Использование: пусть пользователь = новый пользователь («Джон»); пользователь.sayHi();
Когда вызывается new User("John")
:
Создаётся новый объект.
constructor
запускается с данным аргументом и присваивает его this.name
.
…Затем мы можем вызвать методы объекта, такие как user.sayHi()
.
Нет запятой между методами класса
Распространенной ошибкой начинающих разработчиков является постановка запятой между методами класса, что может привести к синтаксической ошибке.
Приведенные здесь обозначения не следует путать с литералами объектов. Внутри класса запятые не требуются.
Итак, что же такое class
? Это не совершенно новая сущность языкового уровня, как можно подумать.
Давайте раскроем любую магию и посмотрим, что такое класс на самом деле. Это поможет понять многие сложные аспекты.
В JavaScript класс — это своего рода функция.
Вот, взгляните:
класс Пользователь { конструктор (имя) { this.name = имя; } SayHi() { alert(this.name); } } // доказательство: пользователь — это функция предупреждение (тип пользователя); // функция
Что на самом деле делает конструкция class User {...}
:
Создает функцию с именем User
, которая становится результатом объявления класса. Код функции берется из метода- constructor
(он считается пустым, если мы не пишем такой метод).
Сохраняет методы класса, такие как sayHi
, в User.prototype
.
После создания new User
, когда мы вызываем его метод, он берется из прототипа, как описано в главе F.prototype. Таким образом, объект имеет доступ к методам класса.
Мы можем проиллюстрировать результат объявления class User
следующим образом:
Вот код для его анализа:
класс Пользователь { конструктор (имя) { this.name = имя; } SayHi() { alert(this.name); } } // класс — это функция предупреждение (тип пользователя); // функция // ...или, точнее, метод конструктора alert(Пользователь === User.prototype.constructor); // истинный // Методы находятся в User.prototype, например: оповещение(User.prototype.sayHi); // код метода SayHi // в прототипе ровно два метода alert(Object.getOwnPropertyNames(User.prototype)); // конструктор, сказать Привет
Иногда люди говорят, что class
— это «синтаксический сахар» (синтаксис, предназначенный для облегчения чтения, но не вносящий ничего нового), потому что на самом деле мы могли бы объявить то же самое, вообще не используя ключевое слово class
:
// переписываем класс User в чистые функции // 1. Создаем функцию-конструктор функция Пользователь(имя) { это.имя = имя; } // прототип функции по умолчанию имеет свойство "конструктор", // поэтому нам не нужно его создавать // 2. Добавляем метод в прототип User.prototype.sayHi = function() { оповещение(это.имя); }; // Использование: пусть пользователь = новый пользователь («Джон»); пользователь.sayHi();
Результат этого определения примерно тот же. Итак, действительно есть причины, по которым class
можно считать синтаксическим сахаром для определения конструктора вместе с его методами-прототипами.
Тем не менее, есть важные различия.
Во-первых, функция, созданная class
помечается специальным внутренним свойством [[IsClassConstructor]]: true
. Так что это не совсем то же самое, что создавать его вручную.
Язык проверяет это свойство в разных местах. Например, в отличие от обычной функции, ее нужно вызывать с помощью new
:
класс Пользователь { конструктор() {} } предупреждение (тип пользователя); // функция Пользователь(); // Ошибка: конструктор класса User не может быть вызван без 'new'
Кроме того, строковое представление конструктора класса в большинстве движков JavaScript начинается с «class…».
класс Пользователь { конструктор() {} } Оповещение (Пользователь); // класс Пользователь { ... }
Есть и другие отличия, мы их скоро увидим.
Методы класса неперечислимы. Определение класса устанавливает для флага enumerable
значение false
для всех методов в "prototype"
.
Это хорошо, потому что если мы for..in
над объектом, нам обычно не нужны его методы класса.
В классах всегда use strict
. Весь код внутри конструкции класса автоматически переходит в строгий режим.
Кроме того, синтаксис class
предоставляет множество других возможностей, которые мы рассмотрим позже.
Как и функции, классы могут быть определены внутри другого выражения, переданы, возвращены, назначены и т. д.
Вот пример выражения класса:
пусть Пользователь = класс { сказатьПривет() { Оповещение("Привет"); } };
Подобно выражениям именованных функций, выражения класса могут иметь имя.
Если выражение класса имеет имя, оно видно только внутри класса:
// "Выражение именованного класса" // (в спецификации такого термина нет, но он похож на выражение именованной функции) пусть Пользователь = класс MyClass { сказатьПривет() { оповещение (МойКласс); // Имя MyClass видно только внутри класса } }; новый Пользователь().sayHi(); // работает, показывает определение MyClass оповещение (МойКласс); // ошибка, имя MyClass не отображается за пределами класса
Мы даже можем создавать классы динамически «по требованию», вот так:
функция makeClass(фраза) { // объявляем класс и возвращаем его возвращаемый класс { сказатьПривет() { предупреждение (фраза); } }; } // Создаем новый класс let User = makeClass("Привет"); новый Пользователь().sayHi(); // Привет
Как и литеральные объекты, классы могут включать в себя методы получения/установки, вычисляемые свойства и т. д.
Вот пример user.name
, реализованный с помощью get/set
:
класс Пользователь { конструктор(имя) { // вызывает установщик это.имя = имя; } получить имя() { верните это._имя; } установить имя (значение) { если (значение.длина <4) { alert("Имя слишком короткое."); возвращаться; } this._name = значение; } } пусть пользователь = новый пользователь («Джон»); оповещение(имя_пользователя); // Джон пользователь = новый пользователь(""); // Имя слишком короткое.
Технически такое объявление класса работает путем создания геттеров и сеттеров в User.prototype
.
Вот пример с вычисленным именем метода с использованием скобок [...]
:
класс Пользователь { ['скажи' + 'Привет']() { Оповещение("Привет"); } } новый Пользователь().sayHi();
Такие особенности легко запомнить, поскольку они напоминают буквальные объекты.
Старым браузерам может потребоваться полифилл
Поля классов — недавнее дополнение к языку.
Раньше в наших классах были только методы.
«Поля классов» — это синтаксис, позволяющий добавлять любые свойства.
Например, давайте добавим свойство name
в class User
:
класс Пользователь { имя = «Джон»; сказатьПривет() { alert(`Привет, ${this.name}!`); } } новый Пользователь().sayHi(); // Привет, Джон!
Итак, мы просто пишем «
Важным отличием полей класса является то, что они задаются для отдельных объектов, а не для User.prototype
:
класс Пользователь { имя = «Джон»; } пусть пользователь = новый пользователь(); оповещение(имя_пользователя); // Джон оповещение(Пользователь.прототип.имя); // неопределенный
Мы также можем присваивать значения, используя более сложные выражения и вызовы функций:
класс Пользователь { name = Prompt("Имя, пожалуйста?", "Джон"); } пусть пользователь = новый пользователь(); оповещение(имя_пользователя); // Джон
Как показано в главе «Привязка функций», функции в JavaScript имеют динамический this
. Это зависит от контекста звонка.
Таким образом, если метод объекта передается и вызывается в другом контексте, this
больше не будет ссылкой на его объект.
Например, этот код покажет undefined
:
класс Кнопка { конструктор(значение) { это.значение = значение; } нажмите() { оповещение(это.значение); } } пусть кнопка = новая кнопка («привет»); setTimeout(button.click, 1000); // неопределенный
Проблема называется «потерять this
».
Существует два подхода к исправлению этого, как описано в главе «Привязка функций»:
Передайте функцию-обертку, например setTimeout(() => button.click(), 1000)
.
Привяжите метод к объекту, например, в конструкторе.
Поля классов имеют другой, довольно элегантный синтаксис:
класс Кнопка { конструктор(значение) { это.значение = значение; } нажмите = () => { оповещение(это.значение); } } пусть кнопка = новая кнопка («привет»); setTimeout(button.click, 1000); // привет
Поле класса click = () => {...}
создается для каждого объекта отдельно, для каждого объекта Button
существует отдельная функция, внутри this
ссылается на этот объект. Мы можем передать button.click
где угодно, и значение this
параметра всегда будет правильным.
Это особенно полезно в среде браузера для прослушивателей событий.
Базовый синтаксис класса выглядит следующим образом:
класс МойКласс { опора = значение; // свойство конструктор(...) { // конструктор // ... } метод(...) {} // метод получить что-то(...) {} // метод получения set Something(...) {} // метод установки [Symbol.iterator]() {} // метод с вычисленным именем (здесь символ) // ... }
MyClass
технически является функцией (той, которую мы предоставляем как constructor
), а методы, геттеры и сеттеры записываются в MyClass.prototype
.
В следующих главах мы узнаем больше о классах, включая наследование и другие функции.
важность: 5
Класс Clock
(см. песочницу) написан в функциональном стиле. Перепишите его в синтаксисе «класс».
PS В консоли тикают часы, откройте ее, чтобы посмотреть.
Откройте песочницу для задачи.
класс Часы { конструктор({шаблон }) { this.template = шаблон; } оказывать() { пусть дата = новая дата(); пусть часы = date.getHours(); если (часы < 10) часы = '0' + часы; пусть mins = date.getMinutes(); если (минуты < 10) минуты = '0' + минуты; пусть секунды = date.getSeconds(); если (секунды < 10) секунды = '0' + секунды; пусть вывод = this.template .replace('ч', часы) .replace('m', мин) .replace('s', секунды); console.log(выход); } останавливаться() { ClearInterval(this.timer); } начинать() { это.рендер(); this.timer = setInterval(() => this.render(), 1000); } } let clock = new Clock({шаблон: 'ч:м:с'}); часы.старт();
Откройте решение в песочнице.