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