Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 958
XåpplI'-I0llwlg'I - Avatar asked Jun 10 '15 14:06

XåpplI'-I0llwlg'I -


People also ask

How do you check if an object is a class JS?

The JavaScript instanceof operator is used to check the type of an object at the run time. It returns a boolean value(true or false). If the returned value is true, then it indicates that the object is an instance of a particular class and if the returned value is false then it is not.

What is an ES6 class?

ES6 Classes formalize the common JavaScript pattern of simulating class-like inheritance hierarchies using functions and prototypes. They are effectively simple sugaring over prototype-based OO, offering a convenient declarative form for class patterns which encourage interoperability.

Can you declare a variable in class JavaScript?

To declare a variable within a class, it needs to be a property of the class or, as you did so, scoped within a method in the class. It's all about scoping and variables are not supported in the scope definition of a class.

Is VAR used in ES6?

var and block scopeWe cannot use the var keyword in this scenario. ES6 introduces the let keyword to overcome this limitation.


3 Answers

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

like image 150
Felix Kling Avatar answered Sep 30 '22 20:09

Felix Kling


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?

like image 22
loganfsmyth Avatar answered Sep 30 '22 21:09

loganfsmyth


Checking the prototype and its writability should allow to determine the type of function without stringifying, calling or instantiating the input.

/**
 * determine if a variable is a class definition or function (and what kind)
 * @revised
 */
function isFunction(x) {
    return typeof x === 'function'
        ? x.prototype
            ? Object.getOwnPropertyDescriptor(x, 'prototype').writable
                ? 'function'
                : 'class'
        : x.constructor.name === 'AsyncFunction'
        ? 'async'
        : 'arrow'
    : '';
}

console.log({
  string: isFunction('foo'), // => ''
  null: isFunction(null), // => ''
  class: isFunction(class C {}), // => 'class'
  function: isFunction(function f() {}), // => 'function'
  arrow: isFunction(() => {}), // => 'arrow'
  async: isFunction(async function () {}) // => 'async'
});
like image 5
Ian Carter Avatar answered Sep 30 '22 21:09

Ian Carter