30

Hey all, I am trying to test if the argument passed into my function is a class name so that I may compare it to other classes using instanceof.

For example:

function foo(class1, class2) {
  // Test to see if the parameter is a class.
  if(class1 is a class)
  {
    //do some kind of class comparison.
    if(class2 is a class)
    {
       if(class1 instanceof class2)
       {
          //...
       }
    }
    else
    {
       //...
    }
  }
  else
    //...
}

Is this possible? I am having trouble googleing an answer.

EndangeredMassa
  • 17,208
  • 8
  • 55
  • 79
kgrad
  • 4,672
  • 7
  • 36
  • 57

11 Answers11

27

There is really no such thing as a "class" in javascript -- everything but primitives are an object. Even functions are objects.

instanceof DOES work with functions though. Check out this link.

function Car(make, model, year)
{
  this.make = make;
  this.model = model;
  this.year = year;
}
var mycar = new Car("Honda", "Accord", 1998);
var a = mycar instanceof Car;    // returns true
var b = mycar instanceof Object; // returns true
thesmart
  • 2,993
  • 2
  • 31
  • 34
26

Now that we have native implementations of ES6, there are "real classes". These are largely syntactic sugar for prototypal inheritance as with constructor functions, but there are subtle differences and the two are not completely interchangeable.

So far, the only way I've found is to get the .toString() of the object's prototype's constructor function and check if it starts with class OR if the object has a constructor and the .toString() of that starts with class.

Note that if your code is compiled (ie: most Babel or TypeScript setups), then this will return function... instead of class... at runtime (since classes are transpiled to constructor functions).

function isClass(obj) {
  const isCtorClass = obj.constructor
      && obj.constructor.toString().substring(0, 5) === 'class'
  if(obj.prototype === undefined) {
    return isCtorClass
  }
  const isPrototypeCtorClass = obj.prototype.constructor 
    && obj.prototype.constructor.toString
    && obj.prototype.constructor.toString().substring(0, 5) === 'class'
  return isCtorClass || isPrototypeCtorClass
}

This will only work in native environments (Chrome, Firefox, Edge, node.js, etc.) that have implemented class for code that has not been transpiled to function.

Usage:

class A {}
class B extends A {}
isClass(A) // true
isClass(new A()) // true
isClass(B) // true
isClass(new B()) // true

function C() {}
isClass(C) // false
isClass(new C()) // false
isClass({}) // false
isClass(Date) // false
isClass(new Date()) // false

//These cases return 'true' but I'm not sure it's desired
isClass(Object.create(A)) // true    
const x = {}
Object.setPrototypeOf(x, A)
isClass(x) // true

If there is a better way, I'd love to know what it is.

aikeru
  • 3,773
  • 3
  • 33
  • 48
  • 5
    In my opinion, isClass(new A()) should return false. A is a class, but new A() is an instance of a class, not a class. – John Deighan Aug 09 '22 at 11:40
21

Here's a quick and dirty way to determine if you have a class or a function.

function myFunc() {};
class MyClass {};

Object.getOwnPropertyNames(myFunc);
// -> [ 'length', 'name', 'arguments', 'caller', 'prototype' ]

Object.getOwnPropertyNames(MyClass);
// -> [ 'length', 'prototype', 'name' ]

So we know we have a function and not a class if arguments is a property name:

Object.getOwnPropertyNames(myFunc).includes('arguments');
// -> true

Object.getOwnPropertyNames(MyClass).includes('arguments');
// -> false

Arrow functions and aysnc functions won't have an arguments property name or a prototype. A more complete example might look like this (assuming we know the input can only be a function or a class):

function isFunction(funcOrClass) {
  const propertyNames = Object.getOwnPropertyNames(funcOrClass);
  return (!propertyNames.includes('prototype') || propertyNames.includes('arguments'));
}

function isFunction(funcOrClass) {
  const propertyNames = Object.getOwnPropertyNames(funcOrClass);
  return (!propertyNames.includes('prototype') || propertyNames.includes('arguments'));
}

