this
Ключове слово this
("це") посилається на контекст, у якому має виконуватися уривок коду. Найчастіше воно використовується в методах об'єктів, де this
посилається на об'єкт, до якого приєднано метод, що дає змогу використовувати один і той же метод для різних об'єктів.
Значення this
у JavaScript залежить від того, як функція закликана (зв'язування під час виконання), а не тим, як вона визначена. Коли звичайна функція закликається як метод об'єкта (obj.method()
), this
вказує на цей об'єкт. Коли вона закликається як самостійна функція (не приєднана до жодного об'єкта – func()
), this
зазвичай посилається на глобальний об'єкт (у несуворому режимі) або undefined
(у суворому режимі). Метод Function.prototype.bind()
може створити функцію, у якої зв'язування this
не змінюється, а методи Function.prototype.apply()
і Function.prototype.call()
можуть задати значення this
для конкретного виклику.
Стрілкові функції відрізняються щодо обробки this
: вони успадковують this
від батьківської області видимості під час свого визначення. Ця поведінка робить стрілкові функції особливо корисними як функцій зворотного виклику та для збереження контексту. Проте стрілкові функції не мають власного зв'язування this
. Тому їх значення this
не можна задати за допомогою методів bind()
, apply()
або call()
, і воно не посилається на поточний об'єкт у методах об'єктів.
Спробуйте його в дії
Синтаксис
this
Значення
У несуворому режимі this
завжди є посиланням на об'єкт. У суворому режимі він може бути будь-яким значенням. Більше про те, як визначається це значення, – в описі нижче.
Опис
Значення this
залежить від того, в якому контексті це ключове слово з'являється: функції, класу чи глобальному.
Контекст функції
Усередині функції значення this
залежить від того, як вона викликана. Про this
слід думати як про прихований параметр функції: як і параметри, оголошені у визначенні функції, this
– це зв'язування, яке мова створює при виконанні тіла функції.
Для звичайної функції (не стрілкової, не зв'язаної тощо) значення this
– це об'єкт, на якому викликається функція. Іншими словами, якщо виклик функції має вигляд obj.f()
, то this
посилається на obj
. Наприклад:
function getThis() {
return this;
}
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
obj1.getThis = getThis;
obj2.getThis = getThis;
console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }
Зверніть увагу на те, що функція одна й та ж, але в залежності від того, як вона викликається, значення this
різне. Це аналогічно тому, як працюють параметри функції.
Значення this
– це не об'єкт, що має функцію як власну властивість, а об'єкт, що використовується для виклику функції. Це можна довести, викликавши метод об'єкта, що знаходиться в ланцюжку прототипів.
const obj3 = {
__proto__: obj1,
name: "obj3",
};
console.log(obj3.getThis()); // { name: 'obj3' }
Значення this
завжди змінюється на основі того, як викликається функція, навіть якщо ця функція була визначена на об'єкті при створенні:
const obj4 = {
name: "obj4",
getThis() {
return this;
},
};
const obj5 = { name: "obj5" };
obj5.getThis = obj4.getThis;
console.log(obj5.getThis()); // { name: 'obj5', getThis: [Function: getThis] }
Якщо значення, на якому викликається метод, є примітивом, то this
також буде примітивом – але лише якщо функція викликається у суворому режимі.
function getThisStrict() {
"use strict"; // Перехід до суворого режиму
return this;
}
// Лише для демонстрації – не слід змінювати вбудовані прототипи
Number.prototype.getThisStrict = getThisStrict;
console.log(typeof (1).getThisStrict()); // "number"
Якщо функція викликається не як метод, то this
буде undefined
– але лише якщо функція викликається у суворому режимі.
console.log(typeof getThisStrict()); // "undefined"
У несуворому режимі особливий процес, що зветься заміною this
, пересвідчується, що значення this
завжди є об'єктом. Це означає, що:
- Якщо функція викликається так, що
this
задано якundefined
абоnull
, тоthis
замінюєтьсяglobalThis
. - Якщо функція викликається так, що
this
задано як примітивне значення, тоthis
замінюється об'єктом-обгорткою цього примітивного значення.
function getThis() {
return this;
}
// Лише для демонстрації – не слід змінювати вбудовані прототипи
Number.prototype.getThis = getThis;
console.log(typeof (1).getThis()); // "object"
console.log(getThis() === globalThis); // true
При типових викликах функцій this
неявно передається, неначе параметр, через префікс функції (частину перед крапкою). Також можна задати значення this
явно – за допомогою методів Function.prototype.call()
, Function.prototype.apply()
або Reflect.apply()
. За допомогою Function.prototype.bind()
можна створити нову функцію з певним значенням this
, яке не змінюється незалежно від того, як вона викликається. При використанні цих методів правила заміни this
, зазначені вище, все одно застосовуються, якщо ця функція є несуворою.
Зворотний виклик
Коли функція передається як зворотний виклик, то значення this
залежить від того, як вона викликається, що визначається автором API. Зазвичай зворотний виклик виконується з this
зі значенням undefined
(безпосередній виклик, без приєднання до будь-якого об'єкта), а отже, якщо функція є несуворою, то значення this
– це глобальний об'єкт (globalThis
). Це стосується ітеративних методів масиву, конструктора Promise()
тощо.
function logThis() {
"use strict";
console.log(this);
}
[1, 2, 3].forEach(logThis); // undefined, undefined, undefined
Частина API дозволяє задати значення this
для виконання зворотного виклику. Наприклад, усі ітеративні методи масиву та пов'язані з ними, як то Set.prototype.forEach()
, приймають необов'язковий параметр thisArg
.
[1, 2, 3].forEach(logThis, { name: "obj" });
// { name: 'obj' }, { name: 'obj' }, { name: 'obj' }
Іноді зворотний виклик виконується зі значенням this
, відмінним від undefined
. Наприклад, і параметр reviver
JSON.parse()
, і параметр replacer
JSON.stringify()
викликаються з this
, чиїм значенням задано об'єкт, до якого належить оброблювана властивість.
Стрілкові функції
У стрілкових функціях this
зберігає значення this
навколишнього лексичного контексту. Іншими словами, при виконанні тіла стрілкової функції мова не створює нового зв'язування this
.
Наприклад, у глобальному коді this
завжди має значення globalThis
, незалежно від суворості, у зв'язку зі зв'язуванням глобального контексту:
const globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); // true
Стрілкові функції утворюють замикання над значенням this
навколишнього лексичного контексту, що означає, що вони поводяться так, ніби вони "автоматично зв'язуються": незалежно від того, як вони закликаються, this
задається таким, яким воно було, коли функція була створена (у прикладі вище – має значення глобального об'єкта). Те ж саме стосується стрілкових функцій, створених всередині інших функцій: їх this
залишається таким, яким воно було в лексичному контексті, що оточує їх. Дивіться приклад нижче.
Понад те, при закликанні стрілкових функцій за допомогою call()
, bind()
або apply()
– параметр thisArg
ігнорується. Проте цим методам все одно можна передавати інші аргументи.
const obj = { name: "obj" };
// Спроба задати this за допомогою call
console.log(foo.call(obj) === globalObject); // true
// Спроба задати this за допомогою bind
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true
Конструктори
Коли функція використовується як конструктор (з ключовим словом new
), то її this
зв'язується з новим об'єктом, який створюється, незалежно від того, на якому об'єкті викликаний такий конструктор. Значення this
стає значенням виразу new
, якщо конструктор не повертає іншого непримітивного значення.
function C() {
this.a = 37;
}
let o = new C();
console.log(o.a); // 37
function C2() {
this.a = 37;
return { a: 38 };
}
o = new C2();
console.log(o.a); // 38
У другому прикладі (C2
), оскільки об'єкт був повернений під час конструювання, то новий об'єкт, з яким було зв'язано this
, відкидається. (По суті це робить інструкцію this.a = 37;
мертвим кодом. Вона не зовсім мертва, оскільки виконується, але її можна виключити без зовнішніх ефектів.)
super
Коли функція закликається в формі super.method()
, то this
всередині функції method
має таке ж значення, як і this
навколо виклику super.method()
, і, загалом, не дорівнює об'єкту, на який посилається super
. Це пов'язано з тим, що super.method
не є звертанням до члена об'єкта, як у випадках вище – це спеціальний синтаксис з іншими правилами зв'язування. Дивіться приклади в довідці по super
.
Класовий контекст
Клас може бути розбитий на два контексти: статичний та примірника. Конструктори, методи та ініціалізатори полів примірника (публічні та приватні) належать до контексту примірника. Статичні методи, ініціалізатори статичних полів та статичні блоки ініціалізації належать до статичного контексту. Значення this
в кожному контексті – різне.
Конструктори класів завжди викликаються з new
, тож їхня поведінка – така сама, як у конструкторів-функцій: значення this
– це новий примірник, що створюється. Методи класів поводяться неначе методи об'єктних літералів: значення this
– це об'єкт, на якому викликається метод. Якщо метод не перенесений до іншого об'єкта, то this
– це, як правило, примірник класу.
Статичні методи не є властивостями this
. Вони є властивостями самого класу. Таким чином, до них, як правило, звертаються через клас, а this
– це значення класу (або підкласу). Статичні блоки ініціалізації також виконуються з this
, що має значення поточного класу.
Ініціалізатори полів також виконуються в контексті класу. Поля примірників обчислюються з this
, що має значення примірника, що конструюється. Статичні поля обчислюються з this
, що має значення поточного класу. Саме тому стрілкові функції в ініціалізаторах полів зв'язуються з примірниками у випадку полів примірника, але з класом у випадку статичних полів.
class C {
instanceField = this;
static staticField = this;
}
const c = new C();
console.log(c.instanceField === c); // true
console.log(C.staticField === C); // true
Конструктори похідних класів
На відміну від конструкторів базових класів, похідні конструктори не мають початкового зв'язування this
. Виклик super()
породжує всередині конструктора зв'язування this
і, по суті, має ефект виконання наступного рядка коду, де Base
– базовий клас:
this = new Base();
[!WARNING] Звертання до
this
перед викликомsuper()
призведе до помилки.
Похідні класи не повинні повертати значення до виклику super()
, якщо конструктор не повертає об'єкт (тобто значення this
перевизначається) або якщо клас не має конструктора взагалі.
class Base {}
class Good extends Base {}
class AlsoGood extends Base {
constructor() {
return { a: 5 };
}
}
class Bad extends Base {
constructor() {}
}
new Good();
new AlsoGood();
new Bad(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Глобальний контекст
У глобальному контексті виконання (поза будь-якими функціями чи класами; можливо, всередині блоків чи стрілкових функцій, що визначені в глобальному контексті) значення this
залежить від того, в якому контексті виконання працює скрипт. Як і для зворотних викликів, значення this
визначається середовищем виконання (викликачем).
На верхньому рівні сценарію this
вказує на globalThis
, незалежно від того, чи використовується суворий режим, чи ні. Це, як правило, те саме, що й глобальний об'єкт — наприклад, якщо вихідний код поміщено всередину елемента HTML <script>
та виконується як сценарій, то this === window
.
[!NOTE]
globalThis
– це, як правило, та ж концепція, що й глобальний об'єкт (тобто додавання властивостей доglobalThis
робить їх глобальними змінними) – це саме так для браузерів та Node – але хостам дозволено надавати інше значення дляglobalThis
, яке не пов'язане з глобальним об'єктом.
// У веббраузерах об'єкт window також є глобальним об'єктом:
console.log(this === window); // true
this.b = "WebDoky";
console.log(window.b); // "WebDoky"
console.log(b); // "WebDoky"
Якщо вихідний код – завантажений як модуль (для HTML це означає додавання type="module"
до тега <script>
), то this
на верхньому рівні завжди дорівнює undefined
.
Якщо вихідний код виконується за допомогою eval()
, то this
такий самий, як і навколишній контекст – для безпосереднього eval, або globalThis
(якщо він виконується в окремому глобальному сценарії) – для опосередкованого eval.
function test() {
// Безпосередній eval
console.log(eval("this") === this);
// Опосередкований eval, несуворий режим
console.log(eval?.("this") === globalThis);
// Опосередкований eval, суворий режим
console.log(eval?.("'use strict'; this") === globalThis);
}
test.call({ name: "obj" }); // Виводить 3 "true"
Зверніть увагу на те, що частина вихідного коду, хоча і має вигляд глобального контексту, насправді обгортається функцією під час виконання. Наприклад, модулі Node.js CommonJS обгортаються функціями та виконуються зі значенням this
, заданим як module.exports
. Атрибути обробників подій виконуються з this
, заданим як елемент, до якого вони прикріплені.
Об'єктні літерали не утворюють області видимості this
– це роблять лише функції (методи), визначені всередині об'єкта. Використання this
в об'єктному літералі успадковує значення з навколишньої області видимості.
const obj = {
a: this,
};
console.log(obj.a === window); // true
Приклади
this у контекстах функцій
Значення this
залежить від того, як функція викликається, а не від того, як вона визначена.
// Об'єкт можна передати як перший аргумент до call
// або apply, і this буде зв'язано з цим об'єктом.
const obj = { a: "Custom" };
// Змінні, оголошені за допомогою var, стають властивостями глобального об'єкта.
var a = "Global";
function whatsThis() {
return this.a; // Значення this залежить від того, як функція викликається.
}
whatsThis(); // 'Global'; this у функції не задано, тож у несуворому режимі отримує усталене значення – глобального об'єкта – window
obj.whatsThis = whatsThis;
obj.whatsThis(); // 'Custom'; this у функції задано як obj
За допомогою call()
та apply()
можна передати значення this
як справжній параметр.
function add(c, d) {
return this.a + this.b + c + d;
}
const o = { a: 1, b: 3 };
// Перший параметр – це об'єкт для використання як 'this'; наступні
// параметри використовуються як аргументи у виклику функції
add.call(o, 5, 7); // 16
// Перший параметр – це об'єкт для використання як 'this'; наступний –
// це масив, чиї члени використовуються як аргументи у виклику функції
add.apply(o, [10, 20]); // 34
this і перетворення об'єктів
У несуворому режимі, якщо функція викликається зі значенням this
, що не є об'єктом, то значення this
замінюється об'єктом. null
і undefined
стають globalThis
. Примітиви штибу 7
і 'foo'
– перетворюються на об'єкт за допомогою відповідного конструктора, тож примітивне число 7
перетворюється на класову обгортку Number
, а рядок 'foo'
– на класову обгортку String
.
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call("foo"); // [object String]
bar.call(undefined); // [object Window]
Метод bind()
Виклик f.bind(someObject)
породжує нову функцію з такими ж тілом і областю видимості, як в f
, але значення this
беззмінно зв'язано з першим аргументом bind
, незалежно від того, як функція викликається.
function f() {
return this.a;
}
const g = f.bind({ a: "azerty" });
console.log(g()); // azerty
const h = g.bind({ a: "yoo" }); // bind працює лише раз!
console.log(h()); // azerty
const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h()); // 37,37, azerty, azerty
this у стрілкових функціях
Стрілкові функції утворюють замикання навколишнього контексту виконання над this
. У наступному прикладі створюється obj
з методом getThisGetter
, що повертає функцію, котра повертає значення this
. Повернена функція створюється як стрілкова, тож її this
беззмінно зв'язане з this
її навколишньої функції. Значення this
всередині getThisGetter
можна задати у виклику, що, своєю чергою, задає повернене значення поверненої функції.
const obj = {
getThisGetter() {
const getter = () => this;
return getter;
},
};
Можна викликати getThisGetter
як метод obj
, що задасть this
усередині тіла значенням obj
. Повернена функція присвоюється змінній fn
. Відтоді, коли викликати fn
, то значення this
, що повертається, все одно буде тим, що задано викликом getThisGetter
, тобто obj
. Якби повернена функція не була стрілковою, такі виклики призвели б до того, що значення this
було б globalThis
, або undefined
у суворому режимі.
const fn = obj.getThisGetter();
console.log(fn() === obj); // true
Однак будьте обережні, коли відв'язуєте метод obj
, його не викликаючи, тому що getThisGetter
все одно лишається методом з мінливим значенням this
. Виклик fn2()()
у наступному прикладі повертає globalThis
, тому що він слідує за this
з fn2
, що дорівнює globalThis
, оскільки виклик відбувається без приєднання до будь-якого об'єкта.
const fn2 = obj.getThisGetter;
console.log(fn2()() === globalThis); // true
Така логіка є дуже корисною при визначенні зворотних викликів. Зазвичай кожен вираз функції утворює власне зв'язування this
, котре затіняє значення this
у вищому контексті. Тепер можна визначати функції як стрілкові, якщо значення this
не цікаве, і створювати зв'язування this
лише там, де воно потрібне (наприклад, у методах класу). Дивіться приклад з setTimeout()
.
this із гетером або сетером
Значення this
у гетерах і сетерах засновано на тому, на якому об'єкті відбувається звертання до властивості, а не тому, на якому об'єкті ця властивість визначена. Функція, що використовується як гетер або сетер, має власне значення this
, зв'язане з об'єктом, на якому властивість задається чи отримується.
function sum() {
return this.a + this.b + this.c;
}
const o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
},
};
Object.defineProperty(o, "sum", {
get: sum,
enumerable: true,
configurable: true,
});
console.log(o.average, o.sum); // 2, 6
Як обробник подій DOM
Коли функція вживається як обробник подій, її this
отримує значення елемента, на якому розташовано слухача (деякі браузери не дотримуються цієї домовленості щодо слухачів, доданих динамічно за допомогою методів, відмінних від addEventListener()
).
// Коли викликається як слухач, робить відповідний елемент блакитним
function bluify(e) {
// Завжди істинно
console.log(this === e.currentTarget);
// істинно, коли currentTarget і target – один і той же об'єкт
console.log(this === e.target);
this.style.backgroundColor = "#A5D9F3";
}
// Отримати список всіх елементів у документі
const elements = document.getElementsByTagName("*");
// Додати bluify як слухач клацання, щоб коли
// елемент був клацнутий, він ставав блакитним
for (const element of elements) {
element.addEventListener("click", bluify, false);
}
this у контекстуальних обробниках подій
Коли код викликається з контекстуального атрибута обробника подій, його this
отримує елемент DOM, на якому розташовано слухача:
<button onclick="alert(this.tagName.toLowerCase());">Покажи this</button>
Виклик alert вище виводить button
. Зверніть увагу, що лише зовнішній код має таке значення this
:
<button onclick="alert((function () { return this; })());">
Покажи внутрішній this
</button>
У такому випадку this
внутрішньої функції не задається, тож це значення повертає глобальний об'єкт – window (тобто усталений об'єкт у несуворому режимі, коли this
не задано викликом).
Зв'язані методи в класах
Як і у звичайних функціях, значення this
у методах залежить від того, як вони викликаються. Іноді корисно перевизначити цю поведінку, щоб this
у класах завжди посилалося на примірник класу. Щоб досягти цього, методи класу необхідно зв'язати у конструкторі:
class Car {
constructor() {
// Зв'язати sayBye, але не sayHi, щоб продемонструвати різницю
this.sayBye = this.sayBye.bind(this);
}
sayHi() {
console.log(`Привіт. ${this.name}`);
}
sayBye() {
console.log(`Бувай. ${this.name}`);
}
get name() {
return "Феррарі";
}
}
class Bird {
get name() {
return "Соловейко";
}
}
const car = new Car();
const bird = new Bird();
// Значення 'this` у методах залежить від їхнього викликача
car.sayHi(); // Привіт. Феррарі
bird.sayHi = car.sayHi;
bird.sayHi(); // Привіт. Соловейко
// Для зв'язаних методів 'this' не залежить від викликача
bird.sayBye = car.sayBye;
bird.sayBye(); // Бувай. Феррарі
[!NOTE] Класи завжди працюють в суворому режимі. Виклик методів з невизначеним
this
викличе помилку, якщо метод спробує отримати доступ до властивостейthis
.const carSayHi = car.sayHi; carSayHi(); // TypeError, тому що метод 'sayHi' намагається звернутися до 'this.name', а 'this' у суворому режимі має значення undefined.
Проте зверніть увагу, що автоматично зв'язані методи страждають від тієї ж проблеми, що й використання стрілкових функцій для класових властивостей: кожний примірник класу матиме власну копію метода, що збільшує використання пам'яті. Їх слід використовувати лише тоді, коли це абсолютно необхідно. Також можна зімітувати реалізацію Intl.NumberFormat.prototype.format()
: визначити властивість як гетер, що повертає зв'язану функцію при звертанні до неї та зберігає її, щоб функція створювалася лише один раз і лише тоді, коли це необхідно.
this в інструкціях with
Попри те, що інструкція with
– нерекомендована, а в суворому режимі – недоступна, вона все одно служить винятком для звичайних правил зв'язування this
. Якщо функція викликана зсередини інструкції with
, і ця функція є властивістю об'єкта області видимості, то значенням this
буде об'єкт області видимості, як якби виклик мав префікс obj1.
.
const obj1 = {
foo() {
return this;
},
};
with (obj1) {
console.log(foo() === obj1); // true
}
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
this
|
Chrome Full support 1 | Edge Full support 12 | Firefox Full support 1 | Internet Explorer Full support 4 | Opera Full support 9.5 | Safari Full support 1 | WebView Android Full support 1 | Chrome Android Full support 18 | Firefox for Android Full support 4 | Opera Android Full support 10.1 | Safari on iOS Full support 1 | Samsung Internet Full support 1.0 | Deno Full support 1.0 | Node.js Full support 0.10.0 |