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

给定一个函数:

function x(arg) { return 30; } 

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

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

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

你怎么能检测函数在函数内部被调用的方式?

无论您的解决scheme是什么,它也必须与以下调用一起工作:

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

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

注意:ES2015及更高版本现在可以使用了。 见Daniel Weiner的答案 。

我不认为你想要什么是可能的[在ES2015之前]。 在函数中没有足够的信息来进行可靠的推断。

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

  • 创build一个新的对象
  • 将其内部[[Prototype]]属性分配给x的prototype属性
  • 像平常一样调用x ,把它作为新的对象传递给它
  • 如果对x的调用返回一个对象,则返回它,否则返回新的对象

没有什么有用的关于如何调用函数被执行的代码,所以唯一可以在xtesting的就是this值,这就是所有的答案。 正如你所看到的,当调用x作为构造x时,* x的新实例与调用x作为函数的x的已存在实例无法区分, 除非将属性分配给由x创build的每个新对象因为它被构造:

 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作为构造函数创build的对象”更加简洁,

1)你可以检查this.constructor

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

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

从ECMAScript 6开始,可以使用new.targetnew.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" 

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

 function x() { if ( (this instanceof arguments.callee) ) { alert("called as constructor"); } else { alert("called as function"); } } 

更新由于claudiu在下面的注释中指出,如果您将构造函数分配给它创build的同一个对象,上面的代码不起作用。 我从来没有写过这样的代码,并且看到其他任何人都能看到这样的代码。

Claudius的例子:

 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中没有本地支持来检测一个函数是否被调用为构造函数。 这是我迄今为止最接近的事情,它应该工作,除非你删除该属性。

两种方式,在引擎盖下基本相同。 你可以testing它的范围,或者你可以testing一下this.constructor是什么。

如果你调用一个方法作为构造函数, this将是一个新的类实例,如果你调用方法作为方法, this将是方法的上下文对象。 同样,如果调用new,则对象的构造函数将是方法本身,否则将是System对象的构造函数。 这显然是泥,但这应该有所帮助:

 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]的实例types是要走的路。 问题是,没有任何进一步的办法,这种方法是容易出错的。 有一个解决scheme,但是。

可以说我们正在处理函数ClassA()。 基本的方法是:

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

有几种方法意味着上述解决scheme将无法按预期工作。 考虑这两个:

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

为了解决这些问题,一些人build议:a)在实例的某个字段中放置一些信息,比如“ConstructorFinished”,然后检查它,或者b)在列表中保留一个已经构build的对象的轨迹。 我对这两种方法都感到不舒服,因为改变ClassA的每一个实例都是非常有创意的,而且对于types相关的function来说是昂贵的。 如果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]上下文,除非您使用new运算符调用它。

高级版本提供了在任意对象上应用构造函数的能力。 一些使用可以使用构造函数作为types转换器,或者在inheritance场景中提供可调用的基类构造函数链。

  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"); } }); 

实际上这个解决scheme是非常可能和简单的……不明白为什么这么多的词语是为这样一个小事物而写的

更新:感谢TwilightSun解决scheme现在已经完成,即使对于Claudiubuild议的testing! 感谢你们!!!

 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" 

在这里演示: https : //jsfiddle.net/9cqtppuf/

扩展Gregs解决scheme,这个与您提供的testing案例完美结合:

 function x(y) { if( this.constructor == arguments.callee && !this._constructed ) { this._constructed = true; alert('called with new'); } else { alert('called as function'); } } 

编辑:添加一些testing用例

 x(4); // OK, function var X = new x(4); // OK, new var Z = new x(); // OK, new Z.lolol = x; Z.lolol(); // OK, function var Y = x; Y(); // OK, function var y = new Y(); // OK, new y.lolol = Y; y.lolol(); // OK, function 

直到我看到这个线程,我从来没有考虑到构造函数可能是一个实例的属性,但我认为下面的代码涵盖了这种罕见的情况。

 // Store instances in a variable to compare against the current this // Based on Tim Down's solution where instances are tracked var Klass = (function () { // Store references to each instance in a "class"-level closure var instances = []; // The actual constructor function return function () { if (this instanceof Klass && instances.indexOf(this) === -1) { instances.push(this); console.log("constructor"); } else { console.log("not constructor"); } }; }()); var instance = new Klass(); // "constructor" instance.klass = Klass; instance.klass(); // "not constructor" 

