Why Might You Need a Class Name at Runtime?
\n\nImagine: you're writing a complex system in TypeScript. You have a class hierarchy — User, Admin, Moderator. Somewhere deep in the code, you receive an object, but you don't know what it really is. You only see object in the console.
Or you're building a logger that should output not just [Object], but a meaningful class name: [User: John]. Or you're writing a decorator that needs to decide how to handle an object based on its type.
This is where the question with thousands of views on Stack Overflow comes to the rescue: "Get an object's class name at runtime". Today, we won't just give the answer; we'll look under the hood of TypeScript and JavaScript to understand how it actually works.
\n\nYes, TypeScript is a superset of JavaScript. All the type magic disappears after compilation. But the class name is not a type; it's a property of the prototype. And it stays with us at runtime.
\n\nThe Simplest Way: constructor.name
\n\nIf you've ever Googled this question, you've probably seen this one-liner:
\n\nclass User {\n constructor(public name: string) {}\n}\n\nconst user = new User("Alice");\nconsole.log(user.constructor.name); // "User"\n\nDoes it work? Yes. Is it fast? Yes. But there are nuances that can bite you if you don't know how it works.
\n\nHow Does It Work Under the Hood?
\n\nEvery object in JavaScript has a constructor property. This is a reference to the function that created the object. And any function (a class is also a function) has a name property.
When you write user.constructor.name, the engine does the following:
- \n
- Walks up the prototype chain of the
userobject. \n - Finds the
constructorproperty onUser.prototype. \n - Reads the
nameof the constructor functionUser. \n
This is a cheap operation, but it's not free. In most cases, you won't even notice it.
\n\nWhen Does constructor.name Fail?
\n\nHere's a classic gotcha:
\n\nconst obj = Object.create(null);\nconsole.log(obj.constructor); // undefined\n// console.log(obj.constructor.name); // TypeError!\n\nAn object created with Object.create(null) has no prototype. It has no constructor. Be careful.
The second case is minification. If you use a bundler (Webpack, Terser) that renames classes, constructor.name might return something like t or e. This is only a problem for production, but it's good to know about.
Alternative: instanceof and Custom Checks
\n\nSometimes you don't just need the name, but a check: "Is this object an instance of class 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 works through the prototype chain. It checks if Dog.prototype is in the prototype chain of the dog object. If yes — true.
But instanceof doesn't give you a string with the name. It gives you a boolean value. This is not suitable for logging or serialization.
Combining: Check + Name
\n\nYou can write a utility that first checks the type and then returns the name:
\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 // Protection against Object.create(null)\n if (!obj.constructor) return 'Object (no prototype)';\n\n const name