如何检测函数是否被调用为构造函数?

2022-08-30 05:01:40

给定一个函数:

function x(arg) { return 30; }

您可以通过两种方式调用它:

result = x(4);
result = new x(4);

第一个返回 30,第二个返回一个对象。

如何检测函数本身内部的调用方式?

无论您的解决方案是什么,它也必须与以下调用一起使用:

var Z = new x(); 
Z.lolol = x; 
Z.lolol();

目前所有解决方案都认为将其称为构造函数。Z.lolol()


答案 1

注意:这在 ES2015 及更高版本中是可能的。看看丹尼尔·韦纳的回答

我不认为你想要的(在ES2015之前)是可能的。函数中根本没有足够的信息来做出可靠的推断。

看看 ECMAScript 第 3 版规范,调用时采取的步骤基本上是:new x()

  • 创建新对象
  • 将其内部 [[原型]] 属性分配给x
  • 正常调用,将其作为新对象传递xthis
  • 如果调用返回一个对象,则返回它,否则返回新对象x

关于函数的调用方式没有任何有用的信息可供执行代码使用,因此唯一可以在内部进行测试的是值,这就是这里的所有答案正在做的事情。正如您所观察到的,作为构造函数调用时* 的新实例与作为函数调用时传递的预先存在的实例没有区别,除非您将属性分配给构造时创建的每个新对象:xthisxxxthisxx

function x(y) {
    var isConstructor = false;
    if (this instanceof x // <- You could use arguments.callee instead of x here,
                          // except in in EcmaScript 5 strict mode.
            && !this.__previouslyConstructedByX) {
        isConstructor = true;
        this.__previouslyConstructedByX = true;
    }
    alert(isConstructor);
}

显然,这并不理想,因为你现在在可以覆盖的每个对象上都有一个额外的无用属性,但我认为这是你能做的最好的事情。x

(*)“实例”是一个不准确的术语,但足够接近,并且比“通过调用构造函数而创建的对象”更简洁x


答案 2

从 ECMAScript 6 开始,这可以通过 new.target 实现。 如果函数被调用 (或 with ),则将设置,其行为类似于 ),否则它是 。new.targetnewReflect.constructnewundefined

function Foo() {
    if (new.target) {
       console.log('called with new');
    } else {
       console.log('not called with new');
    }
}

new Foo(); // "called with new"
Foo(); // "not called with new"
Foo.call({}); // "not called with new"