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 даже несмотря на то, что у него тот же набор методов и свойств.