Menu

TypeScript instanceof: צמצום מחלקות ושגיאות

instanceof בודק בזמן ריצה האם אובייקט נוצר ממחלקה מסוימת, ו-TypeScript מצמצם את המשתנה למחלקה הזו. איך זה עובד עם מחלקות משלכם ועם תתי-מחלקות של Error, למה הוא לא יכול לבדוק interfaces או type aliases, ואיפה הוא נותן תשובות מפתיעות.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

value instanceof SomeClass היא בדיקה של JavaScript שרצה בזמן ריצה: היא true כש-SomeClass.prototype נמצא בשרשרת ה-prototype של האובייקט, כלומר האובייקט נוצר עם new SomeClass (או עם תת-מחלקה). TypeScript מצמצם את value ל-SomeClass בתוך הבדיקה.

השתמשו ב-instanceof לאובייקטים שנוצרו ממחלקות, וב-typeof לערכים פרימיטיביים. כאן צריך את שניהם: instanceof מפריד את ה-Date, ואז typeof מפצל את מה שנשאר.

צמצום של מחלקות משלכם

instanceof מצמצם למחלקה בענף ה-true ומסיר אותה בענף ה-false, כך שאפשר לטפל ב-union של מחלקות איבר אחרי איבר.

מופע של תת-מחלקה עובר גם את הבדיקה של ההורה שלה: אם Square extends Rect, אז new Square(2) instanceof Rect הוא true. כשהענפים שונים, בדקו קודם את המחלקה הספציפית ביותר.

תתי-מחלקות של Error ב-catch

השימוש הנפוץ ביותר ב-instanceof הוא ב-catch. תחת strict, הערך שנתפס הוא unknown (אפשר לזרוק כל דבר), ו-instanceof הוא הדרך לחזור לשגיאה עם טיפוס.

פלט:

404: /missing.txt
TypeError: path must be absolute

class X extends Error עובד עם instanceof בכל target ש-TypeScript 7 תומך בו (ES2015 ומעלה). העצה הישנה לקרוא ל-Object.setPrototypeOf(this, X.prototype) בבנאי התאימה לקוד שקומפל ל-ES5, target ש-TypeScript 7 הסיר.

instanceof לא עובד עם interfaces או טיפוסים

interfaces ו-type aliases קיימים רק בשביל הקומפיילר. אחרי הקומפילציה אין ערך User להשוות אליו, ולכן TypeScript מסרב לבדיקה:

הקומפיילר מדווח index.ts(8,24): error TS2693: 'User' only refers to a type, but is being used as a value here. יש שני פתרונות. בדקו את המבנה בעצמכם עם type guard, פונקציה שמחזירה value is User:

או, אם הקוד שלכם יוצר את האובייקטים האלה, הפכו את User למחלקה ובנו אותם עם new; אז instanceof עובד. העמוד על type guards מסביר לעומק predicates ו-assertion functions.

הטיפוס הנכון אינו המופע הנכון

TypeScript משווה טיפוסים לפי מבנה: ליטרל אובייקט עם אותם איברים כמו מחלקה ניתן להשמה לטיפוס של המחלקה. instanceof לא מסתכל על המבנה. הוא עובר על שרשרת ה-prototype, ואובייקט שאף פעם לא נוצר עם new נכשל בבדיקה גם כשהקומפיילר מקבל אותו כטיפוס הזה.

השורה האחרונה חשובה. structuredClone, JSON.parse(JSON.stringify(...)) והודעות בין workers מחזירים כולם אובייקטים רגילים בלי ה-prototype של המחלקה, למרות שהטיפוס הסטטי שלהם עדיין יכול לומר Point. כשמופעים של מחלקות עוברים גבול כזה, בנו אותם מחדש (new Point(copy.x, copy.y)) לפני שסומכים על instanceof או על מתודות.

instanceof וערכים פרימיטיביים

ערכים פרימיטיביים (string, number, boolean...) אינם אובייקטים ואין להם שרשרת prototype, ולכן "hi" instanceof String הוא false. TypeScript מסמן את הטעות כשהוא יכול: כשבצד שמאל יש ערך מהטיפוס string, instanceof היא השגיאה TS2358, The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter. השתמשו ב-typeof לערכים פרימיטיביים.

ערךבדיקת instanceofתוצאה
new Date()instanceof Datetrue
[1, 2]instanceof Arraytrue (אבל עדיף Array.isArray)
new TypeError("x")instanceof Errortrue (תת-מחלקה)
{ x: 1, y: 2 }instanceof Pointfalse (אף פעם לא נבנה)
Object.create(null)instanceof Objectfalse (אין prototype)
"hi"instanceof Stringfalse (ערך פרימיטיבי)

ערכים מ-realm אחר

instanceof משווה מול אובייקט בנאי אחד מסוים. לקוד שרץ ב-realm אחר (iframe, או הקשר vm של Node) יש Array, Error ו-Date משלו, ולכן מערך שנוצר שם נכשל ב-instanceof Array כאן. אותו דבר קורה כששני עותקים של אותה חבילת npm מגיעים ל-node_modules: לכל עותק יש מחלקה משלו, ומופע מעותק אחד נכשל ב-instanceof מול השני. למערכים, Array.isArray עובד בין realms. לטיפוסים משלכם, בדיקה של מאפיין (type guard, או שדה kind) מונעת את הבעיה לגמרי.

שאלות נפוצות

איך בודקים ב-TypeScript האם אובייקט הוא מופע של מחלקה?

משתמשים ב-value instanceof ClassName. זו בדיקה בזמן ריצה (JavaScript רגיל), ו-TypeScript מצמצם את value ל-ClassName בתוך ה-if. זה עובד עם מחלקות מובנות כמו Date, Map ו-Error, וגם עם מחלקות משלכם.

אפשר להשתמש ב-instanceof עם interface ב-TypeScript?

לא. interfaces ו-type aliases נמחקים כש-TypeScript מקמפל ל-JavaScript, ולכן בזמן ריצה אין מול מה לבדוק. x instanceof User כש-User הוא interface היא השגיאה TS2693, "'User' only refers to a type, but is being used as a value here." במקום זה בדקו את המאפיינים עם פונקציית type guard, או הפכו את User למחלקה אם אתם יוצרים את האובייקטים בעצמכם.

למה instanceof מחזיר false לאובייקט מהטיפוס הנכון?

הטיפוסים של TypeScript מבניים: ליטרל אובייקט { x: 1, y: 2 } ניתן להשמה לטיפוס של המחלקה Point אם יש לו אותם איברים. אבל instanceof בודק את שרשרת ה-prototype, והליטרל אף פעם לא נוצר עם new Point, ולכן התוצאה היא false. אותו דבר קורה למופעים של מחלקות שעברו דרך JSON, structuredClone או ערוץ הודעות, וחוזרים כאובייקטים רגילים.

האם instanceof עובד עם מחלקות Error מותאמות ב-TypeScript?

כן, עם כל target מודרני. class NotFound extends Error {}, ואז err instanceof NotFound הוא true. הבעיה הישנה שבה הוא החזיר false השפיעה רק על פלט שקומפל ל-ES5, ו-TypeScript 7 כבר לא תומך ב-target של ES5.

למה "hello" instanceof String הוא false?

ליטרל של מחרוזת הוא ערך פרימיטיבי, לא אובייקט, ולכן אין לו שרשרת prototype לבדוק. instanceof String נכון רק לאובייקטי עטיפה שנוצרו עם new String(). TypeScript דוחה instanceof על ערך מהטיפוס string (TS2358); השתמשו ב-typeof value === "string" לערכים פרימיטיביים.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל