Прототипы и контексты в JavaScript

Прототипы и контексты в JavaScript

Daniil Shilo

Многие люди не понимают как работают прототипные языки программирования. Сегодня мы немножко приоткроем эту завесу.

Давайте напишем простой объект в JavaScript:

const customer = {
  name: 'Daniil',
  surname: 'Shilo',
  saySomething: function() {
    console.log(`${this.name} ${this.surname} is saying something`);
  }
}

Теперь давайте проверим данный код:

Google Chrome DevTools

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

Вызов метода, которого нет в объекте

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

Object.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();


Report Page