תהליך בקרה הוא הסדר שבו רכיב התרגום של JavaScript מבצע הצהרות. אם סקריפט לא כולל הצהרות שמשנות את הזרימה שלו, מההתחלה ועד הסוף, שורה אחר שורה. מבני בקרה הם משמש כדי לקבוע אם קבוצה של הצהרות מבוצעת או לא על סמך קבוצת קריטריונים מוגדרת, לבצע קבוצה של הצהרות שוב ושוב, או להפריע רצף של הצהרות.
הצהרות מותנות
הצהרות מותנות קובעות אם צריך להריץ את הקוד על סמך
תנאים נוספים. הצהרת תנאי מריצים את הקוד שהיא מכילה אם
התנאי המשויך (או קבוצת תנאים) שווה ל-true
. אחרת, הפרמטר
המערכת מדלגת על הקוד.
if
...else
ההצהרה if
מעריכה תנאי בתוך הסוגריים התואמים אשר
לעקוב. אם התנאי שבתוך הסוגריים מוערך ל-true
,
דף חשבון או דוח חסימה
לאחר הסוגריים התואמים, מבוצעת הפעלה:
if ( true ) console.log( "True." );
> "True."
if ( true ) {
const myString = "True.";
console.log( myString );
}
> "True."
אם התנאי שבתוך הסוגריים שווה ל-false
, המשפט
לאחר שהמערכת מתעלמת ממנו:
if ( false ) console.log( "True." );
מילת מפתח מסוג else
מיד אחרי משפט if
הצהרה בנוגע לביצוע מותנה מציינת את ההצהרה שיש לבצע אם
התנאי if
מקבל ערך false
:
if ( false ) console.log( "True." )''
else console.log( "False" );
> "False."
כדי לשרשר כמה הצהרות if
ביחד, אפשר ליצור את
הצהרה שבוצעה באופן מותנה אחרי else
הצהרת if
נוספת:
const myCondition = 2;
if ( myCondition === 5 ) console.log( "Five." );
else if ( myCondition === 2 ) console.log( "Two." );
מומלץ מאוד להשתמש בתחביר של הצהרת חסימה אחרי שהתנאים הבאים מתקיימים כדי
משפרות את הקריאות, אבל במקרים רבים סעיפי else if
הם יוצאים מן הכלל:
if ( myCondition === 5 ) {
console.log( "Five." );
} else if ( myCondition === 3 ) {
console.log( "Three" );
} else {
console.log( "Neither five nor three." );
}
> "Neither five nor three."
אופרטור תלת-ממדי
if
יריץ הצהרה באופן מותנה. האופרטור המשולש (באופן מדויק יותר
אבל פחות נפוץ אופרטור מותנה שלישי) הוא שימוש מקוצר.
כדי להפעיל ביטוי באופן מותנה. כפי שמשתמע מהשם, הצבע המשולש
הוא אופרטור ה-JavaScript היחיד שמשתמש בשלושה אופרנדים:
- תנאי לבדיקה ואחריו סימן שאלה (
?
). - הביטוי שיופעל אם התנאי מקבל את הערך
true
, ואחריו נקודתיים (:
). - הביטוי שיופעל אם התנאי מקבל את הערך
false
.
הפלח הזה משמש לעיתים קרובות כדי להגדיר או להעביר ערך באופן מותנה:
const myFirstResult = true ? "First value." : "Second value.";
const mySecondResult = false ? "First value." : "Second value.";
myFirstResult;
> "First value."
mySecondResult;
> "Second value."
switch
...case
צריך להשתמש במשפט switch
כדי להשוות ערך של ביטוי לרשימה של
ערכים פוטנציאליים שהוגדרו באמצעות מילת מפתח אחת או יותר של case
. התחביר הזה
יוצא דופן, כי הוא מגיע מהחלטות העיצוב המוקדמות ביותר של JavaScript.
switch
...case
משתמשים במילת המפתח switch
ולאחר מכן ביטוי כדי
בסוגריים בתוך סוגריים, ואחריהם זוג סוגריים מסולסלים תואמים.
הגוף של switch
יכול להכיל case
מילות מפתח, בדרך כלל אחת או יותר,
ואחריו ביטוי או ערך, ואחריו נקודתיים (:
).
כשהמפענח מגיע ל-case
עם ערך שתואם לביטוי
מוערכת בסוגריים אחרי מילת המפתח switch
, היא מבצעת כל
הצהרות שעומדות בתנאי case
הזה:
switch ( 2 + 2 === 4 ) {
case false:
console.log( "False." );
case true:
console.log( "True." );
}
> "True."
כל ההצהרות שאחרי case
התואמות מתבצעות, גם אם
בתוך הצהרת חסימה.
switch ( 2 + 2 === 4 ) {
case false:
console.log( "False." );
case true:
let myVariable = "True.";
console.log( myVariable );
}
> "True."
אחת מלכודות השימוש ב-switch…case
היא שכאשר נמצאת התאמה,
'תרגום ב-JavaScript' מבצע הצהרה כלשהי שמופיעה אחרי case
שהותאמה,
גם בסעיפים אחרים של case
. הפעולה הזו נקראת 'תהליך כשלים' ל
case
הבא:
switch ( 2 + 2 === 7 ) {
case false:
console.log( "False." );
case true:
console.log( "True." );
}
> "False."
> "True."
כדי למנוע נפילות, יש לסיים כל פנייה במילת המפתח break
,
המערכת מפסיקה מיד את ההערכה של הגוף switch
:
switch ( 2 + 2 === 7 ) {
case false:
console.log( "False." );
break;
case true:
console.log( "True." );
break;
}
> "False."
אם אין case
שתואם לערך המותנה, switch
בוחר את הערך default
אם יש תנאי כזה:
switch ( 20 ) {
case 5:
console.log( "The value was five." );
break;
case 10:
console.log( "The value was ten." );
break;
default:
console.log( "The value was something unexpected." );
}
> "The value was something unexpected."
עם זאת, המעבר האוטומטי חל גם על default
, ועשוי להוביל
מתוצאות בלתי צפויות. כדי לפתור את הבעיה, צריך לסיים את דף החשבון ב-default
עם break
,
או להציב אותו אחרונה ברשימת המקרים.
switch ( 20 ) {
default:
console.log( "The value was something unexpected." );
case 10:
console.log( "The value was ten." );
break;
case 5:
console.log( "The value was five." );
break;
}
> The value was something unexpected.
> The value was ten.
כי בסעיפים של case
לא נדרש
חסימת משפט לקיבוץ
מספר הצהרות, case
ו-default
לא יוצרים
היקף מילוני כשלעצמו:
let myVariable;
switch ( true ) {
case true:
let myVariable = "True.";
break;
default:
let myVariable = "False.";
break;
}
> Uncaught SyntaxError: redeclaration of let myVariable
כדי לנהל את ההיקף, משתמשים בהצהרות חסימה:
let myVariable;
switch ( true ) {
case true: {
let myVariable = "True.";
break;
}
default: {
let myVariable = "False.";
break;
}
}
לולאות ואיטרציה
לולאות מאפשרות לחזור על קבוצה של הצהרות כל עוד מתקיים תנאי מסוים, או עד שתנאי מסוים מתקיים. להשתמש בלולאות כדי להפעיל קבוצה של הוראות מספר פעמים, עד לקבלת תוצאה ספציפית, או עד שהתרגום מגיע לסוף של מבנה נתונים שניתן לחזור עליו (לדוגמה, הרכיב הסופי מערך, מפה או קבוצה, את המאפיין הסופי של אובייקט, או את התו האחרון מחרוזת).
לולאות מפריעות לקטע 'מלמעלה למטה' של ביצוע סקריפט באמצעות חזרה מעל קבוצה של הצהרות עד שתנאי אחד או יותר יתקיימו או שאינם עוד בהתאם לתחביר ששימש ליצירת הלולאה. אחרי שהלולאה מסתיימת, נמשך לכל ההצהרות הבאות אחריו. בדוגמה הבאה, ההצהרות בגוף הלולאה מתבצעות שלוש פעמים לפני תרגום התוכן ממשיך:
let iterationCount = 0;
console.log( "Pre-loop." );
while( iterationCount < 3 ) {
iterationCount++;
console.log( "Loop iteration." );
}
console.log( "Continuing on." );
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Continuing on."
אם לא ניתן לעמוד בתנאים במהלך ביצוע הלולאה, היא תמשיך ללא הגבלת זמן. לולאות האינסופיות האלה הן מלכודת תכנות נפוצה שעלולה גורמים לשרשור הביצוע הראשי כדי להשהות ללא הגבלת זמן או אפילו לקרוס כרטיסייה בדפדפן.
הדוגמה הבאה תפעל כל עוד הערך הבוליאני true
נותר
true
מכיוון שערכים בוליאניים אינם ניתנים לשינוי,
נוצרת לולאה אינסופית.
console.log( "Pre-loop." );
while( true ) {
console.log( "Loop iteration." );
}
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
…
יש להימנע מהשארת לולאות אינסופיות בקוד הייצור. אם יצרתם בטעות במהלך הפיתוח, ניתן לתקן את הבעיה על ידי סגירת כרטיסיית הדפדפן שבה הוא פועל ולעדכן את הקוד כך שהלולאה לא תהיה אינסופית יותר, ופתיחה מחדש של הדף הזה.
while
לולאה של while
נוצרת באמצעות מילת המפתח while
ולאחר מכן צמד
סוגריים תואמים שמכילים תנאי לבדיקה. אם
הערך הראשוני של התנאי הוא true
, את ההצהרה (או
חסימת דוח) שמופיע אחרי
הסוגריים האלה מופעלים. אם לא, הלולאה אף פעם לא תפעל. אחרי כל
איטרציה, התנאי ייבדק מחדש ואם הוא עדיין true
, הלולאה
וחוזרים שלו.
let iterationCount = 0;
while( iterationCount < 3 ) {
iterationCount++;
console.log( `Loop ${ iterationCount }.` );
}
> "Loop 1."
> "Loop 2."
אם המתרגם מוצא הצהרה continue
בלולאה של while
, היא מפסיקה
איטרציה, מעריך מחדש את התנאי וממשיכה את הלולאה אם אפשר:
let iterationCount = 0;
while( iterationCount <= 5 ) {
iterationCount++;
if( iterationCount === 3 ) {
continue;
}
console.log( `Loop ${ iterationCount }.` );
}
console.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Loop 4."
> "Loop 5."
> "Loop ended."
אם המתרגם מוצא את הצהרת break
בלולאת while
, האיטרציה הזו
נפסקת והתנאי לא נבדק מחדש, וכך מאפשר למפענח להמשיך להתקדם:
let iterationCount = 1;
while( iterationCount <= 5 ) {
if( iterationCount === 3 ) {
console.log(`Iteration skipped.``);`
break;
}
console.log( `Loop ${ iterationCount }.` );
iterationCount++;
}
console.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Iteration skipped.
> "Loop ended."
אפשר להשתמש בפונקציה while
כדי לחזור מספר מסוים של פעמים, כפי שניתן לראות ב
אבל התרחיש לדוגמה הנפוץ ביותר של while
הוא לולאה של
אורך קבוע:
let randomize = () => Math.floor( Math.random() * 10 );
let randomNum = randomize();
while( randomNum !== 3 ){
console.log( `The number is not ${ randomNum }.` );
randomNum = randomize();
}
console.log( `The correct number, ${ randomNum }, was found.` );
> "The number is not 0."
> "The number is not 6."
> "The number is not 1."
> "The number is not 8."
> "The correct number, 3, was found."
do
...while
do
...while
הוא וריאנט של לולאת while
שבה התנאי המותנה
ההערכה מתבצעת בסוף כל חזרה של הלולאה. המשמעות היא
גוף הלולאה תמיד מתבצע לפחות פעם אחת.
כדי ליצור לולאה של do
...while
, משתמשים במילת המפתח do
ולאחר מכן במשפט
(או דוח חסימה) להיות
בכל חזרה של הלולאה. מיד לאחר ההצהרה, מוסיפים
while
וסוגריים תואמים שמכילים את התנאי שצריך לבדוק. מתי
התנאי הזה כבר לא מקבל ערך true
, והלולאה מסתיימת.
let iterationCount = 1;
do {
console.log( `Loop ${ iterationCount }.` );
iterationCount++;
} while ( iterationCount < 3 );
> "Loop 1."
> "Loop 2."
> "Loop 3."
כמו לולאת while
, התרחיש לדוגמה הנפוץ ביותר של do
...while
הוא לולאה של
אורך קבוע:
let randomNum;
do {
randomNum = ( () => Math.floor( Math.random() * 10 ) )();
console.log( `Is the number ${ randomNum }?` );
} while ( randomNum !== 3 );
console.log( `Yes, ${ randomNum } was the correct number.` );
> "Is the number 9?"
> "Is the number 2?"
> "Is the number 8?"
> "Is the number 2?"
> "Is the number 3?"
> "Yes, 3 was the correct number."
for
משתמשים בלולאות של for
כדי לחזור על כמות ידועה. בבסיסי קוד מדור קודם, הפעולה הזו הייתה
שמשמשות לעיתים קרובות כדי לבצע איטרציה לרכיבים במערך.
כדי ליצור לולאת for
, משתמשים במילת המפתח for
ולאחר מכן בקבוצה של סוגריים
מקבלת את שלושת הביטויים הבאים לפי הסדר ומופרדים באמצעות
נקודה ופסיק:
- ביטוי שצריך להעריך כשהלולאה מתחילה
- תנאי שקובע אם הלולאה תמשיך
- ביטוי שצריך לבצע בסיומה של כל לולאה
אחרי הסוגריים האלו, מוסיפים את ההצהרה (בדרך כלל חסימת דוח) להיות שבוצעו במהלך הלולאה.
for( let i = 0; i < 3; i++ ) {
console.log( "This loop will run three times.")
}
הביטוי הראשון מאתחל משתנה שמשמש כמונה. הזה
ביטוי מוערך פעם אחת, לפני האיטרציה הראשונה של הלולאה. אפשר
צריך לאתחל את המשתנה הזה באמצעות let
(או var
, מבחינה היסטורית) כמו כל משתנה אחר
וההיקף שלו הוא גוף הלולאה. המשתנים האלה יכולים לכלול כל
אבל הם מכונים לעיתים קרובות i
ל"איטרציה" או 'index'.
נראה שסותרת לכך
שיטות מומלצות לשמות של מזהים שניתן לחזות,
אבל המוסכמה מבוססת מספיק כדי להיות ברורה למפתחים אחרים
בקצרה. מכיוון שאוספים שנוספו לאינדקס לא נוספו לאינדקס,
למשתנים האלה יש כמעט תמיד הערך הראשוני של 0
.
בדומה לצורות אחרות של לולאה, התנאי הוא ביטוי שקובע
האם לבצע את הלולאה. בדרך כלל משתמשים באפשרות הזו כדי להגדיר
למונה האיטרציה. המתורגמן מעריך את התנאי לפני
הרצת הלולאה for
בפעם הראשונה.אם התנאי לא פועל בהתחלה
ולהעריך את הערך true
, גוף הלולאה לא מתבצע.
הביטוי הסופי מופעל בסוף כל איטרציה דרך הלולאה. בדרך כלל משתמשים בו כדי להגדיל את המזהה באחד מהם.
בדרך כלל יופיעו לולאות של for
שחוזרות על עצמן בין מערכים בגרסאות ישנות יותר
ב-codebases. במקרים כאלה, התנאי שצוין להמשך הלולאה הוא
מספר האיטרציה קטן או שווה לאורך המערך שרוצים להסיר
באמצעות. המשתנה שמשמש למעקב אחרי ספירת האיטרציה הנוכחית משמש כדי לבדוק
את הערך שמשויך לאינדקס הזה במערך, וכך מאפשר לכל רכיב
המערך שיש לבצע בו פעולות לפי הסדר:
var myArray = [ true, false, true ];
for( let i = 0; i <= myArray.length; i++ ) {
console.log( myArray[ i ] );
}
> true
> false
> true
הגישה הזו יצאה משימוש לטובת גישות מודרניות יותר חזרה בלופ דרך מבני נתונים שניתנים לאיטרציה.
for
[...] of
[...]
שימוש בלולאות for
...of
... כדי לחזור על הערכים שמאוחסנים
מבנה נתונים שאפשר לשנות, כמו מערך, קבוצה או מפה.
לולאת for
...of
... משתמשת במילת המפתח for
ואחריה קבוצת סוגריים
שמכיל משתנה, ואחריו of
, ואז מבנה הנתונים שעובר איטרציה
מעל. המשתנה יכול להיות הצהרה שמבוצעת כאן באמצעות let
, const
, או
var
, משתנה שהוצהר בעבר במסגרת ההיקף הנוכחי, אובייקט
או מופע של
מסירים את המטלה.
היא מכילה את הערך של הרכיב שתואם לאיטרציה הנוכחית
של הלולאה.
const myIterable = [ true, false, true ];
for( const myElement of myIterable ) {
console.log( myElement );
}
> true
> false
> true
בדוגמה הזו, השימוש ב-const
עבור myElement
עובד למרות שהפונקציה myElement
מקבלים ערך חדש בכל איטרציה של הלולאה. הסיבה לכך היא שמשתנים
שהוצהרו באמצעות let
או const
בהיקף של הצהרת החסימה בתוך
לולאה. המשתנה מאותחל בתחילת כל איטרציה ומוסר ב-
סוף האיטרציה.
for
...in
...
משתמשים בלולאות for
...in
... כדי לבצע איטרציה למאפיינים שניתנים לספירה של אובייקט,
כולל נכסים שעברו בירושה לספירה. כמו בלולאת for
...of
...,
לולאת for
...in
... משתמשת במילת המפתח for
ואחריה קבוצת סוגריים
שמכיל משתנה שמכיל את הערך של מפתח המאפיין,
באיטרציה הנוכחית של הלולאה. אחרי המשתנה הזה מופיע
מילת מפתח אחת (in
), ואז האובייקט שעובר באיטרציה:
const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
console.log( myKey );
}
> "myProperty"
> "mySecondProperty"
שוב, למרות שהערך של myKey
משתנה בכל חזרה של הלולאה,
אפשר להשתמש בפונקציה const
ללא שגיאה כי המשתנה נמחק בפועל
בסוף כל איטרציה, ואז נוצר מחדש בהתחלה.
הערך שמשויך לכל מפתח נכס לא זמין ישירות
תחביר for
...in
.... עם זאת, מכיוון שללולאה יש גישה למפתח מאפיין
בכל חזרה, תוכלו להשתמש במקש הזה כדי "לחפש" הערך שלו:
const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
const myValue = myObject[ myKey ];
console.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "mySecondProperty : false"
מאפיינים שעברו בירושה מבנאים מובנים לא ניתנים לספירה, כלומר
for
...in
... לא מתבצע איטרציה באמצעות נכסים שעברו בירושה מה-Object
constructor. עם זאת, כל מאפיין שניתן לספירה בתוך האובייקט
שרשרת אב הטיפוס כלולה:
const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
myProperty: {
value: true,
enumerable: true
}
});
for ( const myKey in myObject ) {
const myValue = myObject[ myKey ];
console.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "protoProperty : true"
JavaScript מספק שיטות מובנות כדי לקבוע אם נכס הוא נכס
מאפיין ישיר של האובייקט ולא מאפיין באב הטיפוס של האובייקט
שרשרת: מודרנית
Object.hasOwn()
ו-Object.prototype.hasOwnProperty()
methods. האלה
methods בודקות אם נכס שצוין עובר בירושה (או לא מוצהר),
החזרת true
רק למאפיינים המיידיים של אובייקט שצוין:
const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
myProperty: {
value: true,
enumerable: true
}
});
for ( const myKey in myObject ) {
const myValue = myObject[ myKey ];
if ( Object.hasOwn( myObject, myKey ) ) {
console.log( `${ myKey } : ${ myValue }` );
}
}
> "myProperty : true"
יש גם שלוש שיטות סטטיות שכל אחת מהן מחזירה מערך, שמורכב
מפתחות המספור של האובייקט (Object.keys()
), ערכים (Object.values()
) או
צמדי מפתח/ערך (Object.entries()
):
const myObject = { "myProperty" : true, "mySecondProperty" : false };
Object.keys( myObject );
> Array [ "myProperty", "mySecondProperty" ]
כך אפשר לחזור על מפתחות, ערכים או צמדי מפתח/ערך של אובייקטים (באמצעות השמטת מטלה) בלי לכלול מאפיינים שבבעלות אב הטיפוס של האובייקט:
const myPrototype = { "protoProperty" : "Non-enumerable property value." };
const myObject = Object.create( myPrototype, {
myProperty: {
value: "Enumerable property value.",
enumerable: true
}
});
for ( const propKey of Object.keys( myObject ) ) {
console.log( propKey );
}
> "myProperty"
for ( const propValue of Object.values( myObject ) ) {
console.log( propValue );
}
> "Enumerable property value."
for ( const [ propKey, propValue ] of Object.entries( myObject ) ) {
console.log( `${ propKey } : ${ propValue }` );
}
> "myProperty : Enumerable property value."
forEach()
ה-methods forEach()
שמסופקות על ידי המערך,
מפה, הגדרה,
ו-NodeList constructors מספקים קיצור דרך שימושי לאיטרציה על נתונים
בהקשר של פונקציית קריאה חוזרת. בניגוד לצורות אחרות של לולאה,
אי אפשר להפסיק לולאה שנוצרה בכל method של forEach()
באמצעות break
או
continue
.
forEach
היא שיטה בבעלות אב הטיפוס של כל מבנה נתונים. בכל forEach
מצפה לפונקציית קריאה חוזרת כארגומנט, אם כי הם שונים מעט
של הארגומנטים שמופיעים כשקוראים לפונקציה הזו. אפשרות שנייה, לא חובה
הארגומנט מציין ערך this
שישמש בתור ההקשר של ההפעלה עבור
של פונקציית קריאה חוזרת.
פונקציית הקריאה החוזרת שבה נעשה שימוש עם Array.forEach
מספקת פרמטרים שכוללים
הערך של הרכיב הנוכחי, האינדקס של הרכיב הנוכחי והמערך שבו הופעלה השיטה forEach
:
const myArray = [ true, false ];
myArray.forEach( ( myElement, i, originalArray ) => {
console.log( i, myElement, originalArray );
});
> 0 true Array(3) [ true, false ]
> 1 false Array(3) [ true, false ]
פונקציית הקריאה החוזרת שבה נעשה שימוש עם Map.forEach
מספקת פרמטרים שכוללים
שמשויך לאלמנט הנוכחי, המפתח שמשויך לרכיב הנוכחי
והשיטה 'מפה' הופעלה ב:forEach
const myMap = new Map([
['myKey', true],
['mySecondKey', false ],
]);
myMap.forEach( ( myValue, myKey, originalMap ) => {
console.log( myValue, myKey, originalMap );
});
> true "myKey" Map { myKey → true, mySecondKey → false }
> false "mySecondKey" Map { myKey → true, mySecondKey → false }
הקריאה החוזרת (callback) של Set.forEach
כוללת פרמטרים דומים. מפני שבהגדרה אין
או מפתחות נפרדים מערכים, במקום זאת הארגומנט השני מספק
ערך מיותר שאפשר להתעלם ממנו, במטרה לשמור על תחביר עקבי
שיטות forEach
אחרות.
const mySet = new Set([ true, false ]);
mySet.forEach( ( myValue, myKey, originalSet ) => {
console.log( myValue, myKey, originalSet );
});
> true true Set [ true, false ]
> false false Set [ true, false ]
איטרטורים
נתונים שאפשר לבצע עליהם איטרציה הם מבנה נתונים שמורכב מרכיבים נפרדים
בחזרה על שימוש בגישות שפירטנו קודם לכן. איטרטור הוא
אובייקט שניתן לחזור עליו אחרי פרוטוקול איטרטור, כלומר עליו להטמיע
רכיב next()
שמקדם את הרכיבים שהוא מכיל, אחד בכל פעם,
בכל קריאה ל-method הזה, החזרת אובייקט עבור כל רצף
בפורמט מסוים.
מבני הנתונים המובנים של JavaScript שניתנים לחזרה (כמו
Array,
מפה, וגם
Set) אינן של איטרטורים
בעצמם, אבל כולם יורשים את שיטת iterator
שניתן לגשת אליה באמצעות
@@iterator
סמל ידוע,
שמחזיר אובייקט איטרטור שנוצר ממבנה הנתונים האיטרטיבי:
const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();
myIterable;
> (3) [1, 2, 3]
myIterator;
> Array Iterator {}
קריאה לשיטה next()
באיטרטור צעדים דרך הרכיבים שהיא
מכיל מילה אחת בכל פעם, וכל קריאה מחזירה אובייקט שמכיל שני
המאפיינים: value
, שמכיל את הערך של הרכיב הנוכחי, ו-
done
, ערך בוליאני שאומר לנו אם האיטרטור עבר את הרכיב האחרון ב-
את מבנה הנתונים. הערך של done
הוא true
רק כאשר שיחה ל-next()
שינסה לגשת לרכיב מעבר לרכיב האחרון
באיטרטור.
const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();
myIterator.next();
> Object { value: 1, done: false }
myIterator.next();
> Object { value: 2, done: false }
myIterator.next();
> Object { value: 3, done: false }
myIterator.next();
> Object { value: undefined, done: true }
פונקציות של גנרטור
כדי להצהיר על מחולל, צריך להשתמש במילת המפתח function*
(שימו לב לכוכבית)
או להגדיר ביטוי של פונקציית מחולל:
function* myGeneratorFunction() { };
בדומה לאיטרטורים, פונקציות הגנרטור שומרות על המצב. התקשרות אל פונקציית הגנרטור מחזירה אובייקט Generator חדש, אבל לא שולחת אותה באופן מיידי מריצים את הקוד בגוף הפונקציה:
function* myGeneratorFunction() {
console.log( "Generator function body ")
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject;
> Generator { }
typeof myGeneratorObject;
> "object"
אובייקטים של גנרטור פועלים לפי פרוטוקול איטרטור. הערך של כל קריאה
הפונקציה next()
שמחזירה פונקציית מחולל נקבעת באמצעות ביטוי yield
,
שמשהה את הביצוע של פונקציית המחולל ומחזירה את הערך
שמכיל את מילת המפתח yield
. שיחות מאוחרות יותר אל next()
ממשיכים לבצע את הפונקציה, ומשהים בביטוי yield
הבא
שמחזירה את הערך המשויך.
function* myGeneratorFunction() {
yield "My first yielded value.";
yield "My second yielded value.";
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> Object { value: "My first yielded value.", done: false }
myGeneratorObject.next();
> Object { value: "My second yielded value.", done: false }
כשמתבצעת קריאה אל next()
אחרי שלא צוינו ערכים נוספים באמצעות yield
,
return
, או throw
(במקרה של שגיאה), שאר הפונקציה
בגוף מסוים, ובאובייקט שהוחזר יש value
של undefined
ו-done
נכס של true
:
function* myGeneratorFunction() {
console.log( "Start of the generator function." );
yield "First";
console.log( "Second part of the generator function." );
yield "Second";
console.log( "Third part of the generator function." );
yield "Third";
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> "Start of the generator function."
> Object { value: "First", done: false }
myGeneratorObject.next();
> "Second part of the generator function."
> Object { value: "Second", done: false }
myGeneratorObject.next();
> "Third part of the generator function."
> Object { value: "Third", done: false }
myGeneratorObject.next();
> Object { value: undefined, done: true }
משתמשים ב-next()
רק באובייקט שמחזירה פונקציית המחולל, ולא ב
במחולל עצמו. אחרת, כל קריאה לפונקציית המחולל
יוצרת אובייקט יוצר חדש:
function* myGeneratorFunction() {
yield "First";
yield "Second";
};
myGeneratorFunction().next();
> Object { value: "First", done: false }
myGeneratorFunction().next();
> Object { value: "First", done: false }
כמו בכל פונקציה, פונקציית המחולל נעצרת כשהיא נתקלת ב-return
במילת מפתח. לאחר מכן היא מחזירה אובייקט להֶקשר שמפעיל את האובייקט שמכיל את
מוחזר ערך ומאפיין done
עם הערך true
.
function* myGeneratorFunction() {
yield 1;
yield 2;
return 3;
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next().done;
> Object { value: 1, done: false }
myGeneratorObject.next().done;
> Object { value: 2, done: false }
myGeneratorObject.next();
> Object { value: 3, done: true }
ביטוי yield
יכול לקבל חלק מהסמנטיקה של מזהה,
לאפשר "תקשורת" דו-כיוונית ובחזרה אל החלק המושעה
של המחולל. כשערך מועבר ל-method next()
של מחולל כ-
ארגומנט, הוא מחליף את הערך שמשויך לערך הקודם, שהושעה
ביטוי yield
:
function* myGeneratorFunction() {
const firstYield = yield;
yield firstYield + 10;
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> Object { value: undefined, done: false }
myGeneratorObject.next( 5 );
> Object { value: 15, done: false }
חשוב לזכור שהביטוי הזה מחליף את כל הביטוי שמשויך
את yield
הקודם, ולא רק מקצה מחדש את הערך של yield
הקודם ל-
הערך שצוין ב-next()
:
function* myGeneratorFunction() {
const firstYield = yield;
const secondYield = yield firstYield + 100;
yield secondYield + 10;
};
const myGeneratorObject = myGeneratorFunction();
myGeneratorObject.next();
> Object { value: undefined, done: false }
myGeneratorObject.next( 10 ); // Can be thought of as changing the value of the `firstYield` variable to `10
> Object { value: 110, done: false }
myGeneratorObject.next( 20 ); // Can be thought of as changing the value of the `secondYield` variable to `20`, _not_ `20 + 100;`
> Object { value: 30, done: false }
המערכת מתעלמת מכל ארגומנט שמועבר לקריאה הראשונה אל next()
, כי לא
לביטוי yield
הקודם כדי לקבל את הערך הזה. כמו בכל פונקציה אחרת,
הארגומנטים שהועברו להפעלה הראשונית של פונקציית המחולל זמינים בכל
היקף הגוף של פונקציית הגנרטור:
function* myGeneratorFunction( startingValue ) {
let newValue = yield startingValue + 1;
newValue = yield newValue + 10;
yield startingValue + 20;
};
const myGeneratorObject = myGeneratorFunction( 2 );
myGeneratorObject.next( 1 );
> Object { value: 3, done: false }
myGeneratorObject.next( 5 );
> Object { value: 15, done: false }
myGeneratorObject.next( 10 );
Object { value: 22, done: false }
האופרטור yield*
(שים לב לכוכבית) משתמשים באופרטור חזרתי, כמו
פונקציית גנרטור אחרת, שנועדה לבצע איטרציה ולהניב כל ערך האופרנד שלו
החזרות:
function* mySecondaryGenerator() {
yield 2;
yield 3;
}
function* myGenerator() {
yield 1;
yield* mySecondaryGenerator();
yield 4;
return 5;
}
const myIterator = myGenerator();
myIterator.next();
> Object { value: 1, done: false }
myIterator.next();
> Object { value: 2, done: false }
myIterator.next();
> Object { value: 3, done: false }
myIterator.next();
> Object { value: 4, done: false }
myIterator.next();
> Object { value: 5, done: true }
JavaScript אסינכרוני
למרות ש-JavaScript הוא סינכרוני במהותו יש מנגנונים שמאפשרים למפתחים לנצל לולאת האירוע לביצוע ומשימות אסינכרוניות.
הבטחות
הבטחה היא placeholder של ערך שאינו ידוע במועד ההבטחה נוצר. זהו קונטיינר שמכתיב פעולה אסינכרונית, התנאים שהפעולה נחשבת כהצלחה או כנכשלה, אלה הפעולות שצריך לבצע. בכל מקרה, וגם את הערך שיתקבל.
יוצרים מכונה של Promise באמצעות האופרטור new
עם Promise
המובנה
של constructor. ה-constructor הזה מקבל פונקציה שנקראת executor
כארגומנט. פונקציית הביצוע בדרך כלל משמשת לביצוע פעולה אחת או יותר
פעולות אסינכרוניות, ואז מכתיבים את המונחים שלפיהם ההבטחה צריכה להיות
נחשב כמומש או נדחה. הבטחה מוגדרת כ'בהמתנה'
בזמן שפונקציית המפעילים פועלת. לאחר שהמוציא לפועל מסיים, הבטחה
נחשב כפוף (או נפתרה, בחלק ממקורות המסמכים) אם
פונקציית הביצוע והפעולה האסינכרונית שהיא מבצעת הושלמה
בהצלחה, ונדחה אם פונקציית הביצוע נתקלת בשגיאה, או
הפעולה האסינכרונית שמבוצעת נכשלת. לאחר שהבטחה מוגנת או
הוא נחשב הוסדר.
const myPromise = new Promise( () => { });
ה-constructor קורא לפונקציית הביצוע עם שני ארגומנטים. הארגומנטים האלה הן פונקציות שמאפשרות לבצע או לדחות את ההבטחה באופן ידני:
const myPromise = new Promise( ( fulfill, reject ) => { });
לפונקציות המשמשות למילוי או לדחייה של ההבטחה נקראות גם התוצאות של ההבטחה כארגומנט (בדרך כלל שגיאה לדחייה):
const myPromise = new Promise( ( fulfill, reject ) => {
const myResult = true;
setTimeout(() => {
if( myResult === true ) {
fulfill( "This Promise was successful." );
} else {
reject( new Error( "This Promise has been rejected." ) );
}
}, 10000);
});
myPromise;
> Promise { <state>: "pending" }
myPromise;
> Promise { <state>: "fulfilled", <value>: "This Promise was successful." }
קישור בין התחייבויות
אפשר לפעול על אובייקט ה-Promise שנוצר באמצעות then()
, catch()
finally()
methods עברו בירושה מה-Promise constructor. כל אחד מאלה
methods מחזירה הבטחה, שאפשר לבצע עליה פעולה באופן מיידי באמצעות then()
,
catch()
, או finally()
שוב, וכך לשרשר את ההבטחות שהתקבלו.
הפונקציה then()
מספקת שתי פונקציות קריאה חוזרת כארגומנטים. משתמשים בראשון לאספקה
את ההבטחה שהתקבלה, והשנייה לדחות אותה. שתי שיטות התשלום מקבלות קוד אימות אחד
המעניק את הערך של ההבטחה שמתקבלת.
const myPromise = new Promise( ( fulfill, reject ) => {
const myResult = true;
setTimeout(() => {
if( myResult === true ) {
fulfill( "This Promise was fulfilled." );
} else {
reject( new Error( "This Promise has been rejected." ) );
}
}, 100);
});
myPromise.then( successfulResult => console.log( successfulResult ), failedResult => console.error( failedResult ) );
> "This Promise was successful."
אפשר גם להשתמש ב-then()
כדי לטפל רק במצב שסופק, וב-catch
כדי לטפל
תטפל במצב הדחייה. קוראים לפונקציה catch
עם ארגומנט יחיד שמכיל את הפונקציה
הערך שצוין בשיטת הדחייה של Promise:
const myPromise = new Promise( ( fulfill, reject ) => {
const myResult = false;
setTimeout(() => {
if( myResult === true ) {
fulfill( "This Promise was fulfilled." );
} else {
reject( new Error( "This Promise has been rejected." ) );
}
}, 100);
});
myPromise
.then( fulfilledResult => console.log(fulfilledResult ) )
.catch( rejectedResult => console.log( rejectedResult ) )
.finally( () => console.log( "The Promise has settled." ) );
> "Error: This Promise has been rejected."
> "The Promise has settled."
בניגוד ל-then
ול-catch
, שמאפשרים לפונקציית handler לרוץ כשיש הבטחה
מתבצע או נדחה, פונקציה שמועברת כארגומנט אל finally
נקראת בין שההבטחה מומשה או נדחתה.
הפונקציה של handler קריאה ללא ארגומנטים, כי היא לא מיועדת
לעבוד עם הערכים המועברים מה-Promise, ולאחר מכן להריץ את הקוד
ההבטחה בוצעה.
בו-זמניות
Promise constructor מספק ארבע שיטות לעבודה עם מספר
הבטחה, באמצעות שדה חוזר שמכיל אובייקטים של Promise. האלה
שכל אחת מהן מחזירה הבטחה, שמתממשת או נדחתה בהתאם למדינה
מההבטחות שהועברו אליו. לדוגמה, Promise.all()
, יוצר הבטחה
שמתקיימים רק אם כל ההבטחה שמועברת לשיטה הזו מתקיים:
const firstPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const secondPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const thirdPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const failedPromise = new Promise( ( fulfill, reject ) => reject( "Failed.") );
const successfulPromises = [ firstPromise, secondPromise, thirdPromise ];
const oneFailedPromise = [ failedPromise, ...successfulPromises ];
Promise.all( successfulPromises )
.then( ( allValues ) => {
console.log( allValues );
})
.catch( ( failValue ) => {
console.error( failValue );
});
> Array(3) [ "Successful. ", "Successful. ", "Successful. " ]
Promise.all( oneFailedPromise )
.then( ( allValues ) => {
console.log( allValues );
})
.catch( ( failValue ) => {
console.error( failValue );
});
> "Failed."
אלה השיטות של Promise בו-זמניות (concurrency):
Promise.all()
- ממומש רק אם כל ההבטחות שסופקו יתקיימו.
Promise.any()
- מימוש אם אחת מההבטחות שסופקו מתקיים ונדחה בלבד אם כל ההבטחות נדחו.
Promise.allSettled()
- ממומשת לאחר שההבטחות יושבות, ללא קשר לתוצאה שלהן.
Promise.race()
- נדחה או מומש על סמך תוצאת ההבטחה הראשונה להסדר, והתעלמו מכל ההבטחות שהוסדרו מאוחר יותר.
async
/await
כשמשתמשים במילת המפתח async
לפני הצהרה על פונקציה
או בביטוי פונקציה, כל
שהפונקציה מחזירה
עם ערך מסוים. כך אפשר להריץ ולנהל פעולות אסינכרוניות באמצעות
תהליכי עבודה כפיתוח סינכרוני.
async function myFunction() {
return "This is my returned value.";
}
myFunction().then( myReturnedValue => console.log( myReturnedValue ) );
> "This is my returned value."
הביטוי await
משהה את הביצוע של פונקציה אסינכרונית בזמן
ההבטחה המשויכת מוסדרת. לאחר יישוב ההבטחה, הערך של
הביטוי await
הוא הערך של ההבטחה שמולא או נדחה.
async function myFunction() {
const myPromise = new Promise( ( fulfill, reject ) => { setTimeout( () => fulfill( "Successful. "), 5000 ); });
const myPromisedResult = await myPromise;
return myPromisedResult;
}
myFunction()
.then( myResult => console.log( myResult ) )
.catch( myFailedResult => console.error( myFailedResult ) );
> "Successful."
כל ערך שהוא לא אובייקטיבי שנכלל בביטוי await
מוחזר בתור
הבטחה שמולאה:
async function myFunction() {
const myPromisedResult = await "String value.";
return myPromisedResult;
}
myFunction()
.then( myResult => console.log( myResult ) )
.catch( myFailedResult => console.error( myFailedResult ) );
> "String value."
בדיקת ההבנה
באיזה סוג לולאה אתם משתמשים כדי לחזור על כמות ידועה?
for
while
do...while