如何使用call或apply调用javascript构造函数?

如何使用call或apply调用javascript构造函数?,javascript,call,apply,Javascript,Call,Apply,如何将下面的函数推广到N个参数?(使用call或apply?) 是否有一种编程方式将参数应用于“new”?我不希望构造函数被当作普通函数对待 /** * This higher level function takes a constructor and arguments * and returns a function, which when called will return the * lazily constructed value. * * All the argum

如何将下面的函数推广到N个参数?(使用call或apply?)

是否有一种编程方式将参数应用于“new”?我不希望构造函数被当作普通函数对待

/**
 * This higher level function takes a constructor and arguments
 * and returns a function, which when called will return the 
 * lazily constructed value.
 * 
 * All the arguments, except the first are pased to the constructor.
 * 
 * @param {Function} constructor
 */ 

function conthunktor(Constructor) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        console.log(args);
        if (args.length === 0) {
            return new Constructor();
        }
        if (args.length === 1) {
            return new Constructor(args[0]);
        }
        if (args.length === 2) {
            return new Constructor(args[0], args[1]);
        }
        if (args.length === 3) {
            return new Constructor(args[0], args[1], args[2]);
        }
        throw("too many arguments");    
    }
}
qUnit测试:

test("conthunktorTest", function() {
    function MyConstructor(arg0, arg1) {
        this.arg0 = arg0;
        this.arg1 = arg1;
    }
    MyConstructor.prototype.toString = function() {
        return this.arg0 + " " + this.arg1;
    }

    var thunk = conthunktor(MyConstructor, "hello", "world");
    var my_object = thunk();
    deepEqual(my_object.toString(), "hello world");
});
试试这个:

function conthunktor(Constructor) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {

         var Temp = function(){}, // temporary constructor
             inst, ret; // other vars

         // Give the Temp constructor the Constructor's prototype
         Temp.prototype = Constructor.prototype;

         // Create a new instance
         inst = new Temp;

         // Call the original Constructor with the temp
         // instance as its context (i.e. its 'this' value)
         ret = Constructor.apply(inst, args);

         // If an object has been returned then return it otherwise
         // return the original instance.
         // (consistent with behaviour of the new operator)
         return Object(ret) === ret ? ret : inst;

    }
}

此功能在所有情况下都与
new
相同。不过,它可能会比999的答案慢很多,所以只有在你真的需要的时候才使用它

function applyConstructor(ctor, args) {
    var a = [];
    for (var i = 0; i < args.length; i++)
        a[i] = 'args[' + i + ']';
    return eval('new ctor(' + a.join() + ')');
}
…但您不需要这样做,因为标准库函数
Reflect.construct()
正是您所需要的

这就是你的做法:

function applyToConstructor(constructor, argArray) {
    var args = [null].concat(argArray);
    var factoryFunction = constructor.bind.apply(constructor, args);
    return new factoryFunction();
}

var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);
打电话稍微容易一点

function callConstructor(constructor) {
    var factoryFunction = constructor.bind.apply(constructor, arguments);
    return new factoryFunction();
}

var d = callConstructor(Date, 2008, 10, 8, 00, 16, 34, 254);
您可以使用以下任一项创建工厂函数:

var dateFactory = applyToConstructor.bind(null, Date)
var d = dateFactory([2008, 10, 8, 00, 16, 34, 254]);

它将与任何构造函数一起工作,而不仅仅是内置的或可以兼作函数(如Date)的构造函数

但是,它确实需要Ecmascript 5.bind函数。垫片可能无法正常工作

另一种不同的方法是创建内置
new
的函数版本,这与其他一些答案的风格更相似。这不适用于所有内置项(如Date)

现在,您当然可以像往常一样在
neu
上执行
。应用
。调用
。绑定

例如:

var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");

我觉得我给出的第一个解决方案更好,因为它不依赖于您正确地复制内置的语义,而且它可以正确地与内置一起工作。

另一种方法,需要修改被调用的实际构造函数,但对我来说似乎比使用eval()更干净或者在构造链中引入一个新的虚拟函数。。。保持你的conthunktor功能像

function conthunktor(Constructor) {
  // Call the constructor
  return Constructor.apply(null, Array.prototype.slice.call(arguments, 1));
}
并修改正在调用的构造函数

function MyConstructor(a, b, c) {
  if(!(this instanceof MyConstructor)) {
    return new MyConstructor(a, b, c);
  }
  this.a = a;
  this.b = b;
  this.c = c;
  // The rest of your constructor...
}
因此,您可以尝试:

var myInstance = conthunktor(MyConstructor, 1, 2, 3);

var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6

对于这种情况,有一个可重复使用的解决方案。对于要使用apply或call方法调用的每个类,必须在转换为AllowApply(“classNameInString”)之前调用;该类必须位于同一个Scoope o全局Scoope中(例如,我不尝试发送ns.className…)

代码如下:

function convertToAllowApply(kName){
    var n = '\n', t = '\t';
    var scrit = 
        'var oldKlass = ' + kName + ';' + n +
        kName + '.prototype.__Creates__ = oldKlass;' + n +

        kName + ' = function(){' + n +
            t + 'if(!(this instanceof ' + kName + ')){'+ n +
                t + t + 'obj = new ' + kName + ';'+ n +
                t + t + kName + '.prototype.__Creates__.apply(obj, arguments);'+ n +
                t + t + 'return obj;' + n +
            t + '}' + n +
        '}' + n +
        kName + '.prototype = oldKlass.prototype;';

    var convert = new Function(scrit);

    convert();
}

// USE CASE:

