Работа с классами в JavaScript
JavaScript – это основанный на прототипах язык, и каждый объект в JavaScript имеет скрытое внутреннее свойство [[Prototype]], которое может использоваться для расширения свойств и методов объекта.
Читайте также: Прототипы и наследование в JavaScript
До недавнего времени разработчики использовали функции-конструкторы для имитации объектно-ориентированного шаблона проектирования в JavaScript. Спецификация языка ECMAScript 2015, часто называемая ES6, ввела в JavaScript классы. Классы в JavaScript на самом деле не предоставляют дополнительных функций, но предлагают более чистый и понятный синтаксис, чем прототипы и наследование.
Класс как функция
Класс JavaScript – это такой тип функции. Классы объявляются ключевым словом class. Для инициализации функции используется синтаксис выражения функции, а для инициализации класса – синтаксис выражения класса.
// Initializing a function with a function expression
const x = function() {}
// Initializing a class with a class expression
const y = class {}
Получить доступ к [[Prototype]] объекта можно с помощью метода Object.getPrototypeOf(). Используйте его, чтобы протестировать новую пустую функцию.
Object.getPrototypeOf(x);
ƒ () { [native code] }
Также можно применить этот метод на новый класс:
Object.getPrototypeOf(y);
ƒ () { [native code] }
Код, объявленный ключевыми словами function и class, возвращает функцию [[Prototype]]. Благодаря прототипам любая функция может стать экземпляром конструктора с помощью ключевого слова new.
const x = function() {}
// Initialize a constructor from a function
const constructorFromFunction = new x();
console.log(constructorFromFunction);
x {}
constructor: ƒ ()
Это также применимо к классам:
const y = class {}
// Initialize a constructor from a class
const constructorFromClass = new y();
console.log(constructorFromClass);
y {}
constructor: class
Эти примеры показывают, что оба метода выдают один и тот же конечный результат.
Определение классов
В мануале по прототипам и наследованию в качестве примера использовался код для создания персонажа в ролевой игре. Продолжим использовать этот пример здесь – это поможет понять, как обновить синтаксис функций и превратить в классы.
Функция-конструктор инициализируется рядом параметров, которые будут назначены как свойства this, ссылаясь на саму функцию. Первая буква идентификатора по соглашению должна быть заглавной.
// Initializing a constructor function
function Hero(name, level) {
this.name = name;
this.level = level;
}
Если перевести это в синтаксис класса, получится:
// Initializing a class definition
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
}
Единственная разница в синтаксисе инициализации заключается в использовании ключевого слова class вместо function и назначении свойств внутри метода constructor().
Определение методов
В функциях-конструкторах методы чаще не инициализируются, а присваиваются непосредственно прототипу. Это показано в методе greet() ниже.
function Hero(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
Hero.prototype.greet = function() {
return `${this.name} says hello.`;
}
Классы упрощают этот синтаксис, здесь метод можно добавить непосредственно в класс. Короткий синтаксис, введенный в ES6, делает определение методов еще более быстрым и сжатым.
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
greet() {
return `${this.name} says hello.`;
}
}
Давайте посмотрим на эти свойства и методы в действии. Для этого создайте новый экземпляр Hero, используя ключевое слово new, и присвойте ему значение.
const hero1 = new Hero('Varg', 1);
Если запросить дополнительную информацию о новом объекте с помощью console.log(hero1), вы увидите более подробные данные о том, что происходит с инициализацией класса.
Hero {name: "Varg", level: 1}
__proto__:
▶ constructor: class Hero
▶ greet: ƒ greet()
На выходе можно видеть, что функции constructor() и greet() были применены к __proto__ (или [[Prototype]]) hero1, а не непосредственно как метод объекта hero1. Это понятно при создании функций-конструкторов, но это не очевидно при создании классов. Классы предлагают более простой и сжатый синтаксис, но жертвуют при этом понятностью кода.
Расширение классов
Преимуществом функций-конструкторов и классов является то, что их можно расширить в новые объекты, основанные на родительском объекте. Это предотвращает повторение кода для похожих объектов, которые отличаются парой конкретных функций
Новые функции-конструкторы можно создать на основе родительской функции с помощью метода call(). В приведенном ниже примере мы создадим более специфический класс персонажей Mage и присвоим ему свойства Hero с помощью call(), а также добавим дополнительное свойство.
// Creating a new constructor from the parent
function Mage(name, level, spell) {
// Chain constructor with call
Hero.call(this, name, level);
this.spell = spell;
}
На этом этапе можно создать новый экземпляр Mage, используя те же свойства, что и Hero, а также новое свойство.
Отправляя hero2 на консоль, вы увидите, что новый персонаж Mage основан на конструкторе.
Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__:
▶ constructor: ƒ Mage(name, level, spell)
В классах ES6 для доступа к родительским функциям вместо call используется ключевое слово super. Используйте extends для обращения к родительскому классу.
// Creating a new class from the parent
class Mage extends Hero {
constructor(name, level, spell) {
// Chain constructor with super
super(name, level);
// Add a new property
this.spell = spell;
}
}
Теперь создайте нового персонажа Mage тем же образом.
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
Выведите hero2 на консоль и просмотрите вывод:
Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__: Hero
▶ constructor: class Mage
Результат почти точно такой же, за исключением того, что в построении класса [[Prototype]] связан с родителем, в данном случае с Hero.
Ниже приведено сравнение всего процесса инициализации, добавления методов и наследования функции-конструктора и класса.
// файл constructor.js
function Hero(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
Hero.prototype.greet = function() {
return `${this.name} says hello.`;
}
// Creating a new constructor from the parent
function Mage(name, level, spell) {
// Chain constructor with call
Hero.call(this, name, level);
this.spell = spell;
}
// файл class.js
// Initializing a class
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
greet() {
return `${this.name} says hello.`;
}
}
// Creating a new class from the parent
class Mage extends Hero {
constructor(name, level, spell) {
// Chain constructor with super
super(name, level);
// Add a new property
this.spell = spell;
}
}
Хотя синтаксис методов заметно отличается, конечный результат почти одинаков. Классы предлагают более сжатый способ создания объектов, а функции-конструкторы описывают процесс более подробно.
Заключение
В этом мануале вы узнали о сходствах и различиях между функциями-конструкторами JavaScript и классами ES6. Оба метода имитируют объектно-ориентированную модель наследования JavaScript, которая основана на прототипах.
Понимание наследования прототипов имеет первостепенное значение в разработке JavaScript. Навык работы с классами чрезвычайно полезен, поскольку популярные библиотеки JavaScript, такие как React, часто используют синтаксис классов.
Tags: Java, Javascript