w3schools method example javascript oop class inheritance constructor

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



jquery apply (30)

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


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.


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


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.


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



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}


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 .


Este one-liner debe hacerlo:

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


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.


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


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


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


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 .


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


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 solución revisada de la respuesta de @jordancpaul.

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


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


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 .