javascript bind - Uso de.apply()con el operador 'nuevo'.es posible?




method w3schools (25)

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

Answers

Con Function.prototype.bind de ECMAScript5, las cosas se ponen bastante limpias:

function newCall(Cls) {
    return new (Function.prototype.bind.apply(Cls, arguments));
    // or even
    // return new (Cls.bind.apply(Cls, arguments));
    // if you know that Cls.bind has not been overwritten
}

Se puede utilizar de la siguiente manera:

var s = newCall(Something, a, b, c);

o incluso directamente:

var s = new (Function.prototype.bind.call(Something, null, a, b, c));

var s = new (Function.prototype.bind.apply(Something, [null, a, b, c]));

Esta y la solución basada en eval son las únicas que siempre funcionan, incluso con constructores especiales como Date :

var date = newCall(Date, 2012, 1);
console.log(date instanceof Date); // true

editar

Un poco de explicación: necesitamos ejecutar una new función en una función que tenga un número limitado de argumentos. El método de bind nos permite hacerlo así:

var f = Cls.bind(anything, arg1, arg2, ...);
result = new f();

El parámetro de anything no importa mucho, ya que la new palabra clave restablece el contexto de f . Sin embargo, se requiere por razones sintácticas. Ahora, para la llamada de bind : Necesitamos pasar un número variable de argumentos, así que esto hace el truco:

var f = Cls.bind.apply(Cls, [anything, arg1, arg2, ...]);
result = new f();

Vamos a envolver eso en una función. Cls se pasa como argumento 0, por lo que será nuestra anything .

function newCall(Cls /*, arg1, arg2, ... */) {
    var f = Cls.bind.apply(Cls, arguments);
    return new f();
}

En realidad, la variable f temporal no es necesaria en absoluto:

function newCall(Cls /*, arg1, arg2, ... */) {
    return new (Cls.bind.apply(Cls, arguments))();
}

Por último, debemos asegurarnos de que el bind es realmente lo que necesitamos. ( Cls.bind puede haber sido sobrescrito). Reemplácelo por Function.prototype.bind , y obtendremos el resultado final como se indica arriba.


Acabo de encontrar este problema y lo resolví así:

function instantiate(ctor) {
    switch (arguments.length) {
        case 1: return new ctor();
        case 2: return new ctor(arguments[1]);
        case 3: return new ctor(arguments[1], arguments[2]);
        case 4: return new ctor(arguments[1], arguments[2], arguments[3]);
        //...
        default: throw new Error('instantiate: too many parameters');
    }
}

function Thing(a, b, c) {
    console.log(a);
    console.log(b);
    console.log(c);
}

var thing = instantiate(Thing, 'abc', 123, {x:5});

Sí, es un poco feo, pero resuelve el problema, y ​​es muy simple.


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

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

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)

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


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

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

function createSomething() {
    var args = Array.prototype.concat.apply([null], arguments);
    return new (Function.prototype.bind.apply(Something, args));
}

Si su navegador de destino no es compatible con ECMAScript 5 Function.prototype.bind , el código no funcionará. Aunque no es muy probable, ver tabla de compatibilidad .


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(",") + ")");
}

Solución sin ES6 o polyfills:

var obj = _new(Demo).apply(["X", "Y", "Z"]);


function _new(constr)
{
    function createNamedFunction(name)
    {
        return (new Function("return function " + name + "() { };"))();
    }

    var func = createNamedFunction(constr.name);
    func.prototype = constr.prototype;
    var self = new func();

    return { apply: function(args) {
        constr.apply(self, args);
        return self;
    } };
}

function Demo()
{
    for(var index in arguments)
    {
        this['arg' + (parseInt(index) + 1)] = arguments[index];
    }
}
Demo.prototype.tagged = true;


console.log(obj);
console.log(obj.tagged);


salida

Demo {arg1: "X", arg2: "Y", arg3: "Z"}


... o forma más corta:

var func = new Function("return function " + Demo.name + "() { };")();
func.prototype = Demo.prototype;
var obj = new func();

Demo.apply(obj, ["X", "Y", "Z"]);


editar:
Creo que esta podría ser una buena solución:

this.forConstructor = function(constr)
{
    return { apply: function(args)
    {
        let name = constr.name.replace('-', '_');

        let func = (new Function('args', name + '_', " return function " + name + "() { " + name + "_.apply(this, args); }"))(args, constr);
        func.constructor = constr;
        func.prototype = constr.prototype;

        return new func(args);
    }};
}

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 .


