给定一个功能:
function x(arg) { return 30; }
您可以通过两种方式调用它:
result = x(4); result = new x(4);
第一个返回30,第二个返回一个对象.
如何检测函数本身内部调用函数的方式?
无论您的解决方案是什么,它都必须使用以下调用:
var Z = new x(); Z.lolol = x; Z.lolol();
目前认为所有解决方案Z.lolol()
都将其称为构造函数.
注意:现在可以在ES2015及更高版本中使用.请参阅Daniel Weiner的回答.
我不认为你想要的是什么[在ES2015之前].功能中没有足够的信息可用于进行可靠的推理.
查看ECMAScript第3版规范,new x()
调用时采取的步骤基本上是:
创建一个新对象
将其内部[[Prototype]]属性分配给的prototype属性 x
x
正常调用,将新对象传递给this
如果调用x
返回一个对象,则返回它,否则返回新对象
关于如何调用函数没有任何有用的东西可用于执行代码,因此唯一可以在里面测试的x
是this
值,这就是这里所有答案正在做的事情.正如您所观察到的,x
当x
作为构造函数调用时,*的新实例与作为函数调用时x
传递的预先存在的实例无法区分,除非您为构造时创建的每个新对象分配属性:this
x
x
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
,可以被覆盖,但我认为这是你能做的最好的.
(*) "instance of"是一个不准确的术语,但足够接近,比"通过调用x
构造函数创建的对象"更简洁
从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"
1)你可以检查this.constructor
:
function x(y) { if (this.constructor == x) alert('called with new'); else alert('called as function'); }
2)是的,在new
上下文中使用时,返回值才被丢弃
注意:这个答案写于2008年,当时javascript 从1999年开始仍在ES3中.从那时起,添加了许多新功能,因此现在存在更好的解决方案.这个答案是出于历史原因而保留的.
下面代码的好处是您不需要指定函数的名称两次,它也适用于匿名函数.
function x() { if ( (this instanceof arguments.callee) ) { alert("called as constructor"); } else { alert("called as function"); } }
更新 如claudiu在下面的注释中指出的,如果将构造函数分配给它创建的同一对象,则上述代码不起作用.我从来没有编写那样做的代码,并且有更新的人看到其他人做那个打火机.
克劳迪斯的例子:
var Z = new x(); Z.lolol = x; Z.lolol();
通过向对象添加属性,可以检测对象是否已初始化.
function x() { if ( (this instanceof arguments.callee && !this.hasOwnProperty("__ClaudiusCornerCase")) ) { this.__ClaudiusCornerCase=1; alert("called as constructor"); } else { alert("called as function"); } }
如果删除添加的属性,即使上面的代码也会中断.但是,您可以使用您喜欢的任何值覆盖它,包括undefined
,它仍然有效.但如果你删除它,它会破坏.
目前,ecmascript中没有原生支持来检测函数是否被称为构造函数.这是我到目前为止最接近的事情,除非你删除属性,否则它应该有效.
两种方式,在引擎盖下基本相同.您可以测试范围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 } }
在构造函数中检查[this]的实例类型是要走的路.问题是没有任何进一步的麻烦,这种方法容易出错.但是有一个解决方案.
让我们说我们正在处理函数ClassA().最基本的方法是:
function ClassA() { if (this instanceof arguments.callee) { console.log("called as a constructor"); } else { console.log("called as a function"); } }
有几种方法,上述解决方案将无法按预期工作.考虑这两个:
var instance = new ClassA; instance.classAFunction = ClassA; instance.classAFunction(); // <-- this will appear as constructor call ClassA.apply(instance); //<-- this too
为了克服这些问题,一些人建议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();
这将有效地防止所有尝试使用[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"); } });