JavaScript — это язык на базе прототипов, и каждый объект JavaScript имеет скрытое внутреннее свойство [[Prototype]]
, которое можно использовать для расширения свойств и методов объекта. Вы можете узнать больше о прототипах из нашего обучающего модуля Понимание принципов прототипов и наследования в JavaScript.
До недавнего времени промышленные разработчики использовали функции конструктора для имитации объектно-ориентированного шаблона в 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()
. Давайте протестируем созданную нами пустую функцию.
Object.getPrototypeOf(x);
Outputƒ () { [native code] }
Также мы можем использовать этот метод для только что созданного нами класса.
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
Эти примеры конструктора прототипов пустые, но вы видите, как оба метода позволяют добиться одинакового результата вне зависимости от синтаксиса.
В обучающем модуле Прототипы и наследование мы создали пример, основанный на создании персонажа в текстовой ролевой игре. На этом же примере мы рассмотрим, как обновлять синтаксис от функций к классам.
Функция конструктора инициализируется с рядом параметров, которые назначаются как свойства 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
показывает назначение нашей функции более прямо.
Единственная разница в синтаксисе инициализации заключается в использовании ключевого слова class
вместо function
, и в том, что свойства назначаются внутри метода constructor()
.
При использовании функций конструктора методы обычно назначаются непосредственно в prototype
вместо инициализации, как показано ниже на примере метода 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)
, мы более подробно увидим. что происходит при инициализации класса.
OutputHero {name: "Varg", level: 1}
__proto__:
▶ constructor: class Hero
▶ greet: ƒ greet()
В результатах мы видим, что функции constructor()
и greet() functions
были применены к прототипу __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
,а также добавленное свойство.
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
для обозначения родительского класса.
// 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
.
Ниже приводится полное сравнение процесса инициализации, добавления методов и наследования между функцией конструктора и классом.
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;
}
// 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
.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!