Примусове задання this при виклику
Примусово задати this можна методами call, apply і bind, а також через виклик функції як методу об'єкта, стрілкові функції, Reflect.apply і оператор new. Різниця між ними в тому, коли відбувається виклик і чи можна змінити контекст згодом.
Теорія
TL;DR
call(thisArg, a, b)викликає функцію одразу, аргументи через кому.apply(thisArg, [a, b])те саме, але аргументи масивом.bind(thisArg)не викликає, а повертає нову функцію з прив'язанимthis.obj.method()задаєthis = objнеявно.- Стрілкові функції власного
thisне мають і беруть його із зовнішньої області. Reflect.apply(fn, thisArg, args)це сучасний аналогapply.new Func()створює новий об'єкт і перекриває навіть прив'язку черезbind.
Швидкий приклад
function greet() {
console.log(`Привіт, я ${this.name}`);
}
const user = { name: 'Maria' };
greet.call(user); // Привіт, я MariaТут this всередині greet примусово вказує на user.
call і apply: негайний виклик із заданим контекстом
Метод Function.prototype.call() викликає функцію одразу і дозволяє вказати this вручну:
function greet() {
console.log(`Привіт, я ${this.name}`);
}
const user = { name: 'Maria' };
greet.call(user); // Привіт, я MariaМетод apply() робить те саме, що й call, але приймає аргументи у вигляді масиву:
function introduce(city, job) {
console.log(`Я ${this.name} з міста ${city}, працюю як ${job}`);
}
const user = { name: 'Maria' };
introduce.apply(user, ['Kyiv', 'Frontend-розробник']);
// Я Maria з міста Kyiv, працюю як Frontend-розробникbind: нова функція з прив'язаним контекстом
Метод bind() не викликає функцію одразу, а створює нову, у якої this уже «прив'язаний» назавжди:
function sayHi() {
console.log(`Привіт, я ${this.name}`);
}
const user = { name: 'Maria' };
const sayHiBound = sayHi.bind(user);
sayHiBound(); // Привіт, я MariaВідмінність:
call/applyвикликають одразу;bindстворює копію із закріпленим контекстом.
Неявний контекст: метод об'єкта і стрілкові функції
Якщо викликати функцію через крапку в об'єкта, this автоматично вказує на цей об'єкт:
const user = {
name: 'Maria',
sayHi() {
console.log(`Привіт, я ${this.name}`);
}
};
user.sayHi(); // Привіт, я MariaЦе неявне встановлення this, але по суті те саме.
У стрілкових функцій немає власного this: вони «успадковують» його із зовнішньої області:
const user = {
name: 'Maria',
sayHiLater() {
setTimeout(() => {
console.log(`Привіт, я ${this.name}`); // this = user
}, 1000);
}
};
user.sayHiLater(); // Привіт, я MariaЯкби всередині setTimeout була звичайна функція, this загубився б і став би window.
Reflect.apply, new і навмисна втрата контексту
Reflect.apply() це сучасний аналог apply з ES6: робить те саме, але безпечніше і декларативніше, бо не залежить від того, чи не перевизначили apply на самій функції:
function sayHello() {
console.log(`Привіт, ${this.name}`);
}
const user = { name: 'Maria' };
Reflect.apply(sayHello, user, []); // Привіт, MariaПри виклику функції з new створюється новий об'єкт, і this всередині функції вказує на нього, навіть якщо інший this прив'язали через bind:
function User(name) {
this.name = name;
}
const Bound = User.bind({ name: 'Maria' });
const u = new Bound('Alice');
console.log(u.name); // Alice, контекст із new перекриває bindЯкщо ж треба навпаки «відв'язати» this і викликати функцію без контексту, передають null або undefined:
function showThis() {
console.log(this);
}
showThis.call(null); // у 'use strict' -> undefinedУ нестрогому режимі null буде підмінено на глобальний об'єкт (window або global).
Підсумкова таблиця способів
| Спосіб | Викликає одразу | Як задає this | Аргументи |
|---|---|---|---|
call(thisArg, a, b) | Так | Примусово | Через кому |
apply(thisArg, [a, b]) | Так | Примусово | Масивом |
bind(thisArg, a, b) | Ні | Прив'язує назавжди | Через кому |
obj.method() | Так | Неявно (this = obj) | Через кому |
Reflect.apply() | Так | Примусово (сучасний аналог) | Масивом |
new Func() | Так | Новий об'єкт усередині функції | Аргументи конструктора |
Типові помилки
- Прив'язують
thisдо стрілкової функції. У стрілок немає власногоthis, томуcall,applyіbindна нього не впливають, а перший аргумент просто ігнорується. - Шукають
Reflect.call. Такого методу не існує: уReflectє лишеapply, тому аргументи завжди передають масивом. - Вважають, що
bindвикличе функцію. Він лише повертає нову функцію, і без дужок нічого не відбудеться. - Розраховують на
windowу строгому режимі. У модулях і класахthisприnullзалишаєтьсяundefined, томуthis.nameкине помилку. - Втрачають контекст при передачі методу.
setTimeout(user.sayHi, 1000)передає лише функцію без об'єкта; потрібенuser.sayHi.bind(user)або стрілкова обгортка.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.