42

In ECMAScript 6 the typeof of classes is, according to the specification, 'function'.

However also according to the specification you are not allowed to call the object created via the class syntax as a normal function call. In other words, you must use the new keyword otherwise a TypeError is thrown.

TypeError: Classes can’t be function-called

So without using try catch, which would be very ugly and destroy performance, how can you check to see if a function came from the class syntax or from the function syntax?

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
Moncader
  • 3,388
  • 3
  • 22
  • 28
  • 1
    I've added class detection to my javascript library [TypeChecker](https://github.com/bevry/typechecker). It has native es6 class checks, as well as conventional CamelCase function checks. – balupton Aug 26 '15 at 23:26
  • `try/catch` doesn't work either if the function throws an error itself. More importantly, If it's an old-school constructor, calling without `new` will definitely pollute the global, that's even worse than performance issue. – Leo Mar 12 '19 at 07:53

10 Answers10

44

I think the simplest way to check if the function is ES6 class is to check the result of .toString() method. According to the es2015 spec:

The string representation must have the syntax of a FunctionDeclaration FunctionExpression, GeneratorDeclaration, GeneratorExpression, ClassDeclaration, ClassExpression, ArrowFunction, MethodDefinition, or GeneratorMethod depending upon the actual characteristics of the object

So the check function looks pretty simple:

function isClass(func) {
  return typeof func === 'function' 
    && /^class\s/.test(Function.prototype.toString.call(func));
}
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
alexpods
  • 47,475
  • 10
  • 100
  • 94
  • @Moogs ofcourse it doesn't work in jsfiddle. It use some compiler like babel, witch compile ES6 class to function. – alexpods Mar 17 '15 at 08:23
  • @Moogs, are you sure that es6fiddle is not converting your javascript in es5 ? – Hacketo Mar 17 '15 at 08:23
  • @Moogs you can check that it works in `io.js` with `harmony` flag or in Chrome Canary – alexpods Mar 17 '15 at 08:24
  • @Moogs, you can see the converted es5 code in the sources, file is named _1.js for me – Hacketo Mar 17 '15 at 08:26
  • 1
    @alexpods, I guess you could just do `typeof func === "function" && /^class\s/.test(func+"")` – Hacketo Mar 17 '15 at 08:40
  • @Hacketo Yeah, thanks! Why I didn't think about it??? For functions `typeof` check is good. It's not so good for another types though. – alexpods Mar 17 '15 at 08:46
  • Yeah. This method should work with the newest spec: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-function.prototype.tostring Since it's supposed to show a representation of the ClassExpression itself just like Function and it must then start with 'class'. – Moncader Mar 17 '15 at 08:48
  • It seems it's a spec violation that it doesn't work in Edge, though, I reported it to their bug tracker: https://connect.microsoft.com/IE/feedback/details/2211653 – mgol Jan 06 '16 at 15:10
  • Since toString() return the entire class declaration, that regex may match if the class contains the word class anywhere in the body, e.g. in a string. Safer would be to look for class as the first word `class\s{1}` – HandyManDan Mar 07 '17 at 15:47
  • FYI `class\s` can fail for minified classes like `var foo=class{};` since there is no whitespace. – loganfsmyth Feb 09 '18 at 23:09
  • Fails for `class{}`, and `({class (){}}.class)` (A method called "class"). I think `/^class(\s+[^(]|{)/` would work. – Artyer Jan 19 '19 at 21:44
  • This doesn't work if someone modified `Function.prototype.toString`. So to ensure it's reliable, check whether `Function.prototype.toString.toString()` returns `"function toString() { [native code] }"` – Leo Mar 12 '19 at 07:49
  • Note that ES6 includes `String.prototype.startsWith()` so instead of a long regexp and `.call`, we can just use ```func.toString().startsWith(`class `))``` (with a space following `class`) – Mike 'Pomax' Kamermans Sep 10 '21 at 17:55
20