console.log('class isFunction?', isFunction(class A {}));
console.log('function isFunction?', isFunction(function() {}));
console.log('async function isFunction?', isFunction(async function() {}));
console.log('arrow function isFunction?', isFunction(() => {}));
snnsnn
  • 10,486
  • 4
  • 39
  • 44
Nate
  • 6,384
  • 3
  • 25
  • 30
  • This is the best answer so far. – Panu Logic May 27 '19 at 18:31
  • But is it certain that "arguments" is there as an own-property always? It would seem to me it is there only if the function is being called. Whereas if you pass the function as an argument and try to inspect the properties of such an argument it would not be there. – Panu Logic May 27 '19 at 18:42
  • @PanuLogic I'm not sure what you mean. This works for me: `function myFunc() {}; function checkProps(func) { console.log(Object.getOwnPropertyNames(func)); } checkProps(myFunc)` – Nate May 30 '19 at 16:45
  • 2
    In at least node.js environment: Object.getOwnPropertyNames(() => 123).includes('arguments'); // === false – Panu Logic Jun 06 '19 at 13:17
  • 2
    @PanuLogic You're using an arrow function which also doesn't have `prototype`, so if you know you're getting either a class or a function, you can check for `prototype` as well. I'll update my answer. Note that `Object.getOwnPropertyNames(function () { 123 }).includes('arguments');` works in Node, as expected. – Nate Jun 06 '19 at 17:53
  • Good to know. I still wonder a bit if it's part of the standard that 'arguments' is always accessible as own property for all functions except arrow-functions. For instance MDN says: " ... Arguments is an Array-like object accessible INSIDE functions that contains the values of the arguments passed to that function." It seems to me they are meant to be used inside a function call so whether they can always be accessed from outside of a function as well according to the standard(s) I don't know. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments – Panu Logic Jun 08 '19 at 16:41
  • Brilliant! I wasn't aware that a class function didn't come with all the standard function properties – Daniel San Sep 19 '20 at 00:41
  • 3
    Unfortunately it may not work if the function was declared on script where `"use strict"` was being set. – StefansArya Feb 09 '21 at 12:18
2

I know that using constructor.name on a function will return "Function" but doing so on an ES6 class instance will return the name of the class.

I do use it like this:

function isAClassInstance(item){
 return item.constructor.name !== "Function" && item.constructor.name !== "Object";
}

class A {};
function B(){};
const aInstanceClass = new A();
const bFunctionInstance = new B();
const bFunction = B;
const aClass = A;
console.log(aInstanceClass.constructor.name); /*Will return "A"*/
console.log(bFunctionInstance.constructor.name); /*Will return "B"*/
console.log(A.constructor.name); /*Will return "Function"*/
console.log(B.constructor.name); /*Will return "Function"*/
console.log(bFunction.constructor.name); /*Will return "Function"*/
console.log("is A a class instance: ", isAClassInstance(aClass)); /*false*/
console.log("is B a class instance: ", isAClassInstance(aClass)); /*false*/
console.log("is aInstanceClass a class: ", isAClassInstance(aInstanceClass)); /*true*/
console.log("is bFunction a class: ", isAClassInstance(bFunction)); /*false*/
console.log("is bFunctionInstance a class: ", isAClassInstance(bFunctionInstance)); /*true*/

For the cases i used this worked perfectly.

note: since a class is technically a function doing new A() and new B() are both the same thus returning the same result from isAClassInstance. The only way to dinstinguish a function from a class is to not use new on a function and keep this keyword for class only.

Hope this will help :).

  • 1
    `new A()` is not a class; `A` is the class and `new A()` is an instance of that class. Is there a way to have isClass distinguish A and B, like this? `isClass(A) === true` but `isClass(B) === false` ? – dpercy Feb 07 '21 at 21:26
  • I actually haven't thought about it, unfortunatly from my understanding it is not possible to distinguish a function from a class since they are both (under the hood) basically the same, class are only syntax sugar but in the end new A() is the same as doing new B(), i rewrote the test to make it more explicit – Accelerator Lastorder Feb 27 '21 at 19:02
  • Doesn't `(function() {}).constructor.name` throw an error in `'use strict'` mode? – Cody Mar 07 '22 at 03:26
