You are currently viewing Execution Context در جاوا اسکریپت

Execution Context در جاوا اسکریپت

در این مطلب میخوایم باهم بررسی کنیم که Execution Context و Scope Chain در جاوااسکریپت چی هست و همینطور با نحوه ی عملکرد داخلی جاوااسکریپت بیشتر آشنا بشیم .

درک این مسائل علاوه بر شناخت عملکرد جاوا اسکریپت ، به درک مفاهیمی مثل Hoisting و Closure هم میتونه بهتون کمک کنه .

منظور از Execution Context یا EC ، محیط یا Environmentی هست که کدهای جاوااسکریپت درون اون اجرا میشن . وقتی میگیم محیط (Environment) منظور مقدار this ، متغییرها ، Object ها و function هایی هست که کد جاوااسکریپت در یک زمان مشخص بهشون دسترسی داره

در واقع در جاوااسکریپت ما سه نوع EC یا Execution Context داریم:

1. Global Execution Context (GEC)

EC پیش فرضی که در زمان بارگذاری و اجرای اولین فایل JS در مرورگر بارگذاری میشه . تمام کدهای عمومی که داخل یک تابع یا object نیستن، در این EC اجرا میشن.

این مورد رو به یاد داشته باشین بدلیل Single-Thread بودن جاوااسکریپت ما تنها یک GEC داریم .

2. Functional execution Context (FEC)

هر زمانی که Javascript یک فراخوانی تابع رو ببینه ، یک FEC یا Function execution context برای اون تابع ایجاد میکنه . یعنی هر تابع یک Execution context مختص خودش رو داره .

برخلاف GEC ، ما میتونیم بیش از یک FEC داشته باشیم .

در نظر داشته باشین، هر FEC به تمام کدهای global execution دسترسی داره ، اما عکس این مسئله صادق نیست .

3. Eval

اگر از eval برای اجرای دستور/ دستوراتی استفاده کنین ، برای اون هم یک EC مجزا ایجاد میشه .


قبل از اینکه موارد بالا رو در قالب یک سری مثال باهم بررسی کنیم، مفهوم ECS یا Execution context stack رو هم بگیم و بریم سراغ مثال.

Execution context stack (ECS)

منظور از ECS ، یک ساختمان داده ی stack هست که همونطور که مطلع هستین بصورت LIFO عمل میکنه. یعنی اخرین آیتمی که به لیست اضافه میشه ، اولین آیتمی هست که ازش خارج میشه .

از این ساختمان داده برای نگهداری تمام اطلاعات مرتبط با اجرای اسکریپت استفاده میشه .

بطور پیش فرض ، Global execution context در پایینترین بخش این ساختمان داده قرار میگیره و سایر execution context های ایجاد شده در زمان اجرای کدها ، بروی این آیتم قرار میگیرن . برای مثال وقتی موتور javascript به یک فراخوانی تابع برسه ، یک FEC ایجاد میکنه و اون رو در ساختمان داده اضافه میکنه . سپس به سراغ اجرای اون تابع میره و بعد ازاتمام اجرای تابع مربوطه ، FEC این تابع از ساختمان داده خارج میشه و اگر کد دیگه ای نباشه ، GEC هم از لیست خارج میشه و بطور کلی اجرای کدهای جاوااسکریپت ما متوقف میشه .

برای درک بهتر مطالب بالا ، مثال زیر رو در نظر بگیرین:

var a = 10;

function functionA() {

	console.log("Start function A");

	function functionB(){
		console.log("In function B");
	}

	functionB();

}

functionA();

console.log("GlobalContext");

مراحل اجرای کد بالا رو تو تصویر زیر ببینینم باهم تا توضیحاتش رو هم بعدش بگم.

javascript-execution-context-stack

به محض اینکه کد بالا تو مرورگر بارگذاری میشه ، موتور Javascript در ابتدا Global Execution Context  رو داخل Stack قرار میده.

وقتی اجرای کد به فراخوانی تابع functionA میرسه، مجدد موتور جاوا اسکریپت Function Execution Context مربوط به تابع functionA رو داخل stack قرار میده و میره سراغ اجرای تابع functionA.

سپس اجرای کد داخل تابع functionA میرسه به خطی که تابع functionB رو فراخونی میکنه. پس Function Execution Context تابع functionB ایجاد میشه و داخل Stack قرار میگیره .

