پشت‌ پرده جاوااسکریپت- بخش 2

تو بخش اول مقاله درباره‌ execution context, global execution context و execution stack بحث کردیم. اگه یادتون باشه تو بخش اول مقاله گفتیم که:

می‌شه execution context رو به عنوان یک شئ (object) هم در نظر گرفت که ویژگی‌هایی (properties) هم داره.

خب، وقتشه execution context رو بیشتر بررسی کنیم و ببینیم execution context چجوری به وسیله‌ی موتور جاوااسکریپت ساخته می‌شه.

در جاوااسکریپت execution context یک مفهوم انتزاعی و فرضی است که اطلاعات (this keyword, objects variables, functions) قسمتی از کد که در حال تفسیر و اجرا توسط موتور جاوااسکریپت (javascript engine) است در آن ذخیره شده. به عبارت بهتر هر زمانی که کد در جاوااسکریپت اجرا می‌شه در واقع داخل یک execution context این اتفاق می‌افته.

**نکته مهم** در ورژن ES6 جاوااسکریپت، پراپرتی‌های execution context بر اساس دو مفهوم VariableEnvironment و LexicalEnvironment ارائه شدن. هر چند مفهوم کلی چندان تفاوتی نکرده. برای کسانی که می‌خان با این مفاهیم آشنا بشن، به نظرم اگه ابتدا با مفاهیم قدیمی‌تر شروع کنن بهتره و کمتر سردرگم می‌شن.

Execution Context Object Properties:

1- variable object:

یک مفهوم انتزاعی که تمام اطلاعات execution context در اون ذخیره شده. منظور از اطلاعات، variables و function declarations و arguments هایی است که داخل execution context ذخیره شدن.

اگه از بخش اول یادتون باشه، برای اینکه مفهوم execution context رو بهتر درک کنیم، اون رو به یه جعبه تشبیه کردیم. حالا برای اینکه مفهوم variable object رو هم بهتر درک، اون رو هم به عنوان یه جعبه‌ی کوچک در نظر بگیرین که داخل execution context قرار گرفته و در واقع اطلاعات ذخیره شده در execution context در این جعبه و این قسمت از execution context ذخیره می‌شن.

خب الان بیاین کد زیر رو که در global execution context نوشته شده، بررسی کنیم:

var a = 100;
var b = "hello world"
function foo () {}

با توجه به گفته‌های بالا variable object ساخته شده برای global execution context شامل اطلاعات زیر می‌شه:

1- var a

2- var b

3- foo ()

2- scope chain:

scope:

دسترسی به variable ها به وسیله scope تعین می‌شه. یعنی variable هایی که داخل یه scope تعریف شدن، خارج از اون قابل دسترس نیستن. scope باعث می‌شه variable ها فقط در داخل قسمت‌های مشخص شده‌ای از برنامه که به وسیله ما تعین شده قابل استفاده باشن.

در جاوااسکریپت 3 نوع scope داریم:

1- global scope:

همون طور که global execution context داریم، یه global scope هم داریم که به محض ایجاد global execution context، به صورت پیش‌فرض توسط موتور جاوااسکریپت ایجاد می‌شه و تمام scope هایی که بعدا ایجاد می‌شن scope درونی یا فرزند global scope هستن. هر variable که در global scope تعریف شده در همه جای برنامه قابل دسترس است.

var message= 'Hello World!';
function sayHello() {
  console.log(message); // return Hello World!
}
sayHello();

2- Local Scope or Function Scope:

با نوشتن، فراخوانی و ساخت فانکشن می‌شه یه scope جدید ساخت که به این نوع از scope ها Function Scope یا Local Scope گفته ‌می‌شه.

function sayHello() {   
var message= 'Hello World!'; 
console.log(message); // return Hello World! 
} 
sayHello();
console.log(message); // return message is not defined

اگه کد بالا رو تو کنسول مرورگر اجرا کنیم، تو خط 3 کد، «Hello World!» برای ما برگشت داده می‌شه چون var message داخل فانکشن ()sayHello تعریف شده و فانکشن ()console.log خط 3 هم چون داخل این فانکشن و در نتیجه داخل این scope است پس بهش دسترسی پیدا می‌کنه و «Hello World!» برگشت داده می‌شه. ولی چون فانکشن ()console.log خط 6 خارج از scope و فانکشن ()sayHello نوشته شده بنابراین به var message دسترسی نداره.

3- block scope:

به محدوده‌ی بین دو براکت بلاک اسکوپ گفته می‌شه. (در let ،ES6 و const داخل { } قابل دسترس هستند و در واقع بین دو { }، یک اسکوپ ایجاد می‌شه.)

{
var first = 'first message.';
let second = 'second message';
const third = 'third message';
}
console.log(first); // return 'first message'.
console.log(second); // return second is not defined
console.log(third); // return third is not defined

با توجه به کد بالا به این نتیجه می‌رسیم که متغیر var بر خلاف let و const از نوع block scope نیست و خارج از { } هم قابل دسترسه.

