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 |