موتور جاوا اسکریپت کارهای زیادی رو برای ما انجام میده؛ اما بزرگترین کارش خوندن کد و اجرای اونه. دو مورد مهم در این مرحله عبارتند از:
۱. ما به مکانی برای ذخیره و نوشتن دادههای برنامه (متغیرها، اشیاء و غیره) نیاز داریم.
۲. باید خط به خط ردیابی کنیم که چه اتفاقی برای کد ما می افته.
اینجاست که یک چینه یا پشتهی فراخوانی ( CallStack) و یک پشتهی حافظه ( MemoryHeap) وارد میشن. ما به پشتهی حافظه به عنوان مکانی برای ذخیره و نوشتن اطلاعات نیاز داریم؛ چون در نهایت همه برنامهها فقط عملیات خوندن و نوشتن رو انجام میدن (یعنی تخصیص، استفاده و آزادسازی حافظه). پشتهی فراخوانی هم به ما کمک میکنه جایی که در کد هستیم رو پیگیری کنیم، تا بتونیم کد رو به ترتیب و خط به خط اجرا کنیم.
کد بالا به جاوا اسکریپت میگه لطفا یک خانه از حافظه رو به متغیر number با مقدار ۱۱ اختصاص بده.
در اینجا ما به موتور جاوا اسکریپت میگیم که یک بخش از حافظه رو به آبجکت person و مقادیر اون اختصاص بده. بنابراین هر زمان که person رو صدا کنیم، به ناحیهای در پشتهی حافظهی ما برمیگرده که کلیدهای name و age اون به مقادیر "Ali" و "22" اشاره میکنن.
ما میتونیم هر نوع دادهای رو اینجا ذخیره کنیم. دادهها به شکلی نامرتب هستن. از متغیرها برای اشاره به مکانهای مختلف استفاده میکنیم و موتور جاوا اسکریپت دادهها رو توی یکسری جعبه برای ما قرار میده و نگهداری میکنه.
تابع بالا یک بخش از حافظه رو به خودش اختصاص میده تا هر زمان که ما ()calculate را فراخوانی کنیم؛ فقط اون رو توی حافظه جستجو کنه و این قطعه کد رو اجرا کنه.
هر بار که یک تابع رو اجرا میکنیم از پشتهی فراخوانی استفاده میکنیم. ما میتونیم پشتهی فراخوانی رو به عنوان منطقهای درون حافظه در نظر بگیریم که آخرین ورودی رو اول از همه اجرا میکنه. بنابراین در اینجا، تابع calculate رو بالای پشته اضافه میکنه و بعد از تموم شدن اِجراش، اون رو حذف می کنه.
اگر به developer console بریم و این کد رو در قسمت sources -> snippets -> new snippet وارد کنیم، میتونیم مراحل اضافه، اجرا و حذف شدن این توابع رو به پشتهی فراخوانی ببینیم.
این روشیه که توی بیشتر زبانها کار میکنه ( در اکثر زبانها پشتههای فراخوانی و پشتههای حافظه داریم). حالا از اونجایی که پیادهسازی موتور جاوا اسکریپت متفاوته، جایی که به متغیرها حافظه تخصیص داده میشه، همیشه یکسان نیست. فقط باید بدونیم که هر چیزی بالای پشته هست همون چیزیه که در حال اجراست.
سرریز پشته زمانی اتفاق میافته که توابع تو در تو رو بارهای بار فراخوانی کنیم. اگر فقط به اضافه کردن توابع به پشته ادامه بدیم بدون اینکه اونها رو از پشته خارج کنیم و به کارشون پایان بدیم، یک سرریز پشته اتفاق میافته.
ما به راحتی میتونیم با توابع بازگشتی یک سرریز پشته ایجاد کنیم.
با اجرای این کد، ارور زیر رو داخل developer console میگیریم که بهمون میگه اندازه برنامه اجرایی ما از حداکثر اندازه پشتهی فراخوانی فراتر رفته.
قبل از صحبت در مورد نشت حافظه، باید مفهوم جمع آوری زباله را درک کنیم.
جاوا اسکریپت زبانیه که بهطور خودکار جمع آوری زباله انجام میده. این به این معنیه که اگر جاوا اسکریپت حافظه رو به متغیری تخصیص بده ( مثلا در داخل یک تابع، یک آبجکت ایجاد میکنیم و اون آبجکت در جایی در پشتهی حافظهی ما ذخیره میشه)؛ وقتی فراخوانی اون تابع رو به پایان میرسونیم و دیگه به اون متغیر هم نیاز نداریم؛ اون مقدار بهطور خودکار از حافظه حذف میشه.
بنابراین، جاوا اسکریپت به طور خودکار این حافظه رو آزاد میکنه یا به معنای واقعی کلمه زباله های ما رو جمع میکنه.
اینطوری فقط دادههایی که برای ما مفید هستن باقی میمونن و ما مطمئن میشیم که از تمام حافظه ای که داریم، استفاده نمیکنیم؛ چون همونطور که میدونیم حافظهی ما محدوده. این روشیه که جاوا اسکریپت، از ایجاد نشتیهای حافظه جلوگیری میکنه؛ درست مثل سرریز پشته.
اما احمقانهست که فرض کنیم از اونجایی که ما یک زباله جمع کن داریم که حافظه رو برای ما پاک کنه، نباید نگران مدیریت حافظه باشیم؛ چون هیچ سیستم کاملی وجود نداره!
و اگرچه زبالهگیر به اندازهای باهوش هست که بفهمه به چه چیزهایی نیاز داریم و به چه چیزی نیاز نداریم، اما گاهی اوقات اشتباه میکنه و حافظه رو آزاد نمیکنه.
در زبان های سطح پایین مثل C، شما جمع آوری زباله رو کنترل میکنید. این شما هستید که به موتور میگید قسمتهایی از حافظه رو حذف کنه. این خطرناکه، اما برنامه های C بسیار سریع و کارآمد هستن چون شما جمع آوری زباله رو کنترل میکنید.
جمع آوری زباله در جاوا اسکریپت از الگوریتم Mark and Sweep استفاده میکنه. زمانیکه یک مرجع به یک متغیر حذف میشه، اون رو حذف میکنه.
کد بالا حافظه ما رو پر میکنه تا جایی که چیزی از حافظه برای استفاده باقی نمونه؛ و تا زمانیکه مرورگر ما از کار بیفته، هیچ چیز از پشته خارج نمیشه.
var a = '1' var b = '2' var c = '3'
اینجا اگه فقط به افزودن این متغیرها به حافظه ادامه بدیم، تمام حافظه ما در نهایت تموم میشه؛ چون ما فقط در حال استفاده از حافظه هستیم. حالا اگه این متفیرها آبجکتهای تو در تو باشن، ما مقدار زیادی از حافظه رو مصرف میکنیم.
var element = document.getElementById(‘button’) element.addeventListener(‘click’, )
این یک روش معمول برای نشت حافظهست، چون فقط شنوندههای رویداد یا همون eventListenerها رو اضافه میکنیم و زمانی که دیگه به اونها نیاز نداریم، حذفشون نمیکنیم. اونها در پس زمینه میمونن و قبل از اینکه متوجه بشیم، باعث نشتی حافظه میشن.
اگه تابعی رو داخل یک ()setInterval قرار بدیم، هرگز زباله شناخته نشده و حذف نمیشن؛ مگر اینکه خود setInterval رو حذف کنیم.
setInterval( () => { //referencing objects })
بنابراین چیزی که باید در نظر داشت این هست که حافظه محدوده. بنابراین اگه میخوایم کد کارآمدی داشته باشیم، باید مراقب باشیم که نشت حافظه یا سرریز پشته نداشته باشیم.
این به این معناست که در هر زمان تنها یک مجموعه از دستورالعملها اجرا میشن و جاوا اسکریپت نمیتونه چندین کار رو همزمان انجام بده. بهترین راه برای بررسی تک رشتهای بودن یک زبان این هست که آیا یک پشتهی فراخوانی داره یا نه؟ آیا تابعها رو یکی یکی وارد پشته میکنیم و بعد بیرون میاریم؟
اینطوری میتونیم نتیجه بگیریم جاوا اسکریپت یک زبان تک رشتهای و همزمانه؛ یعنی فقط یک تابع میتونه در یک زمان اجرا بشه.