173

Are there any 'harmonious' ways to get the class name from ES6 class instance? Other than

someClassInstance.constructor.name

Currently I'm counting on Traceur implementation. And it seems that Babel has a polyfill for Function.name while Traceur doesn't.

To sum it all up: there was no other way in ES6/ES2015/Harmony, and nothing is expected ATM in ES.Next.

It may provide useful patterns for unminified server-side applications but is unwanted in applications meant for browser/desktop/mobile.

Babel uses core-js to polyfill Function.name, it should be loaded manually for Traceur and TypeScript applications as appropriate.

Estus Flask
  • 150,909
  • 47
  • 291
  • 441
  • 2
    I came across the same issue; for Traceur the only solution was to parse the actual class code itself to extract the name, which I don't think qualifies as harmonious. I just swallowed the pill and switched to Babel; Traceur's development seems to be somewhat stagnant, and many ES6 features are poorly implemented. As you mentioned, `instance.constructor.name` and `class.name` return the class name in proper ES6. – Andrew Odri Mar 27 '15 at 22:09
  • Seems to be the only way. – Frederik Krautwald Apr 26 '15 at 11:48
  • Is this in the ES6 standard? – drudru May 22 '15 at 19:57
  • 15
    It's worth mentioning that ```someClassInstance.constructor.name``` will get mangled if you uglify your code. – JamesB Sep 08 '15 at 14:58
  • http://stackoverflow.com/questions/2648293/javascript-get-function-name Might want to look at this, should work w/ `.constructor`. – Florrie Sep 16 '16 at 01:14

4 Answers4

226

someClassInstance.constructor.name is exactly the correct way to do this. Transpilers may not support this, but it is the standard way per the specification. (The name property of functions declared via ClassDeclaration productions is set in 14.5.15, step 6.)

mikemaccana
  • 81,787
  • 73
  • 317
  • 396
