Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Контекст (This) JS: Введение в ООП

Для полноценного изучения ООП в JavaScript нужно разобраться с таким понятием как контекст this. На базе контекста строится почти все остальное, включая то, как работают методы и классы.

Немного усложняет ситуацию то, что контекст по-разному работает для обычных и стрелочных функций. И так как стрелочные функции появились в языке позже, то для понимания темы нужно начать изучение контекста с обычных функций. Небольшое напоминание про то, как выглядят такие функции:

// Определение стрелочной функции и присваивание константе
const f = () => 'i am an arrow function';

// Определение обычной анонимной функции
function() {
  return 'i am a regular function without name';
}

// Определение обычной именованной функции
function f() {
  return 'i am a regular function with name';
}

В этом уроке мы рассматриваем только обычные функции, стрелочные будут в одном из следующих уроков. Обычные функции работают с контекстом одинаково, независимо от того, именованные они или нет.

В JavaScript функции ведут себя как данные: их можно записывать в переменные, константы и даже в свойства объектов. Функции, записанные внутрь свойств объектов, называют методами:

const company = { name: 'Hexlet' };
// Создание функции, которая сразу же присваивается свойству getName и становится методом
company.getName = function() {
  return 'Hexlet';
};

// Вызов метода
company.getName(); // "Hexlet"

Это всего лишь один из множества возможных вариантов добавления функции в объект. Ниже еще несколько примеров:

// При создании объекта
const obj = {
  getName: function() {
    return 'Hexlet';
  },
};

// Через присваивание константы
const company = { name: 'Hexlet' };

function getHexlet() {
  return 'Hexlet';
};
// Имя не принципиально
company.getName = getHexlet;

company.getName(); // "Hexlet"

Все варианты выше эквивалентны. Они приводят к одному и тому же результату, но есть одна загвоздка. Метод возвращает строку и никак не использует данные объекта. Если поменяется имя, то метод продолжит возвращать "зашитое" в него значение, а не текущее имя компании внутри объекта.

company.getName(); // "Hexlet"
company.name = 'Hexlet Plus';
// Имя поменяли, но очевидно, что возврат остался прежний
company.getName(); // "Hexlet"

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

const company = { name: 'Hexlet', employees: [] };
company.getName = function getName() {
  return this.name;
};

company.getName(); // "Hexlet"
company.name = 'Hexlet Plus';
company.getName(); // "Hexlet Plus"

this дает возможность не только читать данные, но и менять их:

company.setName = function setName(name) {
  this.name = name;
};

company.getName(); // "Hexlet"
company.setName('Hexlet Plus');
company.getName(); // "Hexlet Plus"

Другой пример — изменение внутреннего массива в объекте:

// Добавление нового сотрудника
company.addEmployee = function addEmployee(user) {
  // Важно, что на момент вызова, employees уже добавлен в company
  this.employees.push(user);
};

const user = { name: 'Petya' };
company.addEmployee(user);
company.employees; // [{ name: 'Petya' }]

// Или через метод

company.getEmployees = function() {
  return this.employees;
};

company.getEmployees(); // [{ name: 'Petya' }]

Как видно из примеров выше, свойства можно менять как напрямую, так и из методов. Какой способ предпочесть – зависит от ситуации. С дальнейшим прохождением курсов и опытом вы начнете лучше понимать, какой способ предпочесть.

Контекст

Выше, когда давалось определение this, говорилось, что this ссылается на текущий объект, к которому привязан метод. И здесь кроется ключевое отличие this в JavaScript от this в других языках. В JavaScript this у метода может измениться:

const company1 = { name: 'Hexlet', getName: function getName() { return this.name } };
const company2 = { name: 'Hexlet Plus' };

company1.getName(); // "Hexlet"

company2.getName = company1.getName;

// В обоих случаях одна и та же функция
company2.getName(); // "Hexlet Plus"
company1.getName(); // "Hexlet"

Что здесь произошло? Вызов той же самой функции из другого объекта привел к смене объекта, на который ссылается this. Эта особенность называется поздним связыванием. Значение this ссылается на тот объект, из которого происходит вызов метода.

Лучше всего можно понять эту особенность, познакомившись с тем, как вызываются функции внутри самого JavaScript и откуда там берется this. Так как в JavaScript функции — это тоже объекты, то у них есть свои методы. Среди них есть метод call(), который и используется для вызова:

const sayHi = () => 'Hi!';
sayHi.call(); // "Hi!"

Зачем так сделано? Дело в том, что первым параметром call() принимает контекст - объект, на который и будет ссылаться this внутри функции. Функции для этого не обязательно быть методом:

const getName = function getName() {
  return this.name;
};

const company1 = { name: 'Hexlet' };
// Функция вызывается напрямую, она не является методом
getName.call(company1); // "Hexlet"

const company2 = { name: 'Hexlet Plus' };
getName.call(company2); // "Hexlet Plus"

В примере выше мы заменили контекст функции getName() с помощью call(), передав в него новый контекст.

В этом и заключается весь секрет this. Это ссылка на контекст, который мы можем заменить в функции, как показано выше. Также JavaScript прокидывает контекст автоматически в метод. В этом случае можно точно сказать, на какой объект ссылается контекст. Например, в коде ниже метод getName() принадлежит объекту company и контекст ссылается на этот же объект:

const company = { name: 'Hexlet', getName: function getName() { return this.name } };

Теперь, когда вы знаете как работает this, попробуйте ответить на вопрос, что будет выведено на экран?

const company = {
  name: 'Hexlet',
  country: {
    name: 'Finland',
    getName: function getName() {
      return this.name;
    }
  },
};

console.log(company.country.getName()); // => ?

Правильный ответ: "Finland". Почему? Потому что контекстом для метода getName() является объект country, а не company. Если немного модифицировать код, то понять эту идею станет проще:

const { country } = company;
console.log(country.getName()); // "Finland"

Сокращенное определение методов

Из-за необходимости использовать обычные функции при создании объектов в JavaScript был введен специальный сокращенный синтаксис создания методов при определении объектов:

const company = {
  name: 'Hexlet',
  getName() {
    return this.name;
  },
  // То же самое что
  // getName: function getName() {
  //   return this.name;
  // },
};

Такой способ – всего лишь "синтаксический сахар". Он позволяет сделать запись чуть короче, но не меняет поведение. Главное запомните – это обычная функция, а не стрелочная. В дальнейшем мы будем использовать именно такое определение.


Дополнительные материалы

  1. Объекты первого класса
  2. YDKJS: This

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 5 октября
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 5 октября
профессия
от 10 080 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 5 октября
профессия
новый
Автоматизированное тестирование веб-приложений на JavaScript
8 месяцев
c опытом
в разработке
дата определяется

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»