במאמר הקודם דיברנו על גנרטורים ובסוף הזכרתי שממש מעניין וחשוב להשתמש בגנרטורים ב-for of. מה זו לולאת for of? בואו נדבר קצת על לולאות. הלולאה המוכרת ביותר, שאותה כולנו מכירים היא לולאת for. היא כבר די ותיקה. הנה היא להזכירכם:
var arr = [ 'a', 'b', 'c' ];
for (var i=0; i
יש כמה בעיות עם זה – בראשונה הוא מניח מערך ממוספר מ1 ועד n. אבל אם אין לנו כזה (למשל מערך שבו יש 5 איברים אבל מקום 2 ו-3 חסרים לחלוטין ומקומות 6 עד 7 מאוכלסים) אז הלולאה חסרת ערך לחלוטין.
בדיוק בשביל מקרים כאלו יש לנו את for in שעובד יפה עם מערכים ועם אובייקטים.
var person = {fname:'John', lname:'Doe', age:25};
for (var prop in person){
if (Object.prototype.hasOwnProperty.call(person, prop)){
console.log(prop+' : '+person[prop]); //"fname : John", "lname : Doe", "age : 25"
}
}
מה הבעיה איתו? יש איתו כמה בעיות. הבעיה הראשונה היא שהוא לא עובר על איברי המערך או על תכונות האובייקט בסדר שאתה מצפה לו. בנוסף, הוא גם ידלג על איברים ריקים. הוא גם יעבור על כל האיברים המשותפים בשרשרת הפרוטוטייפ, יש גם בעיות עם המימוש שלו והוא באופן עקרוני לא מומלץ. אני אתן את הדוגמה הפשוטה ביותר :
let iterable = [3, 5, 7];
iterable.foo = "hello";
for (let i in iterable) {
console.log(i); // logs 0, 1, 2, "foo"
}
מה שעשיתי זה להפעיל אותו על מערך, אבל תראו מה קורה. אפשר באופן עקרוני לגרום לו לעבוד כמו שאנחנו רוצים גם במערך וגם באובייקט, אבל זה דורש הרבה קוד.
יש גם את forEach שהיה אפשר להפעיל אותו על ערכים בלבד. הבעיה איתו שאי אפשר לעצור אותו באמצע הפעולה והוא עובד אך ורק במערך.
בגלל זה רוב האנשים השתמשו ב-lodash או ב-underscore ואפילו jQuery על מנת לעשות foreach כמו שצריך על מערך או על אובייקט. for of פותר את הבעיה במערכים ודומיהם כאשר הוא מספק דרך נוחה, קלה וניתנת לעצירה לבצע לולאות בלי להסתבך.
איך זה עובד? פשוט שבפשוטים:
var arr = ['hello', 'world', 'I', 'am', 'Ran'];
for ( var arrValue of arr ) {
console.log(arrValue); // 'hello', 'world', 'I', 'am', 'Ran'
}
אם אני אוסיף כל מיני דברים מוזרים למערך (עם או בלי שרשרת פרוטוטייפ) ה-for of יתעלם באלגנטיות. הנה:
var arr = ['hello', 'world', 'I', 'am', 'Ran'];
arr.moshe = 'Levi';
for ( var arrValue of arr ) {
console.log(arrValue); // 'hello', 'world', 'I', 'am', 'Ran'
}
אפשר לעבור גם על מחרוזות טקסט:
var string = 'hello';
for ( var value of string ) {
console.log(value); // 'h', 'e', 'l', 'l', 'o'
}
גם בנוגע לאובייקטים יש מעקף נאה שאפשר לעשות:
var obj = { foo : 'hello', bar : 'world' };
for ( var key of Object.keys(obj) ) {
console.log(key + "->" + obj[key]); // 'foo->hello', 'bar->world'
}
כפי שהבטחתי בהתחלה, אחד הדברים הטובים ב-for of זה שאפשר לשבור אותה באמצע. דבר מעולה כשמדברים על גנרטורים. רוצים דוגמה? הנה:
function* myGenerator() { // a generator function
var curr = 0;
while (true) {
curr++;
yield curr;
}
}
for (var n of myGenerator()) {
console.log(n);
// truncate the sequence at 100
if (n >= 100) {
break;
}
}
מה קורה פה? יש לנו גנרטור פשוט למדי שעושה איטרציה מ-0 עד אינסוף. בכל איטרציה הוא עושה yield. אני מפעיל את הלולאה for על ה-myGenerator, הלולאה תעבור על כל yield. כיוון שאין לי תנאי עצירה, אני אקבל 1,2,3,4 עד שה-for of יחליט לשבור את הגנרטור. במקרה שלנו כאשר המספר יהיה גדול או שווה למאה.
שימו לב שלא השתמשתי פה ב-next, האיטרטור for of עושה את זה בשבילי.
מסובך? אני אנסה עם דוגמה אחרת:
function* myGenerator() { // a generator function
yield 'a';
yield 'b';
yield 'c';
yield 44;
}
for (var n of myGenerator()) {
console.log(n); //'a', 'b', 'c', 44
}
אולי זה יותר פשוט להבנה, במקום לעשות איזה foreach מגעיל ולעשות next באופן ידני כדי להביא את ה-yield הבא. אני מריץ באמצעות for of את הגנרטור. בכל איטרציה יש לי תוצאה של ה-yield בלי צורך ב-next. אני יכול לשבור את הfor of בכל רגע נתון כמובן עם break.
זו כמובן דוגמה תיאורטית. במציאות לגנרטור יהיו משימות יותר כבדות לבצע והמשמעות של שבירה תהיה משמעותית מבחינת ביצועים. מה שחשוב הוא שבעוד במקומות אחרים אני חייב לעשות next כל הזמן ולחשוב על המשמעות של next, בלולאת for of זה נעשה בשבילי ואני רק צריך לפרט תנאי עצירה. במידה ולא פירטתי, הגנרטור ימשיך עד שייגמרו לו ה-yield. מה שהופך את for of למאוד משמעותי בנוגע לגנרטורים. אולי יותר מכל דבר אחר.
7 תגובות
מתפעל כל פעם מחדש מהיכולת שלך להסביר נושא כל כך מסובך בכל כך קצת מילים
שווה להזכיר שב-ES2017 יכנס Object.entries שאפשר יהיה להשתמש בו כך:
for (let [k, v] of Object.entries(myObj)) {
console.log(k, v);
}
על מנת לקבל את המפתחות והערכים באופן קצת יותר אלגנטי
צודק, אך אני אזכיר את זה בסדרת המאמרים על es2017, מבטיח 🙂
"כפי שהבטחתי בהתחלה, אחד הדברים הטובים ב-for in זה שאפשר לשבור אותה באמצע. דבר מעולה כשמדברים על גנרטורים. רוצים דוגמה? הנה:"
אני חושב שצריך להיות רשום for of
צודק. תודה רבה שהערת 🙂 יום אחד טעויות ההקלדה האלו יהרגו אותי… :O
רן תודה על המאמרים שלך!
מעולה !!!