4

I need to implement two functions in JavaScript: isConstructor(x) and constructorName(x)

isConstructor(x) should return true in case the argument x is a constructor function, and false, otherwise. For example,

isConstructor(Date)      ===  true
isConstructor(Math.cos)  ===  false

constructorName(x) should return the constructor name for each proper constructor function x. So e.g.

constructorName(Date)  ===  'Date'

I can only think of an ugly implementation for both functions, where I create a command string "var y = new x()" which is then called with an eval(command) in a try/catch statement. If that eval call succeeds at all, I call x a constructor. And I retrieve the name of x indirectly by asking for the class name of the prototype of y, something like

var constrName = Object.prototype.toString.call(y).slice(8,-1); // extracts the `ConstrName` from `[object ConstrName]`
return constrName;

But all this is very ugly, of course. How can I do this properly?

7
  • 7
    Technically, any (user defined) function could be used as a constructor. Commented Jun 5, 2014 at 11:48
  • Functions are objects are functions are potentially constructors are functions are objects (ad infinitum) in Javascript. There's no real distinction. Commented Jun 5, 2014 at 11:53
  • "objects are functions" nope. The definition of a function is that it implements an internal [[Call]] method, so can be called. Objects created by calls to constructors are Objects (which don't have [[Call]]), not Functions. Commented Jun 5, 2014 at 11:58
  • @mpm Hence the addition of "(user defined)" :) Commented Jun 5, 2014 at 11:59
  • @mpm—*Math* is a native ECMA object, not a DOM object. Any user defined function is a native function, so is a constructor. Commented Jun 5, 2014 at 12:03

5 Answers 5

2

Like many said in JavaScript almost any function object is a constructor (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function). However if you want to distinguish between the native functions that can't be constructors you can try:

function isConstructor(x) {
    return typeof x === 'function' && typeof x.prototype !== 'undefined';
}

Getting the name is definitely trickier, you can use the Function.prototype.toString.apply(x) and match the name, but for example with the jQuery object that would be an empty string, i.e. if it's anonymous function.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you Tsanyo! Cheers, Tom
1

There's only an approximation to this supposed problem; whether a function can be invoked using new can't be reliably done without actually trying to perform such a call and check for a TypeError:

function isConstructor(fn)
{
    try {
        var r = new fn(); // attempt instantiation
    } catch (e) {
        if (e instanceof TypeError) {
            return false; // probably shouldn't be called as constructor
        }
        throw e; // something else went wrong
    }
    return true; // some constructors may not return an instance of itself
}

The problems with this:

  1. Any function may choose to throw a TypeError for reasons other than the fact that it shouldn't be called as a constructor;
  2. The constructor may require certain arguments to be passed;
  3. The return value of a constructor doesn't have to meet the requirement that instanceof obj === func;
  4. The call itself causes a side effect.

Comments

1

The definition of a constructor is an Object that implements an internal [[Construct]] method. The only way to test for that is to call it as a constructor and see what happens. You can't test for internal properties other than the [[Class]] property using Object.prototype.toString.

Any user defined ECMAScript function is a constructor by default, e.g.

function foo(){}

can be called as a constructor. A function is an object that implements an internal [[Call]] method and therefore typeof will return "function". But not all functions are constructors, e.g.

typeof document.getElementById  // function

but

new document.getElementById()

will throw a type error because it's not a constructor. There are also constructors that aren't functions:

typeof XMLHttpRequest  // object

XMLHttpRequest is a constructor, but it doesn't have [[Call]] so can't be called as a function:

var x = XMLHttpRequest() // TypeError: XMLHttpRequest isn't a function

Lastly, you could try testing for a prototype property that is an object or function, but that also isn't reliable since a prototype property could be added to pretty much any object, host or native.

So the bottom line is that try..catch is probably the only way.

1 Comment

Thank you very much RobG! This is very helpful! Cheers, Tom
0

If you don't mind creating a test instance of the function you could do the following:

function isConstructor(func) {
    var t = new func();
    return (t instanceof func);
}

BUT: this only workes if the constructor accepts no arguments. It might as well throw an exception. If it does you could try other (not so reliable) things, like checking if the name of the function starts with an uppercase Letter.

4 Comments

It also only works if func is a constructor, otherwise it will throw a type error.
Yes, you are probably right. This is probably the best I can get. Thank you all! Tom
@RobG not generally. Technically, you can call every function using the new keyword. However, there are some native functions that check internally how you call them and throw an exception in case you call it with new but afaik every function has an internal [[Construct]] function, so it's not an issue of ECMA script but rather of the implementation.
@basilikum—see my answer, there are functions that can't be called as constructors and even constructors that can't be called as functions (where "can't" means if attempted, a type error is thrown).
0

Thank you all for your very clear and helpful answers! I think I do understand the facts now, but I also got some helpful hints that pointed me to ECMAScript details I didn't know, yet. I need to dive into the literature a little more. Thank you, again! Tom

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.