2016-12-01 130 views
16

我注意到并非所有的Javascript函数都是构造函数。如何检查一个Javascript函数是否是构造函数

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

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

问题2:当我创建一个函数时,是否有可能使它成为而不是的构造函数?

+0

有趣的是'Function'和'Function.prototype'是唯一不是构造函数的函数吗? – Adam

+2

功能是一个construtor。新函数();作品。 –

+0

只需检查该类型是否为函数。 – rlemon

回答

17

背景的一点点:创建

  • 功能:

    的ECMAScript 6+之间可调用(可以不new被称为)和施工的(可以与new调用)功能区分通过箭头函数语法或通过类或对象文字中的方法定义是不可构造的。

  • 通过class语法创建的函数是不可调用
  • 以任何其他方式创建的函数(函数表达式/声明,构造函数Function)都可调用并且可构造。
  • 除非另有明确说明,否则内置函数不可构造。

关于Function.prototype

Function.prototype是所谓的built-in functionthat is not constructable。从规格:

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

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


我如何检查如果一个函数是一个构造函数,以便它可以用一个新的被称为?

有没有一种内置的方式来做到这一点。您可以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()');

5

有确定的功能可以被实例化,而无需一个快速简便的方法诉诸的try-catch语句(可不会被v8优化)

function isConstructor(obj) { 
    return !!obj.prototype && !!obj.prototype.constructor.name; 
} 
  1. 首先我们检查对象是否是原型链的一部分。
  2. 然后我们排除匿名函数

有一个警告,那就是:functions named inside a definition仍会招致name属性,因此通过此检查,所以对功能构造测试依靠时,需要谨慎。

在下面的例子中,函数不是匿名的,但实际上被称为'myFunc'。它的原型可以扩展为任何JS类。

let myFunc = function() {}; 

:)

+0

为什么要排除匿名函数?他们是可修复的。 – Taurus

+0

对于这个函数,我更喜欢Object.hasOwnProperty(“prototype”)。顺便说一下,虽然大多数可构建物确实具有'。原型',但有些像绑定函数那样,但它们仍然是可构造的。这是因为可构造性与'.prototype'没有直接关系,它全部与内部['[[construct]]']有关(https://stackoverflow.com/questions/21874128/construct-internal-方法)方法。因此,对许多案例来说,这是一个体面的解决方案,但这并不完全是无懈可击的(正如你所指出的)._ – Taurus

2

您正在寻找如果一个函数有一个[[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)中使用,其中ca n,其中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); 

<备注>

我发现它的唯一值不起作用的是Symbol,其中虽然new Symbol在Firefox中抛出了TypeError: Symbol is not a constructoris_constructor(Symbol) === true。这是技术上正确的答案,因为Symbol[[Construct]]内部方法(这意味着它也可以被继承),但使用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. 

所以,作为一个构造函数的意图不能gotton这样的(直到财产以后像Symbol.is_constructor,或者加入了一些其他的标志)。

< /注>

+1

这是天才! –

2

随着ES6 +代理,一个可以测试[[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存在时会引发错误,因此,将其作为附加检查明确区分并不合适,但如果您发现该函数有帮助,则可以随意这样做。

相关问题