2009-12-24 9 views
24

gracias a las maravillosas respuestas a this question Entiendo cómo llamar a las funciones de JavaScript con varargs.javascript aplicar en el constructor, lanzando "parámetro formal mal formado"

Ahora estoy buscando a utilizar se aplican con un constructor

he encontrado alguna información interesante on this post.

pero mi código está lanzando errores

intento 1:

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

error:

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

intento 2: intento 1:

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

de error:

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

intento 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 

intento 3:

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

error:

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

tengo una solución para el momento:

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

esto funciona bastante bien, pero no es tan limpia y universal como me gustaría.

¿es posible en javascript llamar a un constructor con varargs?

Respuesta

17

Usted podría utilizar y aplicar un objeto vacío como el argumento this:

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

Pero esa solución no va a tener cuidado acerca de la cadena de prototipo.

Se puede crear un objeto Parser, usando el operador new, pero sin pasar argumentos, y luego usar apply volver a ejecutar la función constructora:

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

+1, fantástico gracias –

+0

Muy interesante. He jugado un poco con esto y funciona muy bien * en ciertos casos *. No todos los constructores apoyan este enfoque. 'Fecha', por ejemplo:' var date = new Date; Date.call (fecha, 1984, 3, 26) '. – davidchambers

+1

Function.apply (new Function(), arg) /* Es una solución de una línea */ – TERMtm

7

Puede explotar el hecho de que puede constructores cadena usando apply(...) para lograr esto, aunque esto requiere la creación de una clase de proxy.La función construct() a continuación le permite hacer:

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

la función construct():

function construct(klass, args) { 

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

    F.prototype = klass.prototype; 

    return new F(args); 

} 

algunos ejemplos de código que lo utiliza:

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(); 
+1

Esto no soluciona todos los casos. Debe devolver el valor de klass.apply en F(): 'función F() {return klass.apply (esto, argumentos [0]); } ' Esto maneja casos en los que las funciones de constructor devuelven el valor que se utilizará como instancia. –

+0

@MonroeThomas Buen punto, hemos actualizado. – mjs

11

Una mejor solución es crear una función constructora temporal , aplique el prototipo de la clase que desee (para garantizar que las cadenas de prototipos se conserven) y luego aplique el constructor manualmente. Esto evita una llamada al constructor dos veces innecesariamente ...

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

He probado el rendimiento y encontré que este método es, de hecho, un poco más lento en el casomuy simple. Sin embargo, solo se necesita la construcción de un único objeto Date() en el constructor para que sea más eficiente. Además, no olvide que algunos constructores pueden lanzar excepciones si no se pasan parámetros, por lo que esto también es más correcto.

Mi código de validación:

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

Los resultados:

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

Me gustaría decir que es mejor solo en el caso de que la función "ctor" sea costosa. Siendo la razón que esta implicación también implica dos funciones, solo que el tempCtor es una función vacía. La invocación call a ctor (ctor.apply) se ejecutaría como una función normal, solo con la palabra clave nueva tendría la sobrecarga de la construcción de un nuevo objeto. –

+0

Actualicé mi publicación para incluir algunas pruebas. Si bien es cierto que con constructores muy simples, este método es más lento, es trivial hacer que el constructor sea "caro" lo suficiente para garantizar este método. Además, como mencioné en mi actualización, este método mejorado maneja los constructores que arrojan excepciones donde los argumentos no son suministrados o son del tipo incorrecto. – jordancpaul

+0

actualicé mi publicación una vez más. Ahora es igual de rápido en el caso simple, y aún más rápido en el caso costoso. – jordancpaul

0

para completar @CMS solución y preservar la cadena de prototipo, se puede hacer esto:

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

Como nota al margen, no funcionará con IE 8-.

Cuestiones relacionadas