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

Javascript 如何检测函数是否作为构造函数调用?,javascript,constructor,Javascript,Constructor,给定一个函数: function x(arg) { return 30; } 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 x(arg) { return 30; }
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;
}
你可以用两种方式来称呼它:

result = x(4);
result = new x(4);
第一个返回30,第二个返回一个对象

如何检测函数本身内部调用函数的方式

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

var Z = new x(); 
Z.lolol = x; 
Z.lolol();
目前所有的解决方案都认为
Z.lolol()
将其称为构造函数。

1)您可以检查
这个构造函数。

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

2) 是的,在
new
context

两种方式中使用时,返回值被丢弃,在引擎盖下基本相同。您可以测试
this
的范围,也可以测试
this.constructor
的范围

如果将方法作为构造函数调用,则此
将是类的新实例;如果将方法作为方法调用,则此
将是方法的上下文对象。类似地,如果作为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
  }
}
注意:这个答案是在2008年写的,当时javascript从1999年起仍在ES3。从那时起,添加了许多新功能,因此现在有了更好的解决方案。这个答案是出于历史原因而保留的

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

function x() {
    if ( (this instanceof arguments.callee) ) {
      alert("called as constructor");
    } else {
      alert("called as function");
    }
}
更新 正如我们在下面的注释中指出的,如果您将构造函数分配给它所创建的同一个对象,那么上面的代码将不起作用。我从来没有写过这样的代码,也没有见过其他人这样做

克劳迪斯的例子:

var Z = new x();
Z.lolol = x;
Z.lolol();
let inst = new x;
x.call(inst);
通过向对象添加属性,可以检测对象是否已初始化

function x() {
    if ( (this instanceof arguments.callee && !this.hasOwnProperty("__ClaudiusCornerCase")) ) {
        this.__ClaudiusCornerCase=1;
        alert("called as constructor");
    } else {
        alert("called as function");
    }
}
如果删除添加的属性,即使上面的代码也会中断但是,您可以使用任何您喜欢的值覆盖它,包括
未定义的
,它仍然可以工作。
但是如果您删除它,它将中断

此时,ecmascript中没有用于检测函数是否作为构造函数调用的本机支持。这是迄今为止我想到的最接近的东西,除非您删除该属性,否则它应该可以工作。

在我的测试中,我发现测试if(This==窗口)在所有情况下都可以跨浏览器工作,因此我最终使用了它


-Stijn

使用
参数的此实例。被调用方
(可以选择将
参数.被调用方
替换为其所在的函数,以提高性能)检查是否调用了构造函数不要使用
这个构造函数,因为它很容易更改。

注意:现在在ES2015和更高版本中都可以这样做。请参阅。

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

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

  • 创建一个新对象
  • 将其内部[[Prototype]]属性分配给
    x的Prototype属性
  • 正常调用
    x
    ,将新对象作为
    this
  • 如果对
    x
    的调用返回了一个对象,则返回它,否则返回新对象
关于如何调用函数的任何有用信息都不会提供给正在执行的代码,因此在
x
内部可以测试的唯一内容是
这个
值,这里的所有答案都是这样做的。正如您所观察到的,当作为构造函数调用
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(y){
var isConstructor=false;

如果(x//的这个实例)我认为是正确的。我认为,一旦你认为你需要能够区分这两种调用模式,那么你就不应该使用“
this
”关键字。
这个
是不可靠的,它可能是全局对象,也可能是完全不同的对象。事实是,有一个具有这些不同激活模式的函数,其中一些按您的预期工作,另一些则执行完全疯狂的操作,这是不可取的。我想也许您正试图弄清楚这一点,因为那个

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

function Thing () {
   var that = Object.create(Thing.prototype);
   that.foo="bar";
   that.bar="baz";
   return that;
}
if(!Object.create) {
    Object.create = function(Function){
        // WebReflection Revision
       return function(Object){
           Function.prototype = Object;
           return new Function;
    }}(function(){});
}
其中Object.create是一个新的ecmascript 5标准方法,可以在常规javascript中实现,如下所示:

function Thing () {
   var that = Object.create(Thing.prototype);
   that.foo="bar";
   that.bar="baz";
   return that;
}
if(!Object.create) {
    Object.create = function(Function){
        // WebReflection Revision
       return function(Object){
           Function.prototype = Object;
           return new Function;
    }}(function(){});
}
Object.create将对象作为参数,并返回一个新对象,其中传入的对象作为其原型


但是,如果您确实试图根据调用方式使函数的行为有所不同,那么您就是一个坏人,不应该编写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"

在大多数情况下,我可能只检查instanceof。

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

但是,函数调用将
this
分配给全局对象,而构造函数将
this
    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();
    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");
            }
        });
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();
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());
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;
}
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"
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
    // ...
});
let inst = new x;
x.call(inst);
(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
})();
function x() {
    if (this instanceof x) {
        /* Probably invoked as constructor */
    } else return 30;
}
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; 
}
function ctor() { 'use strict';
  if (!this) return ctor.apply(Object.create(ctor.prototype), arguments);
  console.log([this].concat([].slice.call(arguments)));
  return this;
}
function Car() {

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

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

}
let c1 = new Car();
console.log(c1);
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; 
         };
    };
})();