تو این مرحله ،داخل Stack ، سه Execution Context داریم. از بالا به پایین میشه

  • functionB Execution Context
  • functionA Execution Context
  • Global Execution Context

وقتی اجرای کد functionB به اتمام میرسه ، Execution Context مربوطه اش از Stack خارج میشه. سپس به پایان اجرای تابع functionA میرسیم و Execution Cotext این تابع هم از Stack خارج میشه. وقتی به خط اخر میرسیم :

javascript-sample

اجرا به اتمام میرسه ، و موتور جاوااسکریپت Global Execution Context رو هم از Stack خارج میکنه و تامام.

تا به اینجا مراحل رو باهم بررسی کردیم ، در ادامه طریقه ی ایجاد Execution Context در جاوا اسکریپت رو باهم بررسی میکنیم.

طریقه ی ساخت Execution Context توسط جاوااسکریپت

ساخت Execution Context توسط موتور جاوااسکریپت در دو مرحله ایجاد میشه :

  1. Creation Phase
  2. Execution Phase

Creation Phase به قسمتی اشاره داره که موتور جاوااسکریپت یک تابع رو فراخوانی کرده ، ولی هنوز کدهاش اجرا نشده. در این بخش موتور جاوااسکریپت در مرحله ی Compilation قرار داره و کدهای تابع رو بررسی میکنه واونا رو کامپایل میکنه. یعنی کدی اجرا نمیشه ، صرفا Compile صورت میگیره.

در فاز Creation ، کارهای زیر توسط موتور جاوااسکریپت صورت میگیره:

1. ایجاد Activation Object یا Variable Object

Activation Object اشاره به یک شیء خاص در جاوااسکریپت اشاره میکنه که شامل تمام متغییرها، آرگمان های تابع و اطلاعات داخلی مرتبط با تعریف توابع میباشد. از اونجایی که Activation Object یک شیء خاص در جاوا اسکریپت هست ، پراپرتی dunder proto رو نداره .

منظور از Dunder proto چیست ؟

در داخل هر ابجکت جاوا اسکریپت ، یک پراپرتی بخصوص به اسم Dunder Proto وجود داره که با نام __proto__ هست. دلیل این اسم گذاری هم نام خود پراپرتی هست که با دو underscore در ابتدا و انتهای کلمه proto هست.

2. ساخت Scope Chain

بعد از ایجاد Activation Object ، موتور جاوااسکریپت ، scope chain رو ایجاد میکنه که شامل لیستی از تمام متغییر ها و آبجکت های داخل تابع هست (این بخش رو بعدا شاید نیاز باشه اصلاح کنم)

همینطور این لیست شامل variable object مربوط به Global Execution Context هست. همینطور Scope chain شامل Variable object تابع فعلی که در حال اجرا هست میشه .

3. تعیین مقدار this

بعد از ایجاد Scope chain، موتور جاوااسکریپت this را مقدار دهی میکنه.


بیاید با بررسی مثال زیر ، نحوه ی ساخت Activation object رو بررسی کنیم:

function funcA (a, b) {
  var c = 3;
  
  var d = 2;
  
  d = function() {
    return a - b;
  }
}


funcA(3, 2);

بعد از فراخوانی تابع funcA، و درست قبل از اینکه تابع اجرا بشه ، موتور جاوا اسکریپت Execution Context مربوط به تابع funcA رو به شکل زیر ایجاد میکنه:

executionContextObj = {
 variableObject: {}, // تمام متغییر ها ، آرگمان ها و اطلاعات داخلی مربوط به تابع funcA
 scopechain: [], // تمام ScopeChain هایی که تابع داخلش قرار داره
 this // مقدار this
}

Activation Object یا Variable Object شامل آبجکت پراپرتی با نام argumentObject هست که اطلاعات مربوط به آرگمان های تابع رو نگه داری میکنه .

همینطور به ازای هر متغییر یا تابعی که داخل این تابع مشخص شده ، یک پراپرتی داریم. با توجه به مثال بالا Activation Object ما به شکل زیر مشخص میشه :

variableObject = {
  argumentObject : {
    0: a,
    1: b,
    length: 2
  },
  a: 3,
  b: 2
  c: undefined,
  d: undefined 
}

در ادامه به بررسی مقادیر مشخص شده در این آبجکت می پردازیم:

1.ArgumentObject: موتور جاوااسکریپت این پراپرتی رو با ساختار مشخص شده در بالا تعریف میکنه . همینطور این آبجکت دارای یک پراپرتی به نام length هست که مقدارش برابر با تعداد آرگمان های تابع فراخوانی شده هست.

