[עמוד ראשי]   [נושא קודם]
משתנים

משתנה הינו שם המקושר לערך מידעי. אנו אומרים כי המשתנה "מאחסן" או "מכיל" את הערך.
משתנים מאפשרים לנו לאחסן ולתפעל מידע בתכניותינו. לדוגמא, השורה הבאה מאתחלת את הערך שתיים לתוך משתנה בשם i :
i=2; 
והשורה הבאה מוסיפה 3 ל- i ומשייכת את התוצאה למשתנה חדש בשם sum:
sum = i + 3;  
שתי שורות הקוד הללו מדגימות את סך כל המידע למעשה שצריך לדעת אודות משתנים, למרות זאת, על מנת להבין באופן מלא ושלם כיצד עובדים משתנים ב- JS עלינו לשלוט / להתמחות במספר רעיונות / מושגים נוספים.
1. טיפוסי משתנים
הבדל בין JS לבין שפות אחרות הוא ש- JS היא untyped, משמע, באופן חלקי, כי משתנה ב- JS יכול להיות בעל ערך מכל סוג / טיפוס שהוא, שלא כמו בשפות האחרות, בהן משתנה יכול להיות מטיפוס ספציפי בלבד כשם שהוא מוכרז / מוצהר.
לדוגמא, חוקי ביותר ב- JS לייחס מספר למשתנה ואחר כך לייחס אליו משתנה מסוג מחרוזת:
i = 10;
i = "ten"; 
מאפיין זה של השפה גורם, כנדרש, להמרה אוטומטית של ערכים מסוג אחד לאחר.
2. הצהרת משתנים
לפני השימוש במשתנה חובה עלינו להצהיר / להכריז עליו. על משתנים מכריזים באמצעות מילת המפתח var.
var i;
var sum;
ניתן להצהיר על מספר משתנים באמצעות אותה המילה var :
var i, sum;
וניתן לשלב הצהרה יחד עם איתחול :
var i = 3, sum = 8;
כאשר איננו מאתחלים את המשתנה בזמן ההצהרה, המשתנה מוגדר, אך ערכו הוא הערך המיוחד של Undifined JS עד שנאחסן בו ערך.
נשים לב כי ניתן להצהיר על משתנה גם כחלק מלולאות, זאת על מנת להצהיר על משתנה הלולאה כחלק מתחביר הלולאה עצמו בצורה תמציתית, לדוגמא:
for(var  i = 0; i < 10; i++) document.write(i, "<BR>");
משתנים המוכרזים באמצעות var הם תמידיים / קבועים. נסיון למחוק אותם באמצעות האופרטור delete, השייך לגרסת 1.2 ומעלה גורם לשגיאה. זה חוקי, ולא נגרם כל נזק כאשר נצהיר על משתנה יותר מפעם אחת באמצעות הביטוי var. אם ההצהרה החוזרת היא בעלת איתחול, נתייחס לכך כשם שנתייחס לביטוי השמה.
3. טווח הכרת המשתנים
טווח ההכרה של המשתנה הוא החלק / "האיזור" בתוכנית בו הוא מוגדר. משתנה גלובלי הינו בעל טווח הכרה גלובלי – הוא מוגדר / מוכר בכל מקום בתוכנית. לעומת זאת, משתנים המוצהרים בתוך פונקציה מוגדרים אך ורק בתוך גוף הפונקציה, הם משתנים מקומיים ובעלי טווח הכרה מקומי.
פרמטרים של פונקציה נחשבים גם הם כמשתנים מקומיים ומוגדרים רק בתוך גוף הפונקציה. בתוך גוף הפונקציה, משתנה מקומי הוא בעל זכות קדימה ביחס למשתנה גלובלי בעל שם זהה. אם נכריז על משתנה מקומי או פונקציה בעלת פרמטרים בעלי אותו שם כשל משתנה גלובלי, אנו ביעילות "מחביאים" את המשנה הגלובלי. לדוגמא, הקוד הבא גורם להדפסת המילה “local”:
var scope = "global";
function checkscope()
{
  var scope = "local";
  document.write(scope);
}
checkscope();
למרות העובדה, כי ניתן להימנע משימוש במושג var בכתיבת קוד בטווח ההכרה הגלובלי, אנו חייבים להשתמש בו בהצהרה על משתנים מקומיים. נשים לב מה יקרה במידה ולא נעשה זאת :
scope = "global";
function checkscope()
{
  scope = "local";
  document.write(scope);  \\  prints "local"
}
checkscope();
document.write(scope);   \\  prints "local", too
בכלליות, פונקציות אינן יודעות אילו משתנים מוגדרים בטווח ההכרה הגלובלי או למה הם משמשים. כך, אם פונקציה משתמשת במשתנה גלובלי במקום במשתנה מקומי, קיים הסיכון של שינוי ערך, שבמקום אחר בתוכנית נזדקק לו. למרבה המזל, הימנעות מבעיה זו היא פשוטה ביותר, נצהיר על כל המשתנים באמצעות הביטוי var.