对于大多数情况下,我可能只是检查instanceof。

没有可靠的方法来区分JavaScript代码中如何调用函数。 1

但是,一个函数调用将把this赋值给全局对象,而一个构造函数将把this赋值给一个新的对象。 这个新的对象永远不可能是全局对象,因为即使一个实现允许你设置全局对象,你仍然没有机会这样做。

你可以通过一个被称为函数(heh)的函数来获取全局对象。

我的直觉是,在ECMAScript 1.3的规范中,当作为函数调用时,具有已定义行为的构造函数应该区分如何使用此比较调用它们:

 function MyClass () { if ( this === (function () { return this; })() ) { // called as a function } else { // called as a constructor } } 

无论如何,任何人都可以使用函数或构造函数的call或者apply任何事物。 但是这样,你可以避免“初始化”全局对象:

 function MyClass () { if ( this === (function () { return this; })() ) { // Maybe the caller forgot the "new" keyword return new MyClass(); } else { // initialize } } 

1.主机(aka实现)可能能够区分它,如果它实现了内部属性[[Call]][[Construct]]的等价物。 前者是为函数或方法expression式调用的,而后者是为newexpression式调用的。

来自John Resig:

 function makecls() { return function(args) { if( this instanceof arguments.callee) { if ( typeof this.init == "function") this.init.apply(this, args.callee ? args : arguments) }else{ return new arguments.callee(args); } }; } var User = makecls(); User.prototype.init = function(first, last){ this.name = first + last; }; var user = User("John", "Resig"); user.name 

如果你打算hackish,那么instanceofnew.target之后的最小解决scheme,就像其他答案一样。 但是使用instanceof解决scheme会失败,例如:

 let inst = new x; x.call(inst); 

结合@TimDown解决scheme,如果您希望与较老的ECMAScript版本兼容,可以使用ES6的WeakSet来防止在实例中放置属性。 那么, WeakSet将被用来允许未使用的对象被垃圾收集。 new.target在相同的源代码中将不兼容,因为它是ES6的语法function。 ECMAScript指定标识符不能是保留字中的一个,反正new不是一个对象。

 (function factory() { 'use strict'; var log = console.log; function x() { log(isConstructing(this) ? 'Constructing' : 'Not constructing' ); } var isConstructing, tracks; var hasOwnProperty = {}.hasOwnProperty; if (typeof WeakMap === 'function') { tracks = new WeakSet; isConstructing = function(inst) { if (inst instanceof x) { return tracks.has(inst) ? false : !!tracks.add(inst); } return false; } } else { isConstructing = function(inst) { return inst._constructed ? false : inst._constructed = true; }; } var z = new x; // Constructing x.call(z) // Not constructing })(); 

ECMAScript 3的instanceof操作符被指定为:

11.8.6运算符的instanceof
—生产RelationalExpression:RelationalExpression instanceof ShiftExpression的计算方法如下:
— 1.评估关系expression式。
— 2.调用GetValue(Result(1))。
— 3.评估ShiftExpression。
— 4.调用GetValue(Result(3))。
5.如果Result(4)不是一个对象,则抛出一个TypeErrorexception。
— 6.如果Result(4)没有[[HasInstance]]方法,则引发TypeErrorexception。
— 7.用参数Result(2)调用Result(4)的[[HasInstance]]方法。
— 8.返回结果(7)。

15.3.5.3 [[HasInstance]](V)
—假设F是一个函数对象。
—当F调用[[HasInstance]]方法的值为V时,采取以下步骤:
— 1.如果V不是一个对象,则返回false
— 2.调用属性名称为“prototype”的F的[[Get]]方法。
— 3.让O为结果(2)。
— 4.如果O不是一个对象,则抛出一个TypeErrorexception。
5.设V是V的[[Prototype]]属性的值。
— 6.如果V是** null **,则返回false
—如果O和V指向同一个对象,或者如果它们指向彼此连接的对象(13.1.2),则返回true
— 8.转到第5步。

这意味着它将在转到原型之后recursion左侧值,直到它不是一个对象,或者直到它与具有指定[[HasInstance]]方法的右侧对象的原型相等为止。 这意味着它会检查左侧是否是右侧的一个实例,尽pipe左侧是所有内部原型。

 function x() { if (this instanceof x) { /* Probably invoked as constructor */ } else return 30; } 

在我testinghttp://packagesinjavascript.wordpress.com/我发现testing如果(这个==窗口)在所有情况下跨浏览器工作,所以这是我最终使用的。

-Stijn

也许我错了,但(以寄生虫的代价)下面的代码似乎是一个解决scheme:

 function x(arg) { //console.debug('_' in this ? 'function' : 'constructor'); //WRONG!!! // // RIGHT(as accepted) console.debug((this instanceof x && !('_' in this)) ? 'function' : 'constructor'); this._ = 1; return 30; } var result1 = x(4), // function result2 = new x(4), // constructor Z = new x(); // constructor Z.lolol = x; Z.lolol(); // function 

使用this instanceof arguments.callee (可选地用它所在的函数replacearguments.callee ,这可以提高性能)来检查是否有东西被称为构造函数。 不要使用this.constructor因为它可以很容易地更改。

Tim Down我认为是正确的。 我认为,一旦你认为你需要能够区分两种呼叫模式,那么你不应该使用“ thisthis关键字。 this是不可靠的,它可能是全球性的对象,也可能是一个完全不同的对象。 事实上,具有这些不同的激活模式的function,其中一些按照你的意图工作,另外一些则完全是狂野的,是不希望的。 我想也许你正试图弄清楚这个。

有一个惯用的方法来创build一个构造函数,无论它如何被调用,其行为都是相同的。 无论是像Thing(),new Thing()还是foo.Thing()。 它是这样的:

 function Thing () { var that = Object.create(Thing.prototype); that.foo="bar"; that.bar="baz"; return that; } 

Object.create是一个新的ecmascript 5标准方法,可以像这样在普通的javascript中实现:

 if(!Object.create) { Object.create = function(Function){ // WebReflection Revision return function(Object){ Function.prototype = Object; return new Function; }}(function(){}); } 

Object.create将一个对象作为参数,并返回一个新的对象作为其原型传入的对象。

但是,如果你真的试图使一个函数的行为有所不同,取决于它的调用方式,那么你是一个坏人,你不应该写JavaScript代码。

如果你不想在对象中放置一个__previouslyConstructedByX属性 – 因为它污染了对象的公共接口并且很容易被覆盖 – 只是不返回一个x

 function x() { if(this instanceof x) { console.log("You invoked the new keyword!"); return that; } else { console.log("No new keyword"); return undefined; } } x(); var Z = new x(); Z.lolol = x; Z.lolol(); new Z.lolol(); 

现在x函数永远不会返回一个xtypes的对象,所以(我认为) this instanceof x只有在函数被new关键字调用时才会计算为true。

不足之处在于,这有效地消除了instanceof的行为 – 但取决于你使用它的多less(我不倾向于),这可能不是一个问题。


如果你的目标是两种情况都返回30 ,你可以返回一个Number的实例,而不是一个x的实例:

 function x() { if(this instanceof x) { console.log("You invoked the new keyword!"); var that = {}; return new Number(30); } else { console.log("No new"); return 30; } } console.log(x()); var Z = new x(); console.log(Z); Z.lolol = x; console.log(Z.lolol()); console.log(new Z.lolol()); 

当我试图实现一个返回一个string而不是一个对象的函数时,我也遇到了同样的问题。

在你的函数开始时,这似乎足以检查“this”的存在:

 function RGB(red, green, blue) { if (this) { throw new Error("RGB can't be instantiated"); } var result = "#"; result += toHex(red); result += toHex(green); result += toHex(blue); function toHex(dec) { var result = dec.toString(16); if (result.length < 2) { result = "0" + result; } return result; } return result; } 

无论如何,最后我只是决定把我的RGB()伪类转换成rgb()函数,所以我不会尝试实例化它,因此根本不需要安全检查。 但是,这取决于你想要做什么。

 function createConstructor(func) { return func.bind(Object.create(null)); } var myClass = createConstructor(function myClass() { if (this instanceof myClass) { console.log('You used the "new" keyword'); } else { console.log('You did NOT use the "new" keyword'); return; } // constructor logic here // ... });