אותו תחביר, שתי עבודות
שלוש הנקודות ... מופיעות בהרבה קוד JavaScript מודרני, והן עושות שני דברים הפוכים לפי המקום שבו הן מופיעות. ברגע שרואים את הדפוס, כל שימוש ב-... נופל לאחת משתי קטגוריות:
- Rest:
...nameבצד המקבל אוסף כמה ערכים למערך או לאובייקט אחד. - Spread:
...valueבצד הנותן פורס מערך או אובייקט לחלקים הבודדים שלו.
זה כל המודל המחשבתי. שאר הדף הוא פשוט דוגמאות לכל אחד מהם, והדפוסים שתחזרו אליהם שוב ושוב.
פרמטרי Rest: איסוף ארגומנטים
פרמטר rest בהגדרה של פונקציה אוסף כל מספר של ארגומנטים למערך אמיתי:
nums הוא מערך רגיל. אפשר להפעיל עליו .map, .filter, לבדוק את .length שלו, להעביר אותו לפונקציה אחרת: כל מה שמערכים עושים.
אפשר לשלב פרמטרי rest עם פרמטרים רגילים, אבל פרמטר ה-rest חייב להיות אחרון:
label בולע את הארגומנט הראשון; כל מה שאחריו נוחת ב-items. כתיבת פרמטר rest בכל מקום אחר חוץ מהסוף היא שגיאת תחביר.
Rest מול האובייקט הישן arguments
קוד JavaScript ישן משתמש במשתנה קסם בשם arguments בתוך פונקציות רגילות. הוא נראה כמו מערך אבל הוא לא מערך, ולכן מתודות מערך לא עובדות עליו ישירות. פרמטרי rest מחליפים אותו בצורה נקייה:
לפונקציות חץ אין בכלל אובייקט arguments, כך שפרמטרי rest הם הדרך היחידה לקבל בהן מספר משתנה של ארגומנטים. העדיפו ...args בכל קוד חדש.
Spread בקריאות לפונקציות
spread עושה את ההפך: הוא לוקח מערך ופורק אותו לארגומנטים בודדים במקום הקריאה.
Math.max מקבלת מספרים בודדים, לא מערך. לפני spread הייתם כותבים Math.max.apply(null, nums). עכשיו זה ... וסיימתם.
שימו לב שאותו ... בדיוק הוא rest ב_הגדרת_ הפונקציה ו-spread ב_קריאה_ לפונקציה: המיקום אומר לכם מה הוא.
Spread בליטרלים של מערכים
פריסה לתוך ליטרל של מערך מעתיקה או משלבת מערכים:
[...a] נותן מערך חדש עם אותם איברים, וזה שימושי כשרוצים למיין או לשנות בלי לגעת במקור:
scores לא השתנה, כי .sort רצה על העותק. הרגל קטן, תמורה גדולה כשכותבים קוד שלא אמורות להיות לו תופעות לוואי מפתיעות.
Spread בליטרלים של אובייקטים
spread עובד גם על אובייקטים רגילים, וממזג את המאפיינים שלהם לאובייקט חדש:
המפתחות המאוחרים יותר מנצחים. updates.age דורס את user.age, ו-city מצטרף לחגיגה. סדר הפריסות קובע את התוצאה, אז זכרו את זה כשאתם מערימים ערכי ברירת מחדל ודריסות:
קודם ברירות המחדל, אחר כך הבחירות של המשתמש. המשתמש מנצח ב-fontSize ויורש את theme.
מלכודת העותק הרדוד
spread מעתיק לעומק של רמה אחת. אובייקטים ומערכים מקוננים עדיין משותפים בין המקור לעותק:
שני המערכים מציגים את התגית החדשה, כי copy.tags ו-original.tags הם אותו מערך. spread לא שכפל את הרשימה המקוננת, הוא רק העתיק את ההפניה.
לעותק עמוק אמיתי של נתונים פשוטים, השתמשו ב-structuredClone:
עכשיו שני המערכים בלתי תלויים. structuredClone מובנה בדפדפנים מודרניים וב-Node, מטפל במבנים מקוננים, והוא הבחירה הנכונה בכל פעם שעותק "רדוד" לא מספיק.
Rest ב-Destructuring
rest עובד גם ב-destructuring, שם הוא אוסף את האיברים או המאפיינים שנשארו:
שליפה של כמה שדות ושמירת השאר באובייקט אחד היא דפוס נפוץ כשמעבירים props הלאה, מסירים שדות רגישים או בונים גרסאות מעודכנות של נתונים:
password נשלף (ומתעלמים ממנו); safe מחזיק את כל השאר. בלי שינוי של המקור, בלי העתקה ידנית.
סיכום קצר
...nameברשימת פרמטרים או בתבנית destructuring הוא rest: הוא אוסף....valueבקריאה לפונקציה, בליטרל של מערך או בליטרל של אובייקט הוא spread: הוא פורס.- עותקים של spread הם רדודים. מבנים מקוננים נשארים משותפים. השתמשו ב-
structuredCloneלעותקים עמוקים. - פרמטרי rest הם מערכים אמיתיים: השתמשו בהם במקום ב-
arguments. - בליטרלים של אובייקטים, פריסות מאוחרות דורסות מוקדמות, וכך בונים ברירות מחדל עם דריסות.
הבא בתור: Closures
פונקציות ב-JavaScript לא רק מקבלות קלט ומחזירות פלט: הן גם זוכרות את ה-scope שבו הוגדרו. הזיכרון הזה נקרא closure, והוא המנגנון שמאחורי callbacks, פונקציות מפעל (factories) ורוב הדפוסים שתפגשו בדף הבא.
שאלות נפוצות
מה ההבדל בין rest ל-spread ב-JavaScript?
הם משתמשים באותו תחביר ... אבל עושים עבודות הפוכות. rest אוסף כמה ערכים למערך אחד, והוא מופיע ברשימות פרמטרים של פונקציות וב-destructuring. spread פורס אובייקט איטרבילי או אובייקט רגיל לחלקים שלו, והוא מופיע בקריאות לפונקציות, בליטרלים של מערכים ובליטרלים של אובייקטים. אם ... נמצא בצד המקבל, זה rest; אם הוא בצד הנותן, זה spread.
איך פרמטרי rest עובדים בפונקציה?
פרמטר rest כמו function sum(...nums) אוסף כל ארגומנט שמועבר לפונקציה למערך אמיתי בשם nums. הוא חייב להיות הפרמטר האחרון ברשימה. בניגוד לאובייקט arguments הישן, פרמטר rest הוא מערך אמיתי, כך ש-.map, .filter ו-.reduce עובדות עליו ישירות.
האם אופרטור ה-spread יוצר עותק עמוק?
לא. spread מעתיק רמה אחת בלבד: עותק רדוד. { ...user } נותן אובייקט חדש עם אותם מפתחות ברמה העליונה, אבל אובייקטים ומערכים מקוננים בתוכו הם עדיין הפניות משותפות. לעותק עמוק, השתמשו ב-structuredClone(value) או העבירו נתונים פשוטים דרך JSON.