مفهوم this و نحوه‌ی تعیین شدن آن

مقدمه

در طول این دوره، چند بار به کلیدواژه‌ی this اشاره کرده‌ایم اما توضیح کامل آن را به این فصل موکول کرده بودیم؛ برای نمونه در فصل شیء، هنگام تعریف متد، و در فصل Arrow Function، هنگام اشاره به رفتار متفاوت آن با this. اکنون وقت آن رسیده تا این مفهوم را از پایه و با دقت کامل بررسی کنیم؛ مفهومی که یکی از سردرگم‌کننده‌ترین بخش‌های جاوااسکریپت برای بسیاری از یادگیرندگان است.

this چیست؟

this یک کلیدواژه‌ی ویژه در جاوااسکریپت است که در هر Execution Context (به‌جز Arrow Function که در درس‌های بعدی بررسی می‌کنیم) به یک مقدار خاص اشاره می‌کند. مهم‌ترین نکته درباره‌ی this این است: مقدار this ثابت نیست؛ بلکه بسته به این‌که یک تابع چگونه فراخوانی می‌شود، تعیین می‌شود. این دقیقاً نقطه‌ای است که this را از متغیرهای معمولی که تابع Lexical Scope خودشان را دارند متفاوت می‌کند.

this در Global Scope

در فصل Execution Model، هنگام بررسی Global Execution Context، دیدیم که this در سطح Global، به شیء Global اشاره می‌کند:

console.log(this); // در مرورگر: شیء window

قانون طلایی: this بر اساس نحوه‌ی فراخوانی تعیین می‌شود

برخلاف تصور رایج، this به محل تعریف تابع بستگی ندارد (برخلاف Scope که Lexical است)؛ بلکه به این بستگی دارد که در لحظه‌ی فراخوانی، تابع چگونه صدا زده شده است. همین یک تابع واحد، بسته به روش فراخوانی، می‌تواند مقادیر کاملاً متفاوتی برای this داشته باشد:

یک تابع واحد با مقادیر متفاوت this
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

با اینکه showThis فقط یک‌بار تعریف شده، اما فراخوانی اول (showThis()) مقدار this را برابر شیء Global (یا undefined در Strict Mode، که در ادامه‌ی همین فصل بررسی می‌کنیم) قرار می‌دهد؛ درحالی‌که فراخوانی دوم (user.show()) مقدار this را برابر user قرار می‌دهد. تنها تفاوت این دو فراخوانی، روش صدا زدن تابع است، نه محل تعریف آن. این دقیقاً همان چیزی است که در ادامه‌ی این فصل، تحت عنوان قوانین مختلف Binding (از جمله Default Binding و Implicit Binding)، با جزئیات دقیق‌تر بررسی خواهیم کرد.

چرا this اصلاً وجود دارد؟

this عمدتاً برای این طراحی شده که به یک تابع (به‌خصوص وقتی به‌عنوان متد یک شیء استفاده می‌شود) اجازه دهد بدون دانستن نام دقیق شیء صاحب خودش، به Property های آن دسترسی داشته باشد:

let user = {
  name: "علی",
  greet: function () {
    console.log("سلام، من " + this.name + " هستم");
  }
};

user.greet(); // سلام، من علی هستم

در این مثال، this.name به‌جای نوشتن مستقیم user.name استفاده شده است. این تفاوت در نگاه اول کوچک به‌نظر می‌رسد، اما مزیت مهمی دارد: اگر همین شیء در متغیر دیگری کپی شود یا نام آن تغییر کند، متد همچنان به‌درستی کار می‌کند؛ چون this در لحظه‌ی فراخوانی، به هر شیئی که متد را صدا زده اشاره می‌کند، نه به یک نام ثابت:

let admin = user;
admin.greet(); // سلام، من علی هستم — همچنان درست کار می‌کند

this در داخل یک متد، همیشه به شیء‌ای که متد را فراخوانی کرده اشاره دارد

