javascript apply on constructor, throwing “malformed formal parameter”

那年仲夏 提交于 2019-12-04 08:16:45

问题


thanks to wonderful responses to this question I understand how to call javascript functions with varargs.

now I'm looking to use apply with a constructor

I found some interesting information on this post.

but my code is throwing errors

attempt 1:

var mid_parser = new Parser.apply(null, mid_patterns);

error:

TypeError: Function.prototype.apply called on incompatible [object Object]

attempt 2: attempt 1:

var mid_parser = new Parser.prototype.apply(null, mid_patterns);

error:

TypeError: Function.prototype.apply called on incompatible [object Object]

attempt 2:

function Parser()
{
    this.comparemanager = new CompareManager(arguments);
}

mid_patterns = [objA,objB,objC]
var mid_parser = new Parser();
Parser.constructor.apply(mid_parser, mid_patterns);

error:

syntax_model.js:91: SyntaxError: malformed formal parameter

attempt 3:

var mid_parser = Parser.apply(null, mid_patterns);

error :

TypeError: this.init is undefined // init is a function of Parser.prototype

I have a workaround for the time being:

function Parser()
{
    if(arguments.length) this.init.call(this,arguments); // call init only if arguments
}
Parser.prototype = {
   //...
   init: function()
   {
         this.comparemanager = new CompareManager(arguments);
   }
   //...
}

var normal parser = new Parser(objA,objB,objC);

mid_patterns = [objA,objB,objC]
var dyn_parser = new Parser();
dyn_parser.init.apply(dyn_parser, mid_patterns);

this works pretty well, but it's not as clean and universal as I'd like.

is it possible in javascript to call a constructor with varargs?


回答1:


You could use apply and pass an empty object as the this argument:

var mid_parser = {};
Parser.apply(mid_parser, mid_patterns);

But that solution will not take care about the prototype chain.

You could create a Parser object, using the new operator, but without passing arguments, and then use apply to re-run the constructor function:

var mid_parser = new Parser();
Parser.apply(mid_parser, mid_patterns);



回答2:


A better solution is to create a temporary constructor function, apply the prototype of the class that you want (to ensure prototype chains are preserved) and then apply the constructor manually. This prevents calling the constructor twice unnecessarily...

applySecond = function(){
    function tempCtor() {};
    return function(ctor, args){
        tempCtor.prototype = ctor.prototype;
        var instance = new tempCtor();
        ctor.apply(instance,args);
        return instance;
    }
}();

I tested the performance and found that this method is, in fact, a bit slower in the very simple case. However, it only takes the construction of a single Date() object in the constructor for this to be more efficient. Also, don't forget that some constructors may throw exceptions if there are no parameters passed, so this is also more correct.

My validation code:

var ExpensiveClass = function(arg0,arg1){
    this.arg0 = arg0;
    this.arg1 = arg1;
    this.dat = new Date();
}

var CheapClass = function(arg0,arg1){
    this.arg0 = arg0;
    this.arg1 = arg1;
}

applyFirst = function(ctor, args){
    var instance = new ctor();
    ctor.apply(instance, args);
    return instance;
}

applySecond = function(){
    function tempCtor() {};
    return function(ctor, args){
        tempCtor.prototype = ctor.prototype;
        var instance = new tempCtor();
        ctor.apply(instance,args);
        return instance;
    }
}();

console.time('first Expensive');
for(var i = 0; i < 10000; i++){
    test = applyFirst(ExpensiveClass ,['arg0','arg1']);
}
console.timeEnd('first Expensive');

console.time('second Expensive');
for(var i = 0; i < 10000; i++){
    test = applySecond(ExpensiveClass ,['arg0','arg1']);
}
console.timeEnd('second Expensive');

console.time('first Cheap');
for(var i = 0; i < 10000; i++){
    test = applyFirst(CheapClass,['arg0','arg1']);
}
console.timeEnd('first Cheap');

console.time('second Cheap');
for(var i = 0; i < 10000; i++){
    test = applySecond(CheapClass,['arg0','arg1']);
}
console.timeEnd('second Cheap');

The results:

first Expensive: 76ms
second Expensive: 66ms
first Cheap: 52ms
second Cheap: 52ms



回答3:


You can exploit the fact that you can chain constructors using apply(...) to achieve this, although this requires the creation of a proxy class. The construct() function below lets you do:

var f1 = construct(Foo, [2, 3]);
// which is more or less equivalent to
var f2 = new Foo(2, 3);

The construct() function:

function construct(klass, args) {

  function F() {
    return klass.apply(this, arguments[0]); 
  }; 

  F.prototype = klass.prototype; 

  return new F(args);

}

Some sample code that uses it:

function Foo(a, b) {
  this.a = a; this.b = b;
}

Foo.prototype.dump = function() {
  console.log("a = ", this.a);
  console.log("b = ", this.b);
};

var f = construct(Foo, [7, 9]);

f.dump();



回答4:


To complete @CMS solution and preserve the prototype chain, you can do this:

var mid_parser = {};
mid_parser.__proto__ = Parser.prototype;
Parser.apply(mid_parser, mid_patterns);

As a side note, it will not work with IE 8-.



来源:https://stackoverflow.com/questions/1959247/javascript-apply-on-constructor-throwing-malformed-formal-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!