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

给定一个函数:

function x(arg) { return 30; }

你可以称之为两种方式:

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

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

How can you detect which way the function was called 在函数本身内部?

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

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

All the solutions currently think the Z.lolol() is calling it as a constructor.

44828 次浏览

1)查询 this.constructor:

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

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

两种方式,本质上是一样的。您可以测试 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
}
}

注意: 这个答案是用 二零零八年写的,当时 javascript 还在 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");
}
}

如果删除添加的属性,即使上面的代码也会中断。但是如果你删除它,它就会坏掉。

目前,ecmascript 中没有检测函数是否被作为构造函数调用的本机支持。这是到目前为止我想到的最接近的方法,它应该可以工作,除非你删除属性。

在我对 http://packagesinjavascript.wordpress.com/的测试中,我发现测试 if (this = = window)在所有情况下都可以跨浏览器工作,所以这就是我最终使用的测试。

Stijn

使用 this instanceof arguments.callee(可以选择用函数替换 arguments.callee,这样可以提高性能)检查某些内容是否被作为构造函数调用。不要使用 this.constructor,因为它可以很容易地改变。

注意: 这在 ES2015和更高版本中是可能的。请参阅 Daniel Weiner 的回答

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

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

  • 创建一个新对象
  • Assign its internal [[Prototype]] property to the prototype property of x
  • 正常调用 x,将新对象作为 this传递给它
  • 如果对 x的调用返回一个对象,则返回它,否则返回新对象

Nothing useful about how the function was called is made available to the executing code, so the only thing it's possible to test inside x is the this value, which is what all the answers here are doing. As you've observed, a new instance of* x when calling x as a constructor is indistinguishable from a pre-existing instance of x passed as this when calling x as a function, 除非 you assign a property to every new object created by x as it is constructed:

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

我认为是正确的。我认为,一旦您认为需要能够区分两种调用模式,那么就不应该使用“ this”关键字。this是不可靠的,它可以是全局对象,也可以是完全不同的对象。事实上,拥有这些不同激活模式的功能,其中一些按照你的意愿工作,另一些则完全疯狂,是不受欢迎的。我觉得你是因为这个才想弄清楚这件事的。

有一种惯用的方法可以创建一个构造函数,无论如何调用它,它的行为都是相同的。不管是 Thing ()、 new Thing ()还是 foo。东西。是这样的:

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

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 will take an object as a parameter, and return a new object with that passed in object as its prototype.

然而,如果你真的想让一个函数根据调用方式的不同而有不同的表现,那么你就是一个坏人,你不应该编写 javascript 代码。

扩展 Gregs 解决方案,这个解决方案与您提供的测试用例完美地结合在一起:

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

编辑: 添加一些测试用例

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

来自 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

在看到这个线程之前,我从未想过构造函数可能是实例的属性,但我认为下面的代码涵盖了这种罕见的情况。

// 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"

For most cases I'll probably just check instanceof.

没有可靠的方法可以区分在 JavaScript code.1中如何调用函数

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

You can get the global object by having a function called as a function (heh) returning this.

My intuition is that in the specification of ECMAScript 1.3, constructors that have a defined behavior for when called as a function are supposed to distinguish how they were called using this comparison:

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

无论如何,任何人都可以使用函数的或构造函数的 callapply,并将 this设置为任何值。但是通过这种方式,您可以避免“初始化”全局对象:

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

1.如果主机(又名实现)实现了与内部属性 [[Call]][[Construct]]等价的属性,那么它可能能够区分这两者。前者用于函数或方法表达式,后者用于 new表达式。

Checking for the instance type of the [this] within the constructor is the way to go. The problem is that without any further ado this approach is error prone. There is a solution however.

假设我们正在处理函数 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)在实例的某个字段中放置一些信息,比如“ Construction torFinish”,并对其进行检查; 或者 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 ]上下文,除非使用 新的操作符调用它。

高级版本提供了将构造函数应用于任意对象的能力。有些用法可以将构造函数用作类型转换器,或者在继承场景中提供基类构造函数的可调用链。

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

如果你不想在对象中放入一个 __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类型的对象,所以(我认为) this instanceof x只有在使用 new关键字调用该函数时才会计算为 true。

缺点是这有效地扰乱了 instanceof的行为-但是取决于你使用它的次数(我不倾向于这样做) ,这可能不是一个问题。


如果你的目标是返回 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());

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

这似乎足以检查函数开始时是否存在“ 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 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

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

更新: 感谢 黄昏太阳的解决方案现在已经完成,甚至对于测试 克劳迪建议! 谢谢大家! ! !

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/

在 ECMAScript 6中,这可以在 new.target中实现。如果用 new调用函数(或者用 Reflect.construct调用函数,它的作用类似于 new) ,则设置为 new.target,否则设置为 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 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
// ...
});

如果你是黑客,那么 instanceof是最小的解决方案之后的 new.target作为由其他答案。但如果使用 instanceof解决方案,下面这个例子将会失败:

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

