javascript - w3schools - math max apply




Uso de.apply() con el operador 'nuevo'. es posible? (20)

¡Esto funciona!

var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);

En JavaScript, quiero crear una instancia de objeto (a través del new operador), pero pasar un número arbitrario de argumentos al constructor. es posible?

Lo que quiero hacer es algo como esto (pero el siguiente código no funciona):

function Something(){
    // init stuff
}
function createSomething(){
    return new Something.apply(null, arguments);
}
var s = createSomething(a,b,c); // 's' is an instance of Something

La respuesta

De las respuestas aquí, quedó claro que no hay una forma .apply() llamar a .apply() con el new operador. Sin embargo, la gente sugirió una serie de soluciones realmente interesantes para el problema.

Mi solución preferida fue esta de Matthew Crumley (la he modificado para pasar la propiedad de los arguments ):

var createSomething = (function() {
    function F(args) {
        return Something.apply(this, args);
    }
    F.prototype = Something.prototype;

    return function() {
        return new F(arguments);
    }
})();

@Matthew Creo que es mejor arreglar la propiedad del constructor también.

// Invoke new operator with arbitrary arguments
// Holy Grail pattern
function invoke(constructor, args) {
    var f;
    function F() {
        // constructor returns **this**
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    f = new F();
    f.constructor = constructor;
    return f;
}

Aquí está mi versión de createSomething :

function createSomething() {
    var obj = {};
    obj = Something.apply(obj, arguments) || obj;
    obj.__proto__ = Something.prototype; //Object.setPrototypeOf(obj, Something.prototype); 
    return o;
}

En base a eso, traté de simular la new palabra clave de JavaScript:

//JavaScript 'new' keyword simulation
function new2() {
    var obj = {}, args = Array.prototype.slice.call(arguments), fn = args.shift();
    obj = fn.apply(obj, args) || obj;
    Object.setPrototypeOf(obj, fn.prototype); //or: obj.__proto__ = fn.prototype;
    return obj;
}

Lo probé y parece que funciona perfectamente bien para todos los escenarios. También funciona en constructores nativos como Date . Aquí hay algunas pruebas:

//test
new2(Something);
new2(Something, 1, 2);

new2(Date);         //"Tue May 13 2014 01:01:09 GMT-0700" == new Date()
new2(Array);        //[]                                  == new Array()
new2(Array, 3);     //[undefined × 3]                     == new Array(3)
new2(Object);       //Object {}                           == new Object()
new2(Object, 2);    //Number {}                           == new Object(2)
new2(Object, "s");  //String {0: "s", length: 1}          == new Object("s")
new2(Object, true); //Boolean {}                          == new Object(true)

Aquí hay una solución generalizada que puede llamar a cualquier constructor (excepto constructores nativos que se comportan de manera diferente cuando se les llama como funciones, como String , Number , Date , etc.) con una matriz de argumentos:

function construct(constructor, args) {
    function F() {
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}

Un objeto creado al llamar a la construct(Class, [1, 2, 3]) sería idéntico a un objeto creado con la new Class(1, 2, 3) .

También puedes hacer una versión más específica para no tener que pasar el constructor cada vez. Esto también es un poco más eficiente, ya que no necesita crear una nueva instancia de la función interna cada vez que la llame.

var createSomething = (function() {
    function F(args) {
        return Something.apply(this, args);
    }
    F.prototype = Something.prototype;

    return function(args) {
        return new F(args);
    }
})();

La razón para crear y llamar a la función anónima externa de esta manera es evitar que la función F contamine el espacio de nombres global. A veces se le llama el patrón del módulo.

[ACTUALIZAR]

Para aquellos que quieren usar esto en TypeScript, ya que TS da un error si F devuelve algo:

function construct(constructor, args) {
    function F() : void {
        constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}

Cualquier función (incluso un constructor) puede tomar un número variable de argumentos. Cada función tiene una variable de "argumentos" que se puede convertir a una matriz con [].slice.call(arguments) .

function Something(){
  this.options  = [].slice.call(arguments);

  this.toString = function (){
    return this.options.toString();
  };
}

var s = new Something(1, 2, 3, 4);
console.log( 's.options === "1,2,3,4":', (s.options == '1,2,3,4') );

var z = new Something(9, 10, 11);
console.log( 'z.options === "9,10,11":', (z.options == '9,10,11') );

Las pruebas anteriores producen el siguiente resultado:

s.options === "1,2,3,4": true
z.options === "9,10,11": true


Esta respuesta es un poco tarde, pero imagino que cualquiera que vea esto podría usarla. Hay una manera de devolver un nuevo objeto utilizando aplicar. Aunque requiere un pequeño cambio en su declaración de objeto.

function testNew() {
    if (!( this instanceof arguments.callee ))
        return arguments.callee.apply( new arguments.callee(), arguments );
    this.arg = Array.prototype.slice.call( arguments );
    return this;
}

testNew.prototype.addThem = function() {
    var newVal = 0,
        i = 0;
    for ( ; i < this.arg.length; i++ ) {
        newVal += this.arg[i];
    }
    return newVal;
}

testNew( 4, 8 ) === { arg : [ 4, 8 ] };
testNew( 1, 2, 3, 4, 5 ).addThem() === 15;

Para que la primera instrucción if funcione en la testNew , tienes que return this; en la parte inferior de la función. Así como un ejemplo con su código:

function Something() {
    // init stuff
    return this;
}
function createSomething() {
    return Something.apply( new Something(), arguments );
}
var s = createSomething( a, b, c );

Actualización: He cambiado mi primer ejemplo para sumar cualquier número de argumentos, en lugar de solo dos.


Este enfoque de constructor funciona con y sin la new palabra clave:

function Something(foo, bar){
  if (!(this instanceof Something)){
    var obj = Object.create(Something.prototype);
    return Something.apply(obj, arguments);
  }
  this.foo = foo;
  this.bar = bar;
  return this;
}

Se supone que es compatible con Object.create pero siempre puede realizar un Object.create si está admitiendo navegadores más antiguos. Vea la tabla de soporte en MDN aquí .

Aquí hay un JSBin para verlo en acción con la salida de la consola .


Gracias a las publicaciones aquí lo he usado de esta manera:

SomeClass = function(arg1, arg2) {
    // ...
}

ReflectUtil.newInstance('SomeClass', 5, 7);

e implementación:

/**
 * @param strClass:
 *          class name
 * @param optionals:
 *          constructor arguments
 */
ReflectUtil.newInstance = function(strClass) {
    var args = Array.prototype.slice.call(arguments, 1);
    var clsClass = eval(strClass);
    function F() {
        return clsClass.apply(this, args);
    }
    F.prototype = clsClass.prototype;
    return new F();
};

Mientras que los otros enfoques son viables, son excesivamente complejos. En Clojure generalmente creas una función que crea instancias de tipos / registros y usas esa función como el mecanismo para crear instancias. Traduciendo esto a JavaScript:

function Person(surname, name){
  this.surname = surname;
  this.name = name;
}

function person(surname, name){ 
  return new Person(surname, name);
}

Al adoptar este enfoque, evita el uso de new excepto como se describe anteriormente. Y esta función, por supuesto, no tiene problemas al trabajar con apply ni ninguna otra función funcional de programación.

var doe  = _.partial(person, "Doe");
var john = doe("John");
var jane = doe("Jane");

Al utilizar este enfoque, todos los constructores de tipo (por ejemplo, Person ) son constructores de vainilla, de no hacer nada. Solo pasas los argumentos y los asignas a propiedades del mismo nombre. Los detalles peludos van en la función de constructor (por ejemplo, person ).

No es una molestia tener que crear estas funciones constructoras adicionales, ya que de todos modos son una buena práctica. Pueden ser convenientes, ya que le permiten tener varias funciones de constructor con diferentes matices.


Puedes mover el material de inicio a un método separado del prototipo de Something :

function Something() {
    // Do nothing
}

Something.prototype.init = function() {
    // Do init stuff
};

function createSomething() {
    var s = new Something();
    s.init.apply(s, arguments);
    return s;
}

var s = createSomething(a,b,c); // 's' is an instance of Something

Respuesta modificada de @Matthew. Aquí puedo pasar cualquier número de parámetros para que funcione como de costumbre (no para una matriz). También 'Algo' no está codificado en:

function createObject( constr ) {   
  var args =  arguments;
  var wrapper =  function() {  
    return constr.apply( this, Array.prototype.slice.call(args, 1) );
  }

  wrapper.prototype =  constr.prototype;
  return  new wrapper();
}


function Something() {
    // init stuff
};

var obj1 =     createObject( Something, 1, 2, 3 );
var same =     new Something( 1, 2, 3 );

Si estás interesado en una solución basada en evaluación.

function createSomething() {
    var q = [];
    for(var i = 0; i < arguments.length; i++)
        q.push("arguments[" + i + "]");
    return eval("new Something(" + q.join(",") + ")");
}

Si su entorno es compatible con el operador de propagación de ECMA Script 2015 ( ... ) , simplemente puede usarlo así

function Something() {
    // init stuff
}

function createSomething() {
    return new Something(...arguments);
}

Nota: Ahora que se han publicado las especificaciones de ECMA Script 2015 y que la mayoría de los motores de JavaScript lo están implementando activamente, esta sería la forma preferida de hacerlo.

Puede consultar el soporte del operador Spread en algunos de los entornos principales, here .


Supongamos que tienes un constructor de elementos que absorbe todos los argumentos que le presentas:

function Items () {
    this.elems = [].slice.call(arguments);
}

Items.prototype.sum = function () {
    return this.elems.reduce(function (sum, x) { return sum + x }, 0);
};

Puede crear una instancia con Object.create () y luego .apply () con esa instancia:

var items = Object.create(Items.prototype);
Items.apply(items, [ 1, 2, 3, 4 ]);

console.log(items.sum());

Que cuando se ejecuta imprime 10 desde 1 + 2 + 3 + 4 == 10:

$ node t.js
10


Una versión mejorada de la respuesta de @ Matthew. Esta forma tiene los beneficios leves de rendimiento obtenidos al almacenar la clase temporal en un cierre, así como la flexibilidad de tener una función que se puede usar para crear cualquier clase

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

Esto se usaría llamando a applyCtor(class, [arg1, arg2, argn]);


Vea también cómo CoffeeScript lo hace.

s = new Something([a,b,c]...)

se convierte en:

var s;
s = (function(func, args, ctor) {
  ctor.prototype = func.prototype;
  var child = new ctor, result = func.apply(child, args);
  return Object(result) === result ? result : child;
})(Something, [a, b, c], function(){});

Soluciones de Matthew Crumley en CoffeeScript:

construct = (constructor, args) ->
    F = -> constructor.apply this, args
    F.prototype = constructor.prototype
    new F

o

createSomething = (->
    F = (args) -> Something.apply this, args
    F.prototype = Something.prototype
    return -> new Something arguments
)()

function FooFactory() {
    var prototype, F = function(){};

    function Foo() {
        var args = Array.prototype.slice.call(arguments),
            i;     
        for (i = 0, this.args = {}; i < args.length; i +=1) {
            this.args[i] = args[i];
        }
        this.bar = 'baz';
        this.print();

        return this;
    }

    prototype = Foo.prototype;
    prototype.print = function () {
        console.log(this.bar);
    };

    F.prototype = prototype;

    return Foo.apply(new F(), Array.prototype.slice.call(arguments));
}

var foo = FooFactory('a', 'b', 'c', 'd', {}, function (){});
console.log('foo:',foo);
foo.print();






constructor