Наследование и цепь прототипов

Вступление

JavaScript выглядит слегка запутанным с точки зрения разработчиков, программирующих на языках, основанных на классах (таких как Java или C++), так как JavaScript это язык с динамической типизацией, не имеющий встроенной реализации классов (хотя ключевое слово class является зарезервированным и не может быть использовано в качестве имени переменной).
Когда речь заходит о наследовании, JavaScript имеет только одну конструкцию для ее реализации: объекты. Каждый объект имеет внутреннюю ссылку на другой объект, который называется его прототипом (prototype). Этот объект-прототип тоже имеет свой прототип, и так далее. Эта цепочка прерывается, когда прототип какого-нибудь объекта содержит не ссылку на другой объект, а имеет значение null. null по определению не имеет прототипа и действует как последнее звено в этой цепочке прототипов.

Наследование с использованием цепочки прототипов

Наследование свойств

Объекты JavaScript это динамические контейнеры со свойствами (именуемыми собственными свойствами объекта), и каждый из них содержит ссылку на объект-прототип. Вот что происходит при попытке получить доступ к свойству объекта:

// Предположим, что у нас есть объект obj с цепочкой прототипов, которая выглядит следующим образом:
// {a:1, b:2} ---> {b:3, c:4} ---> null
// 'a' и 'b' это собственные свойства объекта obj.
 
// В этом примере, какойТоОбъект.[[Прототип]] будет обозначать прототип какогоТоОбъекта.
// Это упрощенная нотация (основанная на нотации, используемой в стандарте ECMAScript) и не может быть использована в скриптах.
 
console.log(obj.a); // 1
// Есть ли собственное свойство 'a' у объекта obj? Да и его значение 1
 
console.log(obj.b); // 2
// Есть ли собственное свойство 'b' у объекта obj? Да, и его значение 2
// Прототип тоже имеет свойство 'b', но оно не используется. Это называется "затенение свойства"
// То есть собственное свойство объекта, перекрывает одноименное свойство из прототипа.
 
console.log(obj.c); // 4
// Есть ли собственное свойство 'c' у объекта obj? Нет, ищем в его прототипе
// Есть ли собственное свойство 'c' у obj.[[Прототип]]? Да, и его значение 4
 
console.log(obj.d); // undefined
// Есть ли собственное свойство 'd' у объекта obj? Нет, ищем в его прототипе
// Есть ли собственное свойство 'd' у obj.[[Прототип]]? Нет, ищем в его прототипе
// obj.[[Prototype]].[[Prototype]] содержит null, прекращаем поиск, такого свойства не найдено, возвращаем undefined


Установка свойства объекту создает собственное свойство этого объекта. Единственное исключение из правил взятия и установки свойств, это когда унаследованное свойство является геттером (a getter) или сеттером (a setter). Но об этом в другой раз.