Since the existing answers address this problem from an ES5-environment perspective I thought it might be worth offering an answer from an ES2015+ perspective; the original question doesn’t specify and today many people no longer need to transpile classes, which alters the situation a bit.

In particular I wanted to note that it is possible to definitively answer the question "can this value be constructed?" Admittedly, that’s not usually useful on its own; the same fundamental problems continue to exist if you need to know if a value can be called.

Is something constructable?

To start I think we need to clarify some terminology because asking whether a value is a constructor can mean more than one thing:

  1. Literally, does this value have a [[construct]] slot? If it does, it is constructable. If it does not, it is not constructable.
  2. Was this function intended to be constructed? We can produce some negatives: functions that cannot be constructed were not intended to be constructed. But we can’t also say (without resorting to heuristic checks) whether a function which is constructable wasn’t meant to used as a constructor.

What makes 2 unanswerable is that functions created with the function keyword alone are both constructable and callable, but such functions are often intended for only one of these purposes. As some others have mentioned, 2 is also a fishy question — it is akin to asking "what was the author thinking when they wrote this?" I don’t think AI is there yet :) While in a perfect world perhaps all authors would reserve PascalCase for constructors (see balupton’s isConventionalClass function), in practice it would not be unusual to encounter false positives/negatives with this test.

Regarding the first version of this question, yes, we can know if a function is constructable. The obvious thing to do is to try constructing it. This isn’t really acceptable though because we don’t know if doing so would have side effects — it seems like a given that we don’t know anything about the nature of the function, since if we did, we wouldn’t need this check). Fortunately there is a way to construct a constructor without really constructing it:

const isConstructable = fn => {
  try {
    new new Proxy(fn, { construct: () => ({}) });
    return true;
  } catch (err) {
    return false;
  }
};

The construct Proxy handler can override a proxied value’s [[construct]], but it cannot make a non constructable value constructable. So we can "mock instantiate" the input to test whether this fails. Note that the construct trap must return an object.

isConstructable(class {});                      // true
isConstructable(class {}.bind());               // true
isConstructable(function() {});                 // true
isConstructable(function() {}.bind());          // true
isConstructable(() => {});                      // false
isConstructable((() => {}).bind());             // false
isConstructable(async () => {});                // false
isConstructable(async function() {});           // false
isConstructable(function * () {});              // false
isConstructable({ foo() {} }.foo);              // false
isConstructable(URL);                           // true

Notice that arrow functions, async functions, generators and methods are not double-duty in the way "legacy" function declarations and expressions are. These functions are not given a [[construct]] slot (I think not many realize that "shorthand method" syntax is does something — it’s not just sugar).

So to recap, if your question is really "is this constructable," the above is conclusive. Unfortunately nothing else will be.

Is something callable?

We’ll have to clarify the question again, because if we’re being very literal, the following test actually works*:

const isCallable = fn => typeof fn === 'function';

This is because ES does not currently let you create a function without a [[call]] slot (well, bound functions don’t directly have one, but they proxy down to a function that does).

This may seem untrue because constructors created with class syntax throw if you try to call them instead of constructing them. However they are callable — it’s just that their [[call]] slot is defined as a function that throws! Oy.

We can prove this by converting our first function to its mirror image.

// Demonstration only, this function is useless:

const isCallable = fn => {
  try {
    new Proxy(fn, { apply: () => undefined })();
    return true;
  } catch (err) {
    return false;
  }
};

isCallable(() => {});                      // true
isCallable(function() {});                 // true
isCallable(class {});                      // ... true!

Such a function is not helpful, but I wanted to show these results to bring the nature of the problem into focus. The reason we can’t easily check whether a function is "new-only" is that the answer is not modeled in terms of "absence of call" the way "never-new" is modeled in terms of "absence of construct". What we’re interested in knowing is buried in a method we cannot observe except through its evaluation, so all that we can do is use heuristic checks as a proxy for what we really want to know.

Heuristic options

We can begin by narrowing down the cases which are ambiguous. Any function which is not constructable is unambiguously callable in both senses: if typeof fn === 'function' but isConstructable(fn) === false, we have a call-only function such as an arrow, generator, or method.

