Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get the class name of ES6 class instance

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.

like image 400
Estus Flask Avatar asked Mar 27 '15 21:03

Estus Flask


5 Answers

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.)

like image 179
Domenic Avatar answered Oct 03 '22 00:10

Domenic


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)
like image 29
James L. Avatar answered Oct 03 '22 00:10

James L.


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());
}
like image 23
Lucio Paiva Avatar answered Oct 03 '22 00:10

Lucio Paiva


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.

like image 30
Ifnot Avatar answered Oct 03 '22 01:10

Ifnot


If you have control of the implementation of the class

I'm adding this here as it was the top of google when I was trying to figure out how to do it.

This seems to be working for me in node.

./SuperClass.js

'use strict';

/**
 * A super class
 *
 * To show that it works with inheritance too
 *
 * In my usage, I'm also extending the Error class
 */
module.exports = class SuperClass extends Error {
    constructor(message, options = {}) {
        super(message);
        this.name = this.constructor.name;
        // ...other shared construction functionality
    }
};

./SubClass.js

'use strict';

const SuperClass = require('./SuperClass');
/**
 * a sub class
 */
module.exports = class SubClass extends SuperClass {
    constructor(message, options = {}) {
        super(message, options);
        // ... specific sub class construction functionality
    }
};
SubClass.name; // -> 'SubClass'
let instance = new SubClass('message');
instance.name; // -> 'SubClass'

Obviously, again this only works with classes you have full implementation control over.

I would probably change it to _name or something else but this is to bring it in line with another library I'm using.

like image 44
DazBaldwin Avatar answered Oct 02 '22 23:10

DazBaldwin