لماذا قد تحتاج إلى اسم الصنف في وقت التشغيل؟
\n\nتخيل: أنت تكتب نظامًا معقدًا بلغة TypeScript. لديك تسلسل هرمي للفئات — User، Admin، Moderator. في مكان ما في عمق الكود، تحصل على كائن، لكنك لا تعرف من هو حقًا. ترى فقط object في وحدة التحكم.
أو أنك تبني مسجلاً (logger) يجب أن يخرج ليس فقط [Object]، بل اسم صنف ذا معنى: [User: John]. أو أنك تكتب مُزينًا (decorator) يجب أن يقرر كيفية معالجة الكائن، اعتمادًا على نوعه.
هنا يأتي دور السؤال الذي حصل على آلاف المشاهدات على Stack Overflow: "Get an object's class name at runtime". اليوم لن نقدم الإجابة فقط، بل سننظر تحت غطاء TypeScript وJavaScript لنفهم كيف يعمل هذا حقًا.
\n\nنعم، TypeScript هي طبقة فوقية على JavaScript. كل سحر الأنواع يختفي بعد التجميع. لكن اسم الصنف ليس نوعًا، بل هو خاصية من النموذج الأولي. وتبقى معنا في وقت التشغيل.
\n\nأبسط طريقة: constructor.name
\n\nإذا كنت قد بحثت يومًا عن هذا السؤال، فمن المحتمل أنك رأيت هذا السطر الواحد:
\n\nclass 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.
عندما تكتب user.constructor.name، يقوم المحرك بما يلي:
- \n
- يسير عبر سلسلة النماذج الأولية للكائن
user. \n - يجد الخاصية
constructorعلىUser.prototype. \n - يقرأ
nameمن دالة المُنشئUser. \n
هذه عملية غير مكلفة، لكنها ليست مجانية. في معظم الحالات، لن تلاحظ ذلك حتى.
\n\nمتى لا يعمل constructor.name؟
\n\nهذا هو المأزق الكلاسيكي:
\n\nconst obj = Object.create(null);\nconsole.log(obj.constructor); // undefined\n// console.log(obj.constructor.name); // TypeError!\n\nالكائن الذي تم إنشاؤه عبر Object.create(null) ليس له نموذج أولي. ليس لديه constructor. كن حذرًا.
الحالة الثانية هي التصغير (minification). إذا كنت تستخدم أداة تجميع (Webpack، Terser) تعيد تسمية الفئات، فقد يعيد constructor.name شيئًا مثل t أو e. هذه مشكلة فقط للإنتاج، لكن من الجيد معرفتها.
البديل: instanceof والتحقق المخصص
\n\nأحيانًا لا تحتاج فقط إلى الاسم، بل إلى تحقق: "هل هذا الكائن هو نسخة من الصنف X؟"
\n\nclass 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\ninstanceof يعمل عبر سلسلة النماذج الأولية. يتحقق مما إذا كان Dog.prototype موجودًا في سلسلة النماذج الأولية للكائن dog. إذا كان موجودًا — true.
لكن instanceof لا يعطيك سلسلة نصية بالاسم. يعطيك قيمة منطقية. للسجلات أو التسلسل، هذا غير مناسب.
الجمع: التحقق + الاسم
\n\nيمكنك كتابة أداة مساعدة تتحقق أولاً من النوع، ثم تعيد الاسم:
\n\nfunction 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