So the four cases of interest are class {} and function() {} plus the bound forms of both. Everything else we can say is only callable. Note that none of the current answers mention bound functions, but these introduce significant problems to any heuristic check.

As balupton points out, the presence or absence of a property descriptor for the 'caller' property can act as an indicator of how a function was created. A bound function exotic object will not have this own-property even if the function it wraps does. The property will exist via inheritence from Function.prototype, but this is true also for class constructors.

Likewise, toString for a BFEO will normally begin 'function' even if the bound function was created with class. Now, a heuristic for detecting BFEOs themselves would be to see if their name begins 'bound ', but unfortunately this is a dead end; it still tells us nothing about what was bound — this is opaque to us.

However if toString does return 'class' (which won’t be true for e.g. DOM constructors), that’s a pretty solid signal that it’s not callable.

The best we can do then is something like this:

const isDefinitelyCallable = fn =>
  typeof fn === 'function' &&
  !isConstructable(fn);

isDefinitelyCallable(class {});                      // false
isDefinitelyCallable(class {}.bind());               // false
isDefinitelyCallable(function() {});                 // false <-- callable
isDefinitelyCallable(function() {}.bind());          // false <-- callable
isDefinitelyCallable(() => {});                      // true
isDefinitelyCallable((() => {}).bind());             // true
isDefinitelyCallable(async () => {});                // true
isDefinitelyCallable(async function() {});           // true
isDefinitelyCallable(function * () {});              // true
isDefinitelyCallable({ foo() {} }.foo);              // true
isDefinitelyCallable(URL);                           // false

const isProbablyNotCallable = fn =>
  typeof fn !== 'function' ||
  fn.toString().startsWith('class') ||
  Boolean(
    fn.prototype &&
    !Object.getOwnPropertyDescriptor(fn, 'prototype').writable // or your fave
  );

isProbablyNotCallable(class {});                      // true
isProbablyNotCallable(class {}.bind());               // false <-- not callable
isProbablyNotCallable(function() {});                 // false
isProbablyNotCallable(function() {}.bind());          // false
isProbablyNotCallable(() => {});                      // false
isProbablyNotCallable((() => {}).bind());             // false
isProbablyNotCallable(async () => {});                // false
isProbablyNotCallable(async function() {});           // false
isProbablyNotCallable(function * () {});              // false
isProbablyNotCallable({ foo() {} }.foo);              // false
isProbablyNotCallable(URL);                           // true

The cases with arrows point out where we get answers we don’t particularly like.

In the isProbablyNotCallable function, the last part of the condition could be replaced with other checks from other answers; I chose Miguel Mota’s here, since it happens to work with (most?) DOM constructors as well, even those defined before ES classes were introduced. But it doesn’t really matter — each possible check has a downside and there is no magic combo.


The above describes to the best of my knowledge what is and isn’t possible in contemporary ES. It doesn’t address needs that are specific to ES5 and earlier, though really in ES5 and earlier the answer to both of the questions is always "true" for any function.

The future

There is a proposal for a native test that would make the [[FunctionKind]] slot observable insofar as revealing whether a function was created with class:

https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md

If this proposal or something like it advances, we would gain a way to solve this problem concretely when it comes to class at least.

* Ignoring the Annex B [[IsHTMLDDA]] case.

Semicolon
  • 6,793
  • 2
  • 30
  • 38
  • 1
    +1 for clever use of proxies :-) – Bergi Apr 29 '18 at 21:05
  • Fun thing that may surprise you: `isConstructable({a(){}}.a)` is false: object shorthand is *not* purely syntax sugar, because methods created with it are not constructors. – Chris Morgan May 08 '19 at 11:47
  • @ChrisMorgan yes, that’s mentioned in this answer: ‘These functions are not given a [[construct]] slot (I think not many realize that "shorthand method" syntax is does something — it’s not just sugar).’ – Semicolon May 08 '19 at 21:24
  • Oops, missed that, sorry! – Chris Morgan May 13 '19 at 00:59
