В этом руководстве вы узнаете, как работает прототипное наследование в JavaScript.
Если вы работали с другими объектно-ориентированными языками программирования, такими как Java или C++, вы знакомы с концепцией наследования.
В этой парадигме программирования класс — это схема создания объектов. Если вы хотите, чтобы новый класс повторно использовал функциональные возможности существующего класса, вы можете создать новый класс, расширяющий существующий класс. Это называется классическим наследованием.
JavaScript не использует классическое наследование. Вместо этого он использует прототипное наследование.
При прототипном наследовании объект «наследует» свойства другого объекта через связь с прототипом.
Наследование прототипов JavaScript и __proto__
Давайте рассмотрим пример, чтобы прояснить концепцию.
Определяем объект person
:
let person = { name: "John Doe", greet: function() { return "Hi, I'm " + this.name; } };
В этом примере объект person
имеет свойство и метод:
name
— это свойство, в котором хранится имя человека.greet
— это метод, который возвращает приветствие в виде строки.
По умолчанию движок JavaScript предоставляет вам встроенную функцию Object()
и анонимный объект, на который может ссылаться Object.prototype
:
Обратите внимание, что кружок представляет собой функцию, а квадрат — объект.
Объект person имеет ссылку на анонимный объект, на который ссылается функция Object()
. [[Prototype]]
представляет связь:
Это означает, что объект person
может вызывать любые методы, определенные в анонимном объекте, на который ссылается Object.prototype
. Например, ниже показано, как вызвать метод toString()
через объект person
:
console.log(person.toString());
Выход:
[object Object]
[object Object]
— это строковое представление объекта по умолчанию.
Когда вы вызываете метод toString()
через person
, движок JavaScript не может найти его в объекте person
. Поэтому движок JavaScript следует цепочке прототипов и ищет метод в объекте Object.prototype
.
Поскольку механизм JavaScript может найти метод toString()
в объекте Object.prototype
, он выполняет метод toString()
.
Чтобы получить доступ к прототипу объекта person
, вы можете использовать свойство __proto__
следующим образом.
console.log(person.__proto__);
Ниже показано, что person.__proto__
и Object.prototype
ссылаются на один и тот же объект:
console.log(person.__proto__ === Object.prototype); // true
Следующий код определяет объект teacher
, который имеет метод teach()
:
let teacher = { teach: function(subject) { return "I can teach " + subject; } };
Как и объект person
, teacher.__proto__
ссылается на Object.prototype
, как показано на следующем рисунке:
Если вы хотите, чтобы объект teacher
имел доступ ко всем методам и свойствам объекта person
, вы можете установить прототип объекта teacher
для объекта person
следующим образом:
teacher.__proto__ = person;
Обратите внимание, что вы никогда не должны использовать свойство __proto__
в производственном коде. Пожалуйста, используйте его только в демонстрационных целях.
Теперь объект teacher
может получить доступ к свойству name
и методу greet()
из объекта person
через цепочку прототипов:
console.log(teacher.name); console.log(teacher.greet());
Выход:
John Doe Hi, I'm John Doe
Когда вы вызываете метод greet()
для объекта teacher
, движок JavaScript сначала находит его в объекте teacher
.
Поскольку движок JavaScript не может найти метод в объекте teacher
, он следует по цепочке прототипов и ищет метод в объекте person
. Движок JavaScript может найти метод greet()
в объекте person
, он выполняет этот метод.
В JavaScript мы говорим, что объект « teacher
» наследует методы и свойства объекта « person
». И этот вид наследования называется прототипным наследованием.
Стандартный способ реализации в ES5
ES5 предоставляет стандартный способ работы с прототипным наследованием с помощью Object.create()
.
Обратите внимание, что теперь вы должны использовать более новый class
ES6 и ключевые слова extends
для реализации наследования. Это намного проще.
Метод Object.create()
создает новый объект и использует существующий объект в качестве прототипа нового объекта:
Object.create(proto, [propertiesObject])
Метод Object.create()
принимает два аргумента:
- Первый аргумент(
proto
) — это объект, используемый в качестве прототипа для нового объекта. - Второй аргумент(
propertiesObject
), если он предоставлен, является необязательным объектом, который определяет дополнительные свойства для нового объекта.
Предположим, у вас есть объект person
:
let person = { name: "John Doe", greet: function() { return "Hi, I'm " + this.name; } };
Создадим пустой объект teacher
с __proto__
объекта person
:
let teacher = Object.create(person);
После этого вы можете определить свойства для объекта teacher
:
teacher.name = 'Jane Doe'; teacher.teach = function(subject) { return "I can teach " + subject; }
Или вы можете сделать все эти шаги в одном выражении следующим образом:
let teacher = Object.create(person, { name: { value: 'John Doe' }, teach: { value: function(subject) { return "I can teach " + subject; } } });
В ES5 также появился метод Object.getPrototypeOf()
, который возвращает прототип объекта. Например:
console.log(Object.getPrototypeOf(teacher) === person);
Выход:
true
Заключение
- Наследование позволяет объекту использовать свойства и методы другого объекта без дублирования кода.
- JavaScript использует прототипное наследование.