1

JavaScript does not have classes. It has functions which, when used with new, can be used to produce object instances. Therefore, you really want to test if class2 is a function. There are numerous ways of accomplishing this; the current (1.3) implementation of isFunction() in jQuery looks like this:

isFunction: function( obj ) {
    return toString.call(obj) === "[object Function]";
},

...But see here for a rundown of the different methods: Best method of testing for a function in JavaScript?

Community
  • 1
  • 1
Shog9
  • 156,901
  • 35
  • 231
  • 235
1

My solution is by checking if the function prototype has property other than constructor and checking if the function prototype is Function.prototype.

This solution can also work even after transpiled with babel, or the script is running with "use strict". But this solution will only work if the class have at least one public function, or have extend other class.

function isClass(func){
  // Class constructor is also a function
  if(!(func && func.constructor === Function) || func.prototype === undefined)
    return false;
  
  // This is a class that extends other class
  if(Function.prototype !== Object.getPrototypeOf(func))
    return true;
  
  // Usually a function will only have 'constructor' in the prototype
  return Object.getOwnPropertyNames(func.prototype).length > 1;
}


// ----Example----

class Fruit{
  hello(){} // At least must have one public function
}

// Or at least it extend other class
class Pear extends Fruit{}

console.log(isClass(Fruit)); // true
console.log(isClass(Pear)); // true
console.log(isClass(function(){})); // false
console.log(isClass(function(){"use strict"})); // false
console.log(isClass(() => {})); // false
console.log(isClass({hello: 1})); // false
StefansArya
  • 2,802
  • 3
  • 24
  • 25
1

Try this one:

function isClass(C) { 
    return typeof C === "function" && C.prototype !== undefined;
}

Every class is internally just a function that creates instances of this class, plus a prototype property that's assigned to [[Prototype]] of newly created objects to make them behave like instances of this particular class. So, to check if C is a class, you need to check these two things - this is what the isClass() code above does.

Note that all functions declared with function keyword can also be used with new as constructors, and inside instanceof tests. They're equivalent to classes, and have a prototype likewise, so every function of this type is (correctly) recognized as a class by the isClass() function above.

console.log(isClass(Object));    // true
console.log(isClass(Number));    // true
console.log(isClass({a:1}));     // false
console.log(isClass(123));       // false
console.log(isClass("123"));     // false

class A {}
class B extends A {}

console.log(isClass(A));         // true
console.log(isClass(B));         // true
console.log(isClass(new B()));   // false

function f(name) { this.name = name; }

console.log(isClass(f));         // true
Marcin Wojnarski
  • 2,362
  • 24
  • 17
0

Hey just updating an old thread I took one of the above answers and made it work with all the predefined classes in javascript. eg. String, Map, extra

Original:

function isClass(obj) {
  const isCtorClass = obj.constructor
      && obj.constructor.toString().substring(0, 5) === 'class'
  if(obj.prototype === undefined) {
    return isCtorClass
  }
  const isPrototypeCtorClass = obj.prototype.constructor 
    && obj.prototype.constructor.toString
    && obj.prototype.constructor.toString().substring(0, 5) === 'class'
  return isCtorClass || isPrototypeCtorClass
}

New:

export const isClass = (obj) => {
    if (obj == null || typeof obj == "undefined") { return false; }
    const isCtorClass = obj.constructor
        && obj.constructor.toString().substring(0, 5) === 'class'
    const isNativeCtorClass= obj.constructor && 
                                  obj.constructor.name != "Function" && 
                                  obj.constructor.name in global;
    if (obj.prototype === undefined) {
        return isCtorClass || isNativeCtorClass
    }
    const isPrototypeCtorClass = obj.prototype.constructor
        && obj.prototype.constructor.toString
        && obj.prototype.constructor.toString().substring(0, 5) === 'class'

    const isNativePrototypeCtorClass = obj.prototype.constructor.name in global && (
            global[obj.prototype.constructor.name] == obj.constructor || 
            global[obj.prototype.constructor.name] == obj
        );
    return isCtorClass || isPrototypeCtorClass || isNativeCtorClass || isNativePrototypeCtorClass
}

