ساختار داخلی Scope: Lexical Environment و Environment Record
مقدمه
در درس قبل دیدیم که هر Execution Context، اطلاعاتی دربارهی متغیرهای در دسترس و ارجاع به Scope بیرونی نگه میدارد. اما این اطلاعات دقیقاً در چه ساختاری ذخیره میشوند؟ پاسخ این سؤال، دو مفهوم فنیتر و دقیقتر است: Lexical Environment و Environment Record. این دو مفهوم، سازوکار داخلیای هستند که Scope Chain از دل آنها شکل میگیرد.
Lexical Environment چیست؟
هر Execution Context، یک Lexical Environment مرتبط با خودش دارد. Lexical Environment یک ساختار دادهای است که دو بخش اصلی دارد:
- Environment Record — جایی که خودِ متغیرها و توابع واقعاً ذخیره میشوند.
- ارجاع به Outer Lexical Environment — یعنی اشارهگری به Lexical Environment مربوط به Scope بیرونی.
به بیان ساده: Environment Record «انبار داده» است، و Lexical Environment آن انبار را به همراه یک «آدرس اشاره به انبار بیرونی» بستهبندی میکند.
Environment Record چیست؟
Environment Record دقیقاً همان جایی است که نام متغیرها بهعنوان کلید، و مقدار آنها بهعنوان مقدار متناظر، نگهداری میشود؛ شبیه یک جدول داخلی که موتور جاوااسکریپت آن را مدیریت میکند (نه یک Object معمولی که در کد به آن دسترسی مستقیم داشته باشید).
تحلیل کد
در این مثال، دو Lexical Environment مجزا ساخته میشود: یکی برای outer و یکی برای inner. Environment Record مربوط به outer شامل متغیر a است، و Environment Record مربوط به inner شامل متغیر b. وقتی داخل inner به دنبال a میگردیم، ابتدا در Environment Record خودِ inner جستوجو میشود (که a در آن نیست)، سپس جاوااسکریپت از طریق ارجاع به Outer Lexical Environment، به Lexical Environment مربوط به outer میرود و a را آنجا پیدا میکند.
این دقیقاً همان مکانیزمی است که در پسصحنهی Scope Chain اتفاق میافتد؛ آنچه پیشتر بهصورت مفهومی «رفتن به Scope بیرونی» نامیدیم، در واقع دنبال کردن همین زنجیرهی ارجاعات بین Lexical Environment هاست.
چه زمانی یک Lexical Environment جدید ساخته میشود؟
یک Lexical Environment جدید در این موارد ساخته میشود:
- هر بار که یک تابع فراخوانی میشود (متناظر با Function Execution Context)
- هنگام ساخته شدن Global Execution Context
- برای هر بلاک مستقل (مثل
{}،if، یا حلقه) در مورد متغیرهایletوconst
همین مورد سوم، دلیل دقیقتری برای رفتاری است که در فصل قبل، هنگام بررسی Block Scope دیدیم:
let x = "بیرون";
{
let x = "داخل";
console.log(x); // داخل
}
console.log(x); // بیرون
هر بلاک {} برای متغیرهای let/const، Lexical Environment مستقل خودش را میسازد؛ به همین دلیل x داخل بلاک، در Environment Record جداگانهای از x بیرون بلاک ذخیره میشود و هیچ تداخلی رخ نمیدهد.
ارتباط با Temporal Dead Zone
حالا میتوانیم توضیح دقیقتری برای TDZ که در دو درس قبل دیدیم ارائه بدهیم: وقتی یک Lexical Environment ساخته میشود، جاوااسکریپت بلافاصله در Environment Record، جایی برای متغیرهای let و const آن Scope در نظر میگیرد، اما این جایگاه را در حالتی «مقداردهینشده» (Uninitialized) علامتگذاری میکند. تا زمانی که اجرای کد به خط تعریف واقعی متغیر نرسد، این علامت برداشته نمیشود؛ و هر تلاشی برای خواندن متغیری با این علامت، همان خطای TDZ را تولید میکند.
تفاوت رفتار var در Environment Record
برخلاف let و const، متغیرهای var در Environment Record بلافاصله با مقدار اولیهی undefined مقداردهی میشوند، نه در حالت «مقداردهینشده». به همین دلیل است که خواندن یک متغیر var پیش از رسیدن به خط تعریفش، خطا نمیدهد و فقط undefined برمیگرداند؛ دقیقاً همان رفتاری که در درس Hoisting دیدیم.
جدول جمعبندی
| مفهوم | نقش |
|---|---|
| Lexical Environment | بستهای شامل Environment Record + ارجاع به Scope بیرونی |
| Environment Record | محل واقعی ذخیرهسازی متغیرها و توابع یک Scope |
| ارجاع به Outer Environment | پایهی ساخت Scope Chain |
ارتباط با سایر مفاهیم
درک اینکه هر Scope، Environment Record مستقل خودش را دارد و این Environment Record ها از طریق ارجاع به یکدیگر زنجیر میشوند، دقیقاً پایهی نظری Closure است که در فصل بعدی بهطور کامل بررسی میکنیم؛ در آنجا خواهیم دید که یک تابع میتواند حتی پس از پایان اجرای تابع بیرونی، همچنان به Lexical Environment آن دسترسی داشته باشد؛ چون Environment Record بیرونی، تا زمانی که ارجاعی به آن وجود داشته باشد، از حافظه حذف نمیشود.
جمعبندی
هر Execution Context، یک Lexical Environment مرتبط دارد که از دو بخش تشکیل شده: Environment Record که محل واقعی ذخیرهی متغیرها و توابع است، و ارجاعی به Lexical Environment بیرونی که مبنای Scope Chain است. متغیرهای let و const در Environment Record ابتدا در حالت «مقداردهینشده» قرار میگیرند (که منشأ TDZ است)، درحالیکه var بلافاصله با مقدار undefined مقداردهی میشود.