myKlass = function(){
    this.data = Array.prototype.slice.call(arguments,0);
    console.log('this: ', this);
}

myKlass.prototype.prop = 'myName is myKlass';
myKlass.prototype.method = function(){
    console.log(this);
}

convertToAllowApply('myKlass');

var t1 = myKlass.apply(null, [1,2,3]);
console.log('t1 is: ', t1);

如果
Object.create
不可用,使用临时构造函数似乎是最好的解决方案

如果
Object.create
可用,那么使用它是一个更好的选择在Node.js上,使用
Object.create
生成更快的代码。
下面是如何使用
Object.create
的示例:

function applyToConstructor(ctor, args) {
    var new_obj = Object.create(ctor.prototype);
    var ctor_ret = ctor.apply(new_obj, args);

    // Some constructors return a value; make sure to use it!
    return ctor_ret !== undefined ? ctor_ret: new_obj;
}
(显然,
args
参数是要应用的参数列表。)

我有一段代码,最初使用
eval
读取另一个工具创建的数据。(是的,
eval
是邪恶的。)这将实例化一个包含数百到数千个元素的树。基本上,JavaScript引擎负责解析和执行一组
new…(…)
表达式。我将系统转换为解析JSON结构,这意味着我必须让代码确定为树中的每种类型的对象调用哪个构造函数。当我在测试套件中运行新代码时,我惊讶地看到相对于
eval
版本的速度大幅降低

  • 测试套件,具有
    eval
    版本:1秒。
  • JSON版本的测试套件,使用临时构造函数:5秒。
  • 用JSON版本测试套件,使用
    对象。创建
    1秒。

  • 测试套件创建多个树。我计算了我的
    applytoConstructor
    函数在运行测试套件时被调用了大约125000次。

    在ECMAScript 6中,您可以使用扩展运算符将带有新关键字的构造函数应用于参数数组:

    var dateFields = [2014, 09, 20, 19, 31, 59, 999];
    var date = new Date(...dateFields);
    console.log(date);  // Date 2014-10-20T15:01:59.999Z
    

    谢谢,这对测试代码有效。它的行为和新的一样吗?(也就是说,找不到令人讨厌的gotchas。)除了一些古怪的函数,如
    Date
    ,行为与new相同;和代理(ECMAScript的下一个版本建议使用的功能,目前只有Firefox支持,您现在可以忽略代理)。这是一个不错的解决方案。只需添加一项,就可以避免使用Temp函数,并使用ES5的Object.create:var inst=Object.create(Constructor.prototype)重写前3行;Chrome中的
    XMLHttpRequest
    (我使用的是OS X 10.9.4上的版本37.0.2062.94)似乎失败了,导致
    TypeError:构造“XMLHttpRequest”失败:请使用“new”操作符,此DOM对象构造函数不能作为函数调用。
    。看起来这是
    XMLHttpRequest
    的一个特例(很可能还有一些我不知道的其他对象)。演示:太棒了。我想没有人找到一种方法来扩展它以允许更多的语义调试?我尝试了
    Temp.name=Constructor.name
    ,但这是非法的(
    name
    )是只读的。目前调试非常困难,因为一切都是
    Temp
    ,我必须查询实例的
    \uuuu proto\uuuu
    ,以了解它们的实际情况。-1对于evalth的使用,它也不适用于复杂的参数,因为参数被转换为字符串:var circle=new circle(new Point(10,10),10);//[对象点x=10 y=10],10它工作正常。参数不会转换为字符串。试试看。谢谢你,老兄,这个对我来说是最好的。如果你知道你在做什么的话,评估也没那么糟糕。我很惊讶你没有得到任何投票支持。基于创建单独函数并更改其原型的解决方案的缺点是更改
    构造函数
    字段,而将
    bind
    apply
    相结合则可以保留它。这很简洁,但IE8及以下版本不支持它。没错,IE8不是ECMAScript 5浏览器(我确实提到过)@kybernetikos使用下划线可以创建与ES4兼容的版本:如果需要,请随时将其添加到anwer中want@rupps这是第一个论点
    function convertToAllowApply(kName){
        var n = '\n', t = '\t';
        var scrit = 
            'var oldKlass = ' + kName + ';' + n +
            kName + '.prototype.__Creates__ = oldKlass;' + n +
    
            kName + ' = function(){' + n +
                t + 'if(!(this instanceof ' + kName + ')){'+ n +
                    t + t + 'obj = new ' + kName + ';'+ n +
                    t + t + kName + '.prototype.__Creates__.apply(obj, arguments);'+ n +
                    t + t + 'return obj;' + n +
                t + '}' + n +
            '}' + n +
            kName + '.prototype = oldKlass.prototype;';
    
        var convert = new Function(scrit);
    
        convert();
    }
    
    // USE CASE:
    
    myKlass = function(){
        this.data = Array.prototype.slice.call(arguments,0);
        console.log('this: ', this);
    }
    
    myKlass.prototype.prop = 'myName is myKlass';
    myKlass.prototype.method = function(){
        console.log(this);
    }
    
    convertToAllowApply('myKlass');
    
    var t1 = myKlass.apply(null, [1,2,3]);
    console.log('t1 is: ', t1);
    
    function applyToConstructor(ctor, args) {
        var new_obj = Object.create(ctor.prototype);
        var ctor_ret = ctor.apply(new_obj, args);
    
        // Some constructors return a value; make sure to use it!
        return ctor_ret !== undefined ? ctor_ret: new_obj;
    }
    
    var dateFields = [2014, 09, 20, 19, 31, 59, 999];
    var date = new Date(...dateFields);
    console.log(date);  // Date 2014-10-20T15:01:59.999Z