משתנים הם מבנה נתונים שמקצה שם מייצג לערך. הם יכולים להכיל נתונים מכל סוג.
השם של המשתנה נקרא מזהה. מזהה חוקי חייב לעמוד בכללים הבאים:
- מזהים יכולים להכיל אותיות 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
.
בדיקת ההבנה
באילו סוגים של תווים אפשר להתחיל מזהה?
מהי השיטה המועדפת להצהרה על משתנה שאפשר לשנות את הערך שלו בכל שלב?