نکته‌ی مهم دیگری که باید در همین ابتدا روشن شود: this به شیء‌ای که تابع در آن تعریف شده اشاره نمی‌کند؛ بلکه به شیء‌ای اشاره دارد که متد را فراخوانی کرده است. این دو مفهوم معمولاً یکسان به‌نظر می‌رسند اما همیشه یکی نیستند؛ همان‌طور که در مثال showThis بالا دیدیم، یک تابع واحد که در Global Scope تعریف شده بود، وقتی از طریق user.show() فراخوانی شد، this آن برابر user شد؛ نه شیئی که تابع در آن تعریف شده بود (که اصلاً هیچ شیئی نبود).

this یک مفهوم پویا (Dynamic) است، نه Lexical

در فصل Execution Model، با Lexical Scope آشنا شدیم که کاملاً بر اساس محل نوشتن کد تعیین می‌شود و هرگز در زمان اجرا تغییر نمی‌کند. this دقیقاً برعکس این رفتار می‌کند: مقدار آن پویا (Dynamic) است و در هر فراخوانی، می‌تواند مقدار متفاوتی داشته باشد؛ حتی اگر کدِ تابع، دقیقاً یکسان و بدون تغییر باقی بماند. این تفاوت بنیادین، دقیقاً همان چیزی است که باعث می‌شود this برای بسیاری از برنامه‌نویسان تازه‌کار، گیج‌کننده به‌نظر برسد؛ چون رفتار آن با مدل Lexical Scope که در فصل قبل به آن عادت کرده بودیم، کاملاً متفاوت است.

پیش‌نمایشی از قوانین Binding

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

  • Default Binding — وقتی تابع به‌تنهایی و بدون هیچ شیئی فراخوانی می‌شود.
  • Implicit Binding — وقتی تابع به‌عنوان متد یک شیء فراخوانی می‌شود (مثل user.show()).
  • Explicit Binding — وقتی با call، apply یا bind، مقدار this صریحاً مشخص می‌شود.
  • Constructor Binding — وقتی تابع با new فراخوانی می‌شود.

همچنین رفتار ویژه‌ی Arrow Function با this که در فصل قبل به‌طور مختصر دیدیم، در آخرین درس همین فصل با جزئیات کامل بررسی خواهد شد.

جدول جمع‌بندی اولیه

ویژگیScope (متغیرها)this
نوع تعیینLexical (بر اساس محل تعریف)Dynamic (بر اساس نحوه‌ی فراخوانی)
تغییر در فراخوانی‌های مختلفخیربله
محل تعیین نهاییزمان نوشتن کدلحظه‌ی فراخوانی تابع

ارتباط با سایر مفاهیم

درک این‌که this برخلاف Scope، پویا و وابسته به نحوه‌ی فراخوانی است، پیش‌نیاز اصلی فهم تمام درس‌های بعدی همین فصل خواهد بود. این تفاوت بنیادین بین Lexical Scope (که در فصل Execution Model دیدیم) و this Binding (که موضوع این فصل است)، یکی از مهم‌ترین نقاط افتراق مفهومی در کل زبان جاوااسکریپت است.

جمع‌بندی

this کلیدواژه‌ای است که مقدار آن، برخلاف متغیرهای معمولی، بر اساس محل تعریف تابع (Lexical) تعیین نمی‌شود؛ بلکه بر اساس نحوه‌ی فراخوانی تابع در هر لحظه، به‌صورت پویا (Dynamic) مشخص می‌شود. یک تابع واحد می‌تواند در فراخوانی‌های مختلف، مقادیر کاملاً متفاوتی برای this داشته باشد. مقدار دقیق this توسط چند قانون Binding مشخص تعیین می‌شود که هرکدام را در درس‌های بعدی همین فصل، جداگانه و با جزئیات کامل بررسی خواهیم کرد.