שלא כמו בשפות האחרות, ב- JS אין טווח הכרת משתנים ברמת ה- block. כל המשתנים מוגדרים בתוך פונקציה, לא משנה היכן מוצהרים, הם מוגדרים במהלך הפונקציה. בדוגמא הבאה, המשתנים i, j, k הם בעלי אותו טווח הכרה, שלושתם מוגדרים במהלך גוף הפונקציה, זה לא היה המקרה, אם הקוד היה נכתב בשפות כמו C, C++, JAVA
function test(o)
{
 var i =0;                     // i is defined throughout function
 if (typeof o == "object")
 {
   var j = 0;                  // j is defined anywhere, not just in block
   for( var k = 0; k < 10; k++)// k is defined anywhere, not just in loop
   {
      document.write(k);
   }
   document.write(k);          // k is still defined, prints 10
 }
 document.write(j);            // j is defined, but may not be initialized
}
חוק זה יכול ליצור תוצאות מפתיעות, הדוגמא הבאה מדגימה זאת:
var scope = "global";
function f()
{
  alert(scope);        // displays "undefined", not "global"
  var scope = "local"; // variable initialized here, but defined everywhere
  alert(scope);        // displays "local"
}
f(); 
אנו עלולים לחשוב, כי הקריאה הראשונה לפונקציה ( )alert תדפיס / תציג “global” משום שהצהרת המשתנה המקומי עדיין לא הופעלה.
בשל חוק טווח ההכרה, למעשה, זה לא מה שמתרחש בפועל. המשתנה המקומי מוגדר במהלך הפונקציה, משמעו כי המשתנה הגלובלי בעל אותו השם "מוחבא" במסגרת הפונקציה. למרות שהמשתנה המקומי מוגדר במהלך הפונקציה, הוא למעשה אינו מאותחל עד שביטוי ה- var מופעל
כך הפונקציה f הקודמת זהה לבאה:
function f()
{
 var scope;           // local variable is declared at the start of function     
 alert(scope);        // it exists here but still have "undefined" value   
 var scope = "local"; // now we initialize it & give it a value
 alert(scope);        // here it has a value
}
דוגמא זו מדגימה מדוע זהו תכנות טוב, למקם את כל הצהרות המשתנים יחדיו בתחילת הפונקציה.
ישנם שני סוגים של משתנים שאינם מוגדרים:
הסוג הראשון של המשתנה שאינו מוגדר הוא אחד שמעולם לא הצהרנו עליו. נסיון לקרא ערך של משתנה מסוג זה גורמת לשגיאת ריצה (runtime error). משתנים אילו אינם מוגדרים בשל העובדה הפשוטה כי אינם קיימים. השמת ערך למשתנה מסוג זה אינה גורמת לשגיאה, במקום זאת, המשתנה מוכרז / מוצהר במרומז.
הסוג השני הוא משתנה שהצהרנו עליו אך מעולם לא אותחל בערך. בנסיון לקרא את ערכו, נקבל את ערך ברירת המחדל, שהוא הערך "undefined" המיוחד, סוג זה של משתנה באופן שמיש יותר נקרא “unassigned”, שלא אותחל בערך, על מנת להבחינו מהמקרה הקודם.
דוגמאות:
הכרזה על משתנה ללא השמת ערך, ערכו הוא “undefined” :
var x;
שימוש בערכו של משתנה שלא הוגדר גורם לשגיאה :
alert(u);
השמה למשתנה שלא הוגדר, יוצרת את המשתנה :
u = 3;
4. טיפוסים פרימיטיביים וטיפוסי מצביעים
לעיתים קרובות, אנו אומרים כי משתנים הם "בעלי" או "מכילים" ערכים, אך מה בעצם הם מכילים ?
הטיפוסים ב- JS ניתנים לחלוקה לשתי קבוצות:
א. טיפוסים פרימיטיביים : מספרים, ערכים בוליאניים, Null , Undefined
ב. טיפוסי מצביעים : אובייקטים, מערכים ופונקציות.
טיפוס פרימיטיבי הוא בעל גודל קבוע בזיכרון, לדוגמא, מספר מאכלס שמונה בתים בזיכרון וערך בוליאני מיוצג על ידי ביט אחד בלבד.
טיפוסי מצביעים הם ענין אחר. אובייקטים, למשל, יכולים להיות בעלי אורך משתנה ולא קבוע. אותו הדבר לגבי מערכים, הם יכולים להכיל מספר משתנה של אלמנטים.
בדומה, פונקציה יכולה להכיל כמות משתנה של קוד. משום שטיפוסים אלו אינם בעלי גודל קבוע, ערכיהם אינם ניתנים לאיחסון ישירות לתוך 8 בתים בזיכרון, במקום זאת, המשתנה מאחסן מצביע (reference) לערך.
reference זה הינו סוג של פויינטר או כתובת בזיכרון, זה אינו הערך המידעי עצמו, אך הוא מורה היכן ניתן למצוא את הערך המידעי עצמו. ההבחנה בין טיפוסים אלו היא חשובה, מכיוון שהם מתנהגים אחרת.
דוגמת קוד המשתמשת במספרים (טיפוסים פרימיטיביים):
הכרזה על משתנה והשמה :
var a = 3.14;
העתקת ערכו של המשתנה אל תוך משתנה חדש :
var b =a;
שינוי ערכו של המשתנה המקורי :
a = 4;
מציג 3.14, ערכו של b לא השתנה :
alert(b);
כעת נראה מה יקרה אם נשנה את הקוד במקצת, כך שנשתמש במערכים (טיפוס מצביע) במקום מספרים:
הכרזה על משתנה והשמה על מנת שיצביע למערך
var a = [1,2,3];
העתקת מצביע זה לתוך משתנה חדש
var b =a;         
שינוי המערך על ידי שימוש במצביע המקורי
a[0] = 99;         
מציג את המערך לאחר השינוי [99,2,3] על ידי שימוש במצביע החדש
alert(b);         
בשורה השניה, נשים לב, כי הדבר היחיד שמקבל השמה הוא המצביע על המערך, לא המערך עצמו, אחרי שורה זו עדיין יש לנו מערך אחד, אך יש לנו שני מצביעים אליו (a, b).
מחרוזות הן מקרה יוצא דופן, יש להן משתנה גודל, ולכן ברור כי לא ניתן לאחסן אותן ישירות כמשתנים קבועי גודל.
לשם יעילות, נצפה כי ב- JS נעתיק את המצביע למחרוזת ולא את תוכנה של המחרוזת. מצד שני, מחרוזות מתנהגות כמו טיפוסים פרימיטיביים במקרים רבים.

