במאמר זה אני אסביר על JavaScript Hoisting – אחת מהתכונות הבסיסיות של JavaScript שכמעט כל מפתח Angular\Node.JS מכיר היטב. מדובר בתכונה מאוד חשובה שהכרות איתה יכולה למנוע מכם למרוט שיערות יקרות מראשכם. עם אלו שחושבים שמדובר בתכונה אלמנטרית – הסליחה.
Hoisting היא בעצם הנטייה של JavaScript להכריז על כל המשתנים בתחילת הבלוק של הקוד. כל המשתנים ב-JavaScript מוגדרים בתחילת הבלוק בלי שום קשר למתי החלטנו להכריז עליהם. נשמע כמו סינית? בואו נסתכל על הקוד הזה:
var myvar = 'hello!!!'; (function() { alert(myvar); })();
אנו מכריזים על המשתנה myvar כמשתנה גלובלי. אחר כך יש לנו פונקציה אנונימית (כלומר פונקציה ללא שם) שמריצה את עצמה. למה אנחנו עושים את זה? כדי לממש בלוק של קוד. מה שיש מחוץ לקוד הוא הסקופ הגלובלי. מה שיש בתוך הבלוק של הקוד (כלומר הפונקציה האנונימית) זה הסקופ של הפונקציה האנונימית. ב-JavaScript, הודות ל-closure, כל המשתנים שזמינים בסביבה הגלובלית זמינים גם בסקופ שלנו.
אם תעתיקו ותדביקו את הקוד שנמצא לעיל בקונסולה שלכם, תקבלו מיד alert של hello.
עד כאן לא משהו שיפתיע אתכם. יכול להיות שהפונקציה האנונימית שמריצה את עצמה תראה לכם מוזר, אז שווה להסתכל על הקוד עוד קצת. אבל בגדול אין כאן משהו שיפיל מתכנתי JavaScript שיש להם מעט ניסיון ויודעים שמשתנה שמוגדר באופן גלובלי יהיה זמין גם בפונקציה שמוגדרת מתוך הסקופ הגלובלי.
על מנת לחדד את העניין, מה דעתכם נקבל אם נריץ את הדבר הבא?
var myvar = 'hello!!!'; (function() { var myvar = 'World!!!' alert(myvar); })(); alert(myvar);
ה-alert הראשון יהיה World!! כמובן. אבל מה יהיה ה-alert השני? התשובה היא כמובן hello!!! רגע, למה?!? הרי שיניתי את myvar! התשובה היא שלא באמת שיניתי אותו – אלא הגדרתי myvar באמצעות var בסקופ הפנימי של הפונקציה האנונימית. "מבחוץ", כלומר בסקופ הגלובלי, myvar עדיין hello. אם אני רוצה לדרוס את myvar הגלובלי, אני צריך לעשות משהו כזה:
var myvar = 'hello!!!'; (function() { myvar = 'World!!!' alert(myvar); })(); alert(myvar);
אם אני לא משתמש ב-var, אני בעצם דורס את המשתנה בסקופ הגלובלי ואז שני ה-alerts יהיו world.
עד כאן בנוגע ל-scoping וגם בנוגע ל-closure. אבל מה בנוגע ל-hoisting? זה השוס האמיתי, מה לפי דעתכם יודפס כאן?
var myvar = 'hello!!!'; (function() { alert(myvar); var myvar = 'World!!!' })();
בואו ונראה: אנו מגדירים את myvar בסקופ הגלובלי ומכניסים לו את ה-hello. נכנסים לסקופ של הפונקציה האנונימית. מה הערך של myvar? עדיין hello. יש לנו alert ורק אחרי ה-alert אנו משנים את ה-myvar. אז די ברור שה-alert יהיה hello.
אבל אם נדביק את הקוד ונריץ אותו בקונסולה, אנו נקבל undefined. למה?!? התשובה היא hosting. בתחילת כל בלוק של קוד, ה-JavaScript אוסף את כל המשתנים שמוגדרים ומכריז עליהם בתחילת הבלוק. כלומר שהקוד שלעיל הוא תמיד משהו כזה:
var myvar; myvar = 'hello!!!'; (function() { var myvar; alert(myvar); myvar = 'World!!!' })();
כך זה נראה מבחינת ההרצה. ואז די ברור למה אנחנו מקבלים undefined. ברגע שאנחנו בעצם משתמשים ב-var, אנו יוצרים hoisting. קודם JavaScript אוסף את כל המשתנים המוגדרים ב-var ומעביר אותם לראשית הבלוק והם מאוכלסים כאשר הם מאוכלסים. זה, אגב, מניסיון, מטריף כל מתכנת JavaScript שעובר פאזה לקוד יותר משמעותי. בגלל זה, מאוד מקובל תמיד להגדיר את כל המשתנים בתחילת בלוק של קוד. כלומר משהו בסגנון הזה:
(function() { var myvar,somevar,anothervar; })();
אם אתם משתמשים ב-jsLint במוד strict, הוא גם יחייב אתכם לעשות את זה. בכל מקרה מדובר בנוהג שמאוד מסייע להבין את ענייני הסקופינג וה-hoisting.
בגדול, זה יכול להראות לכם מוזר ולא קשור לכלום. במיוחד אם עברתם קצת במחוזות ה-jQuery או ספריות נוספות. אבל מהניסיון שלי שווה להכיר את זה במיוחד אם שוקלים לעבור לספריות JavaScript כמו Node.JS.
6 תגובות
חלק הקוד השני אמנם מדגים את הנושא, אבל ההסבר לא באמת ברור.
לא הבהרת מספיק את העובדה ש-javascript מפרידה בין ההצהרה על המשתנה לבין אתחולו ומפצלת את השניים.
אגב הפונקציה האנונימית שעטופה בסוגריים היא נקראת IIFE (Immediately Invoked Function Expression) ששווה לציין בכתבה.
תודה! הסבר טוב פשוט וברור
לא הודגש שהאמור מתייחס לשימוש ב-var. כאשר מצהירים על המשתנה עם let (או const) אין hoisting בכלל.
שקר גס, עיוות של המציאות לצורך יצירת שיטת עבודה
מבחינתי בעולם המקצועי זה לא מעניין אף מפתח שלא בוחר להתייחס לזה,
ב 2023 אם היית מעלה את זה כידע בראיון עבודה הייתי מבין שכל מה שעבדת עליו היה בתאוריה
אתה מודע לזה שמדובר במאמר שנכתב לפני עשור, כן? עם ES6 זה כמובן לא רלוונטי.