javascriptclassprototypeecmascript-6

How to check if a variable is an ES6 class declaration?


I am exporting the following ES6 class from one module:

export class Thingy {
  hello() {
    console.log("A");
  }

  world() {
    console.log("B");
  }
}

And importing it from another module:

import {Thingy} from "thingy";

if (isClass(Thingy)) {
  // Do something...
}

How can I check whether a variable is a class? Not a class instance, but a class declaration?

In other words, how would I implement the isClass function in the example above?


Solution

  • I'll make it clear up front here, any arbitrary function can be a constructor. If you are distinguishing between "class" and "function", you are making poor API design choices. If you assume something must be a class for instance, no-one using Babel or Typescript will be be detected as a class because their code will have been converted to a function instead. It means you are mandating that anyone using your codebase must be running in an ES6 environment in general, so your code will be unusable on older environments.

    Your options here are limited to implementation-defined behavior. In ES6, once code is parsed and the syntax is processed, there isn't much class-specific behavior left. All you have is a constructor function. Your best choice is to do

    if (typeof Thingy === 'function'){
      // It's a function, so it definitely can't be an instance.
    } else {
      // It could be anything other than a constructor
    }
    

    and if someone needs to do a non-constructor function, expose a separate API for that.

    Obviously that is not the answer you are looking for, but it's important to make that clear.

    As the other answer here mentions, you do have an option because .toString() on functions is required to return a class declaration, e.g.

    class Foo {}
    Foo.toString() === "class Foo {}" // true
    

    The key thing, however, is that that only applies if it can. It is 100% spec compliant for an implementation to have

    class Foo{}
    Foo.toString() === "throw SyntaxError();"
    

    No browsers currently do that, but there are several embedded systems that focus on JS programming for instance, and to preserve memory for your program itself, they discard the source code once it has been parsed, meaning they will have no source code to return from .toString() and that is allowed.

    Similarly, by using .toString() you are making assumptions about both future-proofing, and general API design. Say you do

    const isClass = fn => /^\s*class/.test(fn.toString());
    

    because this relies on string representations, it could easily break.

    Take decorators for example:

    @decorator class Foo {}
    Foo.toString() == ???
    

    Does the .toString() of this include the decorator? What if the decorator itself returns a function instead of a class?