@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;
}

Este one-liner debe hacerlo:

new (Function.prototype.bind.apply(Something, [null].concat(arguments)));


En realidad el método más simple es:

function Something (a, b) {
  this.a = a;
  this.b = b;
}
function createSomething(){
    return Something;
}
s = new (createSomething())(1, 2); 
// s == Something {a: 1, b: 2}


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

No puede llamar a un constructor con un número variable de argumentos como quiera con el new operador.

Lo que puedes hacer es cambiar el constructor ligeramente. En lugar de:

function Something() {
    // deal with the "arguments" array
}
var obj = new Something.apply(null, [0, 0]);  // doesn't work!

Haga esto en su lugar:

function Something(args) {
    // shorter, but will substitute a default if args.x is 0, false, "" etc.
    this.x = args.x || SOME_DEFAULT_VALUE;

    // longer, but will only put in a default if args.x is not supplied
    this.x = (args.x !== undefined) ? args.x : SOME_DEFAULT_VALUE;
}
var obj = new Something({x: 0, y: 0});

O si debes usar una matriz:

function Something(args) {
    var x = args[0];
    var y = args[1];
}
var obj = new Something([0, 0]);

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.



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(){});

Sí podemos, javascript es más de un prototype inheritance en la naturaleza.

function Actor(name, age){
  this.name = name;
  this.age = age;
}

Actor.prototype.name = "unknown";
Actor.prototype.age = "unknown";

Actor.prototype.getName = function() {
    return this.name;
};

Actor.prototype.getAge = function() {
    return this.age;
};

cuando creamos un objeto con " new ", nuestro objeto creado INHERITS getAge (), pero si usamos apply(...) or call(...) para llamar a Actor, entonces estamos pasando un objeto para "this" pero el objeto que pasamos WON'T heredará de Actor.prototype

a menos que pasemos directamente aplicar o llamar Actor.prototype pero luego ... "esto" apuntaría a "Actor.prototype" y este.nombre escribiría a: Actor.prototype.name . Esto afecta a todos los demás objetos creados con Actor... ya que sobrescribimos el prototipo en lugar de la instancia

var rajini = new Actor('Rajinikanth', 31);
console.log(rajini);
console.log(rajini.getName());
console.log(rajini.getAge());

var kamal = new Actor('kamal', 18);
console.log(kamal);
console.log(kamal.getName());
console.log(kamal.getAge());

Probemos con apply

var vijay = Actor.apply(null, ["pandaram", 33]);
if (vijay === undefined) {
    console.log("Actor(....) didn't return anything 
           since we didn't call it with new");
}

var ajith = {};
Actor.apply(ajith, ['ajith', 25]);
console.log(ajith); //Object {name: "ajith", age: 25}
try {
    ajith.getName();
} catch (E) {
    console.log("Error since we didn't inherit ajith.prototype");
}
console.log(Actor.prototype.age); //Unknown
console.log(Actor.prototype.name); //Unknown

Al pasar Actor.prototype a Actor.call() como primer argumento, cuando se ejecuta la función Actor (), ejecuta this.name=name , ya que "this" apuntará a Actor.prototype , this.name=name; means Actor.prototype.name=name; this.name=name; means Actor.prototype.name=name;

var simbhu = Actor.apply(Actor.prototype, ['simbhu', 28]);
if (simbhu === undefined) {
    console.log("Still undefined since the function didn't return anything.");
}
console.log(Actor.prototype.age); //simbhu
console.log(Actor.prototype.name); //28

var copy = Actor.prototype;
var dhanush = Actor.apply(copy, ["dhanush", 11]);
console.log(dhanush);
console.log("But now we've corrupted Parent.prototype in order to inherit");
console.log(Actor.prototype.age); //11
console.log(Actor.prototype.name); //dhanush

Volviendo a la pregunta original sobre cómo usar un new operator with apply , aquí está mi opinión ...

Function.prototype.new = function(){
    var constructor = this;
    function fn() {return constructor.apply(this, args)}
    var args = Array.prototype.slice.call(arguments);
    fn.prototype = this.prototype;
    return new fn
};

var thalaivar = Actor.new.apply(Parent, ["Thalaivar", 30]);
console.log(thalaivar);

Uso lo siguiente y funciona muy bien !!!

window.open(url, '_blank').focus();




javascript oop class inheritance constructor