如何使用 call 或 apply 调用 javascript 构造函数?[复制]

How can I call a javascript constructor using call or apply? [duplicate]

提问人:fadedbee 提问时间:7/29/2010 最后编辑:ROMANIA_engineerfadedbee 更新时间:7/23/2017 访问量:54460

问:

我怎样才能推广下面的函数来接受 N 个参数?(使用调用或应用?

有没有一种编程方法可以将参数应用于“新”?我不希望构造函数被视为普通函数。

/**
 * 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");
});
JavaScript 调用 应用

评论

1赞 Eliran Malka 8/28/2013
本·纳德尔(Ben Nadel)对此进行了广泛的报道

答:

50赞 James 7/29/2010 #1

试试这个:

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;

    }
}

评论

1赞 fadedbee 7/29/2010
谢谢,这适用于测试代码。它的行为是否与新的相同?(即没有令人讨厌的陷阱可找。
2赞 Jason Orendorff 12/13/2011
该行为与 new 相同,除了一些奇怪的函数,例如 ;和代理(为下一个版本的 ECMAScript 提出的一个功能,目前仅在 Firefox 中受支持——您可以暂时忽略代理)。Date
3赞 Xose Lluis 1/22/2013
不错的解决方案。只需添加一个,您就可以避免使用 Temp 函数并使用 ES5 的 Object.create 重写前 3 行: var inst = Object.create(Constructor.prototype);
1赞 backus 9/6/2014
看起来这在 Chrome 中失败了(我在 OS X 37.0.2062.94 上使用 10.9.4 版)产生 .看起来这是一个特定的情况(很可能是其他一些我不知道的对象)。演示:jsfiddle.net/yepygdw9XMLHttpRequestTypeError: Failed to construct 'XMLHttpRequest': Please use the 'new' operator, this DOM object constructor cannot be called as a function.XMLHttpRequest
1赞 Barney 9/11/2014
匪夷所思。我不认为有人已经找到了一种扩展它以允许更多语义调试的方法?我试过了,但这是非法的()是只读的。目前调试非常困难,因为一切都是 ,我必须查询实例才能找出它们的实际情况。Temp.name = Constructor.namenameTemp__proto__
16赞 Jason Orendorff 12/13/2011 #2

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

function applyConstructor(ctor, args) {
    var a = [];
    for (var i = 0; i < args.length; i++)
        a[i] = 'args[' + i + ']';
    return eval('new ctor(' + a.join() + ')');
}

更新:一旦 ES6 得到广泛支持,你就可以这样写了:

function applyConstructor(ctor, args) {
    return new ctor(...args);
}

...但您不需要这样做,因为标准库函数完全符合您的要求!Reflect.construct()

评论

18赞 event_jr 2/12/2012
-1 用于 eval
0赞 Dave Stewart 7/16/2012
这也不适用于复杂的参数,因为参数被转换为字符串: var circle = new Circle(new Point(10, 10), 10);[对象点 x=10 y=10], 10
2赞 Jason Orendorff 7/18/2012
它工作正常。参数不会转换为字符串。试试吧。
6赞 Steffen Brem 11/4/2012
谢谢伙计,这对我来说是最好的。eval 还不错,如果你知道你做什么 eval 会非常有用。
98赞 kybernetikos 1/17/2013 #3

这是你如何做到的:

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]);

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

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

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

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

function neu(constructor) {
    // http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2
    var instance = Object.create(constructor.prototype);
    var result = constructor.apply(instance, Array.prototype.slice.call(arguments, 1));

    // The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
    return (result !== null && typeof result === 'object') ? result : instance;
}

function Person(first, last) {this.first = first;this.last = last};
Person.prototype.hi = function(){console.log(this.first, this.last);};

var p = neu(Person, "Neo", "Anderson");

现在,你当然可以像往常一样做或继续。.apply.call.bindneu

例如:

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

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

评论

5赞 mgol 2/8/2013
我很惊讶你没有得到任何投票。基于创建单独函数并更改其原型的解决方案具有更改字段的缺点,而与 组合可以保留它。constructorbindapply
0赞 Dale Anderson 10/25/2013
这很整洁,但在 IE8 及以下版本中不受支持。
2赞 kybernetikos 10/25/2013
没错,ie8 不是 ecmascript5 浏览器(我确实提到过)。
1赞 Creynders 9/12/2014
@kybernetikos 使用下划线可以创建 ES4 兼容版本: jsbin.com/xekaxu/1 如果您愿意,请随时将其添加到您的 anwer 中
1赞 kybernetikos 3/12/2015
@rupps它是要绑定的第一个参数,如果以正常方式调用函数,它将是函数的“this”。正如我们计划用 来称呼它一样,它不是特别相关,所以我在那里将其设置为 null。调用示例中实际上也有一个额外的参数,但是由于我们在参数列表的开头有一个额外的参数(函数本身),因此可以重用它。这确实意味着,在这个调用示例中,如果你只调用绑定函数而不 ,则内部将是函数本身,但它节省了我们创建一个新数组的时间。newnewthis
4赞 Fuerteflojo 2/4/2013 #4

另一种方法,需要修改被调用的实际构造函数,但对我来说似乎比使用 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

评论

0赞 Delta 7/22/2013
这对我来说是最好的,干净而优雅的解决方案。
0赞 Barney 9/10/2014
检查非常酷,但它阻止了构造函数组合(即可扩展构造函数):this instanceof Constructorfunction Foo(){}; function Bar(){ Foo.call(this); }
0赞 1j01 8/17/2015
@Barney 如果 = ,则检查应该有效。Bar.prototypeFooinstanceof
1赞 danyg 4/3/2013 #5

对于这种情况,有一个可回收的解决方案。对于要使用 apply 或 call 方法调用的每个类,必须在 convertToAllowApply('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);
3赞 Louis 5/14/2013 #6

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

如果可用,那么使用它是一个更好的选择。Node.js,使用 Object.create 可以生成更快的代码。下面是如何使用的示例:Object.createObject.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

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

  1. 版本:1 秒的测试套件。eval
  2. 使用 JSON 版本的测试套件,使用临时构造函数:5 秒。
  3. 具有 JSON 版本的测试套件,使用:1 秒。Object.create

测试套件创建多个树。我计算出,当测试套件运行时,我的函数被调用了大约 125,000 次。applytoConstructor

6赞 Ghasem Kiani 9/20/2014 #7

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

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

评论

0赞 Mark K Cowan 10/22/2014
好!­­­­­­­­­­­­