Пошук уроків, статей та іншого контенту
Порівняєте контекст this у звичайних callback-функціях, стрілках і методах масивів та таймерів.
thisthis не є змінною, яка зберігає посилання на об’єкт під час створення функції. Для звичайної функції значення this визначається способом її виклику.
"use strict";
function showThis() {
return this;
}
const object = {
method: showThis,
};
console.log(object.method() === object); // true
console.log(showThis() === undefined); // trueУ методі object.method() значення this — це object.
Під час прямого виклику showThis() у strict mode значення this дорівнює undefined.
Стрілкова функція не має власного this. Вона захоплює його із зовнішнього лексичного контексту під час створення.
"use strict";
const object = {
value: 42,
regularMethod() {
const regularFunction = function () {
return this;
};
const arrowFunction = () => this;
return {
regular: regularFunction(),
arrow: arrowFunction(),
};
},
};
console.log(object.regularMethod().regular); // undefined
console.log(object.regularMethod().arrow === object); // trueЗвичайна функція викликається без об’єкта, тому її this — undefined.
Стрілка захоплює this методу regularMethod, де this — object.
this у callback-функціяхCallback-функція передається іншій функції, яка викликає її пізніше. Важливо розрізняти:
хто створив callback;
хто його викликав;
яке значення this передав API;
чи є callback звичайною функцією або стрілкою.
Сам факт того, що функція є callback-функцією, не встановлює для неї універсальне значення this.
"use strict";
function callback() {
console.log(this);
}
function run(fn) {
fn();
}
run(callback); // undefinedrun викликає fn() без об’єкта, тому звичайна функція отримує undefined.
Якщо callback викликається як метод, значення буде іншим:
"use strict";
function runAsMethod(fn) {
const context = {
fn,
};
context.fn();
}
function callback() {
console.log(this);
}
runAsMethod(callback); // { fn: callback }Назва параметра fn не впливає на this. Впливає лише фактичний синтаксис виклику.
Методи масивів, такі як forEach, map, filter і some, викликають callback зі спеціальними аргументами:
поточне значення;
індекс;
сам масив.
const numbers = [10, 20, 30];
numbers.forEach((value, index, array) => {
console.log(value, index, array === numbers);
});У callback також можна передати thisArg другим аргументом методу:
"use strict";
const calculator = {
multiplier: 3,
};
const numbers = [2, 4, 6];
const result = numbers.map(function (value) {
return value * this.multiplier;
}, calculator);
console.log(result); // [6, 12, 18]У цьому прикладі map викликає callback так, щоб його this дорівнював calculator.
thisArg не працює зі стрілкамиСтрілкова функція ігнорує thisArg, оскільки не має власного this.
"use strict";
const calculator = {
multiplier: 3,
};
const numbers = [2, 4, 6];
const result = numbers.map(
(value) => value * this.multiplier,
calculator,
);
console.log(result); // [NaN, NaN, NaN]У цьому прикладі стрілка захоплює this із зовнішнього контексту. Переданий calculator не змінює його.
Якщо callback має використовувати thisArg, потрібна звичайна функція:
"use strict";
const calculator = {
multiplier: 3,
};
const result = [2, 4, 6].map(function (value) {
return value * this.multiplier;
}, calculator);
console.log(result); // [6, 12, 18]У сучасному коді частіше передають залежність через замикання, а не через thisArg:
const calculator = {
multiplier: 3,
};
const result = [2, 4, 6].map(
(value) => value * calculator.multiplier,
);
console.log(result); // [6, 12, 18]Такий варіант зазвичай простіше читати: залежність явно названа у тілі callback.
setTimeoutsetTimeout приймає функцію та викликає її пізніше. Не слід очікувати, що callback таймера автоматично отримає як this об’єкт, з якого викликали setTimeout.
"use strict";
const user = {
name: "Olena",
start() {
setTimeout(function () {
console.log(this);
}, 0);
},
};
user.start();Звичайна функція передана в setTimeout, а таймер викликає її не як user.start(). У strict mode її this не буде user.
Для браузерного коду значення this у callback таймера може бути пов’язане з глобальним об’єктом, а в Node.js реалізація таймерів має іншу поведінку. Тому покладатися на this у callback setTimeout не варто.
Стрілка захоплює this методу start:
"use strict";
const user = {
name: "Olena",
start() {
setTimeout(() => {
console.log(this.name);
}, 0);
},
};
user.start(); // OlenaCallback стрілки створюється всередині start, де this дорівнює user.
Замість стрілки можна явно зберегти контекст:
"use strict";
const user = {
name: "Olena",
start() {
const currentUser = this;
setTimeout(function () {
console.log(currentUser.name);
}, 0);
},
};
user.start(); // OlenaУ сучасному JavaScript стрілка зазвичай є коротшим варіантом, але локальна змінна може бути корисною, якщо потрібна звичайна функція.
bindbind створює нову функцію, у якої this зафіксовано:
"use strict";
const user = {
name: "Olena",
printName() {
console.log(this.name);
},
start() {
setTimeout(this.printName.bind(this), 0);
},
};
user.start(); // OlenaБез bind посилання this.printName передавалося б як окрема функція:
setTimeout(this.printName, 0);У такому випадку зв’язок із user втрачався б.
bind особливо доречний, коли:
callback є методом об’єкта;
функцію потрібно передати в API;
callback має залишатися звичайною функцією;
функцію потрібно викликати кілька разів із тим самим контекстом.
PromiseКолбеки then, catch і finally не викликаються як методи об’єкта, якому належить Promise. Їхній this не слід використовувати для доступу до стану об’єкта.
"use strict";
const service = {
prefix: "Result",
load() {
return Promise.resolve(42).then(function (value) {
console.log(this); // undefined у strict mode
return value;
});
},
};
service.load();Для Promise callback краще використовувати замикання або стрілку:
"use strict";
const service = {
prefix: "Result",
load() {
return Promise.resolve(42).then((value) => {
return `${this.prefix}: ${value}`;
});
},
};
service.load().then(console.log); // Result: 42Стрілка захоплює this методу load.
Важливо розрізняти this callback і значення, яке передається між Promise:
Promise.resolve(10)
.then((value) => value * 2)
.then((value) => console.log(value)); // 20Тут value — це результат попереднього Promise, а не this.
У DOM-обробниках звичайна функція, зареєстрована через addEventListener, викликається з this, що посилається на елемент, який слухає подію.
const button = document.querySelector("#save");
button.addEventListener("click", function (event) {
console.log(this === event.currentTarget); // true
});Для стрілки це не працює:
const button = document.querySelector("#save");
button.addEventListener("click", (event) => {
console.log(this); // зовнішній this, а не button
});Якщо потрібен елемент події, незалежно від типу функції, використовуйте event.currentTarget:
const button = document.querySelector("#save");
button.addEventListener("click", (event) => {
event.currentTarget.disabled = true;
});Це часто надійніше, ніж покладатися на спеціальну поведінку this.
Поширена помилка — передавання методу як callback без прив’язування:
"use strict";
const counter = {
value: 0,
increment() {
this.value += 1;
},
};
setTimeout(counter.increment, 0);У таймер передано лише функцію. Інформація про те, що вона була властивістю counter, не зберігається.
Виправлення за допомогою стрілки:
setTimeout(() => {
counter.increment();
}, 0);Виправлення за допомогою bind:
setTimeout(counter.increment.bind(counter), 0);Варіант зі стрілкою викликає метод пізніше, тому на момент виклику використовується актуальний counter.
Варіант із bind одразу створює функцію, назавжди прив’язану до counter.
this і вкладені callback-функціїРозглянемо об’єкт, який запускає асинхронну операцію та обробляє результат:
"use strict";
const report = {
title: "Daily report",
run() {
return Promise.resolve(["ok", "warning"])
.then((items) => {
return {
title: this.title,
items,
};
})
.then((result) => {
console.log(result.title);
console.log(result.items);
});
},
};
report.run();Обидва callback є стрілками, тому вони зберігають this методу run.
Якщо замінити внутрішні функції на звичайні, this втратиться:
"use strict";
const report = {
title: "Daily report",
run() {
return Promise.resolve(["ok", "warning"])
.then(function (items) {
return {
title: this.title,
items,
};
});
},
};
report.run().catch((error) => {
console.error(error);
});Звичайний callback then не викликається як метод report, тому this.title не посилається на report.
Наведений приклад можна запустити в браузері. Він демонструє:
звичайний метод;
стрілковий callback таймера;
bind;
callback методу масиву;
асинхронний Promise;
явну передачу даних через замикання.
"use strict";
const account = {
owner: "Ihor",
balance: 100,
deposit(amount) {
this.balance += amount;
},
start() {
// Стрілка зберігає this об'єкта account.
setTimeout(() => {
this.deposit(50);
console.log(`${this.owner}: ${this.balance}`);
}, 0);
const operations = [10, 20, 30];
// Звичайна функція отримує this через thisArg.
const amounts = operations.map(function (amount) {
return amount * this.rate;
}, { rate: 2 });
console.log(amounts); // [20, 40, 60]
// bind фіксує this для переданого методу.
setTimeout(
function () {
console.log(this.owner);
}.bind(this),
0,
);
// Стрілка зберігає this у Promise callback.
return Promise.resolve(25).then((amount) => {
this.deposit(amount);
return this.balance;
});
},
};
account.start().then((balance) => {
console.log(`Баланс після Promise: ${balance}`);
});Порядок виведення асинхронних операцій залежить від черги виконання:
синхронний console.log(amounts);
callback setTimeout;
callback другого setTimeout;
callback Promise.
Promise-обробники виконуються після завершення поточного синхронного коду, але зазвичай раніше за callback таймера з таким самим моментом запуску.
callback має успадкувати this із зовнішнього методу;
ви працюєте з Promise, таймерами або іншими асинхронними API;
callback не повинен мати власного контексту;
ви передаєте функцію в map, filter або forEach і не використовуєте thisArg.
API явно передає потрібний this;
потрібен thisArg методу масиву;
функція має бути методом, який викликається з різними контекстами;
потрібно використати arguments, super або конструкцію з власним динамічним this.
bind, якщопотрібно передати існуючий метод як callback;
callback має бути звичайною функцією;
контекст потрібно зафіксувати один раз;
разом із контекстом потрібно заздалегідь передати аргументи.
const logger = {
prefix: "[app]",
log(message) {
console.log(this.prefix, message);
},
};
const logError = logger.log.bind(logger, "Error:");
logError("Connection failed"); // [app] Error: Connection failedsetTimeout(account.deposit, 0);Передано функцію, а не виклик account.deposit() у контексті account.
Використовуйте:
setTimeout(() => account.deposit(10), 0);або:
setTimeout(account.deposit.bind(account, 10), 0);thisArg стрілціitems.map((item) => item * this.factor, context);context не змінить this стрілки. Потрібно або використати звичайну функцію, або звернутися до context.factor через замикання.
this із параметрами callbackУ map:
items.map((value, index, array) => {
// value, index і array — параметри callback, не this
});У setTimeout:
setTimeout(() => {
// Тут немає автоматичного параметра з контекстом об'єкта.
});this і аргументи функції — різні механізми.
Значення this у callback таймера може відрізнятися між браузером і Node.js. Код, якому потрібен конкретний об’єкт, має передавати його через стрілку, замикання або bind.
thisconst object = {
value: 10,
getValue: () => this.value,
};Стрілка не отримає object як this під час виклику object.getValue(). Для методу використовуйте звичайний синтаксис:
const object = {
value: 10,
getValue() {
return this.value;
},
};У звичайної функції this визначається способом виклику.
Стрілкова функція захоплює this із зовнішнього контексту.
Callback масивів отримує значення, індекс і масив як аргументи.
Методи масивів можуть передати thisArg, але він не впливає на стрілкові функції.
Таймери та Promise не зберігають this об’єкта, з якого їх запустили.
Для асинхронних callback найчастіше використовують стрілки або замикання.
bind створює функцію з явно зафіксованим this.
У DOM-подіях звичайний listener може отримати елемент через this, але event.currentTarget є явнішим і надійнішим способом.
Щоб правильно аналізувати this, потрібно дивитися не на місце оголошення звичайної функції, а на місце та спосіб її виклику.