Стрелочные функции отличаются от обычных не только способом записи. Главное их отличие проявляется в том, как они работают с контекстом. Вкратце: контекст обычных функций зависит от места вызова, а контекст стрелочных функций — от того места, где они были определены.
Рассмотрим несколько примеров. Самый простой случай – определение функции на уровне модуля. В таком варианте, контекстом вызова будет сам модуль.
Примечание: примеры ниже приведены для 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
.
Точно такой же код с обычной функцией уже не заработает:
const printer = {
items: [1],
print() {
this.items.forEach(function () { console.log(this.items); });
},
};
printer.print(); // undefined
Это происходит именно потому, что функция вызывается как обычная функция, а не метод. В таком случае ее контекст равен пустому объекту, а значит this.items
вернет undefined
.
Где все это может понадобиться? В подавляющем большинстве ситуаций нам вообще не нужен this
внутри стрелочной функции. Всегда лучше работать с данными, переданными явно. Однако есть несколько примеров, где эта особенность стрелочных функций помогает упростить код. К таким примерам относятся ситуации, где внутри метода объекта вызывается функция высшего порядка, куда передается стрелочная функция, работающая с this
. Эта ситуация аналогичная коду выше.
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Статья «Ловушки обучения»
Вебинар «Как самостоятельно учиться»
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт