درک «this» در جاوا اسکریپت - راهنمای کامل

اگه یه روزی با جاوا اسکریپت کار کرده باشی، حتماً یه جایی گیر کلمه‌ی this افتادی و با خودت گفتی «این دیگه چیه، چرا هر جا یه معنی می‌ده؟!». خب حق داری، چون this توی جاوا اسکریپت برخلاف خیلی از زبان‌های دیگه، ثابت نیست و بسته به اینکه یه تابع چطور صدا زده بشه، معنیش عوض می‌شه. توی این مقاله قراره قدم به قدم، با مثال‌های ساده، همه‌ی حالت‌های this رو با هم مرور کنیم تا دیگه هیچ‌وقت غافلگیرت نکنه.

1. اصل ماجرا: this یعنی چی؟

به زبون ساده، this یه ارجاع (reference) به «صاحب فراخوانی تابع» هست، نه صاحب تعریف تابع. یعنی مهم نیست تابع کجا نوشته شده، مهمه چطور و از کجا صداش زدی. همین نکته باعث می‌شه خیلی وقت‌ها رفتار this غیرمنتظره به نظر برسه.

مثال ساده از this
خروجی کنسول اینجا نمایش داده می‌شود...

اینجا چون sayHi از طریق آبجکت person صدا زده شده، this اشاره می‌کنه به همون person.

2. this در توابع معمولی (Regular Functions)

وقتی یه تابع معمولی رو مستقل صدا می‌زنی (بدون اینکه به یه آبجکت وصل باشه)، توی حالت غیر strict، this می‌ره سراغ آبجکت global (توی مرورگر یعنی window). اما توی strict mode، مقدارش می‌شه undefined. این رفتار به خود محیط اجرا (مرورگر یا Node) بستگی داره، برای همین به جای اجرای آنلاین، فقط کدش رو می‌بینیم:

function showThis() {
  console.log(this);
}

showThis(); // در حالت عادی: window یا global object

"use strict";
function strictShowThis() {
  console.log(this); // undefined
}
strictShowThis();

این یکی از رایج‌ترین دلایل باگ‌هایی هست که تازه‌کارها باهاش دست‌وپنجه نرم می‌کنن؛ فکر می‌کنن this همیشه به یه چیز خاص اشاره داره، در حالی که کاملاً به نحوه‌ی صدا زدن بستگی داره.

3. this داخل متد یه آبجکت

وقتی یه تابع به عنوان متد یه آبجکت صدا زده بشه (یعنی با نقطه قبلش)، this برابر با همون آبجکته. اما نکته‌ی مهم اینه که این وابستگی به «نحوه فراخوانی» داره نه به «جایی که تابع تعریف شده». بذار ببینیم وقتی متد رو از آبجکتش جدا می‌کنیم چه اتفاقی می‌افته:

از دست دادن this هنگام جدا کردن متد
خروجی کنسول اینجا نمایش داده می‌شود...

همین‌جاست که خیلی‌ها گیج می‌شن؛ تابع دقیقاً همونیه که بود، فقط چون جدا از آبجکت صداش زدیم، ارتباطش با this قطع شده.

4. this در Arrow Function‌ها (تفاوت اساسی)

Arrow function‌ها اصلاً this مخصوص به خودشون رو نمی‌سازن. اونا this رو از محیط اطراف خودشون (یعنی جایی که تعریف شدن، نه جایی که صدا زده می‌شن) به ارث می‌برن. به همین دلیل توی خیلی از موقعیت‌ها، arrow function‌ها راه‌حل مشکلات مربوط به this هستن. بیا نتیجه‌ش رو ببینیم:

مقایسه function معمولی و arrow function
خروجی کنسول اینجا نمایش داده می‌شود...

اگه اینجا به جای arrow function از یه function معمولی داخل forEach استفاده می‌کردیم، this دیگه به team اشاره نمی‌کرد و باید یه فکری براش می‌کردیم (که توی بخش بعدی می‌گیم چطور).

5. کنترل دستی this با call، apply و bind

گاهی خودمون می‌خوایم مشخص کنیم this داخل یه تابع دقیقاً چی باشه. برای این کار سه تا متد داریم:

  • call() - تابع رو صدا می‌زنه و this رو مشخص می‌کنه (آرگومان‌ها تک‌تک)
  • apply() - مثل call ولی آرگومان‌ها به صورت آرایه پاس داده می‌شن
  • bind() - یه نسخه‌ی جدید از تابع می‌سازه که this توش برای همیشه قفل شده

بیایم با یه مثال ببینیم هر کدوم چطور خروجی می‌دن:

استفاده از call, apply و bind
خروجی کنسول اینجا نمایش داده می‌شود...

bind به خصوص توی event handler‌ها خیلی کاربردیه، چون می‌تونی از قبل مطمئن بشی this همیشه همونیه که خودت می‌خوای.

6. this داخل کلاس‌ها (Classes)

داخل کلاس‌های جاوا اسکریپت، this معمولاً به نمونه‌ای (instance) اشاره داره که ساخته شده:

class Counter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count++;
    console.log(this.count);
  }
}

const counter = new Counter();
counter.increment(); // 1
counter.increment(); // 2

اما اگه یه متد کلاس رو جدا از نمونه صدا بزنی (مثلاً به عنوان callback بدی)، همون مشکل قبلی برمی‌گرده و this رو از دست می‌دی. بیایم این حالتِ خرابی رو با اجرای واقعی ببینیم:

از دست دادن this در متد کلاس
خروجی کنسول اینجا نمایش داده می‌شود...

راه‌حل رایج این مشکل، استفاده از arrow function به عنوان class field یا استفاده از bind داخل constructor هست. بیایم راه‌حل رو هم تست کنیم:

راه‌حل با arrow function در class field
خروجی کنسول اینجا نمایش داده می‌شود...

7. this در event listener‌ها

توی مرورگر، وقتی یه تابع معمولی رو به عنوان event handler استفاده می‌کنی، this معمولاً به المنتی اشاره داره که رویداد روش رخ داده. اما اگه از arrow function استفاده کنی، این رفتار عوض می‌شه چون arrow function دیگه this خودش رو نداره. این مثال بیشتر جنبه‌ی نمایشیه و به تعامل با DOM صفحه نیاز داره، پس فقط کدش رو می‌بینیم:

const button = document.querySelector("#myButton");

button.addEventListener("click", function () {
  console.log(this); // خود button
});

button.addEventListener("click", () => {
  console.log(this); // this بیرونی (نه button)
});

8. اشتباهات رایج درباره this

  • فکر کردن به اینکه this بر اساس محل تعریف تابع مشخص می‌شه، نه محل فراخوانی
  • استفاده از function معمولی داخل callback‌هایی مثل setTimeout یا forEach و از دست دادن this
  • فراموش کردن اینکه arrow function اصلاً this مستقل نداره
  • جدا کردن یه متد از آبجکتش و صدا زدن مستقیمش بدون bind

9. جمع‌بندی

اگه بخوایم خیلی خلاصه بگیم: this همیشه به نحوه‌ی صدا زدن تابع بستگی داره، نه جایی که نوشته شده. توابع معمولی this رو در لحظه‌ی فراخوانی می‌گیرن، ولی arrow function‌ها this رو از محیط بیرونی به ارث می‌برن. با ابزارهایی مثل call، apply و bind هم می‌تونی خودت کنترل this رو دستی به دست بگیری. وقتی این قانون‌ها دستت بیاد، دیگه this برات یه معما نیست، بلکه یه ابزار قدرتمنده.

منابع