Вступление
JavaScript - это язык, основанный на прототипах, и каждый объект в JavaScript имеет скрытое внутреннее свойство, называемое[[Prototype]]
, которое можно использовать для расширения свойств и методов объекта. Вы можете узнать больше о прототипах в нашем руководствеUnderstanding Prototypes and Inheritance in JavaScript.
До недавнего времени трудолюбивые разработчики использовалиconstructor functions для имитации объектно-ориентированного шаблона проектирования в JavaScript. Спецификация языка ECMAScript 2015, часто называемая ES6, вводит классы для языка JavaScript. Классы в 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()
method. Давайте воспользуемся этим для проверки созданного нами пустогоfunction.
Object.getPrototypeOf(x);
Outputƒ () { [native code] }
Мы также можем использовать этот метод для только что созданныхclass.
Object.getPrototypeOf(y);
Outputƒ () { [native code] }
Код, объявленный с помощьюfunction
иclass
, возвращает функцию[[Prototype]]
. С прототипами любая функция может стать экземпляром конструктора с помощью ключевого словаnew
.
const x = function() {}
// Initialize a constructor from a function
const constructorFromFunction = new x();
console.log(constructorFromFunction);
Outputx {}
constructor: ƒ ()
Это относится и к классам.
const y = class {}
// Initialize a constructor from a class
const constructorFromClass = new y();
console.log(constructorFromClass);
Outputy {}
constructor: class
Эти примеры конструктора-прототипа пусты, но мы можем видеть, как под синтаксисом оба метода достигают одного и того же конечного результата.
Определение класса
Вprototypes and inheritance tutorial мы создали пример, основанный на создании персонажа в текстовой ролевой игре. Давайте продолжим с этим примером здесь, чтобы обновить синтаксис от функций к классам.
constructor function инициализируется рядом параметров, которые будут назначены как свойстваthis
, относящиеся к самой функции. Первая буква идентификатора будет прописной.
constructor.js
// Initializing a constructor function
function Hero(name, level) {
this.name = name;
this.level = level;
}
Когда мы переводим это в синтаксисclass, показанный ниже, мы видим, что он имеет очень похожую структуру.
class.js
// Initializing a class definition
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
}
Мы знаем, что функция конструктора предназначена для того, чтобы быть объектной схемой путем использования заглавной буквы первой буквы инициализатора (которая является необязательной) и знакомства с синтаксисом. Ключевое словоclass
в более простой форме передает цель нашей функции.
Единственная разница в синтаксисе инициализации заключается в использовании ключевого словаclass
вместоfunction
и назначении свойств внутри методаconstructor()
.
Определение методов
Обычной практикой с функциями конструктора является присвоение методов непосредственноprototype
вместо инициализации, как показано в методеgreet()
ниже.
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.`;
}
С классами этот синтаксис упрощен, и метод может быть добавлен непосредственно в класс. Использованиеmethod definition shorthand, введенного в ES6, делает определение метода еще более кратким.
class.js
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)
, мы сможем увидеть более подробную информацию о том, что происходит с инициализацией класса.
OutputHero {name: "Varg", level: 1}
__proto__:
▶ constructor: class Hero
▶ greet: ƒ greet()
Мы можем видеть в выходных данных, что функцииconstructor()
иgreet()
были применены к__proto__
или[[Prototype]]
изhero1
, а не напрямую как метод для объектhero1
. Хотя это очевидно при создании функций конструктора, это не очевидно при создании классов. Классы допускают более простой и лаконичный синтаксис, но жертвуют некоторой ясностью в процессе.
Расширение класса
Преимущественная функция функций и классов конструктора заключается в том, что они могут быть расширены в новые чертежи объектов на основе родительского элемента. Это предотвращает повторение кода для объектов, которые похожи, но нуждаются в некоторых дополнительных или более специфических функциях.
Новые функции конструктора могут быть созданы из родительского элемента с помощью методаcall()
. В приведенном ниже примере мы создадим более конкретный класс символов под названиемMage
и назначим ему свойстваHero
с помощьюcall()
, а также добавим дополнительное свойство.
constructor.js
// 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
, а также новый, который мы добавили.
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
Отправивhero2
в консоль, мы видим, что создали новыйMage
на основе конструктора.
OutputMage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__:
▶ constructor: ƒ Mage(name, level, spell)
В классах ES6 ключевое словоsuper
используется вместоcall
для доступа к родительским функциям. Мы будем использоватьextends
для ссылки на родительский класс.
class.js
// 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
на консоль и просмотрим результат.
OutputMage {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, часто используют синтаксисclass
.