בלבול וחוסר הבנה של var ב- java script

ניסיתי להבין וקראתי המון חומר לגבי המושג block scope.
אבל עדיין לא הבנתי 2 דוגמאות:

1. למה הקוד הבא מדפיס ל- console את המספר 5, שש פעמים (את ההדפסה החמישית והשישית אני מבין), לא מבין למה 4 ההדפסות הראשונות הן לא: 1,2,3,4:

קוד:
for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop

2. מה ההבדל בין שני קטעי קוד.. לא מצליח להבין למה אחד עובד נכון (כמו שמצפים) והשני לא:
I. עובד לא כפי שמצפים:
קוד:
var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

II. עובד כמו שמצפים:
קוד:
var funcs = [];

function createfunc(i) {
  return function() {
    console.log("My value: " + i);
  };
}

for (var i = 0; i < 3; i++) {
  funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}
 

BravoMan

Active member
וואו, מישהו חזר לכאן!
מגיב.

טוב, אני שונא JS אז אף פעם לא באמת התעמקתי בו, אבל בוא נראה אם אוכל לעזור לך בכל זאת:

1. כדי להבין את מה שקורה צריך להבין 2 דברים:
הראשון הוא ש-JS מבצע הכל ב-thread אחד, כלומר בצורה סדרתית.
שני הוא, שהשורה:
קוד:
setTimeout(() => console.log(x))
לא מעבירה לפונקציה log את ערך המשתנה X באותו רגע שהיא מבוצעת.

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

2. בדומה לדוגמה הראשונה, הטריק הוא להבין למי שייך i.

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

אבל, בדוגמה השנייה, i הוא פרמטר של פונקציה ליצירת פונקציות.
יש פה מעין מעטפת שבכל קריאה מייצרת עותק חדש של i שכבר לא ישתנה לעולם.
לכן, כשאתה משתמש בו בפונקציה שייצרת, אתה למעשה משתמש בעותק זמני של i שהיה קיים רק בזמן שייצרו את הפונקציה, והערך שלו כבר לא יכול להשתנות, כי פונקציית המעטפת שקיבלה את הערך הזה סיימה את הריצה שלה.
 
אם תוכל להבהיר לי גם את העניין הבא.. אז אשמח :)
בדוגמא הראשונה(setTimeout(() => console.log(x)
הכל כתוב ברמת top level (כלומר לא בתוך פונקציה)...
מה היה קורה אם כל הקוד היה נמצא בתוך פונקציה.. כלומר ה- scope של x היה ברמת הפונקציה ולא ברמה גלובלית?
בדקתי אבל קשה לי למצוא הסבר.. שום דבר לא השתנה..
 
נערך לאחרונה ב:

BravoMan

Active member
sope בעברית זה "טווח הכרה", כלומר האזור בתוכנה שבו יש חיים למשתנה או אובייקט מסוים.
זה מונח די אוניברסלי לכל שפות תכנות גבוהות, לא רק ל-JS.

"block" הוא יחידה של קוד, בד"כ מוקפת סוגריים מסולסלים {}, לפחות בשפות שמשתמשות בהן (JS, Java C וכו').

אם מחברים את זה ביחד, מה שיוצא זה שב-JS, טווח הכרה, או "אזור חיים" של המשתנה, הוא הבלוק שבתוכו הוא הוגדר, כלומר, מופיע שורת ה-var שלו, או במקרה של פרמטר, הפונקציה שמקבלת אותו.

כל בלוק מכיר את המשתנים שלו, ושל של האבא והסבא, וכו' שלו.

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

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

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

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