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

Min*_*Sun 33 javascript

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

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

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

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

Fel*_*ing 40

一点背景:

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;
}
Run Code Online (Sandbox Code Playgroud)

但是,这种方法不是故障保护,因为函数可能有副作用,因此在调用之后f,您不知道环境所处的状态.

此外,这只会告诉你一个功能是否可以被称为构造函数,而不是如果打算被称为构造函数.为此,您必须查看文档或函数的实现.

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

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

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

var f = () => console.log('no constructable');
Run Code Online (Sandbox Code Playgroud)

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

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

function foo() {
  if (this instanceof foo) {
    throw new Error("Don't call 'foo' with new");
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,由于还有其他方法来设定价值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()');
Run Code Online (Sandbox Code Playgroud)

  • 如果构造函数有参数 isConstructor 返回 false。 (2认同)
  • “一个函数是否应该用 new 调用应该从它的文档中看出。”- 根本不是。JS 是一种鸭子类型的语言。在某些有效的场景中,人们需要对参数进行某种运行时“类型”检查,函数是否可构造就是其中之一。 (2认同)

Art*_*yer 12

您正在寻找函数是否具有[[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.
Run Code Online (Sandbox Code Playgroud)

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

我发现它在String函数中使用newTarget(new.target在js中),它可以用于Reflect.construct:

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

(我本来可以使用任何东西,比如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)
Run Code Online (Sandbox Code Playgroud)

<注意>

我发现它没有用的唯一价值是Symbol,虽然在Firefox中new Symbol抛出了,但是.这是技术上正确的答案,因为确实有一个内部方法(这意味着它也可以被继承),但使用或者是特殊的套管用于抛出一个错误(所以,是一个构造函数,该错误信息是错误的,它只是可以不能用作一个.)你可以添加到顶部.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.
Run Code Online (Sandbox Code Playgroud)

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

</注释>

  • 这才是天才! (2认同)
  • @LodeMichels 这确实适用于带参数的构造函数。不知道为什么不会。`is_constructor(URL)` 返回 `true`。 (2认同)

Dmi*_*eev 10

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

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

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

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

let myFunc = function () {};
Run Code Online (Sandbox Code Playgroud)

:)

  • 为什么要排除匿名函数?它们是可构建的。 (3认同)
  • 对于这个函数,我更喜欢`Object.hasOwnProperty("prototype")`.顺便说一句,虽然大多数可构造的东西都有`.prototype`,但有些像绑定函数没有,但它们仍然是可构造的.这是因为可构造性与`.prototype`没有直接关系,这一切都与内部[`[[construct]]`]有关(/sf/ask/1531188991/方法)方法._所以,虽然这对许多情况来说都是一个不错的解决方案,但它并不完全是防弹的(如你所知). (3认同)
  • ECMAScript 规范指定规则和实现步骤(例如测试在常规 ECMAScript 中没有明确实现的“[[construct]]”)不是很愚蠢吗?不知怎的,这似乎有点破坏。我们最好的显然可以做的是在大多数情况下近似它。我正在尝试为提议的新方法实现一个polyfill,并尝试遵循提议的规范,但这显然不是完全可能的,因为没有办法测试某些东西是否真的是一个构造函数。 (2认同)

小智 7

使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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


moo*_*moo 6

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

function isConstructor(func) {
    return typeof func === 'function' && !!func.prototype && func.prototype.constructor === func;
}
Run Code Online (Sandbox Code Playgroud)