16

I did some research and found out that the prototype object [spec 19.1.2.16] of ES6 classes seem to be non-writeable, non-enumerable, non-configurable.

Here's a way to check:

class F { }

console.log(Object.getOwnPropertyDescriptor(F, 'prototype'));
// {"value":{},"writable":false,"enumerable":false,"configurable":false

A regular function by default is writeable, non-enumerable, non-configurable.

function G() { }

console.log(Object.getOwnPropertyDescriptor(G, 'prototype'));
// {"value":{},"writable":true,"enumerable":false,"configurable":false}

ES6 Fiddle: http://www.es6fiddle.net/i7d0eyih/

So an ES6 class descriptor will always have those properties set to false and will throw an error if you try to define the descriptors.

// Throws Error
Object.defineProperty(F, 'prototype', {
  writable: true
});

However with a regular function you can still define those descriptors.

// Works
Object.defineProperty(G, 'prototype', {
  writable: false
});

It's not very common that descriptors are modified on regular functions so you can probably use that to check if it's a class or not, but of course this is not a real solution.

@alexpods' method of stringifying the function and checking for the class keyword is probably the the best solution at the moment.

Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108
Miguel Mota
  • 20,135
  • 5
  • 45
  • 64
  • 1
    Interesting solution, but this won't work in all the cases, so this is not a real solution. – Marco Bonelli Mar 17 '15 at 08:09
  • 1
    Yes I mentioned that, but it's a step closer. – Miguel Mota Mar 17 '15 at 08:10
  • 1
    you are trying to solve a language feature here guys... there are no classes in JS even if they introduced keyword class. – webduvet Mar 17 '15 at 08:19
  • 3
    @webduvet I disagree. While the classes introduced in ES6 are certainly not on the level of other languages, and they do in fact just reuse already in-existent features of ECMAScript, the semantics of how things created via the class syntax are different than other methods. Therefore it is a distinct feature that cant be called 'JS Classes'. – Moncader Mar 17 '15 at 08:22
  • @Moogs I think your citation is wrong. `F.prototype` is set when the class body is evaluated, which is described here: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation; the property descriptor for `Object.prototype` is something completely different (though it sometimes effects the algorithm linked above). That algorithm is too complicated for me to quickly see whether you're right about `F.prototype` always being non-enumerable, non-writeable, etc. But if someone wants to look into it more, that's where they should check. – Ethan Oct 19 '15 at 05:53
  • @Moogs I've found a case with Babel 6 where the descriptor of `prototype` has `writable: true`. Hence I edited your answer and removed the **always** part of it in your first paragraph. This check **does not work for me**. – Pierre Arnaud Dec 02 '15 at 04:53
  • @PierreArnaud But the check for the `toString` representation starting with `'class'` also does not work for transpiled code. – Stijn de Witt Apr 09 '17 at 14:39
13

Ran some performance benchmarks on the different approaches mentioned in this thread, here is an overview:


Native Class - Props Method (fastest by 56x on large examples, and 15x on trivial examples):

function isNativeClass (thing) {
    return typeof thing === 'function' && thing.hasOwnProperty('prototype') && !thing.hasOwnProperty('arguments')
}

Which works because the following is true:

> Object.getOwnPropertyNames(class A {})
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} a (b,c) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(function () {})
[ 'length', 'name', 'arguments', 'caller', 'prototype' ]
> Object.getOwnPropertyNames(() => {})
> [ 'length', 'name' ]

Native Class - String Method (faster than regex method by about 10%):

/**
 * Is ES6+ class
 * @param {any} value
 * @returns {boolean}
 */
function isNativeClass (value /* :mixed */ ) /* :boolean */ {
    return typeof value === 'function' && value.toString().indexOf('class') === 0
}

This may also be of use for determining a Conventional Class:

// Character positions
const INDEX_OF_FUNCTION_NAME = 9  // "function X", X is at index 9
const FIRST_UPPERCASE_INDEX_IN_ASCII = 65  // A is at index 65 in ASCII
const LAST_UPPERCASE_INDEX_IN_ASCII = 90   // Z is at index 90 in ASCII

/**
 * Is Conventional Class
 * Looks for function with capital first letter MyClass
 * First letter is the 9th character
 * If changed, isClass must also be updated
 * @param {any} value
 * @returns {boolean}
 */
function isConventionalClass (value /* :any */ ) /* :boolean */ {
    if ( typeof value !== 'function' )  return false
    const c = value.toString().charCodeAt(INDEX_OF_FUNCTION_NAME)
    return c >= FIRST_UPPERCASE_INDEX_IN_ASCII && c <= LAST_UPPERCASE_INDEX_IN_ASCII
}

I'd also recommend checking out my typechecker package which includes the use cases for the above - via the isNativeClass method, isConventionalClass method, and a isClass method that checks for both types.

balupton
  • 47,113
  • 32
  • 131
  • 182
  • You are right. And you cannot add `caller` or `arguments` properties on the class after the fact, since both are _restricted function properties_ and trying to add them throws a `TypeError`. – Pierre Arnaud Dec 02 '15 at 04:58
  • With my setup and Babel 6, functions and classes both return `['length', 'name', 'prototype']`. So this **does not work for me**. – Pierre Arnaud Dec 02 '15 at 05:05
  • This will return false positives for arrow functions, which also don't have an `arguments` or `caller` property. I.e. `(() => {}).hasOwnProperty('arguments')` returns true. – Dave W. Mar 21 '17 at 03:59
  • 2
    @PierreArnaud that is to be expected, as it is no longer a native class when compiled - so a native class check is not what you want - instead you could use `isClass` from the [`typechecker` package](https://github.com/bevry/typechecker) which checks for both native and conventional classes. – balupton Mar 22 '17 at 01:32
3

Looking at the compiled code generated by Babel, I think there is no way you can tell if a function is used as a class. Back in the time, JavaScript didn't have classes, and every constructor was just a function. Today's JavaScript class keyword don't introduce a new concept of 'classes', it's rather a syntax sugar.

ES6 code:

// ES6
class A{}

ES5 generated by Babel:

// ES5
"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var A = function A() {
    _classCallCheck(this, A);
};

Of course, if you are into coding conventions, you could parse the function (the class), and check if it's name starts with a capital letter.

function isClass(fn) {
    return typeof fn === 'function' && /^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(fn);
}

EDIT:

Browsers which already support the class keyword can use it when parsing. Otherwise, you are stuck with the capital letter one.

EDIT:

As balupton pointed out, Babel generates function _class() {} for anonymous classes. Improved regex based on that.

EDIT:

Added _default to the regex, to detect classes like export default class {}

Warning

BabelJS is heavily under development, and there is no guarantee they will not change the default function names in these cases. Really, you shouldn't rely on that.

Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
  • Does this still work with anonymous classes compiled to Babel? So anonymous functions get compiled to `function _class () {}` - source code `function a ( ) { return class {} && class {} }`, the regex could be modified accordingly. – balupton Aug 26 '15 at 22:02
  • 1
    Updated regex for babel anonymous classes: `return typeof fn === 'function' && /^(?:class|function (?:[A-Z]|_class))/.test(fn)` – balupton Aug 26 '15 at 22:08
  • Hrmm, anonymous classes are different with `export default class {}`, perhaps just checking for the `_classCallCheck` is a better option. – balupton Aug 26 '15 at 22:14
  • I think checking for `_classCallCheck` in the entire function is overkill, and has marginal effect. Checking for the capital starting of names is the weak link here – Tamas Hegedus Aug 26 '15 at 22:19
2

You can use new.target to determine whether whether its instantiated by ES6 class function or function constructor

class Person1 {
  constructor(name) {
    this.name = name;
    console.log(new.target) // => // => [Class: Person1]
  }
}

function Person2(){
  this.name='cc'
  console.log(new.target) // => [Function: Person2]
}
vinay0079
  • 21
  • 2
