Bar [[Prototype => Foo]] так обозначу объект которому вы не дали название, создающийся через
new Bar();
1. При первом вызове print в конструкторе Foo:
-
this
ссылается на объект
Bar [[Prototype => Foo]]
- при вызове
this.print()
соотвественно идет поиск метода
print в этом объекте
- до прототипа мы не доходим, поскольку метод есть в объекте Bar и
он уже определен даже до окончания вызова construct(!), вызываем метод
- вызов
super()
еще не закончился,
this.id
установлен со значением
'foo' в строке выше до вызова метода
print()
, соотвественно на выходе получаем:
'bar foo'
2. Вызов
super()
закончился. В свойство
this.id
нашего объекта записываем значение
'bar' в следующей строке.
- Вызваем print в конструкторе Bar
- JS ищет метод в объетке
Bar [[Prototype => Foo]], опять находит его в самом объекте, до прототипа не доходим
- На выходе получаем:
'bar bar'
3. Последний вызов самое легкое.
super.print()
это то же самое что
bar.prototype.print()
. Систему поиска методов в объекте обходим, напрямую вызываем метод прототипа
-
this.id
объекта установили на этапе 2, его последнее значение
'bar'
- напоминаю, вызваем именно метод из прототипа, получаем
'foo bar'
На самом деле все довольно прямолинейно конкретно в этом примере, если понимать протоипное наследование и то, что синтаксис классов лишь синтаксический сахар скрывающий функции-конструкторы с установкой прототипов.
Подробнее почитать тут:
https://learn.javascript.ru/prototypes
Про особенности super:
https://learn.javascript.ru/class-inheritance