this

Ключове слово функцій this ("це") поводиться в JavaScript дещо інакше, коли порівняти з іншими мовами. Також воно має певні відмінності при роботі в суворому та несуворому режимах.

У більшості випадків значення this визначається тим, як функція викликана (зв'язування під час виконання). Воно не може бути задано присвоєнням під час її виконання, і воно може бути різним кожного разу, коли функція викликається. Метод Function.prototype.bind() може задати значення this функції незалежно від того, як вона викликається, а стрілкові функції не надають власного зв'язування this (вони зберігають значення this зовнішнього лексичного контексту).

Спробуйте його в дії

Синтаксис

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();

Застереження: Звертання до 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.

Примітка: 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(); // Бувай. Феррарі

Примітка: Класи завжди працюють в суворому режимі. Виклик методів з невизначеним 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
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
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

Дивіться також