Connect with us

קטגוריות

Javascript

Javascript: מה זה Closure ולמה אני חייב לדעת מה זאת סביבה לקסיקלית

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

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

נושא ה-closures יכול להיות מוסבר בצורה מפחידה ואפשר להתבלבל ולחשוב שמדובר בקונספט מסובך, אבל לא כך הדבר, תנו לי להוכיח לכם באמצעות 6 שורות פשוטות:

function iAmYourFather() { 
  let x = 2
  return function iAmYourSon(y) {
    return x * y
  } 
}

והנה לפניכם closure קצר וקלאסי שבטח רשמתם כמה פעמים, אז מה בעצם ההגדרה של closure (או בעברית סְגוֹר)?

סְגוֹר היא פונקציה יחד עם סביבת ייחוס עבור המשתנים שאינם מקומיים בפונקציה.

סְגוֹר מאפשר לפונקציה לגשת למשתנים שהם מחוץ לטווח-ההכרה הלקסיקלי המידי שלה. כלומר, משתנים שאינם מוגדרים בפונקציה הנוכחית, וגם אינם משתנים גלובליים.

מתוך וויקיפדיה

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

סביבה לקסיקלית

ב-JavaScript כל פונקציה, בלוק של קוד או אפילו לקוד שנמצא ברמה הכי גבוהה יש מן אובייקט מיוחד ופנימי אשר נקרא Lexical Environment (או בעברית סביבה לקסיקלית) אשר מתחלק לשני חלקים:

  1. Environment Record – אובייקט שמכיל בתוכו את כל המשתנים המקומיים כ-properties (ודברים נוספים כמו הערך של this).
  2. הפנייה לסביבה הלקסיקלית החיצונית (זאת שמעליו בהיררכיה), בדרך כלל הכוונה לקוד שנמצא מחוץ לפונקצייה (מחוץ לסוגריים הנוכחיים).

עכשיו אנחנו מבינים שמשתנה הוא בסך הכל property בתוך אובייקט פנימי מיוחד שנקרא Environment Record. כשאנחנו אומרים ״לשנות משתנה״ זה בעצם ״לשנות property של ה-Environment Record"!.

בתמונה למעלה הגדרנו פונקציה בסביבה הלקסיקלית הגלובלית שלנו אשר משוייכת לכל הסקריפט, בדפדפן כל תגיות ה-</script> חולקות את אותה סביבה לקסיקלית.

בתמונה למעלה המרובע מייצג את Environment Record והחץ מייצג את ההפניה לסביבה הלקסיקלית החיצונית ומאחר שהגדרנו את המשתנה בסביבה הלקסיקלית הגלובלית ההפניה מצביעה על null מאחר ולסביבה הגלובלית אין סביבה שנמצאת מעליה.

הצהרה על פונקציות

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

הקוד למעלה מדגים איך הסביבה הלקסיקלית מאותחלת בפונקציה say (בגלל שהצהרנו על הפונקציה) ורק אחר כך המשתנה pharse מתווסף.

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

במהלך הריצה של הפונקציה say יש לנו שתי סביבות לקסיקליות:

  1. הסביבה הלקסיקלית הפנימית אשר מכילה את המשתנה name
  2. הסביבה הלקסיקלית החיצונית שבמקרה הזה היא הסביבה הלקסיקלית הגלובלית.

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

אני אסביר בקצרה את הדוגמא למעלה

  • כשה alert בתוך הפונקציה say רוצה לקבל את הערך של המשתנה name הוא מוצא אותו מיד בתוך הסביבה הלקסיקלית הפנימית.
  • כשה alert רוצה לקבל את הערך של המשתנה phrase אין שום משתנה phrase בסביבה הלקסיקלית של הפונקציה, אז החיפוש עובר לסביבה העליונה ושם הוא מוצא את את המשתנה

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

פונקציות מקוננות

פונקציה מקוננת היא פונקציה שנוצרה בתוך פונקציה אחרת, אנחנו יכולים לעשות את זה בקלות עם Javascript:

function sayHiToTheUser(firstName, lastName) {

  function hiUser() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + hiUser() );
}

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

function sayHiToTheUser(firstName, lastName) {

return { helloUserFunc: function hiUser() {
    return firstName + " " + lastName;
  }
}
  alert( "Hello, " + hiUser() );
}

או שאפשר להחזיר את הפונקציה עצמה ואז אנחנו יכולים להשתמש בה במקום אחר לגמרי (לא משנה איפה) יש לה עדיין גישה למשתנים החיצוניים, לדוגמא:

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  }
}

let counter = makeCounter();

// בדוגמא זו הפונקציה counter רצה בסביבה הלקסיקלית הגלובילית באופן תקין, למרות שהיא נוצרה בתוך makeCounter!

alert( counter() ); // 0 
alert( counter() ); // 1
alert( counter() ); // 2

בדוגמא זו הפונקציה makeCounter יוצרת ומחזירה פונקציה אנונימית שהיא בעצם ה-״counter״ שלנו, ומה שהפונקציה האנונימית עושה היא מחזירה את המספר הבא בכל קריאה, כשהפונקציה האנונימית  רצה היא מחפשת את המשתנה count בסביבה הלקסיקלית שלה היא כמובן לא מוצאת אותו אז היא עולה רמה לסביבה הלקסיקלית שמעליה ושם היא כמובן מוצאת אותו ומעלה את הערך במשתנה בסביבה הלקסיקלית שלו.

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

שאלות להעשרת הידע

שאלה: האם אנחנו יכולים בשלב מסויים לאפס את המשתנה count?

תשובה: לא, אין גישה מבחוץ למשתנים פנימיים.

שאלה: אם אנחנו קוראים כמה פעמים לmakeCounter היא תחזיר לנו כמה פונקציות אנונימיות. האם הן עצמאיות או שכולן חולקות את אותו משתנה count?

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


עכשיו אתם מבינים טוב יותר איך שפת Javascript עובדת ״מתחת לפני השטח״ ויכולים להסביר מה זה Closure גם אם יעירו אתכם ב3 לפנות בוקר (השעה שבה כתבתי את המאמר).

נתראה במאמר הבא, תשמרו על עצמכם ולא לפחד מ-Closures.

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

עוד ב Javascript

Meni Edri היי אני מני, מתכנת מנוסה ומאוד אוהב קוד ואת עולם התכנות, הקמתי את CodeHub על מנת שתיהיה לי פינה שבה אוכל לכתוב, ללמד וללמוד, מקווה שתימצאו את הבלוג הזה שימושי.
קריאה מהנה.

מאמרים פופולריים

נושאים פופולריים

חזרה למעלה