Symbol הוא ערך שמובטח להיות ייחודי
Symbol הוא אחד מהטיפוסים הפרימיטיביים של JavaScript, לצד string, number, boolean, null, undefined ו-bigint. התכונה שמגדירה אותו פשוטה: כל symbol שיוצרים שונה מכל symbol אחר, לתמיד.
שני symbols, שניהם נוצרו בלי ארגומנטים, והם עדיין לא שווים. זה לא מקרה, זו כל המטרה. אי אפשר לזייף symbol, ליצור אחד מחדש בטעות או להתנגש ב-symbol של מישהו אחר.
אפשר להצמיד תיאור כדי לעזור בדיבוג. הוא לא משפיע על הזהות:
אותו תיאור, symbols שונים. התיאור הוא רק תווית בשביל בני אדם שקוראים לוגים.
למה הם קיימים: מפתחות בלי התנגשויות
הסיבה המרכזית לקיומם של symbols היא לאפשר לכם להוסיף מאפיינים לאובייקט בלי לדאוג שהמפתח שלכם יתנגש במפתח של מישהו אחר. מפתחות מסוג מחרוזת מתחרים במרחב שמות שטוח: אם שתי ספריות מחליטות לשמור metadata ב-obj.meta, הן דורסות זו את זו. מפתחות מסוג symbol לא מתחרים, כי לאף אחד אחר אין את ה-symbol שלכם.
הסוגריים המרובעים ב-[ID]: 42 הם תחביר של computed property: הם אומרים "השתמשו בערך של ID כמפתח". רק קוד שמחזיק את אותו symbol ID יכול לקרוא את המאפיין הזה או לדרוס אותו. כל מודול אחר שמשתמש במחרוזת "id" או ב-Symbol("id") משלו מדבר על משבצת אחרת לגמרי.
מפתחות Symbol מוסתרים (ברובם)
מאפיינים עם מפתח symbol לא מופיעים ב-for...in, ב-Object.keys או ב-JSON.stringify. הם לא סודיים, קוד נחוש עדיין יכול למצוא אותם, אבל הם לא מפריעים במעבר רגיל על האובייקט.
Object.keys ו-JSON.stringify מדלגים לגמרי על מפתח ה-symbol. אם צריך למצוא מאפייני symbol בכוונה, Object.getOwnPropertySymbols ו-Reflect.ownKeys חושפים אותם. זו בדיוק ההתנהגות שרוצים עבור metadata: גלויה כשמחפשים אותה, בלתי נראית לקוד שרק עובר על מפתחות מסוג מחרוזת.
שיתוף Symbols עם Symbol.for
Symbol() נותן symbol מקומי וחד פעמי. לפעמים רוצים את ההפך: symbol שהוא אותו symbol בכל מקום בתוכנית, בין מודולים, כדי שחלקי קוד שונים יוכלו להסכים על מפתח. בשביל זה יש את Symbol.for.
Symbol.for(key) בודק ברישום גלובלי: אם כבר קיים symbol עבור המפתח הזה, מקבלים אותו בחזרה; אחרת הוא יוצר אחד ושומר אותו. Symbol.keyFor הולך בכיוון ההפוך: הוא מחזיר את המפתח שאיתו נוצר symbol רשום (או undefined עבור symbols שאינם רשומים).
ברוב המקרים, Symbol() רגיל הוא מה שאתם צריכים. לכו על Symbol.for כש-symbol באמת צריך להיות משותף מעבר לגבולות של מודול.
Well-Known Symbols: נקודות חיבור לשפה
כאן symbols מוכיחים את ערכם. JavaScript חושפת קבוצה של symbols מוגדרים מראש, ה-well-known symbols, שהשפה עצמה בודקת אם הם קיימים על האובייקטים שלכם. ממשו מתודה באחד מהמפתחות האלה, והאובייקט שלכם מתחבר ישירות ליכולת מובנית.
השימושי ביותר הוא Symbol.iterator. כל אובייקט שיש לו מתודה במפתח הזה הוא iterable: הוא עובד עם for...of, spread, destructuring ו-Array.from.
range הוא לא מערך. זה אובייקט רגיל עם מתודה אחת במפתח מיוחד. אבל מכיוון שהמתודה הזו מחזירה iterator, גם for...of וגם spread עובדים עליו, בדיוק כמו שהם עובדים על מערכים, מחרוזות ו-Map. זה החוזה: ממשו את Symbol.iterator, והשפה מתייחסת אליכם כ-iterable.
Well-Known Symbols נוספים ששווה להכיר
יש עוד כמה, וכל אחד מהם הוא נקודת חיבור לחלק אחר של השפה:
Symbol.iterator: הופך אובייקט ל-iterable.Symbol.asyncIterator: אותו דבר, אבל עבורfor await...of.Symbol.toPrimitive: קובע איך אובייקט מומר לערך פרימיטיבי (number, string או ברירת מחדל) בהקשרים של המרת טיפוסים.Symbol.hasInstance: מתאים אישית מהinstanceofמחזיר עבור המחלקה שלכם.Symbol.toStringTag: קובע את התגית שמוצגת ב-Object.prototype.toString.call(obj).
אין צורך לשנן אותם. מספיק לדעת שהם קיימים: כשתגיעו למצב שבו אתם רוצים שהאובייקט שלכם יתנהג כמו טיפוס מובנה, בדרך כלל יש well-known symbol בשביל זה.
Symbols הם לא מחרוזות
מלכודת נפוצה: symbols לא מומרים אוטומטית למחרוזות. שרשור של symbol זורק שגיאה, וכך גם העברה שלו ל-API שמצפה למחרוזת:
Template literals זורקים את אותו TypeError. השתמשו ב-String(symbol) או ב-symbol.toString() כשאתם באמת רוצים טקסט. הקצוות החדים האלה מכוונים: השפה מגינה עליכם מפני התייחסות בטעות לערך של זהות ייחודית כאל עוד פיסת טקסט.
מתי להשתמש ב-Symbols (ומתי לא)
השתמשו ב-symbols כאשר:
- אתם מצמידים metadata לאובייקטים שאינם שלכם, וצריכים מפתח שלא יתנגש.
- אתם מתכננים פרוטוקול: "אובייקטים שרוצים לעבוד עם הספרייה שלי צריכים לממש מתודה ב-symbol הזה".
- אתם רוצים מאפיין שלא ידלוף ל-
JSON.stringifyאו ל-for...in. - אתם מממשים את
Symbol.iteratorאו well-known symbol אחר.
וותרו על symbols כאשר:
- אתם רק צריכים מפתח לאובייקט ואין סיכון להתנגשות. מחרוזת פשוטה יותר ומופיעה בלוגים כפי שהיא.
- אתם רוצים שדות "פרטיים". מאפיינים עם מפתח symbol לא באמת פרטיים,
Object.getOwnPropertySymbolsמוצא אותם. לפרטיות אמיתית, השתמשו בשדות מחלקה#private. - אתם שומרים נתונים שצריכים לשרוד
JSON.stringify. הם לא ישרדו.
רוב קוד ה-JavaScript מסתדר מצוין בלי לכתוב אי פעם Symbol(...) ישירות. אבל ברגע שאתם רוצים שהאובייקט שלכם יעבוד עם for...of או יתנהג באופן טבעי בהקשר של המרת טיפוסים, symbols הם הדלת למנגנון הזה.
הבא בתור: הצהרות על פונקציות
Symbols, iterators ו-generators נשענים כולם בכבדות על פונקציות: המתודות שנשמרות ב-Symbol.iterator, הפונקציות שמייצרות iterators, וה-generators שהצורה function* שלהם מסתירה את רוב הקוד החוזר. פונקציות הן הפרק הבא, ומתחילים בדרכים השונות להצהיר עליהן ובמה שמשתנה בין צורה לצורה.
שאלות נפוצות
מה זה symbol ב-JavaScript?
Symbol הוא טיפוס פרימיטיבי שהערכים שלו מובטחים להיות ייחודיים. יוצרים אחד עם Symbol() או Symbol('description'), ואף פעם שתי קריאות לא יפיקו symbols שווים. משתמשים בהם בעיקר כמפתחות של אובייקטים שלא יתנגשו עם מפתחות אחרים, וכנקודות חיבור ליכולות של השפה דרך well-known symbols כמו Symbol.iterator.
מתי כדאי להשתמש ב-symbol במקום במפתח מסוג מחרוזת?
השתמשו ב-symbol כשאתם רוצים להצמיד מאפיין לאובייקט בלי להסתכן בהתנגשות שמות עם קוד אחר: ספריות שמוסיפות metadata, frameworks שמסמנים אובייקטים, או הגדרה של מפתח שבכוונה אינו חלק מה-API הציבורי. למפתחות יומיומיים, שבהם קריאות חשובה ואין חשש להתנגשות, מחרוזות הן עדיין הבחירה הנכונה.
למה משמש Symbol.iterator?
Symbol.iterator הוא well-known symbol שאומר ל-for...of, ל-spread ול-destructuring איך לעבור על אובייקט. אם מגדירים במפתח Symbol.iterator מתודה שמחזירה iterator, האובייקט הופך ל-iterable. כך בדיוק עובדים מאחורי הקלעים מערכים, מחרוזות, Map ו-Set.
מה ההבדל בין Symbol() ל-Symbol.for()?
Symbol('x') יוצר symbol חדש לגמרי בכל פעם: שתי קריאות עם אותו תיאור עדיין שונות זו מזו. Symbol.for('x') מחפש ברישום גלובלי ומחזיר את אותו symbol עבור אותו מפתח בכל התוכנית. השתמשו ב-Symbol.for כשצריך לשתף symbols בין מודולים או realms; השתמשו ב-Symbol() לייחודיות מקומית.