من أنت أيها الكائن؟ معرفة اسم الصنف في TypeScript (وقت التشغيل)

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا قد تحتاج إلى اسم الصنف في وقت التشغيل؟

\n\n

تخيل: أنت تكتب نظامًا معقدًا بلغة TypeScript. لديك تسلسل هرمي للفئات — User، Admin، Moderator. في مكان ما في عمق الكود، تحصل على كائن، لكنك لا تعرف من هو حقًا. ترى فقط object في وحدة التحكم.

\n\n

أو أنك تبني مسجلاً (logger) يجب أن يخرج ليس فقط [Object]، بل اسم صنف ذا معنى: [User: John]. أو أنك تكتب مُزينًا (decorator) يجب أن يقرر كيفية معالجة الكائن، اعتمادًا على نوعه.

\n\n

هنا يأتي دور السؤال الذي حصل على آلاف المشاهدات على Stack Overflow: "Get an object's class name at runtime". اليوم لن نقدم الإجابة فقط، بل سننظر تحت غطاء TypeScript وJavaScript لنفهم كيف يعمل هذا حقًا.

\n\n

نعم، TypeScript هي طبقة فوقية على JavaScript. كل سحر الأنواع يختفي بعد التجميع. لكن اسم الصنف ليس نوعًا، بل هو خاصية من النموذج الأولي. وتبقى معنا في وقت التشغيل.

\n\n

أبسط طريقة: constructor.name

\n\n

إذا كنت قد بحثت يومًا عن هذا السؤال، فمن المحتمل أنك رأيت هذا السطر الواحد:

\n\n
class User {\n  constructor(public name: string) {}\n}\n\nconst user = new User("أليس");\nconsole.log(user.constructor.name); // "User"
\n\n

هل يعمل؟ نعم. سريع؟ نعم. لكن هناك تفاصيل قد تلدغك إذا لم تعرف كيف يعمل.

\n\n

كيف يعمل تحت الغطاء؟

\n\n

كل كائن في JavaScript له خاصية constructor. هذا مرجع للدالة التي أنشأت هذا الكائن. وأي دالة (الصنف هو أيضًا دالة) لها خاصية name.

\n\n

عندما تكتب user.constructor.name، يقوم المحرك بما يلي:

\n
    \n
  • يسير عبر سلسلة النماذج الأولية للكائن user.
  • \n
  • يجد الخاصية constructor على User.prototype.
  • \n
  • يقرأ name من دالة المُنشئ User.
  • \n
\n\n

هذه عملية غير مكلفة، لكنها ليست مجانية. في معظم الحالات، لن تلاحظ ذلك حتى.

\n\n

متى لا يعمل constructor.name؟

\n\n

هذا هو المأزق الكلاسيكي:

\n\n
const obj = Object.create(null);\nconsole.log(obj.constructor); // undefined\n// console.log(obj.constructor.name); // TypeError!
\n\n

الكائن الذي تم إنشاؤه عبر Object.create(null) ليس له نموذج أولي. ليس لديه constructor. كن حذرًا.

\n\n

الحالة الثانية هي التصغير (minification). إذا كنت تستخدم أداة تجميع (Webpack، Terser) تعيد تسمية الفئات، فقد يعيد constructor.name شيئًا مثل t أو e. هذه مشكلة فقط للإنتاج، لكن من الجيد معرفتها.

\n\n

البديل: instanceof والتحقق المخصص

\n\n

أحيانًا لا تحتاج فقط إلى الاسم، بل إلى تحقق: "هل هذا الكائن هو نسخة من الصنف X؟"

\n\n
class Animal {}\nclass Dog extends Animal {}\n\nconst dog = new Dog();\n\nconsole.log(dog instanceof Dog);    // true\nconsole.log(dog instanceof Animal); // true\nconsole.log(dog instanceof Object); // true
\n\n

instanceof يعمل عبر سلسلة النماذج الأولية. يتحقق مما إذا كان Dog.prototype موجودًا في سلسلة النماذج الأولية للكائن dog. إذا كان موجودًا — true.

\n\n

لكن instanceof لا يعطيك سلسلة نصية بالاسم. يعطيك قيمة منطقية. للسجلات أو التسلسل، هذا غير مناسب.

\n\n

الجمع: التحقق + الاسم

\n\n

يمكنك كتابة أداة مساعدة تتحقق أولاً من النوع، ثم تعيد الاسم:

\n\n
function getClassName(obj: any): string | null {\n  if (obj === null || obj === undefined) return null;\n  if (typeof obj !== 'object' && typeof obj !== 'function') return null;\n\n  // حماية من Object.create(null)\n  if (!obj.constructor) return 'Object (no prototype)';\n\n  const name                

المدونات

توصيات الكتب