תהליך הבקרה הוא הסדר שבו רכיב התרגום של 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."
מפעיל Ternary
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
עבור 'איטרציה' או 'אינדקס'.
נראה שהשיטה הזו סותרת את השיטות המומלצות לעבודה עם שמות של מזהים שניתנים לחיזוי, אבל המוסכמה הזו מבוססת מספיק כדי שתהיה ברורה למפתחים אחרים במבט חטוף. מכיוון שאוספים שנוספו לאינדקס לא נכללים באינדקס, למשתנים האלה יש כמעט תמיד ערך ראשוני של 0
.
כמו במקרים אחרים של לולאה, התנאי הוא ביטוי שקובע אם יש לבצע את הלולאה. בדרך כלל משתמשים באפשרות הזו כדי להגדיר גבול עליון למונה האיטרציה. התרגום מעריך את התנאי לפני הפעלת הלולאה for
בפעם הראשונה.אם התנאי לא מבצע בתחילה את הערך true
, גוף הלולאה לא מופעל.
הביטוי הסופי מבוצע בסוף כל איטרציה בלולאה. לרוב משתמשים בו כדי להגדיל את המזהה ב-1.
בדרך כלל, for
לולאות חוזרות דרך מערכים בבסיסי קוד ישנים יותר. במקרים כאלה, התנאי שמצוין להמשך הלולאה הוא מספר איטרציה קטן או שווה לאורך המערך שעובר חזרה. המשתנה המשמש למעקב אחר מספר האיטרציה הנוכחי משמש לחיפוש הערך שמשויך לאינדקס הזה במערך, וכך ניתן לבצע פעולה לגבי כל רכיב במערך לפי הסדר:
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
... לא חוזר על עצמו דרך מאפיינים שהועברו בירושה מה-constructor Object
. עם זאת, כל המאפיינים המספורים בשרשרת אב הטיפוס של האובייקט נכללים:
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()
הקודמות. השיטות האלה בודקות אם המאפיין שצוין עובר בירושה (או לא מוצהר), ומחזיר 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()
השיטות forEach()
שמספקות הבנאים Array, Map, Set ו-NodeList מספקות קיצור דרך שימושי לאיטרציה על מבנה נתונים בהקשר של פונקציית קריאה חוזרת. בניגוד לצורות אחרות של לולאה, לא ניתן להפריע ללולאה שנוצרה באמצעות כל 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
כוללת פרמטרים דומים. מכיוון של-Set אין אינדקסים או מפתחות נפרדים לערכים, הארגומנט השני מספק ערך מיותר, שניתן להתעלם ממנו, רק כדי לשמור על התחביר עקבי עם השיטות האחרות של 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 ]
איטרטורים
רכיב iterable הוא כל מבנה נתונים שמורכב מאלמנטים נפרדים שאפשר לחזור עליהם באמצעות הגישות שפירטנו קודם. איטרטור הוא אובייקט איטרטור שתואם לפרוטוקול איטרטור, כלומר הוא צריך ליישם שיטת next()
שמתקדמת בין הרכיבים, היא מכילה אחד בכל פעם, בכל פעם שמופעלת לשיטה הזו, ומחזירה אובייקט לכל רכיב רציף בפורמט ספציפי.
מבני הנתונים המובנים שניתנים להעברה ב-JavaScript (כמו מערך, מפה ו-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 הוא סינכרוני, יש מנגנונים שמאפשרים למפתחים לנצל את לולאת האירועים כדי לבצע משימות אסינכרוניות.
הבטחות
Promise הוא placeholder לערך שלא ידוע מתי ההבטחה נוצרת. זהו קונטיינר שמכתיב פעולה אסינכרונית, המונחים שלפיהם הפעולה נחשבת להצלחה או לכישלון, הפעולות שצריך לבצע בכל אחד מהמקרים והערך שמתקבל כתוצאה מכך.
יוצרים מכונה של Promise באמצעות האופרטור new
עם הפונקציה המובנית של Promise
. ה-constructor הזה מקבל פונקציה בשם executor כארגומנט. הפונקציה הביצוע משמשת בדרך כלל לביצוע פעולה אסינכרונית אחת או יותר, ואז מכתיבות את המונחים שבהם יש להחשיב את ההבטחה כהפרה או דחייה שלה. Promise מוגדרת כ-Pending (בהמתנה) בזמן שפונקציית הביצוע פועלת. אחרי שה-הפעלה מסיים, הבטחה נחשבת כמומשה (או נפתרה, במקורות מסוימים לתיעוד) אם פונקציית הביצוע והפעולה האסינכרונית שהיא מבצעת הושלמו בהצלחה, ונדחית אם פונקציית הביצוע נתקלת בשגיאה או שהפעולה האסינכרונית שמבוצעת נכשלת. אחרי מימוש או דחייה של התחייבות, היא נחשבת להסדרה.
const myPromise = new Promise( () => { });
ה-constructor קורא לפונקציה מבצעים עם שני ארגומנטים. הארגומנטים האלה הם פונקציות שמאפשרות לממש או לדחות באופן ידני את Promise:
const myPromise = new Promise( ( fulfill, reject ) => { });
הפונקציות שמשמשות למימוש או לדחייה של ה-Promise מופעלות כארגומנט (בדרך כלל בתור שגיאה לדחייה):
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()
שהועברו בירושה מ-Promise. כל אחת מהשיטות האלה מחזירה Promise (הבטחה) שאפשר לפעול לפיה באופן מיידי באמצעות then()
, catch()
או finally()
, וכך לשרשר את ההבטחות שמתקבלות.
then()
מספקת שתי פונקציות של קריאה חוזרת כארגומנטים. משתמשים במודל הראשון כדי לקיים את ההבטחה שהתקבלה, ובשני כדי לדחות אותה. שתי השיטות מקבלות ארגומנט אחד שמספק ל-Promise שמתקבל את הערך שלה.
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 לרוץ כשה-Promise מתמלא או נדחה, מתבצעת קריאה לפונקציה שמועברת כארגומנט ב-method finally
, גם אם ההבטחה מומשה וגם אם נדחתה.
הפונקציה של ה-handler מופעלת ללא ארגומנטים, כי היא לא מיועדת לפעול עם הערכים שהועברו מה-Promise, אלא רק כדי להפעיל את הקוד אחרי שה-Promise הושלם.
בו-זמניות (concurrency)
הבנאי של Promise מספק ארבע שיטות לעבודה עם מספר הבטחות קשורות, באמצעות רכיב iterable שמכיל אובייקטים של 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
משהה את הביצוע של פונקציה אסינכרונית בזמן הסדרת ה-Promise המשויך. אחרי הסדרת ההבטחה, הערך של הביטוי 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