השאלה האם מחרוזות הן מסוג זה או אחר של טיפוסים הופכת להיות מבלבלת יותר בשל העובדה שהן בלתי ניתנות לשינוי, אין שום דרך לשנות את תוכנן / ערכן, משמע, איננו יכולים לכתוב דוגמא כמו הקודמת, בה מערכים מועתקים על ידי מצביעים.
5. "איסוף זבל" - Garbage Collection
טיפוסי מצביעים אינם בעל גודל קבוע, כמה מהם אף הופכים לגדולים מאוד, ובשל עובדה זו, אחסונם בזיכרון צריך להתבצע באופן דינמי, כאשר הגודל ידוע. בכל פעם שנוצר אובייקט, מערך, מחרוזת במהלך תוכנית, המפענח/ מתרגם צריך להקצות מקום בזיכרון על מנת לאחסן יישות זאת. לבסוף לשחרר מקום זה לשם שימוש חוזר, אחרת המערכת תקרוס.
JS מסתמכת על טכניקה שנקראת Garbage Collection. מפענח / מתרגם JS מסוגל לגלות מתי לא נשתמש יותר באובייקט מסוים בתוכנית. כאשר הוא קובע כי בשום פנים ואופן לא נתייחס / נשתמש באובייקט זה בתוכנית (אובייקט unreachable) ניתן להשתמש במקום בזיכרון בו הוא מאוחסן לשימוש חוזר, לדוגמא:
הקצאת מקום עבור מחרוזת :
var s ="Hello";
יצירת מחרוזת חדשה :
var u = s.toUpperCase();
דריסת המצביע למחרוזת המקורית על יד המחרוזת החדשה :
s = u;
לאחר ריצת קוד זה, המחרוזת המקורית “hello” אינה נגישה יותר, אין שום מצביעים עבורה במשתנים אחרים בתוכנית. המערכת מגלה עובדה זו ומשחררת את מקום אחסונו בזיכרון לשם שימוש חוזר.
Garbage Collection הוא אוטומטי ושקוף למתכנת, כל שעלינו לעשות הוא לסמוך על כך שטכניקה זו עובדת ולא לתהות לאן נעלמים כל האובייקטים הישנים.
6. משתנים כתכונות
משתנים / תכונות של אובייקטים הם בעלי תכונות זהות. השמה מתבצעת באותו האופן בשניהם, משתמשים בהם באותו אופן בביטויים וכו'… אין שום הבדל משמעותי ביניהם.
כאשר המפענח של JS מתחיל "בעבודתו", אחד הדברים הראשונים שהוא עושה, לפני ביצוע כל קוד, הוא יצירת אובייקט גלובלי. התכונות של אובייקט זה הם המשתנים הגלובליים של התוכנית, יתרה מכך, כל הפונקציות והתכונות המוגדרות מראש בסביבת JS הם גם כן תכונות של אובייקט זה.
לדוגמא, מתייחסים לפונקציה ( )parseInt והאובייקט Math על ידי התכונות של האובייקט הגלובלי. בחלק העליון של הקוד (שאינו חלק מהפונקציות), ניתן להשתמש במילת המפתח this על מנת להתייחס לאובייקט הגלובלי. בצד הלקוח ב- JS, אובייקט ה- Window משרת כאובייקט הגלובלי לכל הקוד המוכל בחלון הדפדפן שהוא מייצג. לאובייקט זה ישנה תכונה עצמית בשם window, שיכולה להיות שימושית במקום this, על מנת להתייחס לאובייקט הגלובלי.

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

כל ביצוע תוכן של JS הוא בעל טווח הכרה שרשרתי “scope chain” המקושרת לכך. שרשרת זו הינה רשימה או שרשרת של אובייקטים.
כאשר צריך לחפש את ערכו של משתנה X (תהליך שנקרא variable name resolution), הוא מתחיל בחיפוש אחר האובייקט הראשון בשרשרת. אם אובייקט זה מכיל תכונה בשם X, הערך של אותה תכונה הוא הנלקח, אם הוא אינו תכונה בשם זה, אנו ממשיכים לחפש אצל האובייקט הבא בשרשרת וכן הלאה.

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