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

Особенности работы this со стрелочными функциями JS: Введение в ООП

Стрелочные функции отличаются от обычных не только способом записи. Главное их отличие проявляется в том, как они работают с контекстом. Вкратце: контекст обычных функций зависит от места вызова, а контекст стрелочных функций — от того места, где они были определены.

Рассмотрим несколько примеров. Самый простой случай – определение функции на уровне модуля. Здесь контекстом вызова будет сам модуль.

Примечание: примеры ниже приведены для Node.js. В браузере this по умолчанию будет содержать глобальный объект Window.

const f1 = () => { // стрелочная функция
  console.log(this);
};

f1(); // undefined

function f2() { // обычная функция
  console.log(this);
}

f2(); // undefined

Здесь поведение функций не отличается, так как контекстом вызова у обеих функций является сам модуль, а в es6 this у модулей не определен. Теперь попробуем добавить эти функции в объект:

const obj = {
  f1, f2,
};

obj.f1(); // undefined
obj.f2(); // { f1: [Function: f1], f2: [Function: f2] }

Обычная функция ожидаемо связалась с контекстом того объекта, на котором она вызвана. А вот в случае стрелочной функции такого не произошло. Почему? Стрелочная функция не имеет своего контекста, она связывается с лексическим окружением, то есть функцией, внутри которой определена стрелочная функция. Это очень важный момент. Именно функция верхнего уровня задаёт контекст стрелочной функции, а не что-то другое. И это поведение нельзя изменить с помощью функций call или bind.

f1.call({ name: 'hexlet' }); // undefined
f1.bind({ name: 'hexlet' })(); // undefined

Теперь определим стрелочную функцию внутри какого-нибудь объекта и попробуем вызвать:


const company = {
  f1: () => { // стрелочная функция
    console.log(this);
  },
  f2() { // обычная функция
    console.log(this);
  },
};

company.f1(); // undefined
company.f2(); // { f1: [Function: f1], f2: [Function: f2] }

Здесь мы видим точно такую же картину. Несмотря на то, что стрелочная функция описывается внутри объекта и вызывается из этого же объекта, контекст все равно связан с местом определения функции (лексическим окружением) – а это сам модуль.

Теперь попробуем определить стрелочную функцию внутри другой функции:

const printer = {
  items: [1],
  print() { // важно что внешняя функция имеет контекст
    // Стрелочная функция определяется внутри функции print,
    // но вызывается внутри метода forEach
    this.items.forEach(() => console.log(this.items));
  },
};

printer.print(); // [1]

Кажется, что у стрелочной функции появился this, но этот контекст не принадлежит функции, она заимствовала его у внешней функции print. Чтобы лучше это понять, представьте, как вызывается стрелочная функция внутри forEach. Она вызывается напрямую, а не из объекта. Обычные функции в такой ситуации создают свой собственный контекст, а стрелочные нет. Они берут контекст из того места, где были определены. Стрелочная функция определена в функции print, поэтому берёт контекст функции print.

Точно такой же код с обычной функцией уже не заработает:

const printer = {
  items: [1],
  print() {
    this.items.forEach(function () { console.log(this.items); });
  },
};

printer.print(); // Error!

Это происходит именно потому, что функция вызывается как обычная функция, а не метод. В таком случае ее контекст будет равен глобальному контексту. Обычно этот контекст равен undefined, а значит this.items вызовет ошибку.

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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