Пошук уроків, статей та іншого контенту
Дослідите лексичний this стрілкових функцій і зрозумієте, чому bind, call та apply не змінюють його.
thisУ звичайній функції значення this визначається під час виклику. Воно залежить від того, як саме функцію викликали.
const user = {
name: "Олена",
sayHello: function () {
console.log(this.name);
}
};
user.sayHello(); // ОленаПід час виклику user.sayHello() значенням this є об’єкт user.
Стрілкові функції працюють інакше: вони не створюють власного this. Замість цього вони беруть this із зовнішнього лексичного контексту — тобто з місця, де були створені.
const user = {
name: "Олена",
sayHello: () => {
console.log(this.name);
}
};
user.sayHello(); // undefinedСтрілкова функція не отримує this від об’єкта user. Вона використовує this із зовнішнього контексту.
thisТермін «лексичний» означає, що значення визначається структурою коду, а не способом виклику функції.
Порівняймо звичайну та стрілкову функції:
"use strict";
const user = {
name: "Олена",
regularMethod: function () {
console.log("Звичайна функція:", this.name);
},
arrowMethod: () => {
console.log("Стрілкова функція:", this.name);
}
};
user.regularMethod(); // Звичайна функція: Олена
user.arrowMethod(); // Стрілкова функція: undefinedУ regularMethod this дорівнює user, тому що функцію викликали через user.regularMethod().
У arrowMethod власного this немає. Функція бере його із зовнішнього контексту, де властивості name немає.
Найчастіше лексичний this корисний усередині методів об’єктів або класів. Стрілкова функція може «запам’ятати» this зовнішнього методу.
const user = {
name: "Олена",
showNameLater: function () {
setTimeout(() => {
console.log(this.name);
}, 100);
}
};
user.showNameLater();Метод showNameLater викликається як метод об’єкта, тому його this — це user.
Стрілкова функція, передана в setTimeout, не створює власного this. Вона використовує this методу showNameLater, тому через 100 мілісекунд буде виведено:
ОленаЯкби тут використали звичайну функцію, значення this було б іншим:
const user = {
name: "Олена",
showNameLater: function () {
setTimeout(function () {
console.log(this.name);
}, 100);
}
};
user.showNameLater();Звичайна функція зворотного виклику має власний this. У середовищі Node.js або в суворому режимі він не буде об’єктом user, тому результатом може бути undefined або інша поведінка залежно від середовища.
Той самий принцип працює в методах класів:
class Timer {
constructor(label) {
this.label = label;
}
start() {
setTimeout(() => {
console.log(`Таймер: ${this.label}`);
}, 100);
}
}
const timer = new Timer("завершено");
timer.start();Стрілкова функція всередині start використовує this методу start. Тому this.label доступне навіть після виконання асинхронного колбека.
Це одна з причин, чому стрілкові функції часто використовують у:
setTimeout;
обробниках подій;
промісах;
методах масивів;
асинхронному коді.
call, apply і bindДля звичайних функцій методи call, apply та bind дозволяють явно задати this.
callcall викликає функцію одразу, передаючи this першим аргументом.
function introduce(greeting) {
console.log(`${greeting}, мене звати ${this.name}`);
}
const user = {
name: "Олена"
};
introduce.call(user, "Привіт");
// Привіт, мене звати Оленаapplyapply працює подібно до call, але аргументи передаються масивом.
function introduce(greeting, punctuation) {
console.log(`${greeting}, мене звати ${this.name}${punctuation}`);
}
const user = {
name: "Олена"
};
introduce.apply(user, ["Привіт", "!"]);
// Привіт, мене звати Олена!bindbind не викликає функцію одразу. Він повертає нову функцію, у якої this прив’язано до переданого об’єкта.
function introduce() {
console.log(`Мене звати ${this.name}`);
}
const user = {
name: "Олена"
};
const boundIntroduce = introduce.bind(user);
boundIntroduce(); // Мене звати Оленаcall, apply і bind не змінюють this стрілкової функціїУ стрілкової функції немає власного this, тому механізми call, apply та bind не можуть його перевизначити.
"use strict";
const user = {
name: "Олена"
};
const arrowFunction = () => {
console.log(this);
};
arrowFunction.call(user);
arrowFunction.apply(user);
const boundArrowFunction = arrowFunction.bind(user);
boundArrowFunction();Усі три виклики використають той самий лексичний this, який стрілкова функція отримала під час створення. Об’єкт user не стане її this.
Важливо: bind все одно може частково застосувати аргументи стрілкової функції, але не може змінити її this.
const add = (a, b) => a + b;
const addFive = add.bind(null, 5);
console.log(addFive(3)); // 8У цьому прикладі bind зафіксував перший аргумент a, але переданий null не вплинув на this.
У наступному прикладі звичайний метод використовує this об’єкта, а вкладена стрілкова функція зберігає цей контекст:
"use strict";
const profile = {
name: "Андрій",
messages: 3,
printInfo: function () {
console.log(`Користувач: ${this.name}`);
const printMessages = () => {
console.log(`Нових повідомлень: ${this.messages}`);
};
printMessages();
}
};
profile.printInfo();
// Користувач: Андрій
// Нових повідомлень: 3
const detachedPrintInfo = profile.printInfo;
detachedPrintInfo();
// Користувач: undefined
// Нових повідомлень: undefinedПісля присвоєння profile.printInfo змінній метод викликається вже не через profile. Тому звичайна функція printInfo втрачає контекст об’єкта.
Водночас внутрішня стрілкова функція правильно успадковує this від printInfo. Проблема виникає раніше — під час виклику самого методу.
Щоб зберегти контекст звичайного методу, можна використати bind:
const boundPrintInfo = profile.printInfo.bind(profile);
boundPrintInfo();
// Користувач: Андрій
// Нових повідомлень: 3Не варто оголошувати методи об’єкта стрілковими функціями, якщо вони мають працювати з this.
Невдалий варіант:
const cart = {
items: 2,
getItems: () => {
return this.items;
}
};
console.log(cart.getItems()); // undefinedПравильний варіант:
const cart = {
items: 2,
getItems() {
return this.items;
}
};
console.log(cart.getItems()); // 2Синтаксис скороченого методу getItems() {} створює звичайний метод із динамічним this.
Стрілкові функції не мають власного this і не підтримують виклик через new.
const User = (name) => {
this.name = name;
};
// TypeError: User is not a constructor
// const user = new User("Олена");Для конструкторів використовують звичайні функції або, частіше, класи:
class User {
constructor(name) {
this.name = name;
}
}
const user = new User("Олена");
console.log(user.name); // Оленаconst counter = {
value: 0,
increment: () => {
this.value++;
}
};Стрілкова функція не має this, пов’язаного з counter. Для методу використовуйте звичайну функцію:
const counter = {
value: 0,
increment() {
this.value++;
}
};
counter.increment();
console.log(counter.value); // 1bind змінить this стрілкиconst arrow = () => this;
const anotherObject = {};
const boundArrow = arrow.bind(anotherObject);
console.log(boundArrow() === anotherObject); // falsebind не змінює лексичний this стрілкової функції.
Стрілка успадковує this, а звичайна вкладена функція — ні:
const user = {
name: "Олена",
show() {
const regular = function () {
console.log(this.name);
};
const arrow = () => {
console.log(this.name);
};
regular(); // Значення залежить від режиму та середовища
arrow(); // Олена
}
};
user.show();Якщо вкладеній звичайній функції потрібен зовнішній this, використовуйте стрілкову функцію або явно прив’яжіть контекст через bind.
Використовуйте звичайну функцію, якщо:
функція має бути методом об’єкта;
потрібно керувати this через call, apply або bind;
функція буде конструктором;
значення this має залежати від способу виклику.
Використовуйте стрілкову функцію, якщо:
потрібно успадкувати this із зовнішнього контексту;
функція є коротким колбеком;
потрібно зберегти контекст методу в асинхронній операції;
функція не повинна мати власного this.
Стрілкові функції не створюють власного this.
Їхній this визначається лексично — у місці створення функції.
Виклик через object.method() не змінює this стрілкової функції.
call, apply та bind не можуть змінити this стрілки.
bind може зафіксувати аргументи стрілкової функції, але не її this.
Стрілкові функції зручні як вкладені колбеки та обробники асинхронних операцій.
Стрілкові функції не варто використовувати як методи об’єктів, якщо метод звертається до this.
Стрілкові функції не можна використовувати з оператором new.