2.سپس ، به ازای هر متغییر موجود در داخل تابع فراخوانی شده ، یک پراپرتی همنام با متغییرها با مقادیر undefined در داخل Activation Object یا Variable Object تعریف میشه . همینطور از اونجایی که آرگمان های تابع که در واقع خودشون هم بعنوان متغییر های در دسترس داخل تابع هستن ، به عنوان پراپرتی های دیگه  مشخص میشن. (a,b)

3.در صورتی که موتور جاوا اسکریپت به تعریف تابعی در داخل تابع فراخوانی شده برسه (متغییر d که برابر با یک تابع شده ) ، یک پراپرتی هم نام با تابع مشخص شده در داخل Activation object ایجاد میکنه .

در نظر داشته باشین که تعریف توابع در داخل حافظه ی heap صورت میگیره ، ومقدار  پراپرتی همنام با تابع  که در Activation object مشخص میشه (d) صرفا یک اشاره گر یا pointer به تابع تعریف شده در heap  هست.

در مرحله اجرا ، موتور جاوااسکریپت کدهای داخل تابع رو بررسی میکنه و مقادیر متغییر ها در variable object رو مقدار دهی میکنه و سپس کد ها رو اجرا میکنه .

بعد از این مرحله Variable Object به شکل زیر در میاد :

variableObject = {
  argumentObject : {
    0: a,
    1: b,
    length: 2
  },
  a: 3,
  b: 2,
  c: 3,
  d: pointer to function
}

در ادامه مثال دیگری رو بطور کامل مرحله به مرحله باهم بررسی میکنیم

a = 1;

var b = 2;

cFunc = function(e) {
  var c = 10;
  var d = 15;
  
  a = 3
  
  function dFunc() {
    var f = 5;
  }
  
  dFunc();
}

cFunc(10);

وقتی کد بالا در مرورگر بارگذاری میشه، موتور جاوااسکریپت وارد مرحله ی Compilation یا کامپایل کد میشه تا execution object ها رو ایجاد کنه . در این مرحله ، موتور جاوا اسکریپت صرفا تعارف متغییر ها و توابع رو مدیریت میکنه و کاری به مقادیر اونها نداره .

خط 1: در این خط مقدار 1 به متغییر a تخصیص داده شده . چون نه متغییری تعریف شده (با استفاده از var) یا اینکه تابعی تعریف بشه و اینکه ما در مرحله ی compilation هستیم ، موتور جاوا اسکریپت کاری انجام نمیده و میره خط 3.

خط 3: از اونجایی که این خط در global scope قرار داره و یک متغییر تعریف شده ، موتور جاوااسکریپت  ، درآبجکت Global Execution Context ، یک پراپرتی با نام این متغییر ایجاد میکنه و مقدار undefined رو براش تعیین میکنه .

خط 5: در این قسمت ، چون ما یک تابع تعریف کردیم ، موتور جاوا اسکریپت ساختار تعریف تابع رو در heap ذخیره میکنه و یک پراپرتی با نامی که ما مشخص کردیم (cFunc) در آبجکت GEC تعریف میکنه و بعدا مقدارش رو برابر با آدرسی که این تابع در heap تعریف شده قرار میده .

خط 18: از اونجایی که در این خط ، ما هیچ تعریف متغییر یا تابعی نداریم ، پس کاری انجام نمیشه .

بعد از این مرحله ، آبجکت GEC به شکل زیر درمیاد:

globalExecutionContextObj = {
  activationbj: {
      argumentObj : {
          length:0
      },
      b: undefined,
      cFunc: Pointer to the function definition
  },
  scopeChain: [GEC variable object],
  this: value of this
}

از اونجایی که دیگه کدی برای Compile کردن وجود نداره ، موتور جاوااسکریپت میره سراغ فاز execution ، یعنی مجدد کد هارو اسکن میکنه و مقادیر متغییر ها رو بروزرسانی میکنه و کد ها رو اجرا میکنه .

خط 1: موتور جاوا اسکریپت ، پراپرتی با نام a در variable object پیدا نمیکنه ، بنابراین این پراپرتی رو به لیست اضافه میکنه و بهش مقدار 1 رو میده

خط 3: در این مرحله موتور جاوا اسکریپت ، مجدد چک میکنه که آیا پراپرتی با نام b داریم یا نه ، و سپس بهش مقدار 2 را ست میکنه.