Domenic
  • 100,627
  • 39
  • 205
  • 257
  • 2
    That's what I was afraid of. Are you aware of any reasonable polyfill for that? I've tried to figure out how Babel does that but had very little success. – Estus Flask Jun 01 '15 at 12:22
  • I don't really know what you mean by a polyfill for a language feature (classes). – Domenic Jun 02 '15 at 00:58
  • I mean the polyfill for constructor.name. It seems that Babel has it implemented, but I did not succeed in understanding how it does that. – Estus Flask Jun 10 '15 at 12:49
  • I tried on Firefox Nightly (which supports basic classes) and it returned the string "constructor" instead of my class name "Hello" :( – Zorgatone Aug 10 '15 at 09:14
  • There's a reason it's not in Firefox release yet – Domenic Aug 10 '15 at 20:44
  • 2
    @estus `someClassInstance.constructor` is a Function. All Functions have a `name` property that's set to its name. That's why babel doesn't need to do anything. Please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name – Esteban Jan 06 '16 at 01:39
  • 2
    @Esteban It appears that Babel persistently promotes core-js polyfills (including a polyfill for Function.name), that's why some Babel builds may work out of the box in all browsers. – Estus Flask Feb 22 '16 at 16:34
  • @estus `constructor.name` is the name of the function/class, this is generally set on the function's prototype. So if you have function/class like `function MyClass(){}` then, to make sure instances of MyClass always have a .constructor property, you would set it via `MyClass.prototype.constructor = MyClass`, so now you have `let myInst = new MyClass(); console.log( myInst.constructor.name ) // MyClass` – flcoder Oct 03 '16 at 13:54
56

As @Domenic says, use someClassInstance.constructor.name. @Esteban mentions in the comments that

someClassInstance.constructor is a Function. All Functions have a name property...

Thus, to access the class name statically, do the following (this works with my Babel version BTW. According to the comments on @Domenic your mileage may vary).

class SomeClass {
  constructor() {}
}

var someClassInstance = new SomeClass();
someClassInstance.constructor.name;      // === 'SomeClass'
SomeClass.name                           // === 'SomeClass'

Update

Babel was fine, but uglify/minification did end up causing me problems. I am making a game, and am creating a hash of pooled Sprite resources (where the key is the function name). After minification, every function/class was named t. This kills the hash. I am using Gulp in this project, and after reading the gulp-uglify docs I discovered there is a parameter to prevent this local variable/function name mangling from happening. So, in my gulpfile I changed

.pipe($.uglify()) to .pipe($.uglify({ mangle: false }))

There is a trade-off of performance vs readability here. Not mangling the names will result in a (slightly) larger build file (more network resources) and potentially slower code execution (citation needed - may be BS). On the other hand, if I kept it the same I would have to manually define getClassName on every ES6 class - at a static and instance level. No thanks!

Update

After the discussion in the comments, it seems like avoiding the .name convention in favor of defining those functions is a good paradigm. It only takes a few lines of code, and will allow full minification and generality of your code (if used in a library). So I guess I change my mind and will manually define getClassName on my classes. Thanks @estus!. Getter/Setters are usually a good idea compared to direct variable access anyways, especially in a client based application.

class SomeClass {
  constructor() {}
  static getClassName(){ return 'SomeClass'; }
  getClassName(){ return SomeClass.getClassName(); }
}
var someClassInstance = new SomeClass();
someClassInstance.constructor.getClassName();      // === 'SomeClass' (static fn)
someClassInstance.getClassName();                  // === 'SomeClass' (instance fn)
SomeClass.getClassName()                           // === 'SomeClass' (static fn)
Community
  • 1
  • 1
James L.
  • 7,029
  • 4
  • 28
  • 45
  • 3
    Disabling mangling completely is not a very good idea because mangling contributes to minification a lot. It isn't very good idea either to use the subject in client-side code in the first place, but the classes can be protected from mangling with `reserved` Uglify option (a list of classes can be obtained with regexp or whatever). – Estus Flask Sep 16 '16 at 22:15
  • Very true. There's a trade-off of having a larger file size. [Looks like this is how you can use RegEx to prevent mangling for only select items](http://stackoverflow.com/questions/30840889/uglifyjs-property-mangling). What do you mean it "isn't a very good idea either to use the subject in client-side code"? Would it present a security risk in some scenarios? – James L. Sep 19 '16 at 12:47
  • 1
    Nope, just what was already said. It is normal for client-side JS to be minified, so it is already known that this pattern will cause problems for the app. Extra one line of code for class string identifier instead of neat `name` pattern may just be a win-win. The same thing may be applied to Node, but to a lesser extent (e.g. obfuscated Electron app). As a rule of thumb, I would rely on `name` in server code, but not in browser code and not in common library. – Estus Flask Sep 19 '16 at 13:07
  • OK so you're recommending manually defining 2 getClassName functions (static & instance) to get around mangling hell & to allow full minification (without an annoying RegEx). That point about the library makes a lot of sense. Makes a lot of sense. For me, my project is a self-made small Cordova app, so those aren't really problems. Anything beyond that I can see these issues rising up. Thanks for the discussion! If you can think of any improvements to the post, feel free to edit it. – James L. Sep 19 '16 at 13:13
  • 1
    Yes, I've initially used `name` for DRYer code to extract the name of the entity that uses class (service, plugin, etc) from class name, but in the end I've found that it duplicating it explicitly with static prop (`id`, `_name`) is just most solid approach. A good alternative is to not care about class name, use a class itself (function object) as an identifier for an entity that is mapped to this class and `import` it wherever needed (an approach that was used by Angular 2 DI). – Estus Flask Sep 19 '16 at 18:18
10

Getting class name directly from class

Previous answers explained that someClassInstance.constructor.name works just fine, but if you need to programmatically convert class name into a string and don't want to create an instance just for that, remember that:

typeof YourClass === "function"

And, since every function has a name property, another nice way to get a string with your class name is to just do:

YourClass.name

What follows is a good example of why this is useful.

Loading web components

As the MDN documentation teaches us, this is how you load a web component:

customElements.define("your-component", YourComponent);

Where YourComponent is a class extending from HTMLElement. Since it is considered good practice to name your component's class after the component tag itself, it would be nice to write a helper function that all your components could use to register themselves. So here's is that function:

function registerComponent(componentClass) {
    const componentName = upperCamelCaseToSnakeCase(componentClass.name);
    customElements.define(componentName, componentClass);
}

So all you need to do is:

registerComponent(YourComponent);

Which is nice because it's less error-prone than writing the component tag yourself. To wrap it up, this is the upperCamelCaseToSnakeCase() function:

// converts `YourString` into `your-string`
function upperCamelCaseToSnakeCase(value) {
    return value
        // first char to lower case
        .replace(/^([A-Z])/, $1 => $1.toLowerCase())
        // following upper chars get preceded with a dash
        .replace(/([A-Z])/g, $1 => "-" + $1.toLowerCase());
}
Lucio Paiva
  • 13,507
  • 6
  • 71
  • 90
  • Thanks. The example is client-side. As it was already mentioned, there are some problems with using function names in browsers. Almost every browser piece of code is expected to be minified but this will ruin the code that relies on function name. – Estus Flask Sep 15 '18 at 22:43
  • Yes, you're totally right. The minifier would have to be configured not to touch class names for this approach to work. – Lucio Paiva Sep 15 '18 at 23:08
1

For babel transpilation (before minification)

If you are using Babel with @babel/preset-env, it is possible to keep classes definitions without converting them to functions (which removes the constructor property)

You can drop some old browser compatibility with this configuration in your babel.config / babelrc:

{
  "presets": [
    ["@babel/preset-env", {"targets": {"browsers": ["> 2%"]}}]
  ]
}

More informations about targets : https://babeljs.io/docs/en/babel-preset-env#targets

For babel minification (after transpilation)

It looks there is no easy solution right now ... We need to look at mangling exclusions.

marc_s
  • 675,133
  • 158
  • 1,253
  • 1,388
Ifnot
  • 4,472
  • 3
  • 30
  • 47
  • Can you explain further how this is supposed to help with minification? `class Foo {}` will be minified to something like `class a{}` with any target. There will be no `Foo` word in minified source code. – Estus Flask May 26 '20 at 16:25
  • Honestly I did not dig more than the documentations and the fact it helped me using this configuration ... I am using ECSY into babel transpiled project and it require this param for getting valid class names : https://github.com/MozillaReality/ecsy/issues/119 – Ifnot May 27 '20 at 08:57
  • I see. This is very specific to the code you dealt with. E.g. names may be preserved for ES modules and ES6 because `export class Foo{}` cannot be efficiently uglified further but this may be different in other places, we cannot know how exactly without having a good idea what happens with specific code pieces at build time. Any way, this hasn't changed since 2015. It was always possible for some compilation configs and code. And I'd say this possibility is still too fragile to use class names for app logic. Class name you rely on may become garbage after one accidental change in source code – Estus Flask May 28 '20 at 06:51
  • 1
    Ok I found what is hapenning by looking at the code. My solution fixes the transpilation of classes to functions. So it helps before the minification. But not with the minification problem. I have to continue digging because, I could not understand how all my code using `constructor.name` still works in the minified version ... illogical :/ – Ifnot May 29 '20 at 08:53