Прототипы и контексты в JavaScript
Daniil ShiloМногие люди не понимают как работают прототипные языки программирования. Сегодня мы немножко приоткроем эту завесу.
Давайте напишем простой объект в JavaScript:
const customer = {
name: 'Daniil',
surname: 'Shilo',
saySomething: function() {
console.log(`${this.name} ${this.surname} is saying something`);
}
}
Теперь давайте проверим данный код:

Как мы видим наш объект корректно работает. Теперь давайте вызовем функцию, которой нет в объекте:

Действительно, у нас нет функции customerSay(), однако, если мы например вызовем функцию toLocaleString, то она корректно выполнится, хоть мы её и не прописывали в самом объекте:

Дело в том, что у каждого объекта есть свой прототип.
Прототипное программирование — стиль объектно-ориентированного программирования, при котором отсутствует понятие класса, а наследование производится путём клонирования существующего экземпляра объекта — прототипа.
То есть существует объект, который JavaScript клонирует, а затем добавляет к его свойствам наши новые свойства и методы.
Посмотреть какие есть методы и свойства у объекта можно раскрыв сам объект:

Мы можем добавить свои методы и свойства для каждого объекта с помощью следующего кода:
Object.prototype.customerSay = function() {
console.log("I'll buy it!");
}
Вызвать данную функцию можно просто обратившись к объекту. JavaScript ищет методы и свойства в объекте, если он их не находит он "спускается на уровень вниз" и начинает искать в прототипе.

Вывод
- Вы можете свободно добавлять в прототипы объектов свои функции
- Все типы данных в JavaScript являются объектами (String, Number и так далее - тоже. Методы по типу
String.normalize()и есть методы прототипа) - Методы из прототипов задействуются, если они не были найдены в самом объекте
Контекст
this - ключевое слово, которое описывает контекст вызова. Давайте попробуем его просто вывести:

Window - глобальный объект, из которого осуществляются все вызовы. this по умолчанию указывает на него, так как это контекст для всех объявлений по умолчанию. Приведём пример:

А теперь сделаем тоже самое, только внутри объекта:

Как мы видим this в данном случае вывел сам объект, но почему?
Дело в том, что функция в которой используется this была вызвана из объекта. this возвращает объект из которого он был вызван. По умолчанию это window, однако, когда контекст вызова меняется, то меняется и значение this.
Теперь давайте инициализировать функцию отдельно от объекта, а затем присвоить её объекту и посмотрим, что будет со значением this:

Как мы видим this не привязывается к объекту, данное значение привязывается только к контексту из которого оно вызывается.
bind, apply, call
Для взаимодействия с this придумали несколько методов. Давайте начнём с самого простого: call, он нужен для того, чтобы вызывать функции с определенным контекстом.
Давайте создадим объект с некоторой функцией, которая показывает возраст пользователя:

Как мы видим функция showAge была инициализирована в контексте объекта Daniil, однако, мы смогли вызвать её с контекста Masha. call буквально взял функцию из объекта Daniil временно инициализировал её в объекте Masha, а затем выполнил её. Таким образом this указывал на объект Masha.
Следующие функции bind и apply делают то же самое с некоторыми различиями:
bindвозвращает функцию с определенным контекстом, вместо того чтобы вызывать еёapplyработает точно так же, как иcall, однако, аргументы вapplyпередаются через массив вторым аргументом при вызове
Думаю, тут всё ясно. Но, на всякий случай давайте покажем пример apply с аргументами:

Соединяем знания
Теперь давайте соединим темы, которые мы только что изучили.
Давайте сделаем функцию, которая будет выводить все свойства и их значения в консоль:
Object.prototype.showInfo = function() {
for (let i in this} {
console.log(`${i} is ${this[i]}`);
}
}
const Dog = {
name: 'Lucky',
age: 5,
bark: function() {
console.log('Woof');
}
}
Dog.showInfo();