تا اینجا با مفهوم scope آشنا شدیم ولی این همه‌ی مواردی که باید درباره‌ scope بدونیم نیست. الان وقتشه درباره‌ lexical scoping بحث کنیم.

lexical scope:

در جاوااسکریپت lexical scoping به این معنی و مفهوم است که scope های درونی به scope های بیرونی دسترسی دارند. یعنی اگه یه variable در یه scope تعریف شده، scope (های) درونی اون می‌تونن به اون variable دسترسی پیدا کنن. ولی scope (های) بیرونی نمی‌تونن به اون variable دسترسی پیدا کنن.

** یک نکته مهم که باید بهش توجه داشته باشیم اینه که scope ها مستقیما به variable (ها) دسترسی ندارن. در واقع scope ها به variable object ها دسترسی پیدا می‌کنن و چون variable ها داخل variable object ها ذخیره شدن، از این طریق دسترسی حاصل می‌شه.

بیایین کد زیر رو تو 3 مرحله بررسی کنیم:

var a = 'north London ';
first();
function first () {
var b = 'is ';
second();
function second () {
var c = 'red.';
console.log(a + b + c); // return "north London is red."
}
}  

1- مرحله اول:

یه global scope داریم و همون طور که قبلا گفته شد به محض ایجاد global execution context، به صورت پیش‌فرض توسط موتور جاوااسکریپت ایجاد می‌شه و تمام scope هایی که بعدا ایجاد می‌شن scope درونی یا فرزند global scope هستن.

یه scope توسط ()first ایجاد شده که scope درونی یا فرزند global scope است و یه scope هم توسط فانکشن () second ایجاد شده که scope درونی یا فرزند first() scope و global scope است.

2- مرحله دوم:

در var a ،global scope تعریف شده. var b داخل فانکشن ()first و var c داخل فانکشن () second تعریف شدن.

3- مرحله سوم:

درونی‌ترین scope در کد بالا second() scope است. پس این scope به variable object دو scope بیرونی دسترسی پیدا می‌کنه و چون var a و var b داخل variable object ها ذخیره شدن، این دسترسی حاصل می‌شه و مقدار «.north London is red» برگشت داده می‌شه. در حالی که اگه ()console.log داخل فانکشن ()first بود مقدار «c is not defined» برگشت داده می‌شد. و اگه داخل global scope بود ابتدا «b is not defined» و بعدا «c is not defined» برگشت داده می‌شد.

VO= global object
VO= global object

scope chain:

تو کد بالا دیدیم که فانکشن ()console.log حاصل جمع سه var رو بدون هیچ اروری برگشت داد و این یعنی فانکشن ()console.log به هر سه var دسترسی پیدا کرده. در واقع var c تو همون second() scope قرار داره و طبیعیه که بهش دسترسی داشته باشه، چون باهم تو یه scope قرار گرفتن. ولی او دو تا var دیگه تو اون scope نیستن، بنابراین موتور جاوااسکریپت با استفاده از ویژگی lexical scoping جاوااسکریپت به scope های بیرونی مراجعه می‌کنه تا شاید مقدار اون دو تا var رو پیدا کنه که در مرحله اول می‌رسه به first() scope. تو این scope هم var b تعریف شده و مقدار این متغیر پیدا می‌شه. ولی هنوز مقدار var a مشخص نشده، بنابراین در مرحله دوم موتور جاوااسکریپت یه مرحله جلوتر می‌ره و مراجعه می‌کنه به scope بیرونی‌تر و آخرین scope موجود که همون global scope هست. تو این scope هم var a مشخص شده و تمام variables مقدارشون مشخص می‌شن و کدی که نوشتیم بدون هیچ اروری نتیجه رو به ما برگشت می‌ده.

پس اگه یه variable تو scope فعلی مقدارش مشخص نشده باشه، موتور جاوااسکریپت تو scope های بیرونی‌تر دنبالش می‌گرده و این جستجو تا آخرین scope بیرونی که همون global scope است ادامه پیدا می‌کنه و اگه اونجا هم پیدا نشه برنامه ارور می‌ده. همه‌ی این اتفاق‌ها به لطف scope chain هست. scope chain جاییه که نه‌تنها variable object اون scope ذخیره شده بلکه یه راه ارتباطی هم وجود داره تا scope های درونی به variable object های scope های بیرونی دسترسی پیدا کنن. و این در حالیه که این ارتباط یه طرفست و فقط scope درونی می‌تونه به scope های بیرونی دسترسی داشته باشه.

* درواقع می‌شه scope chain یه execution context رو دربردانده variable object اون scope و execution context دونست که به variable objectهای scope ها و execution context های بیرونی دسترسی داره.

تا اینجا با 2 تا از پراپرتی‌های execution context آشنا شدیم ولی پراپرتی سوم رو تو بخش سوم مقاله باهم بررسی میکنیم.