משתנים

משתנים הם מבנה נתונים שמקצה שם מייצג לערך. הם יכולים להכיל נתונים מכל סוג.

השם של המשתנה נקרא מזהה. מזהה חוקי חייב לעמוד בכללים הבאים:

  • מזהים יכולים להכיל אותיות Unicode, סימני דולר ($), תווים של קו תחתון (_), ספרות (0-9) ואפילו חלק מתווי Unicode.
  • מזהים לא יכולים להכיל רווחים לבנים, כי המנתח משתמש ברווחים לבנים כדי להפריד בין רכיבי הקלט. לדוגמה, אם מנסים להפעיל משתנה my Variable במקום myVariable, המנתח רואה שני מזהים, my ו-Variable, ומציג שגיאת תחביר (‎"unexpected token: identifier"‎).
  • המזהים חייבים להתחיל באות, בקו תחתון (_) או בסימן דולר ($). הם לא יכולים להתחיל בספרות, כדי למנוע בלבול בין מספרים למזהים:

    let 1a = true;
    
    > Uncaught SyntaxError: Invalid or unexpected token
    

    אם JavaScript תאפשר להשתמש במספרים בתחילת מזהה, אפשר יהיה ליצור מזהים שמכילים רק מספרים, וכתוצאה מכך יהיו התנגשויות בין מספרים המשמשים כמספרים לבין מספרים המשמשים כמזהים:

    let 10 = 20
    
    10 + 5
    > ?
    
  • אי אפשר להשתמש כמזהים ב"מילים שמורות" שכבר יש להן משמעות תחבירית.

  • המזהים לא יכולים להכיל תווים מיוחדים (! . , / \ + - * =).

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

בהתאם לדוגמה של השיטות והמאפיינים המובנים של JavaScript, camel case (שנקרא גם 'camelCase') היא שיטה נפוצה מאוד למזהים שמכילים כמה מילים. שימוש באותיות רישיות בכותרות (באנגלית: Camel case) הוא שיטה שבה האות הראשונה של כל מילה, מלבד המילה הראשונה, היא אות רישית. השיטה הזו נועדה לשפר את הקריאוּת של הטקסט בלי רווחים.

let camelCasedIdentifier = true;

בחלק מהפרויקטים נעשה שימוש במוסכמות אחרות למתן שמות, בהתאם להקשר ולאופי הנתונים. לדוגמה, האות הראשונה של class בדרך כלל כתובה באות רישית, ולכן שמות של כיתות שמכילות כמה מילים בדרך כלל נכתבים באותיות רישיות בתחילת כל מילה (camel case). לרוב, קוראים לזה 'אותיות רישיות בתחילת כל מילה (upper camel case)' או 'אותיות רישיות בתחילת כל מילה (Pascal case)'.Pascal

class MyClass {

}

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

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

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

הצהרת משתנה

יש כמה דרכים להודיע ל-JavaScript על מזהה, תהליך שנקרא 'הצהרה' על משתנה. מכריזים על משתנה באמצעות מילות המפתח let,‏ const או var.

let myVariable;

משתמשים ב-let או ב-var כדי להצהיר על משתנה שאפשר לשנות בכל שלב. מילות המפתח האלה מאפשרות למפרש JavaScript לדעת שמחרוזת של תווים היא מזהה שעשוי להכיל ערך.

כשעובדים בקוד בסביבה מודרנית, צריך להשתמש ב-let במקום ב-var. הפונקציה var עדיין פועלת בדפדפנים מודרניים, אבל יש לה כמה התנהגויות לא אינטואיטיביות שהוגדרו בגרסאות המוקדמות ביותר של JavaScript, ולא ניתן היה לשנות אותן מאוחר יותר כדי לשמור על תאימות לאחור. let נוסף ב-ES6 כדי לטפל בבעיות מסוימות בעיצוב של var.

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

let myVariable = 5;

myVariable + myVariable
> 10

אפשר גם להצהיר על משתנה באמצעות let (או var) בלי לאתחל אותו מיד. אם תעשו זאת, הערך הראשוני של המשתנה יהיה undefined עד שהקוד יקצה לו ערך.

let myVariable;

myVariable;
> undefined

myVariable = 5;

myVariable + myVariable
> 10

משתנה עם ערך undefined שונה ממשתנה לא מוגדר שהמזהה שלו עדיין לא הוצהר. הפניה למשתנה שלא הכרזתם עליו תגרום לשגיאה.

myVariable
> Uncaught ReferenceError: myVariable is not defined

let myVariable;

myVariable
> undefined

השיוך של מזהה לערך נקרא בדרך כלל 'קישור'. התחביר שמופיע אחרי מילות המפתח let,‏ var או const נקרא 'רשימת קישור', והוא מאפשר להצהיר על מספר משתנים מופרדים בפסיקים (מסתיימים בנקודה-פסיק הצפויה). לכן, קטעי הקוד הבאים זהים מבחינה פונקציונלית:

let firstVariable,
     secondVariable,
     thirdVariable;
let firstVariable;
let secondVariable;
let thirdVariable;

כשמקצים מחדש ערך למשתנה, לא משתמשים ב-let (או ב-var), כי ל-JavaScript כבר ידוע שהמשתנה קיים:

let myVariable = true;

myVariable
> true

myVariable = false;

myVariable
> false

אפשר להקצות למשתנים ערכים חדשים על סמך הערכים הקיימים שלהם:

let myVariable = 10;

myVariable
> 10

myVariable = myVariable * myVariable;

myVariable
> 100

אם תנסו להצהיר מחדש על משתנה באמצעות let בסביבת ייצור, תופיע שגיאת תחביר:

let myVariable = true;
let myVariable = false;
> Uncaught SyntaxError: redeclaration of let myVariable

