JavaScript поддерживает наследование, что позволяет нам при создании новых типов объектов при необходимости унаследовать их функционал от уже существующих. Однако нужно понимать, что наследование в JavaScript отличается от наследования в других распространенных и популярных языках типа Java, C++, C# и ряде других. В JavaScript наследование - это наследование объектов (а не наследование классов или типов), которое еще называют наследование прототипов или прототипное наследование.
Для создания объекта на основе некоторого прототипа применяется функция Object.create(), в которую передается наследуемый прототип:
const person = { name: "", age: 0, print: function(){ console.log(`Name: ${this.name} Age: ${this.age}`); } }; const employee = Object.create(person); // employee использует прототип объекта person // получаем прототип console.log(employee.__proto__); // {name: "", age: 0, print: ƒ} employee.name = "Tom"; employee.age = 39; employee.print(); // Name: Tom Age: 39
В данном случае объект employee создан на основе прототипа объекта person, по сути объект employee наследует прототип объекта person. Благодаря такому наследованию объект employee обладает всеми теми же свойствами и методами, которые определены в объекте person.
В дополнение объекты могут определять свои свойства и методы. Например:
const person = { name: "", age: 0, print: function(){ console.log(`Name: ${this.name} Age: ${this.age}`); } }; const employee = Object.create(person); // employee использует прототип объекта person employee.name = "Tom"; employee.age = 39; employee.company = "Google"; // новое свойство // новый метод employee.work = function(){ console.log(`${this.name} works in ${this.company}`); } employee.print(); // Name: Tom Age: 39 employee.work(); // Tom works in Google
В данном случае объект employee дополнительно определяет свойство company и метод work.
При необходимости можно переопределить унаследованные методы:
const person = { name: "", age: 0, print: function(){ console.log(`Name: ${this.name} Age: ${this.age}`); } }; const employee = Object.create(person); employee.name = "Tom"; employee.age = 39; employee.company = "Google"; // переопределяем метод print employee.print = function(){ console.log(`Name: ${this.name} Age: ${this.age} Company: ${this.company}`); } employee.print(); // Name: Tom Age: 39 Company: Google
Здесь переопределяем функцию print, чтобы она также выводила компанию работника. Можно пойти дальше и увеличить цепочку наследования:
const person = { name: "", age: 0, print: function(){ console.log(`Name: ${this.name} Age: ${this.age}`); } }; // объект employee наследует прототип объекта person const employee = Object.create(person); employee.company = ""; // объект manager наследует прототип объекта employee const manager = Object.create(employee); // переопределяем метод print manager.print = function(){ console.log(`Name: ${this.name} Age: ${this.age}\nManager in ${this.company}`); } manager.name = "Bob"; manager.age = 43; manager.company = "Microsoft"; manager.print(); // Name: Bob Age: 43 // Manager in Microsoft
Таким образом, получаем цепочку прототипов - person-employee-manager
: employee наследует прототип от person, manager наследует прототип от employee
Иногда может быть необходимо вызвать методы, которые определены в прототипе. Это может быть полезно для сокращения кода, уменьшения дублирования, особенно когда код переопределенного метода повторяет логику метода из прототипа. Получив прототип объекта, мы можем вызвать у него методы с помощью функции call():
const person = { name: "", age: 0, print: function(){ console.log(`Name: ${this.name} Age: ${this.age}`); } }; // объект employee наследует прототип объекта person const employee = Object.create(person); employee.name = "Tom"; employee.age = 39; employee.company = "Google"; // переопределяем метод print employee.print = function(){ this.__proto__.print.call(this); // вызываем версию метода из person // Object.getPrototypeOf(this).print.call(this); // альтернативный вариант console.log(`Company: ${this.company}`); } employee.print(); // Name: Tom Age: 39 // Company: Google
В данном случае в переопределенном методе print у типа employee вызываем через прототип версию метода print из person.
С помощью метода Object.isPrototypeOf() можно проверить, является ли объект прототипом другого объекта:
const person = { name: "", print: ()=>console.log("Name:", this.name) }; const user = { name: "", print: ()=>console.log("Name:", this.name) }; // объект employee наследует прототип объекта person const employee = Object.create(person); console.log(person.isPrototypeOf(employee)); // true console.log(user.isPrototypeOf(employee)); // false
Здесь объект employee наследует прототип от person. Соответственно вызов person.isPrototypeOf(employee)
возвратит true
.
А объект user не является прототипом для employee даже несмотря на то, что у него тот же набор методов и свойств.