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

29,945

Solution 1

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?

Solution 2

If you want to ensure that the value is not only a function, but really a constructor function for a class, you can convert the function to a string and inspect its representation. The spec dictates the string representation of a class constructor.

function isClass(v) {
  return typeof v === 'function' && /^\s*class\s+/.test(v.toString());
}

Another solution would be to try to call the value as a normal function. Class constructors are not callable as normal functions, but error messages probably vary between browsers:

function isClass(v) {
  if (typeof v !== 'function') {
    return false;
  }
  try {
    v();
    return false;
  } catch(error) {
    if (/^Class constructor/.test(error.message)) {
      return true;
    }
    return false;
  }
}

The disadvantage is that invoking the function can have all kinds of unknown side effects...

Solution 3

Checking for both prototype and arguments should allow to determine the type of function without stringifying, calling or instantiating the input.

    function isFunction(x) {
        return typeof x !== 'function'
           ? ''
           : x.hasOwnProperty('arguments')
              ? 'function'
              : x.prototype
                 ? 'class'
                 : x.constructor.name === 'AsyncFunction'
                    ? 'async'
                    : 'arrow';
    }

... and so it does (in node and client)

console.assert([
   isFunction(null),
   isFunction(class C{}),
   isFunction(function f(){}),
   isFunction(()=>{})
   isFunction(async function(){})
].join(',') === ',class,function,arrow,async');
Share:
29,945
XåpplI'-I0llwlg'I  -
Author by

XåpplI'-I0llwlg'I -

im bill grates of michaelsoft

Updated on October 13, 2021

Comments

  • XåpplI'-I0llwlg'I  -
    XåpplI'-I0llwlg'I - over 2 years

    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?