تو بخش اول و دوم مقاله به ترتیب درباره execution context و دو تا از پراپرتیهاش صحبت کردیم و قرار شد تو بخش سوم درباره پراپرتی سوم که this هست بحث کنیم تا برسیم به چگونگی ایجاد execution context.
وقتی execution context ساخته میشه this هم مثل scope chain و variable object باهاش همراه و تو execution context ذخیره میشه تا مثل اون دو تا یه سری امکانات در اختیار ما قرار بده.
به صورت خلاصه this به یک object اشاره داره. به عبارت بهتر this به آبجکتی اشاره داره که فانکشن در حال اجرای فعلی رو اجرا (execute) میکنه. (به فارسی سخت!)
یکی از مزیتهای this این هست که میتونیم با استفاده از اون، فانکشنها رو با مقادیر مختلف قابل استفاده کنیم. یعنی یه فانکشن بنویسیم و در موقعیتهای مختلف اون فانکشن رو با مقادیر مختلف استفاده کنیم و این کار باعث میشه هم در کد و هم در حافظه برنامه صرفهجویی کنیم.
اگه یادتون باشه تو بخش اول گفتیم که:
میشه execution context رو به عنوان یک شئ (object) در نظر گرفت.
و همینطور گفته شد:
اگر execution context رو به عنوان object درنظر بگیریم، global execution context هم به عنوان global object شناخته میشه.
با توجه به تعریفی که از this نوشتیم و این دو یادآوری، this درواقع به یک execution context اشاره داره.
پس در موقعیتهای مختلف، this به آبجکتها و execution context های مختلفی اشاره میکنه:
console.log(this); // return window object
اگه this رو در global scope استفاده کنیم، به global execution context یا همون global object اشاره داره. اگه بخش اول مقاله یادتون باشه، گفتیم که:
در مرورگر، window object همون global object هست.
var name = 'Milan'; function footballClub () { var name = 'Arsenal'; console.log(this); // return window object console.log(this.name); // return Milan } footballClub();
وقتی this داخل یه فانکشن نوشته میشه به global execution context یا همون global object اشاره داره!
به همین دلیل this موجود در فانکشن ()footballClub به gloabl object اشاره میکنه و this.name داخل فانکشن به name موجود در global object.
var name = 'Milan'; var footballClub = { name : 'Arsenal', clubDetail : function () { console.log(this); // return footballClub object console.log(this.name); // return Arsenal } } footballClub.clubDetail();
برخلاف فانکشنها وقتی this داخل یه متد بکار میره، اشارش به اون آبجکتی هست که داخلش تعریف شده و (آبجکت) اون method رو فراخوانی میکنه.
اشاره this به آبجکت footballClub هست. چون this داخل متدی که تو این آبجکت بهکار رفته، استفاده شده. this.name هم به name موجود در footballClub object اشاره میکنه نه name موجود در global object.
* یه نکته که باید به اون توجه داشته باشیم اینه که اگه یه فانکشن داخل یه متدی بکار رفته باشه، this موجود در اون فانکش باز هم به global object اشاره خواهد داشت.
function Person (fullName, yearOfBirth, job) { this.fullName = fullName; this.yearOfBirth = yearOfBirth; this.job = job; console.log(this) // retun Elon object and Jeffrey object } var Elon = new Person('Elon Reeve Musk', 1971, 'CEO'); var Jeffrey = new Person('Jeffrey Preston Bezos', 1964, 'CEO');
در جاوااسکریپت یکی از راههای ساخت object، استفاده از function constructor هست. تو این روش ابتدا یه فانکشن به عنوان نمونه اولیه ایجاد میشه و بعدا آبجکتها به عنوان نمونههای کپی شده از این function constructor ساخته میشن.
خب بیاین کد بالا رو یکم دقیقتر بررسی کنیم:
1- در مرحله اول فانکشن ()Person رو که یه function constructor هست رو میسازیم که 3 پارامتر داره و به عنوان نمونه اولیه از آبجکتهایی که بعدا از روی اون ساخته میشن شناخته میشه.
2- تو این مرحله با استفاده از new و فانکشن ()Person، آبجکت Elon رو به وجود میاریم. به این صورت که:
ابتدا new یک آبجکت خالی رو میسازه. بعد از new، فانکشن ()Person فراخوانی میشه که 3 پارامتر هم داره. پس آبجکت جدید (با استفاده از پارامترهای وارد شده) ساخته و در داخل متغیر Elon ذخیره میشه.
3- در این مرحله هم آبجکت Jeffrey مثل مرحله قبل ساخته میشه.
کاری که new در این موقعیت انجام میده درواقع اینه که اشاره this رو از window object (چون اینجا this داخل یه فانکشن بکار رفته) به آبجکتی که تازه ساخته شده (Elon و Jeffrey) منتقل کنه. یعنی اگه کد بالا رو به صورت زیر که بدون بکار بردن new هست بنویسیم، this به window object اشاره میکنه:
function Person (fullName, yearOfBirth, job) { this.fullName = fullName; this.yearOfBirth = yearOfBirth; this.job = job; console.log(this) // return Window object twice } var Elon = Person('Elon Reeve Musk', 1971, 'CEO'); var Jeffrey = Person('Jeffrey Preston Bezos', 1964, 'CEO');
var name = 'Adam Wathan'; var job = 'back-end developer'; var developer = function () { console.log('My name is ' + this.name + ', I am ' + this.job); } var rachel = { name: 'Rachel Andrew', job: 'front-end developer' } developer(); // return "My name is Adam Wathan, I am back-end developer" developer.call(rachel); // return "My name is Rachel Andrew, I am front-end developer"
در مثال بالا یه فانکشن داریم به اسم ()developer. بار اولی که این فانکشن فراخوانی شده (خط 10)، مقدار «My name is Adam Wathan, I am back-end developer» برگشت داده میشه. چون this داخل یه فانکشن بکار رفته و به همین دلیل شاره اون به window object هست.
ولی بار دومی که این فانکشن فراخوانی شده همراه با متد ()call بوده. تو این مثال متد ()call همون نقش new رو در مثال قبلی بازی میکنه. یعنی اشاره this رو از window object برمیداره و به آبجکتی که ما میخایم (آبجکت rachel) منتقل میکنه. و این بار مقدار «My name is Rachel Andrew, I am front-end developer» برگشت داده میشه.
شاید سوال پیش بیاد که چرا اینجوری کد رو نوشتیم و چرا از همون اول فانکشن ()developer رو به عنوان متد داخل خود آبجکت بکار نبردیم؟
فرض کنین به جای یک آبجکت در مثال بالا، هزار آبجکت داشتیم و فانکشن ()developer هم به جای اینکه یک خط بود، هزار خط کد داشت. اگر میخاستیم این فانکشن هزار خطی رو به عنوان متد به هر یک از این هزار آبجکت اضافه کنیم، حافظه برنامه خیلی زود پر میشد و یا برنامه با کندی عمل میکرد. ولی با استفاده از متد ()call هر هزار آبجکت میتونن از این فانکشن بدون اشغال بیش از حد حافظه استفاده کنن.
یه نکته که باید در مورد متد call بدونیم این هست که میتونیم بیش از یک آرگومان به این متد وارد کنیم.
var developer = function (skill1, skill2, skill3) { console.log('My name is ' + this.name + ', I am ' + this.job + '. I know ' + skill1 + ', ' + skill2 + ', ' + skill3); } var rachel = { name: 'Rachel Andrew', job: 'front-end developer' } developer.call(rachel, 'HTML', 'CSS', 'Javascript'); // return "My name is Rachel Andrew, I am front-end developer. I know HTML, CSS, Javascript"
عملکرد متد ()aplly مشابه متد ()call هست، با این تفاوت که به این متد فقط دو آرگومان میتونیم وارد کنیم که آرگومان دوم باید به صورت آرایه باشه.
var developer = function (skill1, skill2, skill3) { console.log('My name is ' + this.name + ', I am ' + this.job + '. I know ' + skill1 + ', ' + skill2 + ', ' + skill3); } var rachel = { name: 'Rachel Andrew', job: 'front-end developer' } var skills = ['HTML', 'CSS', 'Javascript']; developer.apply(rachel, skills); "My name is Rachel Andrew, I am front-end developer. I know HTML, CSS, Javascript"
همون طور که میبینید، عملکرد این متد شبیه متد ()call هست و دقیقا همون مقدار رو برگشت داده. فقط به جای اینکه «HTML»، «CSS» و «Javascript» رو به صورت آرگومانهای جدا به متد اضافه کنیم، اونها رو تو یه آرایه ذخیره و اون آرایه رو به عنوان آرگومان دوم به متد وارد کردیم.
متد ()bind هم با کمی تفاوت مثل متد ()call عمل میکنه:
var developer = function (skill1, skill2, skill3) { console.log('My name is ' + this.name + ', I am ' + this.job + '. I know ' + skill1 + ', ' + skill2 + ', ' + skill3); } var rachel = { name: 'Rachel Andrew', job: 'front-end developer' } var developerRachel = developer.bind(rachel, 'HTML', 'CSS', 'Javascript'); developerRachel(); // return "My name is Rachel Andrew, I am front-end developer. I know HTML, CSS, Javascript"
تفاوت متد ()bind در این هست که بلافاصله فانکشن مورد نظر (فانکشن ()developer) رو فراخوانی و اجرا نمیکنه، بلکه یک نمونه کپی شده از اون تابع رو تولید میکنه و برگشت میده و ما میتونیم با ذخیره اون در یک متغیر (متغیر developerRachel) در مواقع نیاز، اون فانکشن رو فراخوانی و اجرا کنیم.
تا اینجا دربارهی سه ویژگی execution context بحث کردیم و الان هر سه ویژگی رو به صورت کامل میشناسیم.از این به بعد دربارهی چگونگی ساخته شدن execution context بحث میکنیم.
موتور جاوااسکریپت execution context رو در دو مرحله ساخته و اجرا میکنه:
قبل از اجرای کد، یک مرحله داریم به اسم creation phase.
تو اولین قدم از این مرحله، variable object ساخته میشه. به این صورت که:
در صورتی که سازندهی execution context یک فانکشن باشه، ابتدا آرگومانهای فانکشن (در صورت وجود) داخل argument object ذخیره میشه (خود argument object بعد از ساخته شدن VO، داخل اون ذخیره میشه).
بعد موتور جاوااسکریپت، داخل کدها جست و جو میکنه تا جایی که فانکشن فراخوانی شده رو پیدا کنه. بعد از اینکه فراخوانی تابع پیدا شد، VO مختص به اون فانکشن ساخته و اطلاعات اون فانکشن داخل VO ذخیره میشه. پس تمام فانکشنها قبل از اجرای کد، داخل VO و در نتیجه execution context خودشون ذخیره میشن.
بعد اینکه تکلیف فانکشنهای فراخوانی شدهی کد روشن شد، نوبت به متغیرهایی میرسه که فراخوانی شدن. در این مرحله متغیرهایی که فراخوانی شدن داخل VO ذخیره میشن و مقدارشون برابر با undefined قرار داده میشه. متغیر حتی اگه مقداردهی هم شده باشه، تو این مرحله مقدارش برابر با undefined هست.
با توجه به گفتههای دو پاراگراف آخر، چون فانکشنها (declaration functions) و متغیرها در مرحلهی creation در VO ذخیره میشن، قبل از اجرای کد قابل دسترس هستند و در اصطلاح گفته میشه که hoisted شدن و به این عمل hoisting گفته میشه.
به همین دلیل هست که ما میتونیم از declaration functions قبل از فراخوانی و از متغیرها قبل از مقداردهی استفاده کنیم. هر چند در صورت اینکه اگر از متغیرها قبل مقداردهی استفاده کنیم، مقدار برگشتی برابر با undefined خواهد بود.
در دومین قدم scope chain اون execution context ساخته میشه.
و در سومین قدم مشخص میشه که this اون execution context به چه آبجکتی اشاره میکنه.
در دومین مرحله، کدهای execution contextهای execution stack به صورت خط به خط اجرا میشن و بعد از اینکه همهی کدهای یک execution context اجرا شد، اون execution context از execution stack حذف میشه.