כלים למפתחים בדפדפנים מאפשרים יותר הצהרות חוזרות על let (ועל class), ולכן יכול להיות שלא תראו את אותה שגיאה במסוף הפיתוח.

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

var myVariable = true;
var myVariable = false;

myVariable\
> false

const

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

const myConstant = true;

myConstant
> true

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

const myConstant;
Uncaught SyntaxError: missing = in const declaration

ניסיון לשנות את הערך של משתנה שהוצהר באמצעות const באותו אופן שבו משנים את הערך של משתנה שהוצהר באמצעות let (או var) גורם לשגיאת סוג:

const myConstant = true;

myConstant = false;
> Uncaught TypeError: invalid assignment to const 'myConstant'

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

const constantObject = { "firstvalue" : true };

constantObject
> Object { firstvalue: true }

constantObject.secondvalue = false;

constantObject
> Object { firstvalue: true, secondvalue: false }

קבוע שמכיל אובייקט הוא הפניה לערך נתונים שניתן לשינוי שלא ניתן לשנות. אי אפשר לשנות את הקבוע עצמו, אבל אפשר לשנות, להוסיף או להסיר את המאפיינים של האובייקט שאליו הוא מפנה:

const constantObject = { "firstvalue" : true };

constantObject = false
> Uncaught TypeError: invalid assignment to const 'constantObject'

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

היקף המשתנה

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

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

היקף החסימה

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

{
    let scopedVariable = true;
    console.log( scopedVariable );
}
> true

scopedVariable
> ReferenceError: scopedVariable is not defined

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

{
  const myConstant = false;
}
const myConstant = true;

scopedConstant;
> true

למרות שמשתנה שהוצהר לא יכול להימשך לבלוק ההורה שלו, הוא זמין לכל הבלוקים הצאצאים:

{
    let scopedVariable = true;
    {
    console.log( scopedVariable );
    }
}
> true

אפשר לשנות את הערך של משתנה שהוצהר מתוך בלוק צאצא:

{
    let scopedVariable = false;
    {
    scopedVariable = true;
    }
    console.log( scopedVariable );
}
> true

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

{
    let scopedVariable = false;
    {
    let scopedVariable = true;
    }
    console.log( scopedVariable );
}
> false

היקף הפונקציה

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

function myFunction() {
    var scopedVariable = true;

    return scopedVariable;
}

scopedVariable;
> ReferenceError: scopedVariable is not defined

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

function myFunction() {
    var scopedVariable = true;

    return scopedVariable;
}

scopedVariable;
> ReferenceError: scopedVariable is not defined

myFunction();
> true

scopedVariable;
> ReferenceError: scopedVariable is not defined

היקף גלובלי

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

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

כל משתנה שמוצהר באמצעות var מחוץ לפונקציית הורה, או באמצעות let או const מחוץ לבלוק הורה, הוא גלובלי:

var functionGlobal = true; // Global
let blockGlobal = true; // Global

{
    console.log( blockGlobal );
    console.log( functionGlobal );
}
> true
> true

(function() {
    console.log( blockGlobal );
    console.log( functionGlobal );
}());
> true
> true

הקצאת ערך למשתנה בלי להצהיר עליו באופן מפורש (כלומר, לעולם לא משתמשים ב-var, ב-let או ב-const כדי ליצור אותו) מעלה את המשתנה להיקף הגלובלי, גם אם הוא מופעל בתוך פונקציה או בלוק. משתנה שנוצר לפי התבנית הזו נקרא לפעמים 'גלובלי משתמע'.

function myFunction() {
    globalVariable = "global";

    return globalVariable
}

myFunction()\
> "global"

globalVariable\
> "global"

Variable Hoisting

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

hoistedVariable
> undefined

var hoistedVariable;

מאחר שמתאר המשתנה מתארח, ולא האיניציאליזציה, משתנים שלא הוגדרו באופן מפורש באמצעות var,‏ let או const לא מועברים ל-hoist:

unhoistedVariable;
> Uncaught ReferenceError: unhoistedVariable is not defined

unhoistedVariable = true;

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

hoistedVariable
> undefined

var hoistedVariable = 2 + 2;

hoistedVariable\
> 4

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

let ו-const מטפלים בהתנהגות הזו על ידי העלאת שגיאה כשמתבצעת גישה למשתנה לפני שהוא נוצר:

{
    hoistedVariable;

    let hoistedVariable;
}
> Uncaught ReferenceError: can't access lexical declaration 'hoistedVariable' before initialization

השגיאה הזו שונה מהשגיאה 'hoistedVariable is not defined' שעשויה להופיע כשמנסים לגשת למשתנה שלא הוצהר. מכיוון ש-JavaScript העבירה את המשתנה, היא יודעת שהמשתנה נוצר בהיקף הנתון. עם זאת, במקום להפוך את המשתנה הזה לזמין לפני ההצהרה שלו עם הערך undefined, המפרש יוצר שגיאה. משתנים שמוצהרים באמצעות let או const (או class) נמצאים ב'אזור מת זמני' (TDZ) מתחילת הבלוק שמקיף אותם ועד לנקודה בקוד שבה המשתנה מוצהר.

בעזרת אזור המוות הזמני, ההתנהגות של let אינטואיטיבית יותר מזו של var עבור המחברים. הוא גם קריטי לתכנון של const. מכיוון שאי אפשר לשנות משתני קבוע, לא ניתן להגדיר ערך משמעותי למשתנה קבוע שהועתק לחלק העליון של ההיקף שלו וקיבל ערך משתמע של undefined.

בדיקת ההבנה

באילו סוגים של תווים אפשר להתחיל מזהה?

אות
קו תחתון
ספרה

מהי השיטה המועדפת להצהרה על משתנה שאפשר לשנות את הערך שלו בכל שלב?

let
const
var