Вступление
JavaScript - это * язык на основе прототипов *, означающий, что свойства и методы объекта могут совместно использоваться обобщенными объектами, которые могут быть клонированы и расширены. Это известно как прототип наследования и отличается от наследования классов. Среди популярных объектно-ориентированных языков программирования JavaScript относительно уникален, так как другие известные языки, такие как PHP, Python и Java, являются языками на основе классов, которые вместо этого определяют классы в качестве чертежей для объектов.
В этом уроке мы узнаем, что такое прототипы объектов и как использовать функцию конструктора для расширения прототипов в новые объекты. Мы также узнаем о наследовании и цепочке прототипов.
Прототипы JavaScript
В Understanding Objects in JavaScript мы рассмотрели тип данных объекта, как создать объект и как получить доступ и изменить объект свойства. Теперь мы узнаем, как прототипы можно использовать для расширения объектов.
Каждый объект в JavaScript имеет внутреннее свойство, называемое + +
. Мы можем продемонстрировать это, создав новый пустой объект.
let x = {};
Таким способом мы обычно создаем объект, но учтите, что другой способ сделать это - с помощью конструктора объекта: + let x = new Object () +
.
Object.getPrototypeOf(x);
Вывод будет состоять из нескольких встроенных свойств и методов.
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
Другой способ найти + +
- через свойство + proto +
. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto [+ proto +
] - это свойство, которое предоставляет внутреннее + +
объекта.
x.__proto__;
Вывод будет таким же, как если бы вы использовали + getPrototypeOf () +
.
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
Наследование прототипа
Когда вы пытаетесь получить доступ к свойству или методу объекта, JavaScript сначала выполняет поиск по самому объекту, а если он не найден, он ищет объект + +
. Если после консультации с объектом и его + +
совпадение все еще не найдено, JavaScript проверит прототип связанного объекта и продолжит поиск, пока не будет достигнут конец цепочки прототипов.
В конце цепочки прототипов находится https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype [+ Object.prototype +
]. Все объекты наследуют свойства и методы https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object [+ Object +
]. Любая попытка поиска за концом цепочки приводит к + null +
.
В нашем примере + x +
- это пустой объект, который наследуется от + Object +
. + x +
может использовать любое свойство или метод, которые имеет + Object +
, например + toString () +
.
x.toString();
Output[object Object]
Эта цепь прототипа имеет длину только одно звено. + x +
→ + Object +
. Мы знаем это, потому что если мы попытаемся связать два свойства + +
вместе, это будет + null +
.
x.__proto__.__proto__;
Outputnull
Давайте посмотрим на другой тип объекта. Если у вас есть опыт Working с массивами в JavaScript, вы знаете, что у них есть много встроенных методов, таких как + pop () + `и
+ push () + . Причина, по которой у вас есть доступ к этим методам при создании нового массива, заключается в том, что любой создаваемый вами массив имеет доступ к свойствам и методам `+ Array.prototype +
.
Мы можем проверить это, создав новый массив.
let y = [];
Имейте в виду, что мы могли бы также написать его как конструктор массива + let y = new Array () +
.
Если мы посмотрим на + +
нового массива + y +
, мы увидим, что он имеет больше свойств и методов, чем объект + x +
. Он унаследовал все от + Array.prototype +
.
y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]
Вы заметите свойство + constructor +
в прототипе, которое установлено в + Array () +
. Свойство + constructor +
возвращает функцию-конструктор объекта, который представляет собой механизм, используемый для конструирования объектов из функций.
Теперь мы можем связать два прототипа вместе, так как в этом случае наша цепочка прототипов длиннее. Это выглядит как + y +
→ + Array
→` + Object N`.
y.__proto__.__proto__;
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
Эта цепочка теперь ссылается на + Object.prototype +
. Мы можем проверить внутреннее + +
со свойством + prototype +
функции конструктора, чтобы убедиться, что они ссылаются на одно и то же.
y.__proto__ === Array.prototype; // true
y.__proto__.__proto__ === Object.prototype; // true
Мы также можем использовать метод + isPrototypeOf () +
для этого.
Array.prototype.isPrototypeOf(y); // true
Object.prototype.isPrototypeOf(Array); // true
Мы можем использовать оператор + instanceof +
, чтобы проверить, появляется ли свойство конструктора + prototype +
где-либо в цепочке прототипов объекта.
y instanceof Array; // true
Подводя итог, все объекты JavaScript имеют скрытое внутреннее свойство + +
(которое может быть открыто через + proto +
в некоторых браузерах). Объекты могут быть расширены и будут наследовать свойства и методы в + +
своего конструктора.
Эти прототипы могут быть объединены в цепочку, и каждый дополнительный объект будет наследовать все в цепочке. Цепочка заканчивается + Object.prototype +
.
Функции конструктора
-
Функции конструктора * - это функции, которые используются для создания новых объектов. Https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new [
+ new +
operator] используется для создания новых экземпляров на основе функции конструктора. Мы видели некоторые встроенные конструкторы JavaScript, такие как+ new Array () +
и+ new Date () +
, но мы также можем создавать собственные шаблоны для создания новых объектов.
В качестве примера, допустим, мы создаем очень простую текстовую ролевую игру. Пользователь может выбрать персонажа, а затем выбрать класс персонажа, например, воина, целителя, вора и так далее.
Поскольку у каждого персонажа будет много общих характеристик, таких как имя, уровень и точки попадания, имеет смысл создать конструктор в качестве шаблона. Однако, поскольку у каждого класса персонажа могут быть совершенно разные способности, мы хотим убедиться, что у каждого персонажа есть доступ только к его собственным способностям. Давайте посмотрим, как мы можем достичь этого с помощью наследования прототипов и конструкторов.
Начнем с того, что функция конструктора - это обычная функция. Он становится конструктором, когда он вызывается экземпляром с ключевым словом + new +
. В JavaScript мы прописываем заглавную первую букву функции конструктора по соглашению.
characterSelect.js
// Initialize a constructor function for a new Hero
function Hero(name, level) {
this.name = name;
this.level = level;
}
Мы создали функцию конструктора с именем + Hero +
с двумя параметрами: + name +
и + level +
. Поскольку каждый персонаж будет иметь имя и уровень, имеет смысл, чтобы каждый новый персонаж обладал этими свойствами. Ключевое слово + this +
будет ссылаться на созданный новый экземпляр, поэтому установка для параметра + this.name +
параметра + name +
гарантирует, что для нового объекта будет установлено свойство + name +
.
Теперь мы можем создать новый экземпляр с помощью + new +
.
let hero1 = new Hero('Bjorn', 1);
Если мы утешим + hero1 +
, мы увидим, что новый объект был создан с новыми свойствами, установленными, как и ожидалось.
OutputHero {name: "Bjorn", level: 1}
Теперь, если мы получим + +
из + hero1 +
, мы сможем увидеть конструктор + + как
+ Hero () + . (Помните, что он имеет тот же ввод, что и `+ hero1 . proto +
, но это правильный метод для использования.)
Object.getPrototypeOf(hero1);
Outputconstructor: ƒ Hero(name, level)
Вы можете заметить, что в конструкторе у нас есть только определенные свойства, а не методы. В JavaScript принято определять методы в прототипе для повышения эффективности и читабельности кода.
Мы можем добавить метод к + Hero +
используя + prototype +
. Мы создадим метод + greet () +
.
characterSelect.js
...
// Add greet method to the Hero prototype
Hero.prototype.greet = function () {
return `${this.name} says hello.`;
}
Поскольку + greet () +
находится в + prototype +
+ Hero +
, а + hero1 +
является экземпляром + Hero +
, метод доступен для + hero1 +
.
hero1.greet();
Output"Bjorn says hello."
Это хорошо, но теперь мы хотим создать классы персонажей для героев. Не имеет смысла помещать все способности для каждого класса в конструктор + Hero + ', потому что разные классы будут иметь разные способности. Мы хотим создать новые функции конструктора, но мы также хотим, чтобы они были подключены к исходному `+ Hero +
.
Мы можем использовать метод https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call [+ call () +
] для копирования свойств из одного конструктора в другой конструктор. Давайте создадим Воина и Целителя.
characterSelect.js
...
// Initialize Warrior constructor
function Warrior(name, level, weapon) {
// Chain constructor with call
Hero.call(this, name, level);
// Add a new property
this.weapon = weapon;
}
// Initialize Healer constructor
function Healer(name, level, spell) {
Hero.call(this, name, level);
this.spell = spell;
}
Оба новых конструктора теперь имеют свойства + Hero
и несколько уникальных. Мы добавим метод + attack () +
к + Warrior +
, а метод + heal () +
к + Healer +
.
characterSelect.js
...
Warrior.prototype.attack = function () {
return `${this.name} attacks with the ${this.weapon}.`;
}
Healer.prototype.heal = function () {
return `${this.name} casts ${this.spell}.`;
}
На этом этапе мы создадим наших персонажей с помощью двух новых доступных классов персонажей.
characterSelect.js
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');
+ hero1 +
теперь распознается как + Warrior +
с новыми свойствами.
OutputWarrior {name: "Bjorn", level: 1, weapon: "axe"}
Мы можем использовать новые методы, которые мы установили в прототипе + Warrior +
.
hero1.attack();
Console"Bjorn attacks with the axe."
Но что произойдет, если мы попытаемся использовать методы дальше по цепочке прототипов?
hero1.greet();
OutputUncaught TypeError: hero1.greet is not a function
Свойства и методы прототипа не связаны автоматически, когда вы используете + call () +
для цепочки конструкторов. Мы будем использовать + Object.create () +
, чтобы связать прототипы, убедившись, что поставили его перед созданием и добавлением каких-либо дополнительных методов в прототип.
characterSelect.js
...
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);
// All other prototype methods added below
...
Теперь мы можем успешно использовать методы-прототипы из + Hero +
на экземпляре + Warrior And
+ Healer`.
hero1.greet();
Output"Bjorn says hello."
Вот полный код нашей страницы создания персонажа.
characterSelect.js
// Initialize constructor functions
function Hero(name, level) {
this.name = name;
this.level = level;
}
function Warrior(name, level, weapon) {
Hero.call(this, name, level);
this.weapon = weapon;
}
function Healer(name, level, spell) {
Hero.call(this, name, level);
this.spell = spell;
}
// Link prototypes and add prototype methods
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);
Hero.prototype.greet = function () {
return `${this.name} says hello.`;
}
Warrior.prototype.attack = function () {
return `${this.name} attacks with the ${this.weapon}.`;
}
Healer.prototype.heal = function () {
return `${this.name} casts ${this.spell}.`;
}
// Initialize individual character instances
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');
С помощью этого кода мы создали наш класс «+ Hero » с базовыми свойствами, создали два класса символов « Warrior » и « Healer +» из исходного конструктора, добавили методы к прототипам и создали отдельные экземпляры символов.
Заключение
JavaScript является языком на основе прототипов и функционирует иначе, чем традиционная парадигма на основе классов, которую используют многие другие объектно-ориентированные языки.
В этом уроке мы узнали, как прототипы работают в JavaScript, и как связать свойства и методы объекта через скрытое свойство + +
, которое разделяют все объекты. Мы также узнали, как создавать пользовательские функции конструктора и как работает наследование прототипов для передачи значений свойств и методов.