Пошук уроків, статей та іншого контенту
Розберете контекст виклику, значення this і способи безпечно використовувати this у методах.
Методом називають функцію, яка зберігається у властивості об’єкта і викликається через цей об’єкт.
const user = {
name: "Олена",
introduce() {
return `Мене звати ${this.name}`;
},
};
console.log(user.introduce()); // Мене звати ОленаУ цьому прикладі introduce — метод об’єкта user, а this під час виклику посилається на user.
Значення this не визначається місцем, де функцію написали. Воно зазвичай визначається способом її виклику.
Для звичайної функції важливе не лише її оголошення, а й те, як саме її викликали.
const calculator = {
value: 10,
add(number) {
this.value += number;
},
};
calculator.add(5);
console.log(calculator.value); // 15Вираз calculator.add(5) означає:
знайти функцію у властивості calculator.add;
викликати її як метод об’єкта calculator;
встановити this рівним calculator.
Метод можна зберігати в змінній або використовувати для різних об’єктів. У такому разі this залежатиме від конкретного виклику.
function describe() {
return `${this.name}: ${this.role}`;
}
const developer = {
name: "Ірина",
role: "Frontend developer",
describe,
};
const designer = {
name: "Максим",
role: "UI designer",
describe,
};
console.log(developer.describe()); // Ірина: Frontend developer
console.log(designer.describe()); // Максим: UI designerФункція describe одна, але під час виклику через різні об’єкти отримує різний this.
Якщо передати метод як окрему функцію, зв’язок з об’єктом може бути втрачений.
const user = {
name: "Олена",
greet() {
return `Привіт, ${this.name}`;
},
};
const greet = user.greet;
console.log(greet());У сучасному JavaScript-файлі або модулі ця функція працюватиме в strict mode. Для звичайної функції this буде undefined, тому звернення до this.name спричинить помилку.
Сама передача методу не викликає його:
const callback = user.greet; // функцію відокремлено від user
callback(); // виклик без об'єктаЦя проблема часто виникає, коли метод передають як callback:
const timer = {
seconds: 0,
tick() {
this.seconds += 1;
console.log(this.seconds);
},
};
// Контекст timer буде втрачено
setTimeout(timer.tick, 1000);Найпростіший спосіб — викликати метод усередині стрілкової функції.
const timer = {
seconds: 0,
tick() {
this.seconds += 1;
console.log(`Минуло секунд: ${this.seconds}`);
},
};
setTimeout(() => {
timer.tick();
}, 1000);Стрілкова функція не має власного this, але в цьому прикладі це не проблема: вона явно викликає timer.tick().
bindМетод bind створює нову функцію, у якої this назавжди прив’язаний до вказаного об’єкта.
const user = {
name: "Олена",
greet() {
console.log(`Привіт, ${this.name}`);
},
};
const boundGreet = user.greet.bind(user);
boundGreet(); // Привіт, Оленаbind особливо корисний, коли функцію потрібно передати як callback:
const user = {
name: "Олена",
greet() {
console.log(`Привіт, ${this.name}`);
},
};
setTimeout(user.greet.bind(user), 1000);Важливо: bind не змінює оригінальну функцію. Він повертає нову.
const boundGreet = user.greet.bind(user);
console.log(boundGreet === user.greet); // falsecall і applyМетоди call та apply дозволяють вручну визначити значення this під час виклику функції.
callАргументи передаються через кому:
function introduce(language, experience) {
return `${this.name} — ${language}-розробник зі стажем ${experience} років`;
}
const developer = {
name: "Ірина",
};
console.log(introduce.call(developer, "JavaScript", 4));
// Ірина — JavaScript-розробник зі стажем 4 роківapplyapply працює подібно до call, але приймає аргументи масивом:
function calculateTotal(tax, discount) {
return this.price + tax - discount;
}
const product = {
price: 1000,
};
const result = calculateTotal.apply(product, [200, 100]);
console.log(result); // 1100У сучасному коді замість apply часто використовують оператор розпилення:
const values = [200, 100];
const result = calculateTotal.call(product, ...values);
console.log(result); // 1100call, apply і bindcall викликає функцію одразу й передає аргументи окремо.
apply викликає функцію одразу й передає аргументи масивом.
bind не викликає функцію одразу, а повертає нову прив’язану функцію.
thisСтрілкові функції не мають власного this. Вони використовують this із зовнішнього контексту.
Через це стрілкова функція зазвичай не підходить для методу об’єкта:
const user = {
name: "Олена",
greet: () => {
return `Привіт, ${this.name}`;
},
};
console.log(user.greet());У цьому випадку this не посилається на user. Стрілкова функція бере його із зовнішнього контексту, де властивості name може не бути.
Для методу, якому потрібен об’єкт через this, використовуйте звичайну функцію:
const user = {
name: "Олена",
greet() {
return `Привіт, ${this.name}`;
},
};
console.log(user.greet()); // Привіт, ОленаСтрілкові функції корисні всередині методів, коли потрібно зберегти контекст зовнішнього методу:
const team = {
name: "Web Team",
members: ["Олена", "Максим", "Ірина"],
printMembers() {
this.members.forEach((member) => {
console.log(`${this.name}: ${member}`);
});
},
};
team.printMembers();Стрілкова функція в forEach не створює власного this, тому використовує this методу printMembers.
Для порівняння, звичайна callback-функція має окремий контекст виклику:
const team = {
name: "Web Team",
members: ["Олена", "Максим"],
printMembers() {
this.members.forEach(function (member) {
console.log(`${this.name}: ${member}`);
});
},
};
team.printMembers();У цьому прикладі this усередині callback не є team. Щоб виправити код, можна використати стрілкову функцію або явно прив’язати контекст:
this.members.forEach(function (member) {
console.log(`${this.name}: ${member}`);
}.bind(this));this у вкладених функціяхЗвичайна вкладена функція не успадковує this зовнішнього методу.
const counter = {
value: 0,
increase() {
function change() {
this.value += 1;
}
change();
},
};
counter.increase();У strict mode this усередині change буде undefined.
Правильний варіант зі стрілковою функцією:
const counter = {
value: 0,
increase() {
const change = () => {
this.value += 1;
};
change();
},
};
counter.increase();
console.log(counter.value); // 1Або можна зберегти посилання на контекст у змінній:
const counter = {
value: 0,
increase() {
const self = this;
function change() {
self.value += 1;
}
change();
},
};Сьогодні перевагу зазвичай надають стрілковій функції, оскільки вона робить намір очевиднішим.
this у конструкторах і класахУ конструкторі класу this посилається на новий екземпляр, який створюється за допомогою new.
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Привіт, ${this.name}`;
}
}
const firstUser = new User("Олена");
const secondUser = new User("Максим");
console.log(firstUser.greet()); // Привіт, Олена
console.log(secondUser.greet()); // Привіт, МаксимКожен екземпляр має власне значення name, а метод greet використовує this для доступу до даних поточного екземпляра.
Однак під час передачі методу класу як callback контекст також можна втратити:
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Привіт, ${this.name}`);
}
}
const user = new User("Олена");
setTimeout(user.greet, 1000);Без прив’язки виклик відбувається вже не як user.greet(). Один із варіантів виправлення:
setTimeout(user.greet.bind(user), 1000);Інший варіант — створити властивість зі стрілковою функцією в конструкторі:
class User {
constructor(name) {
this.name = name;
this.greet = () => {
console.log(`Привіт, ${this.name}`);
};
}
}
const user = new User("Олена");
setTimeout(user.greet, 1000);Такий підхід автоматично зберігає контекст, але створює окрему функцію для кожного екземпляра. Тому його варто застосовувати, коли це справді потрібно.
this у гетерах і сетерахГетери й сетери також можуть використовувати this.
const rectangle = {
width: 10,
height: 5,
get area() {
return this.width * this.height;
},
set size(value) {
this.width = value;
this.height = value;
},
};
console.log(rectangle.area); // 50
rectangle.size = 4;
console.log(rectangle.area); // 16Гетер викликається без дужок:
rectangle.area;Сетер викликається під час присвоєння:
rectangle.size = 4;this у ланцюжках викликівМетод може повертати this, щоб дозволити ланцюжок викликів.
const builder = {
result: "",
add(text) {
this.result += text;
return this;
},
clear() {
this.result = "";
return this;
},
};
builder
.add("JavaScript ")
.add("дає змогу ")
.add("створювати застосунки");
console.log(builder.result);
// JavaScript дає змогу створювати застосункиПовернення this означає, що наступний метод викликається на тому самому об’єкті.
Такий стиль називають fluent interface. Він зручний, але не слід використовувати його без потреби: надто довгі ланцюжки можуть ускладнювати читання коду.
this у різних ситуаціяхДля звичайних функцій корисно пам’ятати такі правила:
object.method()this — це object.
function.call(object)this — це явно переданий object.
function.apply(object, args)this — це явно переданий object.
function.bind(object)
створюється функція з прив’язаним this.
new Function()this — новий створений екземпляр.
Стрілкова функція
власного this немає, використовується this із зовнішнього контексту.
Відокремлений виклик звичайної функції в strict modethis — undefined.
Під час аналізу коду спочатку знайдіть місце виклику функції. Саме воно найчастіше визначає значення this.
Нижче наведено повний приклад об’єкта кошика. Метод getTotal працює з контекстом кошика, а callback у map є стрілковою функцією, тому безпечно використовує this.discount.
"use strict";
const cart = {
discount: 0.1,
items: [
{ name: "Клавіатура", price: 2500, quantity: 1 },
{ name: "Миша", price: 1200, quantity: 2 },
],
getSubtotal() {
return this.items.reduce((total, item) => {
return total + item.price * item.quantity;
}, 0);
},
getTotal() {
const subtotal = this.getSubtotal();
return subtotal * (1 - this.discount);
},
getSummary() {
const lines = this.items.map((item) => {
const itemTotal = item.price * item.quantity;
return `${item.name}: ${item.quantity} × ${item.price} = ${itemTotal} грн`;
});
lines.push(`Знижка: ${this.discount * 100}%`);
lines.push(`Разом: ${this.getTotal()} грн`);
return lines.join("\n");
},
};
console.log(cart.getSummary());Результат:
Клавіатура: 1 × 2500 = 2500 грн
Миша: 2 × 1200 = 2400 грн
Знижка: 10%
Разом: 4410 грнМетод getSummary викликає інші методи через this, а стрілкова функція в map зберігає контекст методу.
thisПід час проєктування методів дотримуйтеся кількох правил:
Викликайте методи через об’єкт, якщо вони використовують this.
Не передавайте метод напряму як callback без bind або обгортки.
Використовуйте стрілкові функції для вкладених callback, яким потрібен зовнішній this.
Не оголошуйте метод об’єкта стрілковою функцією, якщо він має звертатися до об’єкта через this.
Не покладайтеся на неявне глобальне значення this.
Для функцій, які передаються між компонентами або в обробники подій, заздалегідь визначте, хто має бути їхнім контекстом.
Якщо функція не потребує стану об’єкта, іноді краще не робити її методом і не використовувати this.
const user = {
name: "Олена",
getName() {
return this.name;
},
};
const getName = user.getName;
// getName() — контекст user втраченоВиправлення:
const getName = user.getName.bind(user);
console.log(getName()); // Оленаconst user = {
name: "Олена",
getName: () => this.name,
};Стрілкова функція не отримує this від user. Використовуйте скорочений синтаксис методу:
const user = {
name: "Олена",
getName() {
return this.name;
},
};this збережеться в setTimeoutconst counter = {
value: 0,
increment() {
this.value += 1;
},
};
setTimeout(counter.increment, 1000);Виправлення:
setTimeout(() => {
counter.increment();
}, 1000);або:
setTimeout(counter.increment.bind(counter), 1000);const user = {
name: "Олена",
greet() {
return this.name;
},
};
const anotherGreet = user.greet;
console.log(anotherGreet === user.greet); // trueФункція та сама, але спосіб виклику інший. Порівняння функцій не прив’язує їх до об’єкта.
bindbind фіксує посилання на об’єкт, а не копіює його значення.
const user = {
name: "Олена",
};
function greet() {
return this.name;
}
const boundGreet = greet.bind(user);
user.name = "Максим";
console.log(boundGreet()); // МаксимПрив’язана функція все ще використовує той самий об’єкт user, тому бачить його актуальні властивості.
Метод — це функція у властивості об’єкта.
Значення this для звичайної функції визначається способом її виклику.
У виклику object.method() значення this дорівнює object.
Передавання методу як окремої функції може призвести до втрати контексту.
bind створює функцію з постійно прив’язаним this.
call і apply дозволяють викликати функцію з указаним контекстом.
Стрілкові функції не мають власного this.
Стрілкові функції зручні у вкладених callback, але зазвичай не підходять як методи об’єкта.
У класах this посилається на поточний екземпляр.
Щоб правильно працювати з this, завжди аналізуйте фактичне місце виклику функції.