如何检查Javascript函数是否为构造函数

时间:2016-12-01 23:56:10

标签: javascript

我注意到并非所有的Javascript函数都是构造函数。

var obj = Function.prototype;
console.log(typeof obj === 'function'); //true
obj(); //OK
new obj(); //TypeError: obj is not a constructor

问题1:如何检查函数是否为构造函数,以便可以使用新函数调用它?

问题2:当我创建一个函数时,是否可以使 NOT 成为构造函数?

7 个答案:

答案 0 :(得分:38)

一点背景知识:

ECMAScript 6+区分可调用(可以不带new调用)和可构造(可以使用new调用)函数:< / p>

  • 通过箭头函数语法或类或对象文字中的方法定义创建的函数不可构造
  • 通过class语法创建的函数无法调用
  • 以任何其他方式创建的函数(函数表达式/声明,Function构造函数)是可调用和可构造的。
  • 除非另有明确说明,否则内置函数不可构造。

关于Function.prototype

Function.prototype是一个所谓的built-in function that is not constructable。来自规范:

  

未标识为构造函数的内置函数对象不实现[[Construct]]内部方法,除非在特定函数的描述中另有说明。

Function.prototype的值是在运行时初始化的最开始创建的。它基本上是一个空函数,并没有明确说明它是可构造的。

  

如何检查函数是否为构造函数,以便可以使用new调用它?

没有内置的方法可以做到这一点。您可try使用new调用该函数,并检查错误或返回true

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

但是,这种方法不是故障保护,因为功能可能有副作用,因此在致电f后,您不知道环境所处的状态。

此外,这只会告诉您函数是否可以作为构造函数调用,而不是意图作为构造函数调用。为此,您必须查看文档或函数的实现。

注意:在生产环境中永远不应该有理由使用像这样的测试。是否应该使用new调用函数应该可以从其文档中辨别出来。

  

当我创建一个函数时,我该如何使它不是一个构造函数?

要创建一个真正不是可构造的的函数,可以使用箭头函数:

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

根据定义,箭头函数不可构造。或者,您可以将函数定义为对象或类的方法。

否则,您可以通过检查new this值来检查是否使用function foo() { if (this instanceof foo) { throw new Error("Don't call 'foo' with new"); } } (或类似的东西)调用函数,如果出现错误则抛出错误:

this

当然,由于还有其他方法可以设置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()');的值,因此可能存在误报。

<强>实施例

&#13;
&#13;
ngrx-store-freeze
&#13;
&#13;
&#13;

答案 1 :(得分:8)

有一种快速简便的方法可以确定函数是否可以实例化,而不必使用try-catch语句(v8无法优化)

function isConstructor(obj) {
  return !!obj.prototype && !!obj.prototype.constructor.name;
}
  1. 首先,我们检查对象是否是原型链的一部分。
  2. 然后我们排除匿名函数
  3. 有一个警告,即:functions named inside a definition仍会产生名称属性,从而通过此检查,因此在依赖函数构造函数的测试时需要谨慎。

    在下面的示例中,该函数不是匿名函数,但实际上称为“myFunc”。它的原型可以像任何JS类一样扩展。

    let myFunc = function () {};
    

    :)

答案 2 :(得分:6)

您正在寻找函数是否具有[[Construct]]内部方法。内部方法IsConstructor详细说明了这些步骤:

  

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.

现在我们需要查找使用IsConstructor的地方,但不会调用[[Construct]](通常通过Construct内部方法调用。)

我发现它用于String函数的newTarget(js中的new.target),可与Reflect.construct一起使用:

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

(我本可以使用任何内容,例如Reflect.construct(Array, [], f);,但String是第一个)

产生以下结果:

// 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)

&LT;请注意&GT;

我发现它不起作用的唯一值是Symbol,虽然new Symbol在Firefox中引发了TypeError: Symbol is not a constructor is_constructor(Symbol) === true。这是技术上正确答案,因为Symbol 具有[[Construct]]内部方法(这意味着它也可以是子类),但使用{ {1}}或newsuper特殊情况下引发错误的(因此,Symbol是构造函数,错误消息错误,它不能用作一个。)你可以将Symbol添加到顶部。

同样的事情:

if (f === Symbol) return false;

因此,作为构造函数的功能的意图不能像这样(直到添加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. 或其他标志之类的东西。)

&LT; /注释&GT;

答案 3 :(得分:6)

使用ES6 + Proxies,可以在不实际调用构造函数的情况下测试[[Construct]]。这是一个片段:

const handler={construct(){return handler}} //Must return ANY object, so reuse one
const isConstructor=x=>{
    try{
        return !!(new (new Proxy(x,handler))())
    }catch(e){
        return false
    }
}

如果传递的项目不是对象,则Proxy构造函数会抛出错误。如果它不是可构造对象,则new会抛出错误。但是如果它是一个可构造的对象,那么它会返回handler对象,而不会调用它的构造函数,然后它不会被注入true

正如您所料,Symbol仍被视为构造函数。这是因为它是,并且实现仅在调用[[Construct]]时抛出错误。这可能是任何用户定义的函数在new.target存在时抛出错误的情况,因此将其作为附加检查专门清除它似乎没有问题,但如果你这样做,请随意这样做发现这很有帮助。

答案 4 :(得分:1)

如果函数是构造函数,则它将具有“原型”成员,而该成员又具有与函数本身相同的“构造函数”成员。

function isConstructor(func) {
    return (func && typeof func === "function" && func.prototype && func.prototype.constructor) === func;
}

答案 5 :(得分:0)

对于问题1,这个助手呢?

Function.isConstructor = ({ prototype }) => Boolean(prototype) && Boolean(prototype.constructor)

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

对于问题2,箭头功能是解决方案。它不能用作构造函数,因为它不依赖于与常规函数相同的作用域,并且没有原型(实例的定义,类似于真实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

答案 6 :(得分:0)

作为Felix Kling's answer的补充,即使一个函数不可构造,如果它具有prototype属性,我们仍然可以像构造函数一样使用它。我们可以借助Object.create()来做到这一点。示例:

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