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

Cla*_*diu 104 javascript constructor

给定一个功能:

function x(arg) { return 30; }
Run Code Online (Sandbox Code Playgroud)

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

result = x(4);
result = new x(4);
Run Code Online (Sandbox Code Playgroud)

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

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

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

var Z = new x(); 
Z.lolol = x; 
Z.lolol();
Run Code Online (Sandbox Code Playgroud)

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

Tim*_*own 88

注意:现在可以在ES2015及更高版本中使用.请参阅Daniel Weiner的回答.

我不认为你想要的是什么[在ES2015之前].功能中没有足够的信息可用于进行可靠的推理.

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

  • 创建一个新对象
  • 将其内部[[Prototype]]属性分配给的prototype属性 x
  • x正常调用,将新对象传递给this
  • 如果调用x返回一个对象,则返回它,否则返回新对象

关于如何调用函数没有任何有用的东西可用于执行代码,因此唯一可以在里面测试的xthis值,这就是这里所有答案正在做的事情.正如您所观察到的,xx作为构造函数调用时,*的新实例与作为函数调用时x传递的预先存在的实例无法区分,除非您为构造时创建的每个新对象分配属性:thisxx

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

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

(*) "instance of"是一个不准确的术语,但足够接近,比"通过调用x构造函数创建的对象"更简洁

  • 我独立提出了这个技术,我正在使用`Object.defineProperty('__ previousConstructedByX',{value:true,writeable:false,enumerable:false})`这使得额外的属性更不容易找到意外地,并保证它永远不会改变.我在node.js工作,所以我可以肯定实现将支持它,但在浏览器中你必须使用测试来使用它. (10认同)
  • 我只是停下来,并且非常惊讶地看到这个答案只有一个upvote.这是一个很好的答案.来吧,人们,不要那么小气.(不用说,加1). (3认同)

Dan*_*ner 66

从ECMAScript 6开始,这是可行的new.target.new.target如果函数被调用new(或者使用Reflect.construct,其行为类似new),则将被设置,否则它将被设置undefined.

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

  • 另请参见[什么是"new.target"?](http://stackoverflow.com/q/32450516/1048572) (5认同)
  • @Melab:它调用 Foo,而不是作为构造函数,将 `this` 设置为 `{}`。 (2认同)

Gre*_*reg 52

1)你可以检查this.constructor:

function x(y)
{
    if (this.constructor == x)
        alert('called with new');
    else
         alert('called as function');
}
Run Code Online (Sandbox Code Playgroud)

2)是的,在new上下文中使用时,返回值才被丢弃

  • 注意:不丢弃返回值; 如果返回值是Object,则返回该对象而不是'this'. (33认同)
  • 如果`window.constructor == x`,则会失败.使用`instanceof`或`Object.getPrototypeOf`. (13认同)
  • 这不起作用.看哪:{var Z = new x(); Z.lolol = x; Z.lolol();}.它认为第二次调用是被称为构造函数. (3认同)
  • 如果"x.prototype = new Array()"之类的内容,并且未重新分配x.prototype.constructor字段,这也不起作用. (2认同)

som*_*ome 19

注意:这个答案写于2008年,当时javascript 从1999年开始仍在ES3中.从那时起,添加了许多新功能,因此现在存在更好的解决方案.这个答案是出于历史原因而保留的.

下面代码的好处是您不需要指定函数的名称两次,它也适用于匿名函数.

function x() {
    if ( (this instanceof arguments.callee) ) {
      alert("called as constructor");
    } else {
      alert("called as function");
    }
}
Run Code Online (Sandbox Code Playgroud)

更新claudiu在下面的注释中指出的,如果将构造函数分配给它创建的同一对象,则上述代码不起作用.我从来没有编写那样做的代码,并且有更新的人看到其他人做那个打火机.

克劳迪斯的例子:

var Z = new x();
Z.lolol = x;
Z.lolol();
Run Code Online (Sandbox Code Playgroud)

通过向对象添加属性,可以检测对象是否已初始化.

function x() {
    if ( (this instanceof arguments.callee && !this.hasOwnProperty("__ClaudiusCornerCase")) ) {
        this.__ClaudiusCornerCase=1;
        alert("called as constructor");
    } else {
        alert("called as function");
    }
}
Run Code Online (Sandbox Code Playgroud)

如果删除添加的属性,即使上面的代码也会中断.但是,您可以使用您喜欢的任何值覆盖它,包括undefined,它仍然有效.但如果你删除它,它会破坏.

