Что вернёт метод book.getUpperName()?

Дан код:

function Book() {
  this.name = 'foo';
}

Book.prototype = {
  getName: function () {
    return this.name;
  },
};

var book = new Book();

Book.prototype.getUpperName = function () {
  return this.getName().toUpperCase();
};

console.log(book.getUpperName());

Что вернет метод console.log(book.getUpperName())?

Теория по задаче

Рассмотрим детально, как устроены прототипы и работа с ними в JavaScript.

Концепция прототипов в JavaScript

Каждый объект в JavaScript имеет скрытое свойство __proto__, которое ссылается на его прототип. Именно через прототип реализовано наследование в JavaScript. Когда мы пытаемся обратиться к свойству или методу объекта, JavaScript первым делом ищет это свойство непосредственно в самом объекте. Если свойство отсутствует, JavaScript поднимается вверх по цепочке прототипов и продолжает поиск в родительском прототипе, пока либо не найдет нужное свойство, либо не достигнет вершины цепочки (свойство __proto__, равный null).

Работа с функцией-конструктором и её прототипом

Функция-контруктор служит шаблоном для создания нового объекта. Каждый раз, когда мы используем оператор new, создаётся новый объект, связанный с прототипом конструктора. Например, рассмотрим фрагмент кода:

function Book() {
    this.name = 'foo';
}

Здесь конструктор Book устанавливает свойство name каждого создаваемого экземпляра. Однако важно отметить, что методы и общие свойства часто определяются на уровне прототипа, чтобы избежать дублирования памяти для каждого отдельного объекта.

Далее в коде изменяется прототип конструктора Book следующим образом:

Book.prototype = {
    getName: function() {
        return this.name;
    }
};

Теперь каждый экземпляр, созданный через конструктор Book, получит доступ к методу getName, который возвращает значение свойства name. Обратите внимание, что сам объект-потомок не хранит копию метода, а берёт его из своего прототипа.

Создание экземпляра и расширение прототипа

Создав экземпляр класса Book:

const book = new Book();

мы получили объект с единственным собственным свойством name. Остальные методы берутся из прототипа. Важно помнить, что изменения в прототипе влияют на существующие экземпляры, поскольку все они связаны с одним и тем же прототипом.

Следующая часть кода добавляет дополнительный метод к прототипу:

Book.prototype.getUpperName = function() {
    return this.getName().toUpperCase();
}

Этот метод вызывает ранее определенный метод getName и преобразует полученный результат в верхний регистр.

Важность понимания контекста this

Контекст this в JavaScript определяет, какой объект рассматривается при выполнении метода. Внутри конструктора и методов, вызванных через экземпляр, this относится к самому этому экземпляру. То есть, когда мы вызываем book.getUpperName(), this внутри метода указывает на объект book, а значит, обращение к this.name действительно даст нам установленное значение "foo".

Итоговая логика выполнения

Итак, выполняя вызов:

console.log(book.getUpperName());

происходит следующее:

  1. Метод getUpperName находит и вызывает метод getName, передавая текущий контекст (this), т.е. экземпляр book.
  2. Метод getName возвращает значение свойства name, которое установлено как "foo".
  3. Полученная строка "foo" преобразуется в верхний регистр, давая итоговую строку "FOO".