How to check if a Javascript function is a constructor

前端 未结 7 1662
名媛妹妹
名媛妹妹 2020-12-05 00:08

I noticed not all the Javascript functions are constructors.

var obj = Function.prototype;
console.log(typeof obj === \'function\'); //true
obj(); //OK
new          


        
相关标签:
7条回答
  • 2020-12-05 00:09

    There is a quick and easy way of determining if function can be instantiated, without having to resort to try-catch statements (which can not be optimized by v8)

    function isConstructor(obj) {
      return !!obj.prototype && !!obj.prototype.constructor.name;
    }
    
    1. First we check if object is part of a prototype chain.
    2. Then we exclude anonymous functions

    There is a caveat, which is: functions named inside a definition will still incur a name property and thus pass this check, so caution is required when relying on tests for function constructors.

    In the following example, the function is not anonymous but in fact is called 'myFunc'. It's prototype can be extended as any JS class.

    let myFunc = function () {};
    

    :)

    0 讨论(0)
  • 2020-12-05 00:11

    As an addition to Felix Kling's answer, even if a function is not constructable, we can still use it like a constructor if it has a prototype property. We can do this with the help of Object.create(). Example:

    // The built-in object Symbol is not constructable, even though it has a "prototype" property:
    new Symbol
    // TypeError: Symbol is not a constructor.
    Object.create(Symbol.prototype);
    // Symbol {}
    //   description: (...)
    //   __proto__: Symbol
    
    0 讨论(0)
  • 2020-12-05 00:24

    For question 1, what about this helper?

    Function.isConstructor = ({ prototype }) => Boolean(prototype) && Boolean(prototype.constructor)
    
    Function.isConstructor(class {}); // true
    Function.isConstructor(function() {}); // true
    Function.isConstructor(() => {}); // false
    Function.isConstructor("a string"); // false
    

    For question 2, the arrow function is the solution. It cannot be used as a constructor since it does not rely on the same scope as a regular function and does not have a prototype (definition of instances, similar to class definition for real OOP)

    const constructable = function() { console.log(this); };
    const callable = () => { console.log(this); };
    
    constructable(); // Window {}
    callable(); // Window {}
    new constructable(); // aConstructableFunction {}
    new callable(); // Uncaught TypeError: callable is not a constructor
    
    0 讨论(0)
  • 2020-12-05 00:28

    A little bit of background:

    ECMAScript 6+ distinguishes between callable (can be called without new) and constructable (can be called with new) functions:

    • Functions created via the arrow functions syntax or via a method definition in classes or object literals are not constructable.
    • Functions created via the class syntax are not callable.
    • Functions created in any other way (function expression/declaration, Function constructor) are callable and constructable.
    • Built-in functions are not constructrable unless explicitly stated otherwise.

    About Function.prototype

    Function.prototype is a so called built-in function that is not constructable. From the spec:

    Built-in function objects that are not identified as constructors do not implement the [[Construct]] internal method unless otherwise specified in the description of a particular function.

    The value of Function.prototype is create at the very beginning of the runtime initialization. It is basically an empty function and it is not explicitly stated that it is constructable.


    How do I check if an function is a constructor so that it can be called with a new?

    There isn't a built-in way to do that. You can try to call the function with new, and either inspect the error or return true:

    function isConstructor(f) {
      try {
        new f();
      } catch (err) {
        // verify err is the expected error and then
        return false;
      }
      return true;
    }
    

    However, that approach is not failsafe since functions can have side effects, so after calling f, you don't know which state the environment is in.

    Also, this will only tell you whether a function can be called as a constructor, not if it is intended to be called as constructor. For that you have to look at the documentation or the implementation of the function.

    Note: There should never be a reason to use a test like this one in a production environment. Whether or not a function is supposed to be called with new should be discernable from its documentation.

    When I create a function, how do I make it NOT a constructor?

    To create a function is truly not constructable, you can use an arrow function:

    var f = () => console.log('no constructable');
    

    Arrow functions are by definition not constructable. Alternatively you could define a function as a method of an object or a class.

    Otherwise you could check whether a function is called with new (or something similar) by checking it's this value and throw an error if it is:

    function foo() {
      if (this instanceof foo) {
        throw new Error("Don't call 'foo' with new");
      }
    }
    

    Of course, since there are other ways to set the value of this, there can be false positives.


    Examples

    function isConstructor(f) {
      try {
        new f();
      } catch (err) {
        if (err.message.indexOf('is not a constructor') >= 0) {
          return false;
        }
      }
      return true;
    }
    
    function test(f, name) {
      console.log(`${name} is constructable: ${isConstructor(f)}`);
    }
    
    function foo(){}
    test(foo, 'function declaration');
    test(function(){}, 'function expression');
    test(()=>{}, 'arrow function');
    
    class Foo {}
    test(Foo, 'class declaration');
    test(class {}, 'class expression');
    
    test({foo(){}}.foo, 'object method');
    
    class Foo2 {
      static bar() {}
      bar() {}
    }
    test(Foo2.bar, 'static class method');
    test(new Foo2().bar, 'class method');
    
    test(new Function(), 'new Function()');

    0 讨论(0)
  • 2020-12-05 00:29

    If the function is a constructor then it will have a "prototype" member which in turn has a "constructor" member that is equal to the function itself.

    function isConstructor(func) {
        return (func && typeof func === "function" && func.prototype && func.prototype.constructor) === func;
    }
    
    0 讨论(0)
  • 2020-12-05 00:30

    You are looking for if a function has a [[Construct]] internal method. The internal method IsConstructor details the steps:

    IsConstructor(argument)

    ReturnIfAbrupt(argument).  // (Check if an exception has been thrown; Not important.)  
    If Type(argument) is not Object, return false.  // argument === Object(argument), or (typeof argument === 'Object' || typeof argument === 'function')  
    If argument has a [[Construct]] internal method, return true.  
    Return false.
    

    Now we need to find places where IsConstructor is used, but [[Construct]] isn't called (usually by the Construct internal method.)

    I found that it is used in the String function's newTarget (new.target in js), which can be used with Reflect.construct:

    function is_constructor(f) {
      try {
        Reflect.construct(String, [], f);
      } catch (e) {
        return false;
      }
      return true;
    }
    

    (I could have used anything really, like Reflect.construct(Array, [], f);, but String was first)

    Which yields the following results:

    // true
    is_constructor(function(){});
    is_constructor(class A {});
    is_constructor(Array);
    is_constructor(Function);
    is_constructor(new Function);
    
    // false
    is_constructor();
    is_constructor(undefined);
    is_constructor(null);
    is_constructor(1);
    is_constructor(new Number(1));
    is_constructor(Array.prototype);
    is_constructor(Function.prototype);
    is_constructor(() => {})
    is_constructor({method() {}}.method)
    

    <note>

    The only value that I found it didn't work for is Symbol, which, although new Symbol throws a TypeError: Symbol is not a constructor in Firefox, is_constructor(Symbol) === true. This is technically the correct answer, as Symbol does have a [[Construct]] internal method (Which means it can also be subclassed), but using new or super is special cased for Symbol to throw an error (So, Symbol is a constructor, the error message is wrong, it just can't be used as one.) You can just add if (f === Symbol) return false; to the top though.

    The same for something like this:

    function not_a_constructor() {
      if (new.target) throw new TypeError('not_a_constructor is not a constructor.');
      return stuff(arguments);
    }
    
    is_constructor(not_a_constructor);  // true
    new not_a_constructor;  // TypeError: not_a_constructor is not a constructor.
    

    So the intentions of the function of being a constructor can't be gotton like this (Until somthing like Symbol.is_constructor or some other flag is added).

    </note>

    0 讨论(0)
提交回复
热议问题