目前,ecmascript中没有原生支持来检测函数是否被称为构造函数.这是我到目前为止最接近的事情,除非你删除属性,否则它应该有效.

  • @MattFenwick正确,因为`arguments.callee`在严格模式下被禁止.如果用构造函数的名称替换它,它应该工作. (3认同)
  • 这是行不通的。看哪: { var Z = new x(); Z.lolol = x; Z.lolol();}。它认为第二次调用是作为构造函数调用的。 (2认同)
  • "使用严格"似乎不允许这样做;`. (2认同)

ann*_*ata 8

两种方式,在引擎盖下基本相同.您可以测试范围this是什么,或者您可以测试什么this.constructor是.

如果您调用方法作为构造函数this将是该类的新实例,如果您将方法作为方法调用this将是方法的上下文对象.类似地,如果调用new,则对象的构造函数将是方法本身,否则将是系统Object构造函数.这显然是泥,但这应该有所帮助:

var a = {};

a.foo = function () 
{
  if(this==a) //'a' because the context of foo is the parent 'a'
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

var bar = function () 
{
  if(this==window) //and 'window' is the default context here
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

a.baz = function ()
{
  if(this.constructor==a.baz); //or whatever chain you need to reference this method
  {
    //constructor call
  }
  else
  {
    //method call
  }
}
Run Code Online (Sandbox Code Playgroud)


Pet*_*tai 5

在构造函数中检查[this]的实例类型是要走的路.问题是没有任何进一步的麻烦,这种方法容易出错.但是有一个解决方案.

让我们说我们正在处理函数ClassA().最基本的方法是:

    function ClassA() {
        if (this instanceof arguments.callee) {
            console.log("called as a constructor");
        } else {
            console.log("called as a function");
        }
    }
Run Code Online (Sandbox Code Playgroud)

有几种方法,上述解决方案将无法按预期工作.考虑这两个:

    var instance = new ClassA;
    instance.classAFunction = ClassA;
    instance.classAFunction(); // <-- this will appear as constructor call

    ClassA.apply(instance); //<-- this too
Run Code Online (Sandbox Code Playgroud)

为了克服这些问题,一些人建议a)将一些信息放在实例的字段中,例如"ConstructorFinished"并检查它或b)在列表中跟踪构造的对象.我对两者都感到不舒服,因为改变ClassA的每个实例对于类型相关的功能来说太过侵入性和昂贵.如果ClassA将具有许多实例,则收集列表中的所有对象可能会提供垃圾收集和资源问题.

要走的路是能够控制ClassA功能的执行.简单的方法是:

    function createConstructor(typeFunction) {
        return typeFunction.bind({});
    }

    var ClassA = createConstructor(
        function ClassA() {
            if (this instanceof arguments.callee) {
                console.log("called as a function");
                return;
            }
            console.log("called as a constructor");
        });

    var instance = new ClassA();
Run Code Online (Sandbox Code Playgroud)

这将有效地防止所有尝试使用[this]值进行欺骗.绑定函数将始终保持其原始[this]上下文,除非您使用new运算符调用它.

高级版本返回了在任意对象上应用构造函数的功能.某些用途可能是使用构造函数作为类型转换器或在继承方案中提供可调用的基类构造函数链.

    function createConstructor(typeFunction) {
        var result = typeFunction.bind({});
        result.apply = function (ths, args) {
            try {
                typeFunction.inApplyMode = true;
                typeFunction.apply(ths, args);
            } finally {
                delete typeFunction.inApplyMode;
            }
        };
        return result;
    }

    var ClassA = createConstructor(
        function ClassA() {
            if (this instanceof arguments.callee && !arguments.callee.inApplyMode) {
                console.log("called as a constructor");
            } else {
                console.log("called as a function");
            }
        });
Run Code Online (Sandbox Code Playgroud)


ymz*_*ymz 5

实际上解决方案是非常可能且简单的...不明白为什么为这么小的事情写了这么多文字

更新:感谢TwilightSun,解决方案现已完成,即使是Claudiu建议的测试!感谢你们!!!

function Something()
{
    this.constructed;

    if (Something.prototype.isPrototypeOf(this) && !this.constructed)
    {
        console.log("called as a c'tor"); this.constructed = true;
    }
    else
    {
        console.log("called as a function");
    }
}

Something(); //"called as a function"
new Something(); //"called as a c'tor"
Run Code Online (Sandbox Code Playgroud)

此处演示: https: //jsfiddle.net/9cqtppuf/