0

Although it's not directly related, but if the class, constructor or function is generated by you and you want to know whether you should call the function or instantiate an object using new keyword, you can do that by adding a custom flag in the prototype of the constructor or class. You can certainly tell a class from a function using methods mentioned in other answers (such as toString). However, if your code is transpiled using babel, it would certainly be a problem.

To make it simpler, you can try the following code -

class Foo{
  constructor(){
    this.someProp = 'Value';
  }
}
Foo.prototype.isClass = true;

or if using constructor function -

function Foo(){
  this.someProp = 'Value';
}
Foo.prototype.isClass = true;

and you can check whether it's class or not by checking on the prototype property.

if(Foo.prototype.isClass){
  //It's a class
}

This method obviously won't work if the class or function is not created by you. React.js uses this method to check whether the React Component is a Class Component or a Function Component. This answer is taken from Dan Abramov's blog post

0xC0DED00D
  • 19,522
  • 20
  • 117
  • 184
0

I know that this is a very old question, but I have been wondering the same thing recently, specifically because React throws an error if you try to use a function as a class component (such as if you set up babel to transform classes into functions, which I had). I sat for a while, and was able to come up with this:

function isClass(f) {
  return typeof f === "function" && (() => { try { f(); return false } catch { return true } })();
}

isClass(function() {}); //false
isClass(() => {}); //false
isClass(class {}); //true

This is based on the fact that ES6 classes require the usage of the new keyword, and throws an error if it is missing. The function first checks to see if the type of the passed argument is a function, which applies to both classes and functions. It then attempts to call it without the new keyword, something that can only be done by a normal function. Keep in mind, however, that this can be fooled if the function throws an error if it checks to see if it is being called with new through a method like if (!(this instanceof ConstructorName)) throw:

isClass(function Constructor() {
  if (!(this instanceof Constructor)) throw new Error();
}); // true

function isClass(f) {
  return typeof f === "function" && (() => { try { f(); return false } catch { return true } })();
}

console.log(isClass(function() {})); //false
console.log(isClass(() => {})); //false
console.log(isClass(class {})); //true

console.log(isClass(function Constructor() {
  if (!(this instanceof Constructor)) throw new Error();
})); // true
ErrorGamer2000
  • 295
  • 3
  • 8
0
function isClass(obj) {
  return 'class' === obj.toString().split(' ')[0];
}
-1

if I understood ES6 correctly using class has the same effect as if you were typing

var Foo = function(){}
var Bar = function(){
 Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;

syntax error when typing MyClass() without keyword new is just to prevent polluting global space with variables intended to be used by object.

var MyClass = function(){this.$ = "my private dollar"; return this;}

if you have

// $ === jquery
var myObject = new MyClass();
// $ === still jquery
// myObject === global object

but if you do

var myObject = MyClass();
// $ === "My private dollar"

because this in Constructor called as function refers to global object, but when called with keyword new Javascript first creates new empty object and then calls the constructor on it.

webduvet
  • 4,220
  • 2
  • 28
  • 39
  • 2
    Yes, one of the reasons is probably this. However, how can you check if it's a class or function like the question asks? – Moncader Mar 17 '15 at 07:48
  • As I said using class does the above sequence of steps. so there is no such a thing as typeof 'class'. it is type of function because it is function. Checking on runtime whether to use 'new' or nor is really really really bad practice. So bad that ES6 creators decided to throw syntax error in that case. – webduvet Mar 17 '15 at 07:58
  • The ES6 creators decided to throw a syntax error if you attempt to call the function, not check it. Say you are building a library that will receive function objects and decide to execute them or store them in some class database (just a random idea). You have no way to know what kind of object you have received right now it seems. So without executing, how can you check if it can be executed or not? – Moncader Mar 17 '15 at 08:02
  • any function can be called as constructor, that is just how javascript works. so if you are building a class library any function is just as good in it as ES6 'class' . – webduvet Mar 17 '15 at 08:13