给定一个函数:
function x(arg) { return 30; }
您可以通过两种方式调用它:
result = x(4);
result = new x(4);
第一个返回 30,第二个返回一个对象。
如何检测函数本身内部调用函数的方式?
无论您的解决方案是什么,它都必须与以下调用一起使用:
var Z = new x();
Z.lolol = x;
Z.lolol();
目前所有的解决方案都认为将Z.lolol()
其称为构造函数。
给定一个函数:
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 及更高版本中现在是可能的。见丹尼尔韦纳的回答。
我不认为你想要什么是可能的[在 ES2015 之前]。函数中根本没有足够的信息来进行可靠的推断。
查看 ECMAScript 第 3 版规范,new x()
调用时采取的步骤基本上是:
x
x
,将新对象传递为this
x
返回一个对象,则返回它,否则返回新对象函数的调用方式对执行代码没有任何用处,因此唯一可以在内部测试x
的是this
值,这就是这里所有答案所做的事情。正如您所观察到的,作为构造函数x
调用时的 * 新实例与作为函数调用时传递x
的预先存在的实例无法区分,除非您将属性分配给由 as 创建的每个新对象: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
,可以被覆盖,但我认为这是你能做的最好的。
(*)x
“instance of”是一个不准确的术语,但与“通过调用构造函数创建的对象”相比,它足够接近且更简洁
从 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');
}
new
2)是的,返回值在上下文中使用时只是被丢弃
注意:这个答案是在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,否则系统对象构造函数。这很清楚,但这应该会有所帮助:
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");
}
});
实际上解决方案非常可能且简单...不明白为什么要为这么小的东西写这么多字
更新:感谢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"
扩展 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
在我看到这个线程之前,我从未考虑过构造函数可能是实例的属性,但我认为下面的代码涵盖了这种罕见的场景。
// 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
分配给新对象。这个新对象永远不可能是全局对象,因为即使实现允许您设置全局对象,您仍然没有机会这样做。
您可以通过调用作为函数(呵呵)返回的函数来获取全局对象this
。
我的直觉是,在 ECMAScript 1.3 的规范中,当作为函数调用时具有定义行为的构造函数应该使用以下比较来区分它们是如何被调用的:
function MyClass () {
if ( this === (function () { return this; })() ) {
// called as a function
}
else {
// called as a constructor
}
}
无论如何,任何人都可以使用函数或构造函数的call
orapply
并设置this
为任何内容。但是这样,您可以避免“初始化”全局对象:
function MyClass () {
if ( this === (function () { return this; })() ) {
// Maybe the caller forgot the "new" keyword
return new MyClass();
}
else {
// initialize
}
}
1.宿主(又名实现)可能能够分辨出差异,如果它实现了与内部属性[[Call]]
和[[Construct]]
. 前者用于函数或方法表达式,后者用于new
表达式。
在我对http://packagesinjavascript.wordpress.com/的测试中,我发现测试 if (this == window) 在所有情况下都可以跨浏览器工作,所以这就是我最终使用的测试。
-Stijn
来自约翰·雷西格:
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
但是使用该instanceof
解决方案会在此示例中失败:
let inst = new x;
x.call(inst);
结合@TimDown 解决方案,WeakSet
如果您希望与旧的 ECMAScript 版本兼容以防止将属性放入实例中,则可以使用 ES6。好吧,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
运算符 of 被指定为:
11.8.6 instanceof 运算符
--- 产生式 RelationalExpression:RelationalExpression instanceof ShiftExpression 的计算方法如下:
--- 1. 计算 RelationalExpression。
--- 2. 调用 GetValue(Result(1))。
--- 3. 评估 ShiftExpression。
--- 4. 调用 GetValue(Result(3))。
--- 5. 如果 Result(4) 不是对象,则抛出TypeError异常。
--- 6. 如果 Result(4) 没有 [[HasInstance]] 方法,则抛出TypeError异常。
--- 7. 调用Result(4) 的[[HasInstance]] 方法,带参数Result(2)。
--- 8. 返回结果(7)。
15.3.5.3 [[HasInstance]] (V)
--- 假设 F 是一个 Function 对象。
--- 当 F 的 [[HasInstance]] 方法以值 V 被调用时,采取以下步骤:
--- 1. 如果 V 不是对象,则返回false。--- 2. 调用属性名为“prototype”
的F的[[Get]]方法。 --- 3. 令 O 为 Result(2)。 --- 4.如果O不是对象,抛出TypeError异常。 --- 5. 令 V 为 V 的 [[Prototype]] 属性的值。 --- 6. 如果 V 为 **null**,则返回false。 --- 7. 如果 O 和 V 指的是同一个对象,或者如果它们指的是相互连接的对象(13.1.2),则返回true。
--- 8. 转到步骤 5。
这意味着它将在转到其原型之后递归左侧值,直到它不是一个对象或直到它等于具有指定[[HasInstance]]
方法的右侧对象的原型。这意味着它将检查左侧是否是右侧的实例,但会消耗左侧的所有内部原型。
function x() {
if (this instanceof x) {
/* Probably invoked as constructor */
} else return 30;
}
也许我错了,但(以寄生虫为代价)以下代码似乎是一个解决方案:
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
虽然这个线程很古老,但我很惊讶没有人提到在严格模式下('use strict'
)一个函数的默认this
值是未定义的,而不是像以前那样设置为全局/窗口,所以要检查是否没有使用 new 只需测试falsey
值!this
- 例如:
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;
}
如果您按原样测试该函数this
,由于'use strict'
函数开头的指令,您将得到未定义的值。当然,如果已经开启了严格模式,那么如果你删除'use strict'
指令它不会改变,但如果你删除它,this
值将被设置为window
or global
。如果您使用new
调用该函数,则该this
值将与 instanceof 检查匹配(尽管如果您检查了其他内容,则 instance 是最后一个选项,因此不需要此检查,如果您想继承实例,则应避免)
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
then ,则假定它是一个有效实例并返回。
我相信解决方案是将您的 Constructor 函数转换为真正的 Constructor 函数及其原型 Constructor 的包装器(如果需要)。该方法将从 2009 年开始在 ES5 中工作,并且也可以在严格模式下工作。在下面的代码窗口中,我有一个使用模块模式的示例,将真正的构造函数及其原型的构造函数保存在一个闭包中,可以通过constructor(wrapper)内的范围访问。这是有效的,因为没有属性被添加到 Constructor(wrapper) 中的“this”关键字并且没有设置 Constructor(wrapper).prototype,默认情况下是Object;因此从Object.getpropertyNames返回的数组如果 new 关键字已与 Constructor(wrapper) 一起使用,则长度将等于 0。如果为真则返回新的 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;
};
};
})();
使用this instanceof arguments.callee
(可选地用arguments.callee
它所在的函数替换,这可以提高性能)来检查是否有东西被称为构造函数。不要使用this.constructor
,因为它很容易改变。
Tim Down 我认为是正确的。我认为一旦你到了你认为需要能够区分这两种调用模式的地步,那么你就不应该使用“ this
”关键字。this
是不可靠的,它可能是全局对象,也可能是一些完全不同的对象。事实是,具有这些不同激活模式的功能,其中一些按您的预期工作,另一些则完全疯狂,这是不可取的。我想也许你正试图解决这个问题。
有一种惯用的方法可以创建一个构造函数,无论它如何调用,它的行为都相同。无论是像 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
函数永远不会返回 type 的对象x
,因此(我认为)仅在使用关键字this instanceof x
调用该函数时才评估为 true 。new
缺点是这有效地搞砸了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 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
// ...
});
在问题的顶部,下面的代码将自动修复问题,以防在没有 new 的情况下调用函数。
function Car() {
if (!(this instanceof Car)) return new Car();
this.a = 1;
console.log("Called as Constructor");
}
let c1 = new Car();
console.log(c1);
这可以在不使用 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()