this Keyword & Execution Context

"this բանալի բառը JavaScript-ի ամենակարևոր, բայց միևնույն ժամանակ ամենաշատ շփոթմունք առաջացնող հասկացություններից մեկն է։"

this-ը JavaScript-ում փոփոխական չէ և կապված չէ այն scope-ի հետ, թե որտեղ է ֆունկցիան գրված։

Ոսկե կանոն:

this-ի արժեքը որոշվում է ֆունկցիայի կանչման պահին (Dynamic Binding)՝ կախված նրանից, թե ԻՆՉՊԵՍ է այն կանչվել, այլ ոչ թե ՈՐՏԵՂ է հայտարարված:

JavaScript-ում this-ի արժեքը որոշելու համար գործում է 4 հիմնական կանոն՝ ըստ առաջնահերթության (Priority):

1. Explicit Binding (Բացահայտ կապում՝ call, apply, bind)

Ամենաբարձր առաջնահերթությունն է։ Մենք ինքներս ենք ստիպողաբար ֆունկցիային ասում, թե ինչ լինի this-ի արժեքը։

  • call(thisArg, arg1, arg2...) — Կանչում է ֆունկցիան անմիջապես՝ փոխանցելով this-ը և արգումենտները հատ-հատ։
  • apply(thisArg, [args]) — Նույնն է, ինչ call-ը, բայց արգումենտները փոխանցվում են զանգվածով (array)։
  • bind(thisArg, arg1...) — Չի կանչում ֆունկցիան անմիջապես, այլ վերադարձնում է նոր ֆունկցիա՝ պինդ կապված (bound) this-ով։
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const user = { name: 'Hayk' };

// Call & Apply
greet.call(user, 'Hello', '!');  // Output: "Hello, Hayk!"
greet.apply(user, ['Hi', '.']);  // Output: "Hi, Hayk."

// Bind
const boundGreet = greet.bind(user, 'Welcome');
boundGreet('!'); // Output: "Welcome, Hayk!"

2. Implicit Binding (Անուղղակի / Օբյեկտային կապում)

Եթե ֆունկցիան կանչվում է որպես օբյեկտի մեթոդ (obj.method()), this-ը դառնում է կետից (dot) ձախ գտնվող օբյեկտը։

const user = {
  name: 'Ani',
  showName() {
    console.log(this.name);
  }
};

user.showName(); // `this`-ը `user` օբյեկտն է -> Output: "Ani"

⚠️ Կարևոր վտանգ (Loss of Implicit Binding / «Կորսված this»):

Եթե մեթոդը հանում ենք օբյեկտից և վերագրում փոփոխականի կամ փոխանցում որպես callback, կապը կորչում է։

const fn = user.showName;
fn(); // undefined (կամ TypeError strict mode-ում), որովհետև կանչի պահին կետ չկա!

3. New Binding (Կոնստրուկտորի կապում)

Երբ ֆունկցիան կանչվում է new օպերատորով (new User()), JavaScript-ը ետնաբեմում կատարում է հետևյալ քայլերը․

  1. Ստեղծում է նոր դատարկ օբյեկտ {}։
  2. this-ը կապում է այդ նոր ստեղծված օբյեկտին։
  3. Կապում է նոր օբյեկտի prototype-ը ֆունկցիայի prototype հղման հետ։
  4. Վերադարձնում է այդ օբյեկտը (եթե ֆունկցիան explicit ուրիշ օբյեկտ չի վերադարձնում)։
function Person(name) {
  // 1. this = {}; (աննկատ)
  this.name = name;
  // 3. return this; (աննկատ)
}

const dev = new Person('Dave');
console.log(dev.name); // Output: "Dave"

4. Default Binding (Լռելյայն կապում)

Եթե նախորդ 3 կանոններից և ոչ մեկը չի համապատասխանում (սովորական ֆունկցիայի կանչ առանց կետի)․

  • Non-strict mode: this-ը հղվում է գլոբալ օբյեկտին (window բրաուզերում, global Node.js-ում)։
  • Strict mode ('use strict'): this-ը ստանում է undefined արժեք։
function show() {
  'use strict';
  console.log(this); 
}

show(); // Output: undefined

Սլաքային ֆունկցիաները չունեն իրենց սեփական this-ը։ Նրանք օգտագործում են Lexical this — վերցնում են this-ի արժեքը այն արտաքին scope-ից, որտեղ ստեղծվել են։

Կարևոր: call(), apply() կամ bind() մեթոդները չեն կարող փոխել սլաքային ֆունկցիայի this-ը։
const timer = {
  name: 'Counter',
  start() {
    // Սովորական ֆունկցիայի մեջ this-ը `timer`-ն է
    setTimeout(() => {
      // Arrow function-ը վերցնում է `this`-ը start()-ից
      console.log(this.name); 
    }, 1000);
  }
};

timer.start(); // 1 վայրկյան անց տպում է "Counter"
  1. 🎯 Արդյո՞ք սլաքային ֆունկցիա է (=>):
    👉 ԱՅՈ: Look outside! Վերցնում է արտաքին scope-ի this-ը։
  2. 🏗️ Արդյո՞ք կանչվել է new-ով:
    👉 ԱՅՈ: this-ը ստեղծվող նոր օբյեկտն է։
  3. ⚙️ Արդյո՞ք կանչվել է call, apply կամ bind-ով:
    👉 ԱՅՈ: this-ը փոխանցված առաջին արգումենտն է։
  4. 📦 Արդյո՞ք կանչվել է օբյեկտի միջոցով (obj.method()):
    👉 ԱՅՈ: this-ը կետից ձախ գտնվող օբյեկտն է (obj)։
  5. 🌐 Սովորական կանչ է (fn()):
    👉 Strict mode-ում ➔ undefined
    👉 Non-strict mode-ում ➔ window / global

Խնդիր 1: Arrow Function vs Regular Function

const obj = {
  a: 10,
  b: function() {
    console.log(this.a);
  },
  c: () => {
    console.log(this.a);
  }
};

obj.b(); // ❓
obj.c(); // ❓

Պատասխան:
• obj.b() ➔ 10 (Implicit Binding: this-ը obj-ն է)
• obj.c() ➔ undefined (Arrow function-ի արտաքին scope-ը global scope-ն է)

Խնդիր 2: Context-ի կորուստ Callback-ում

const user = {
  name: 'Hayk',
  greet() {
    console.log(`Hi, ${this.name}`);
  }
};

setTimeout(user.greet, 500); // ❓

Պատասխան: Hi, undefined (կամ սխալ strict mode-ում), որովհետև setTimeout-ին փոխանցվել է ֆունկցիայի հղումը, և այն կանչվելու է առանց context-ի։
Լուծումներ:
1. setTimeout(user.greet.bind(user), 500)
2. setTimeout(() => user.greet(), 500)