如何检测一个函数是否被称为构造函数?
给定一个函数:
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
的调用返回一个对象,则返回它,否则返回新的对象
没有什么有用的关于如何调用函数被执行的代码,所以唯一可以在x
testing的就是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.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"
下面的代码的好处是,你不需要指定两次函数的名称,它也适用于匿名函数。
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式调用的,而后者是为new
expression式调用的。
来自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,那么instanceof
是new.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我认为是正确的。 我认为,一旦你认为你需要能够区分两种呼叫模式,那么你不应该使用“ this
” this
关键字。 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
函数永远不会返回一个x
types的对象,所以(我认为) 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 // ... });