خط 5: از اونجایی که این خط تعریف تابع هست ، کاری باهاش نداره و میره سراغ اجرای خط  18

تا این مرحله ، GEC Object به شکل زیر در اومده:

globalExecutionContextObj = {
  activationbj: {
      argumentObj : {
          length:0
      },
      b: 2,
      cFunc: Pointer to the function definition,
      a: 1
  },
  scopeChain: [GEC variable object],
  this: value of this
}

خط 18: در این قسمت تابع cFunc فراخونی میشه . پس مجدد موتور جاوااسکریپت ، وارد مرحله ی Compilation میشه تا آبجکت Execution Context رو برای تابع cFunc ایجاد کنه .

از اونجایی که تابع cFunc دارای یک آرگمان با نام e هست ، موتور جاوا اسکریپت ، e رو در argument object مربوط به execution context تابع cFunc اضافه میکنه و یک پراپرتی با نام e داخل EC اضافه میکنه و مقدارش رو برابر با 2 میکنه.

خط 6: موتور جاوا اسکریپت بررسی میکنه که آیا پراپرتی با نام c در activation object مربوط به cFunc هست یا نه. از اونجایی که این پراپرتی وجود نداره ، اون رو اضافه میکنه و مقدارش رو برابر با undefined قرار میده .

خط7: مشابه خط6 ، برای d عمل میشه .

خط 9: از اونجایی که در این خط هیچ تعریفی وجود نداره ، سراغ خط اجرایی بعدی میره

خط 11:در این قسمت یک تعریف تابع داریم. پس مشابه مراحل قبلی ، ساختار تابع و تعریفش در heap انجام میشه و پراپرتی مربوطه در EC تابع cFunc ایجاد میشه و مقدارش اشاره گری هست که به ادرس این تابع در heap اشاره میکنه.

تا به اینجا EC مربوط به تابع cFunc به شکل زیر هست :

cFuncExecutionContextObj = {
  activationbj: {
      argumentObj : {
          0: e,
          length:1
      },
      e: 10,
      c: undefined,
      d: undefined
      dFunc: Pointer to the function definition,
  },
  scopeChain: [cFunc variable object, GEC variable object],
  this: value of this
}

خط 15: چون این خط هیچ تعریفی داخلش نیست ، پس اتفاق خاصی نم یافته.

از اونجایی که دیگه کدی برای بررسی نیست ، فاز compilation به اتمام میرسه و وارد مرحله ی execution میشیم که با اسکن مجدد cFunc ، اجرای کد تابع cFunc صورت میگیره .

خط 6 و7: پراپرتی های c و d مقادیر 10 و 15 رو میگیرن

خط 9: از اونجایی که در EC تابع cFunc ، پراپرتی با نام a نداریم، و اینکه این خط تعریف متغییری صورت نگرفته، موتور جاوا اسکریپت از طریق scope chain سراغ GEC میره و داخل اون رو چک میکنه که آیا پراپرتی a وجود داره یا نه . اگر وجود نداشته باشه ، پراپرتی رو اونجا اضافه میکنه . ولی از اونجایی که این پراپرتی در GEC وجود داره ، پس مقدارش رو از 1 به 3 تغییر میده .

خط 11: مجدد تعریف تابع داریم ، پس مشابه مراحل قبلی پراپرتی dFunc ایجاد میشه و مقدار دهیش به آدرس heap صورت میگیره .

در حال حاضر ، EC مربوط به cFunc به شکل زیر در اومده :

cFuncExecutionContextObj = {
  activationbj: {
      argumentObj : {
          0: e,
          length:1
      },
      e: 10,
      c: 10,
      d: 15
      dFunc: Pointer to the function definition,
  },
  scopeChain: [cFunc variable object, GEC variable object],
  this: value of this
}

خط 15: از اونجایی که ما مجدد یک فراخوانی به تابع dFunc داریم ، موتور جاوا اسکریپت میره سراغ ساخت EC مربوط به تابع dFunc که دیگه این مورد رو خودتون بررسی کنین و ساختارش رو در بیارین.

EC مربوط به dFunc ، از طریق Scope chain ، به تمام متغییر ها و توابع تعریف شده در داخل cFunc و GEC دسترسی دارد.

همینطور EC مربوط به cFunc به تمام متغییر ها و توابع تعریف شده در GEC دسترسی داره ، اما به متغییر ها و توابع dFunc دسترسی نداره.

دیدگاهتان را بنویسید