This checks the object in the global javascript object to check if it's a currently registered object.

Let me know if this would break for some use cases but it works for me.

isClass(function test() {})    // false
isClass(() => {})              // false
isClass(function  String() {}) // false
isClass(String)                // true
isClass("")                    // true
isClass(0)                     // true
isClass(class Test {})         // true
isClass(new Number(0))         // true
isClass(new (class Test{})())  // true
mullin
  • 133
  • 1
  • 6
0

After reviewing the answers here I built out my own internal solution. I wanted to ensure that it only returns true if its a Class and not an instance of a class and I wanted distinguish between ES6 and classic / builtin (eg. Array) classes.

This solution should work for anything you throw at it. Please let me know if you find any exceptions.

// Will only return true for ES6 Class definitions
function isES6Class(o) {
    return isClass(o, {es6: true})
}

// Will return any class unless {es6:true} is passed (see isES6Class)
function isClass(o, opts) {
    if(!o)return false
    if(!o.prototype) return false
    if(__cls(o.constructor) || __cls(o.prototype.constructor)) return true
    if(opts && opts.es6) return false

    // Old school or builtin class
    // If is function that has name with first letter capitalized
    if((typeof o) === 'function'){ 
        var s = funcName(o)
        if (s.length > 0){
            var i = s.charCodeAt(0)
            if (i > 64 && i < 91){ return true }
        }
    }
    return false
    function __cls(v){
        return v.toString && v.toString().indexOf('class')===0
    }
}
// helper
var RE_FUNC_DEF = new RegExp("^\\s*function\\s*(\\S*)\\s*\\((.*)\\)");
function funcName(f){
    if (!f) return("(funcName() - f is Nothing!)")
    if(!_.isFunction(f))return("(funcName() - f is not a function!) - f = " + String(f))
    var s = f.toString(), m = s.match(RE_FUNC_DEF)
    if (m && m.length > 1) return m[1]
    return null
}


Timothy C. Quinn
  • 3,739
  • 1
  • 35
  • 47
0

Based on @Nate answer, which is isFunction I refactored function to return isClass as my reserch and this question is how to check if something is a class.

function isClass(funcOrClass) {
    const propertyNames = Object.getOwnPropertyNames(funcOrClass);
    return propertyNames.includes('prototype') && !propertyNames.includes('arguments');
}

function isFunction(funcOrClass) {
    return typeof funcOrClass === 'function' && !isClass(funcOrClass);
}
MrHIDEn
  • 1,723
  • 1
  • 25
  • 23
-3

I haven't found any 100% certain way to figure out if something is a class and not a function. The accepted answer above works in most cases but if someone redefines the method toString() of their class it may break in that case. Why would they ever redefine toString()? I don't know, ask them if they are still around :-)

So instead I took the approach of defining this function:

function isClass(v)
{ if (typeof v !== "function")
  { return false;
  }
  if ( typeof v.isClass === "function" &&
       v.isClass()
     )
  { return true;
  }
}

This means I can mark my own classes as being classes I recognize as classes by giving them the static method isClass() which returns true. If such classes have subclasses the subclasses inherit the method and are thus automatically recognized as "classes" as well.

If I need to work with someone else's classes which does not have this method I might first subclass their class so that my subclass is like their class except it also has the isClass() -method.

I hope the next version of EcmaScript will remedy this lack of standardized solution but for now this is the best I can do.

It has the additional benefit that I might in fact give the method isClass() to some of my non-class-functions as well, which are not classes proper, and then be able to treat such functions as if they were classes, for whatever reason I might want to.

Panu Logic
  • 2,193
  • 1
  • 17
  • 21