זהו השני בסדרת מאמרים על איך שפת javscript עובדת מתחת למנוע. המאמר הראשון נמצא כאן.


מחסנית קריאות וערימת זיכרון (Call Stack and Memory Heap):
במהלך תהליך הרצת המנוע את קוד ה-javascript, המנוע משתמש בשני סוגים של אזורי זיכרון במנוע, שבהם הוא נעזר במהלך ההרצה:

אזור הזיכרון הראשון נקרא ערימת זיכרון (Memory Heap), והוא משמש לאחסן בתוכו את הנתונים (data) שבקוד, ולשלוף אותם בעת הצורך.
כך למשל, פעולה של יצירת משתנים או אובייקטים, יוצרת כעין קופסת זיכרון שמכילה את פיסת המידע (המספר או המחרוזת או שאר סוגי ה-type הבסיסים של js) ונמצאת בתוך ה-Memory Heap, כאשר ניתן לגשת לאותה פיסת זיכרון על ידי קריאה לה באמצעות הכתובת שלה - שם המשתנה או האובייקט.
JavaScript:
const a = 248;  // אחסון המספר 248 בתוך הערמת זיכרון
const b = 365;  // אחסון המספר 365 בתוך הערמת זיכרון

/*
,שליפת מספרים מתוך ערימת הזיכרון על ידי השתמשות ב'כתובת' שלהם - שם המשתנה,
יצירת מקום אחסון חדש בזיכרון המכיל את המספר 613
*/
const all = a + b;


אזור הזיכרון השני הוא מחסנית קריאות (Call Stack) ותפקידו העיקרי הוא לעקוב אחר המקום שאנו נמצאים בו בקוד במהלך ההרצה שלו, ולשמור שהרצת הקוד תהיה לפי סדר.
ה-Call Stack עובד בצורה שהוא מבצע תהליך אחד בלבד בכל רגע נתון ולא כמה תהליכים במקביל (מה שנקרא one thread).

התהליך של הרצת הקוד ב-Call Stack מתבצע בשיטה של מה שנכנס אחרון יוצא ראשון, כלומר כל פונקציה נכנסת לסטוק לפי הסדר של הרצת הקוד, כשמסתיימת הרצתה היא נמחקת מהסטוק ולתוך הסטוק נכנסת הפונקציה הבאה להרצה, במקרה של פונקציה שמריצה בתוכה פונקציה אחרת הרי שנכנסת הפונקציה הראשונה ואחריה השניה ואז מבוצעת הפונקציה השניה ומסתלקת ומבוצעת הראשונה ומסתלקת.

והנה דוגמא עם הסבר לתהליך:
JavaScript:
const one = () => two();
const two = () => three();
const three = () => console.log("Call Stack");

one();
בתחילה מכניס המנוע לתוך המחסנית את הפונקציה הראשונה שנקראה להפעלה - פונקציה one, וכיון שבתוכה יש קריאה לעוד פונקציה - פונקציה two, גם פונקציה זו נכנסת למחסנית מעל הפונקציה הראשונה, וכן הלאה, לבסוף כאשר אין עוד פונקציה שנקראת להפעלה, מתחיל הביצוע של הפונקציה האחרונה שנכנסה למחסנית - פונקציה three ומודפס לקונסול המילים Call Stack, כאשר ברגע שנגמר הביצוע הפונקציה מסתלקת מהמחסנית, לאחריה הפונקציה שנכנסה לפניה מתבצעת ומסולקת מהמחסנית עד שלבסוף מתבצעת גם הפונקציה הראשונה והמחסנית נותרת ריקה.
וכך זה נראה בתוך המחסנית:
jhghbk18 181215541516526.png


למעשה לא מאה אחוז מהמשתנים מאוחסנים בערימת זיכרון וישנם משתנים שמאוחסנים במחסנית הקריאות, ובדרך כלל דברים כבדים יותר כמו אובייקטים ופונקציות הם אלו שמאוחסנים בערימת זיכרון, והדברים תלוים גם במנוע שמריץ את הקוד.

Stack Overflow (הצפת מחסנית):
כאמור המחסנית מאחסנת בתחילה את הפונקציה שרצה ברגע זה (שנקראה בסקופ הגלובלי של התוכנית) ולאחר מכן מכניסה לאחסון גם את הפונקציות שמורצות מתוך הפונקציה (שנקראו בתוכה).
במקרה שישנם יותר מדי פונקציות שנכנסות לתוך המחסנית, כגון במקרה של פונקציה שקוראת לעצמה שוב ושוב, מתרחש דבר שנקרא הצפת מחסנית (Stack Overflow), והמנוע שמריץ את הקוד קורס - מפסיק את ההרצה של הפונקציה, ניתן לראות זאת בקלות על ידי הרצת פונקציה שקוראת לעצמה בדפדפן.
הניסיון להריץ את הקוד הבא בדפדפן, יגרום ל-Stack Overflow, נסו זאת!
JavaScript:
function a() {
  a()
};

a()

דליפת זיכרון (Memory Leaks):
זיכרון מעצם טבעו הוא דבר מוגבל, מוגבלות זו עשויה לגרום לבעיה שנקראת דליפת זיכרון (Memory Leaks).
אנו קוראים דליפת זיכרון למקרה של קוד שנשלח לאיחסון בזיכרון ה-Memory Heap ונשאר תופס מקום בזיכרון גם לאחר שכבר אין בו צורך (ולא נמחק מהזיכרון כפי שהיה צריך), דבר שפוגם בביצועי התוכנה.
במקרה הגרוע ביותר התוכנה עלולה לקרוס מעומס יתר על מקום האחסון - ה-Memory Heap.
דוגמא קטנה למקרה כזה (זהירות, בהרצה של קוד זה הדפדפן עלול לקרוס!):
JavaScript:
let arr = [];

for (let i = 5; i > 2; i++) {
  arr.push(i);
};
שלושה דוגמאות נפוצות לדברים שעשויים לגרום לדליפת זיכרון:
1. משתנים גלובליים, כיוון שהם גלובליים הם לא מסתיימים אף פעם ונשארים בזיכרון, בניגוד למשתנה שהוכרז בתוך פונקציה שמיד שהרצת הפונקציה מסתיימת נמחק המשתנה מהזיכרון.
2. אירוע הקשבה (event listenet), הרבה פעמים הוא נשאר ברקע (בתוך ה-Memory Heap) מקשיב לאלמנט גם לאחר שנגמר האירוע שלשמו הוא נוצר.
3. פונקציית setInterval עם אובייקט בתוכה, ששוכחים לסיים אותה וממשיכה כל הזמן להפעיל את האובייקט שבתוכה.