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

IT技术 javascript
2021-02-27 13:24:07

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

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

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

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

6个回答

一点背景:

ECMAScript 6+ 区分了可调用(可以不用 调用new)和可构造(可以用 调用new)函数:

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

关于 Function.prototype

Function.prototype是一个所谓的内置函数 ,它是不可构造的从规范:

[[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');

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

否则,您可以通过检查函数newthis来检查函数是否被调用(或类似的东西),如果是,则抛出错误:

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()');

如果构造函数有参数 isConstructor 返回 false。
2021-04-16 13:24:07
怎么样const isConstructor = fn => typeof fn === 'function' && 'prototype' in fn我可以看到箭头函数没有原型
2021-04-17 13:24:07
“一个函数是否应该用 new 调用应该可以从它的文档中看出。” - 根本不是。JS 是一种鸭子类型的语言。在某些情况下,需要对参数进行某种运行时“类型”检查,并且函数是否可构造就是其中之一。
2021-05-15 13:24:07
@Frondor:对于 99% 的情况,这可能已经足够了。我仍然要补充一点,当然可以手动定义prototype箭头函数属性。另请参阅此评论:stackoverflow.com/questions/40922531/...
2021-05-16 13:24:07

您正在寻找函数是否具有[[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函数的newTargetnew.target在js中),可以与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)

<注意>

我发现它唯一不起作用的值是Symbol,尽管在 Firefox 中new Symbol抛出了 a ,但. 这在技术上是正确的答案,因为确实有一个内部方法(这意味着它也可以被子类化),但是使用or是特例用于抛出错误(所以,是一个构造函数,错误消息是错误的,它只是可以不能用作一个。)不过,您可以添加到顶部。TypeError: Symbol is not a constructoris_constructor(Symbol) === trueSymbol [[Construct]]newsuperSymbolSymbolif (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.

因此,作为构造函数的功能的意图不能像这样(直到Symbol.is_constructor添加了类似的东西或其他标志)。

</note>

我相信这仅适用于没有参数的构造函数。is_constructor(URL); 例如会失败
2021-04-29 13:24:07
这是天才!
2021-05-10 13:24:07
@JeffRose 抱歉,我似乎没有添加足够的负面示例。特别是箭头函数。is_constructor(() => {})是假的。
2021-05-14 13:24:07
这是一个聪明的解决方案,但除此之外Function.prototype,通过检查typeof f === "function".
2021-05-16 13:24:07

有一种快速简便的方法可以确定函数是否可以实例化,而不必求助于 try-catch 语句(v8 无法对其进行优化)

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

有一个警告,即:在定义中命名的函数仍然会产生 name 属性并因此通过此检查,因此在依赖函数构造函数的测试时需要谨慎。

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

let myFunc = function () {};

:)

为什么要排除匿名函数?它们是可构建的。
2021-04-21 13:24:07
isConstructor(class {}) 回报 false
2021-04-29 13:24:07
@doubleOrt 除此之外,拥有prototype属性并不意味着函数是构造函数。例如,built inSymbol有一个prototype属性。但是,new Symbol()抛出的不是构造函数TypeError。
2021-04-29 13:24:07
我更喜欢Object.hasOwnProperty("prototype")这个功能。顺便说一句,虽然大多数可构造物确实有.prototype's,但有些像绑定函数却没有,但它们仍然是可构造物。这是因为可构造性与 没有直接关系.prototype,它与内部[[construct]]方法有关。因此,虽然在许多情况下这是一个不错的解决方案,但它并不完全是防弹的(正如您所指出的)。
2021-05-09 13:24:07
ECMAScript 规范指定规则和实现步骤是不是有点愚蠢(比如[[construct]]在常规 ECMAScript 中没有明确实现的测试?不知何故这似乎有点坏。我们显然能做的最好的事情就是近似它对于大多数情况。我正在尝试为提议的新方法实现 polyfill,并尝试遵循提议的规范,但这显然不是完全可能的,因为没有办法测试某些东西是否真的是构造函数。
2021-05-13 13:24:07

使用 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存在时抛出错误的情况,因此将其作为额外检查专门清除似乎是不正确的,但如果您发现这有帮助,请随时这样做。

如果函数是构造函数,那么它将有一个“原型”成员,而该成员又具有一个等于函数本身的“构造函数”成员。

function isConstructor(func) {
    return (func && typeof func === "function" && func.prototype && func.prototype.constructor) === func;
}
引用@doubleOrt:“虽然大多数可构造函数确实有.prototype's,但有些像绑定函数没有,但它们仍然是可构造函数。这是因为可构造性与.prototype没有直接关系
2021-05-01 13:24:07
请修正括号。试试isConstructor(false)SCNR :-)
2021-05-02 13:24:07