زنجیره‌ی جست‌وجوی متغیر: Scope Chain

مقدمه

در درس قبل دیدیم که هر تابع بر اساس محل تعریف خود (Lexical Scope)، به متغیرهای Scope های بیرونی‌تر دسترسی دارد. اما دقیقاً هنگام اجرای کد، وقتی جاوااسکریپت به یک متغیر برمی‌خورد، چگونه تصمیم می‌گیرد که مقدار آن را از کجا بخواند؟ پاسخ این سؤال، مفهومی به نام Scope Chain (زنجیره‌ی حوزه) است.

Scope Chain چیست؟

وقتی جاوااسکریپت به یک متغیر در کد برمی‌خورد، ابتدا آن را در Scope فعلی جست‌وجو می‌کند. اگر پیدا نشد، به Scope بیرونی‌تر (یعنی Scope‌ای که تابع فعلی داخل آن تعریف شده) می‌رود و دوباره جست‌وجو می‌کند. این روند تا رسیدن به Global Scope ادامه پیدا می‌کند. به این مسیر پله‌پله‌ی جست‌وجو، Scope Chain گفته می‌شود.

جست‌وجوی متغیر در طول Scope Chain
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

وقتی console.log(c) اجرا می‌شود، جاوااسکریپت c را مستقیماً در Scope خودِ inner پیدا می‌کند و جست‌وجو همان‌جا تمام می‌شود. اما برای console.log(b)، جاوااسکریپت ابتدا در Scope inner به دنبال b می‌گردد؛ چون آن‌جا پیدا نمی‌شود، یک قدم به بیرون می‌رود و آن را در Scope outer پیدا می‌کند. برای console.log(a)، جاوااسکریپت باید دو قدم به بیرون برود: ابتدا inner، سپس outer، و در نهایت a را در Global Scope پیدا می‌کند.

این مسیر جست‌وجو، دقیقاً همان زنجیره‌ای است که Scope Chain نام دارد: inner → outer → Global.

Scope Chain همیشه رو به بیرون حرکت می‌کند

نکته‌ی بسیار مهم این است که این زنجیره فقط در یک جهت حرکت می‌کند: از داخلی‌ترین Scope به سمت بیرونی‌ترین. هرگز برعکس این مسیر اتفاق نمی‌افتد؛ یعنی یک Scope بیرونی هرگز نمی‌تواند به متغیرهای یک Scope داخلی‌تر دسترسی پیدا کند:

function outer() {
  function inner() {
    let secret = "فقط داخل inner";
  }

  inner();
  console.log(secret); // خطا: secret تعریف نشده است
}

خطای ReferenceError در انتهای زنجیره

اگر جاوااسکریپت متغیری را در هیچ‌کدام از Scope های زنجیره، حتی تا Global Scope، پیدا نکند، یک خطای ReferenceError صادر می‌کند:

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

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

در این مثال، undefinedVariable نه در Scope تابع showValue و نه در Global Scope تعریف شده است؛ به همین دلیل جاوااسکریپت پس از طی کردن کل Scope Chain و ناموفق بودن جست‌وجو، خطای ReferenceError: undefinedVariable is not defined صادر می‌کند. این دقیقاً همان اتفاقی است که به‌طور خلاصه در درس قبل هم دیدیم؛ اینجا با جزئیات بیشتر متوجه شدیم که این خطا نتیجه‌ی جست‌وجوی ناموفق در تمام سطوح زنجیره است، نه فقط یک سطح.

Shadowing — پوشاندن متغیر بیرونی

وقتی یک Scope داخلی، متغیری هم‌نام با یک متغیر در Scope بیرونی تعریف کند، متغیر داخلی اولویت پیدا می‌کند و جست‌وجو در همان سطح داخلی متوقف می‌شود؛ حتی اگر متغیری هم‌نام در سطوح بیرونی‌تر هم وجود داشته باشد. به این پدیده Shadowing (سایه انداختن) می‌گویند:

Shadowing متغیر بیرونی
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

داخل printColor، متغیر محلی color با مقدار "آبی"، متغیر color در Global Scope را می‌پوشاند؛ به همین دلیل console.log(color) داخل تابع، بدون اینکه حتی نیازی به رفتن به Global Scope باشد، همان مقدار محلی را پیدا و چاپ می‌کند. متغیر Global بدون هیچ تغییری باقی می‌ماند و بعد از فراخوانی تابع، همچنان مقدار "قرمز" را دارد. این دقیقاً همان مفهومی است که در درس قبل، در مثال Nested Scope دیدیم؛ اینجا نام رسمی آن رفتار را یاد گرفتیم.

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

مرحلهمحل جست‌وجونتیجه
۱Scope فعلی (تابع یا بلاک)اگر پیدا شد، جست‌وجو تمام می‌شود
۲Scope بیرونی بعدی (بر اساس محل تعریف)در صورت نبود در مرحله‌ی قبل، ادامه پیدا می‌کند
.........
آخرGlobal Scopeاگر اینجا هم پیدا نشد، ReferenceError

Best Practices

  • از انتخاب نام‌های تکراری برای متغیرهای محلی و بیرونی خودداری کنید، مگر با قصد آگاهانه؛ Shadowing ناخواسته می‌تواند باعث سردرگمی در خواندن کد شود.
  • هنگام دیباگ کردن خطای ReferenceError، به یاد داشته باشید که جاوااسکریپت کل Scope Chain را جست‌وجو کرده و متغیر را در هیچ سطحی پیدا نکرده است.

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

Scope Chain مکانیزمی است که در پس‌زمینه، هر بار که یک متغیر در کد استفاده می‌شود، فعال است. این مفهوم مستقیماً پایه‌ی درک صحیح Closure در فصل بعدی خواهد بود؛ جایی که یک تابع حتی پس از پایان اجرای تابع بیرونی‌اش، همچنان به Scope Chain خود در لحظه‌ی تعریف دسترسی خواهد داشت. همچنین، Scope Chain ارتباط مستقیمی با Execution Context و Lexical Environment دارد که در درس‌های بعدی همین فصل بررسی می‌شوند.

جمع‌بندی

Scope Chain مسیری است که جاوااسکریپت برای پیدا کردن مقدار یک متغیر طی می‌کند: از داخلی‌ترین Scope شروع کرده و در صورت نیافتن، پله‌پله به سمت Scope های بیرونی‌تر تا رسیدن به Global Scope پیش می‌رود. این مسیر همیشه یک‌طرفه و رو به بیرون است. اگر متغیری در هیچ سطحی از این زنجیره پیدا نشود، خطای ReferenceError رخ می‌دهد؛ و اگر متغیری هم‌نام در سطح داخلی‌تر تعریف شود، آن را Shadowing می‌نامند.