结合@TimDown 解决方案,如果希望与较早的 ECMAScript 版本兼容,可以使用 ES6的 WeakSet来防止将属性放入实例中。好的,将使用 WeakSet以允许对未使用的对象进行垃圾收集。new.target在同一源代码中不兼容,因为它是 ES6的语法特性。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 运算符
——-生产关系表达式: 关系表达式 Instanceof ShiftExpression 被评估 如下:
——1. 评估关系表达式。
——-2. 调用 GetValue (Result (1))。
——-3. 评估 Shift 表达式。
--- 4. Call GetValue(Result(3)).
--- 5. If Result(4) is not an object, throw a 输入错误 exception.
--- 6. If Result(4) does not have a [[HasInstance]] method, throw a 输入错误 exception.
——-7. 使用 Result (2)参数调用 Result (4)的[[ HasInstance ]]方法。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー。
15.3.5.3[[ HasInstance ]](V)
——-假设 F 是一个函数对象。
——-当用值 V 调用 F 的[[ HasInstance ]]方法时,需要执行以下步骤:
——-1. 如果 V 不是一个对象,返回 假的
——-2. 使用属性名 “原型”调用 F 的[[ Get ]]方法。
--- 3. Let O be Result(2).
——-4. 如果 O 不是对象,则抛出 TypeError异常。
——-5. 设 V 是 V 的[[原型]]属性的值。
--- 6. If V is **null**, return 假的.
-7.如果 O 和 V 引用同一个对象,或者它们引用彼此连接的对象(13.1.2) ,则返回 没错
八,转到第五步。

这意味着它将在到达原型之后递归左边的值,直到它不是一个对象,或者直到它等于使用指定的 [[HasInstance]]方法的右边对象的原型。这意味着它将检查左手边是否是右手边的实例,并消耗左手边的所有内部原型。

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

虽然这个线程是古老的,但是我很惊讶没有人提到在严格模式('use strict')下,函数的默认 this值是未定义的,而不是像以前一样设置为 global/window,所以检查 new 是否没有使用,只需测试 !thisfalsey值 - 例如:

function ctor() { 'use strict';
if (typeof this === 'undefined')
console.log('Function called under strict mode (this == undefined)');
else if (this == (window || global))
console.log('Function called normally (this == window)');
else if (this instanceof ctor)
console.log('Function called with new (this == instance)');
return this;
}

如果按原样测试该函数,则由于函数开始处的 'use strict'指令,将得到未定义为 this值的值。当然,如果已经有严格的模式,那么它不会改变,如果你删除 'use strict'指令,但否则,如果你删除它的 this值将被设置为 windowglobal。 如果使用 new调用函数,那么 this值将与 instanceof check 匹配(尽管如果选中了其他选项,那么 instance 是最后一个选项,因此不需要这个 check,如果想继承实例,应该避免使用这个选项)

function ctor() { 'use strict';
if (!this) return ctor.apply(Object.create(ctor.prototype), arguments);
console.log([this].concat([].slice.call(arguments)));
return this;
}

这将记录 this值和传递给控制台的函数的任何参数,并返回 this值。如果 this值是 falsey,那么它将使用 Object.create(ctor.prototype)创建一个新实例,并使用 Function.apply()重新调用具有相同参数但具有正确实例 this的构造函数。如果 this值不是 falsey,则假定它是一个有效的实例并返回。

在问题的顶部,下面的代码将自动修复的情况下,函数调用没有新的问题。

function Car() {


if (!(this instanceof Car)) return new Car();


this.a = 1;
console.log("Called as Constructor");


}
let c1 = new Car();
console.log(c1);

I believe the solution is to turn your Constructor function into a wrapper of the real Constructor function and its prototype Constructor if required. This method 将工作在 ES5从2009年,也工作在严格的模式. In the code window below I have an example using the module pattern, to hold the real constructor and its prototype's constructor, in a closure, which is accessible through scope within the 构造函数(包装). This works because no property is added to the "this" keyword within the Constructor(wrapper) and the Constructor(wrapper).prototype is not set, so is 反对 by default; thus the array returned from Object.getPropertyNames will have a length equal to 0, if the new keyword has been used with the Constructor(wrapper). If true then 返回新的 Vector.

var Vector = (function() {
        

var Vector__proto__ = function Vector() {
// Vector methods go here
}
            

var vector__proto__ = new Vector__proto__();;
        

var Vector = function(size) {
// vector properties and values go here
this.x = 0;
this.y = 0;
this.x = 0;
this.maxLen = size === undefined? -1 : size;
                

};
Vector.prototype = vector__proto__;
        

return function(size){
                

if ( Object.getOwnPropertyNames(this).length === 0 ) {
// the new keyword WAS USED with the wrapper constructor
return new Vector(size);
} else {
// the new keyword was NOT USED with the wrapper constructor
return;
};
};
})();

这可以在不使用 ES6 new.target 的情况下实现。您可以在严格模式下运行您的代码,在这种情况下,如果不使用 new 调用,那么 this 的值将是未定义的,否则它将是空对象。 例子:

"use strict"
function Name(){
console.log(this)
if(this){
alert("called by new")
}
else
alert("did not called using new")
}
new Name()