Простое наследование в JavaScript: что нужно знать

Многие мои друзья занимаются разработкой программного обеспечения на C# или C++. Они привыкли использовать наследование в своих проектах, и когда они хотят изучить JavaScript, один из первых вопросов, который они задают, звучит так: «А как реализовать наследование на JavaScript?».

И в самом деле, JavaScript использует другой подход, чем C# или C++. JavaScript является прототип-ориентированным языком. Концепция прототипного программирования предполагает, что поведение может быть повторно использовано путем клонирования существующих объектов, выступающих в роли прототипов.

Каждый объект в JavaScript зависит от прототипа, который определяет набор функций и членов, доступных объекту для использования. Нет класса как такового — только объекты. Любой объект может быть использован в качестве прототипа для другого объекта.

Эта концепция является очень гибкой, поэтому может использоваться для реализации некоторых понятий объектно-ориентированного программирования, таких как наследование.

Реализация наследования

Давайте взглянем на иерархию наследования, которую мы создадим, используя JavaScript:

JS-inheritance

Можно легко создать ClassA. Поскольку нет явных классов, мы можем определить поведенческий набор, просто создав функцию, подобную этой:

var ClassA = function() {
    this.name = "class A";
}

Экземпляр этого «класса» может быть создан с помощью ключевого слова new:

var a = new ClassA();
ClassA.prototype.print = function() {
    console.log(this.name);
}

Используем его с помощью нашего объекта:

a.print();

Довольно просто, не правда ли?

Весь пример занимает всего восемь строк кода:

var ClassA = function() {
    this.name = "class A";
}
ClassA.prototype.print = function() {
    console.log(this.name);
}
var a = new ClassA();
a.print();

Теперь давайте добавим инструмент для создания «наследования» между классами. Этот инструмент должен делать всего одну вещь: клонировать прототип:

var inheritsFrom = function (child, parent) {
    child.prototype = Object.create(parent.prototype);
};

Именно здесь происходит магия! Клонируя прототип, мы передаем все члены и функции в новый класс.

Таким образом, если мы хотим добавить второй класс, который будет наследником первого, нам нужно просто использовать следующий код:

var ClassB = function() {
    this.name = "class B";
    this.surname = "I'm the child";
} 

inheritsFrom(ClassB, ClassA);

Так как ClassB наследует функцию print от ClassA, следующий код работает:

var b = new ClassB();
b.print();

И создает такой вывод:

class B

Мы даже можем переопределить функцию print для ClassB:

ClassB.prototype.print = function() {
    ClassA.prototype.print.call(this);
    console.log(this.surname);
}

В данном случае, полученный результат выглядит следующим образом:

class B 
I’m the child

Хитрость здесь в том, чтобы вызвать ClassA.prototype для получения базовой функции print. Затем благодаря функции call мы можем вызвать базовую функцию для текущего объекта (this).

Создание ClassC:

var ClassC = function () {
    this.name = "class C";
    this.surname = "I'm the grandchild";
}
 
inheritsFrom(ClassC, ClassB);
 
ClassC.prototype.foo = function() {
    // Do some funky stuff here...
}
 
ClassC.prototype.print = function () {
    ClassB.prototype.print.call(this);
    console.log("Sounds like this is working!");
}
 
var c = new ClassC();
c.print();

Результат выполнения кода следующий:

class C 
I’m the grandchild
Sounds like this is working!

И немного философии…

В заключение, я хочу объяснить, что JavaScript – это не C# или C++. У него своя философия. Если вы C# или C++ разработчик и действительно хотите ощутить всю силу JavaScript, лучший совет, который я могу вам дать: не пытайтесь применять методики объектно-ориентированного программирования в JavaScript. Не существует лучшего или худшего языка. Есть просто разные философии!

Перевод статьи «Simple JavaScript Inheritance: What You Need to Know» был подготовлен дружной командой проекта Сайтостроение от А до Я.