در این قسمت میخوایم عمیق تر به نحوه ی عملکرد closure ها در جاوااسکریپت بپردازیم.
اما ابتدا باید دو مفهوم را باهم بررسی کنیم :
1- Execution Context
2- Lexical Environment
تعریف Context Execution :
مفهوم انتزاعی از محیطی است که کد فعلی در آن اعتبار سنجی میشود.
در جاوااسکریپت execution context به دو دسته تقسیم میشوند :
1- Global execution context (GEC)
به هنگام شروع اجرای کد جاوااسکریپت این نوع context execution به صورت پیش فرض وجود دارد. همه ی کدهای موجود در global scope که داخل هیچ تابع یا object نیستند ، در global execution context قرار میگیرند.
فقط یک GEC در هنگام اجرای کد جاوااسکریپت وجود دارد و نه بیشتر . به این دلیل که جاوااسکریپت اصولا single thread است.
2- Functional execution context (FEC)
نوعی execution contextاست که هنگام صدازده شدن هر تابع جاوااسکریپتی ، توسط موتور javascript ایجاد میشود. هر تابع، execution context مربوط به خود را دارد. البته میتواند بیشتر از یکی هم باشد. FEC قابلیت دسترسی به تمام کدهای Global execution context را داراست ولی برعکس این ممکن نیست. هنگام اجرای global execution contextاگر موتور جاوااسکریپت فراخوانی تابعی را پیدا کند، برای آن تابع یک FEC ایجاد خواهد کرد
3- Eval
درواقع execution context داخل تابع eval می باشد.
برای َاشنایی با تابع eval به لینک زیر مراجعه کنید : https://www.w3schools.com/jsref/jsref_eval.asp
تا به این مرحله با انواع execution context ها اشنا شدیم.
مفهومی دیگر در جاوااسکریپت وجود دارد که بسیار مرتبط با مفاهیمی است که تا به حال بررسی کردیم.
Execution context stack (ECS)
همانطور که واضح است execution context stackیک پشته است که طی اجرا شدن اسکریپت وجود دارد. به این دلیل که global execution context در ابتدای اجرا شکل میگیرد همیشه اولین آیتمی که در این پشته push میشود global execution context است. سپس هنگامی که جاوااسکیپت با فراخوانی توابع مواجه میشود ، برای تابع صدازده شده یک execution context ایجاد میکند و آن را در پشته push میکند. هنگامی که تمامی کدها اجرا شدند ، جاوااسکریپت آیتم های پشته را یک به یک pop میکند و تابع مربوط به آن را اجرا میکند.
هنگامی که تمام کدها اجرا شد موتور جاوااسکریپت global execution context را pop میکند و اجرای برنامه ی جاوااسکریپتی به پایان میرسد.
تا به این مرحله بررسی کردیم که جاوااسکریپت چگونه execution context را مدیریت میکند . در مرحله ی بعدی به چگونگی ایجاد execution context میپردازیم .
جاوااسکریپت در دو مرحله execution context را ایجاد میکند.
1- Creation phase
مرحله ای است که موتور جاوااسکریپت تابعی را فراخوانی میکند ولی اجرای آن آغاز نمیشود. در این مرحله موتور جاوااسکریپت در حال کامپایل کردن است و هیچ کدی را اجرا نمیکند.
2- Execution phase
در فاز execution ، موتور جاوااسکریپت دوباره تابع را بررسی میکند تا مقادیر متغیرها را به روز کند و در نهایت کد اجرا میشود.
مفهوم بعدی که باید مورد بررسی قرار داد Lexocal Environment است.
تعریف Lexical Environment :
it's the internal js engine construct that holds identifier-variable mapping. (here identifier refers to the name of variables/functions, and variable is the reference to actual object [including function type object] or primitive value). A lexical environment also holds a reference to a parent lexical environment.
به زبان ساده تر هر دفعه که موتور جاوااسکریپت یک execution context ایجاد میکند تا کدهای global scope یا کدهای یک تابع را اجرا کند ، یک lexical environment جدید هم ایجاد میکند تا متغیرهای تعریف شده در تابع را ضمن اجرای تابع ذخیره کند.
درواقع lexical environment شامل دوبخش میباشد :
1- Environment Record:
محل واقعی که تعریف تابع در آن ذخیره میشود.
2- Reference to the outer environment
اشاره گری به environment بیرونی
به این معنا است که دسترسی به lexical environment بیرونی دارد. این قسمت مهترین مفهوم مورد نیاز برای یادگیری و فهم closure ها است.
میتوان فرض کرد که lexical environment به شکل زیر است :
برای درک بهتر ،مثال زیر را بررسی میکنیم :
در این مثال global lexical environment به شکل زیر خواهد بود :
همانطور که مشاهده کردید در global lexical environment اشاره گر outer برابر با null است. به این دلیل که هیچ lexical environment خارجی برای آن وجود ندارد.
هنگامی که موتور جاوااسکریپت execution context برای تابع first را ایجاد میکند، یک lexical environment هم ایجاد میکند تا متغیرهای تعریف شده در این تابع را در طی اجرای تابع در آن ذخیره کند. پس lexical environment مربوط به این تابع به شکل زیر خواهد بود :
حالا که execution context و lexical Environment را بررسی کردیم یک مثال کامل درباره ی closure ها را بررسی میکنیم :
درواقع lexical environment مربوط به تابع getCounter به شکل زیر خواهد بود :
و هنگامی که تابع hits اجرا میشود lexical Environmentمربوط به آن به شکل زیر تشکیل میشود :