Uso di .apply () con l’operatore ‘new’. È ansible?

In JavaScript, voglio creare un’istanza di object (tramite il new operatore), ma passare un numero arbitrario di argomenti al costruttore. È ansible?

Quello che voglio fare è qualcosa di simile (ma il codice sotto non funziona):

 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 risposta

Dalle risposte qui, è diventato chiaro che non esiste un modo integrato per chiamare .apply() con il new operatore. Tuttavia, le persone hanno suggerito una serie di soluzioni davvero interessanti al problema.

La mia soluzione preferita era quella di Matthew Crumley (l’ho modificata per passare la proprietà arguments ):

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

Con Function.prototype.bind di ECMAScript5 le cose diventano abbastanza pulite:

 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 } 

Può essere usato come segue:

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

o anche direttamente:

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

Questa e la soluzione basata su eval sono le uniche che funzionano sempre, anche con costruttori speciali come Date :

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

modificare

Un po ‘di spiegazione: dobbiamo eseguire una new funzione su una funzione che richiede un numero limitato di argomenti. Il metodo bind ci consente di farlo in questo modo:

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

Il parametro anything non ha importanza, poiché la new parola chiave reimposta il contesto di f . Tuttavia, è necessario per motivi sintattici. Ora, per la chiamata di bind : dobbiamo passare un numero variabile di argomenti, quindi questo è il trucco:

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

Diamo questo in una funzione. Cls è passato come arugmento 0, quindi sarà il nostro anything .

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

In realtà, la variabile f temporanea non è affatto necessaria:

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

Infine, dovremmo assicurarci che il bind sia davvero ciò di cui abbiamo bisogno. ( Cls.bind potrebbe essere stato sovrascritto). Quindi sostituirlo con Function.prototype.bind , e otteniamo il risultato finale come sopra.

Ecco una soluzione generalizzata che può chiamare qualsiasi costruttore (tranne i costruttori nativi che si comportano diversamente quando chiamati come funzioni, come String , Number , Date , ecc.) Con una matrice di argomenti:

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

Un object creato chiamando construct(Class, [1, 2, 3]) sarebbe identico a un object creato con una new Class(1, 2, 3) .

Puoi anche creare una versione più specifica in modo da non dover passare il costruttore ogni volta. Anche questo è leggermente più efficiente, poiché non è necessario creare una nuova istanza della funzione interna ogni volta che la si chiama.

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

Il motivo per cui si crea e chiama la funzione anonima esterna è quello di impedire alla funzione F di inquinare lo spazio dei nomi globale. A volte viene chiamato il modello del modulo.

[AGGIORNARE]

Per coloro che vogliono usare questo in TypeScript, poiché TS dà un errore se F restituisce qualcosa:

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

Se il tuo ambiente supporta l’operatore di diffusione di ECMA Script 2015 ( ... ) , puoi semplicemente usarlo in questo modo

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

Nota: ora che le specifiche di ECMA Script 2015 sono pubblicate e la maggior parte dei motori JavaScript la stanno implementando triggersmente, questo sarebbe il modo preferito per farlo.

Puoi controllare il supporto dell’operatore di Spread in alcuni degli ambienti principali, qui .

Supponiamo che tu abbia un costruttore di Elementi che strappa su tutti gli argomenti che gli lanci:

 function Items () { this.elems = [].slice.call(arguments); } Items.prototype.sum = function () { return this.elems.reduce(function (sum, x) { return sum + x }, 0); }; 

È ansible creare un’istanza con Object.create () e poi .apply () con tale istanza:

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

Che quando si stampa stampa 10 da 1 + 2 + 3 + 4 == 10:

 $ node t.js 10 

In ES6, Reflect.construct() è abbastanza conveniente:

 Reflect.construct(F, args) 

@Matthew penso che sia meglio correggere anche la proprietà del costruttore.

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

Una versione migliorata della risposta di @ Matthew. Questa forma ha i piccoli vantaggi prestazionali ottenuti memorizzando la class temp in una chiusura, così come la flessibilità di avere una funzione che può essere utilizzata per creare qualsiasi class

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

Questo sarebbe usato chiamando applyCtor(class, [arg1, arg2, argn]);

Potresti spostare le cose di init in un metodo separato del prototipo di 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 

Questa risposta è un po ‘in ritardo, ma ho pensato che chiunque lo veda potrebbe essere in grado di usarlo. C’è un modo per restituire un nuovo object usando apply. Anche se richiede una piccola modifica alla dichiarazione dell’object.

 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; 

Per la prima istruzione if a lavorare in testNew è necessario return this; nella parte inferiore della funzione. Quindi, ad esempio con il tuo codice:

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

Aggiornamento: ho modificato il mio primo esempio per sumre qualsiasi numero di argomenti, anziché solo due.

Ho appena trovato questo problema e l’ho risolto in questo modo:

 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ì, è un po ‘brutto, ma risolve il problema, ed è semplicemente semplice.

se sei interessato a una soluzione basata su eval

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

Vedi anche come lo fa CoffeeScript.

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

diventa:

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

Funziona!

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

Questo approccio del costruttore funziona sia con che senza la new parola chiave:

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

Presuppone il supporto per Object.create ma puoi sempre Object.create polyfill se stai supportando i browser più vecchi. Vedi la tabella di supporto su MDN qui .

Ecco un JSBin per vederlo in azione con l’output della console .

Non è ansible chiamare un costruttore con un numero variabile di argomenti come si desidera con il new operatore.

Quello che puoi fare è cambiare leggermente il costruttore. Invece di:

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

Fatelo invece:

 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 se è necessario utilizzare un array:

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

Le soluzioni di Matthew Crumley in 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 createSomething() { var args = Array.prototype.concat.apply([null], arguments); return new (Function.prototype.bind.apply(Something, args)); } 

Se il tuo browser di destinazione non supporta ECMAScript 5 Function.prototype.bind , il codice non funzionerà. Tuttavia, non è molto probabile vedere la tabella delle compatibilità .

modificato @ risposta MATTINI. Qui posso passare qualsiasi numero di parametri per funzionare come al solito (non array). Inoltre ‘Something’ non è codificato in:

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

Questo one-liner dovrebbe farlo:

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

Soluzione senza ES6 o polyfill:

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

produzione

Demo {arg1: “X”, arg2: “Y”, arg3: “Z”}

… o “più breve”:

 var func = new Function("return function " + Demo.name + "() { };")(); func.prototype = Demo.prototype; var obj = new func(); Demo.apply(obj, ["X", "Y", "Z"]); 

modificare:
Penso che questa potrebbe essere una buona soluzione:

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

È anche interessante vedere come è stato risolto il problema del riutilizzo del costruttore temporaneo F() utilizzando arguments.callee , ovvero la funzione creatore / fabbrica stessa: http://www.dhtmlkitchen.com/?category=/JavaScript/&date = 2008/05/11 / & entry = Decorator-Factory-Aspect

Qualsiasi funzione (anche un costruttore) può prendere un numero variabile di argomenti. Ogni funzione ha una variabile “arguments” che può essere [].slice.call(arguments) su un array 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') ); 

I test sopra riportati producono il seguente risultato:

 s.options === "1,2,3,4": true z.options === "9,10,11": true 
 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(); 

Ecco la mia versione di createSomething :

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

Sulla base di ciò, ho provato a simulare la new parola chiave di 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; } 

L’ho provato e sembra che funzioni perfettamente bene per tutti gli scenari. Funziona anche su costruttori nativi come Date . Ecco alcuni test:

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

Mentre gli altri approcci sono fattibili, sono indebitamente complessi. In Clojure generalmente crei una funzione che crea istanze di tipi / record e usa quella funzione come meccanismo per l’istanziazione. Traducendo questo in JavaScript:

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

Adottando questo approccio si evita l’uso di new eccetto come descritto sopra. E questa funzione, naturalmente, non ha problemi con l’ apply o con un numero qualsiasi di altre funzionalità di programmazione funzionale.

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

Usando questo approccio, tutti i costruttori del tuo tipo (ad es. Person ) sono costruttori vanilla, do-nothing. Devi solo passare argomenti e assegnarli a proprietà con lo stesso nome. I dettagli pelosi vanno nella funzione costruttore (es. person ).

È un po ‘di fastidio dover creare queste funzioni extra di costruzione poiché sono comunque una buona pratica. Possono essere utili poiché consentono di avere potenzialmente diverse funzioni di costruzione con sfumature diverse.

Sì, possiamo, javascript è più del prototype inheritance in natura.

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

quando creiamo un object con ” new “, il nostro object creato INHERITS getAge (), ma se usiamo apply(...) or call(...) per chiamare Actor, allora stiamo passando un object per "this" ma l’object che passiamo WON'T erediterà da Actor.prototype

a meno che non passiamo direttamente applicare o chiamare Actor.prototype ma poi …. “questo” punterebbe a “Actor.prototype” e this.name scriverà a: Actor.prototype.name . Influenzando così tutti gli altri oggetti creati con Actor... poiché sovrascriviamo il prototipo piuttosto che l’istanza

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

Proviamo 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 

Passando Actor.prototype in Actor.call() come primo argomento, quando viene eseguita la funzione Actor (), esegue this.name=name , poiché “this” punterà su 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 

Tornando alla domanda originale su come utilizzare il new operator with apply , ecco la mia ripresa ….

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

poiché ES6 è ansible tramite l’operatore Spread, vedere https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new

Questa risposta era già, sorta di dato nel commento https://stackoverflow.com/a/42027742/7049810 , ma sembra essere mancato dalla maggior parte

In realtà il metodo più semplice è:

 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} 

Una soluzione rivista dalla risposta di @jordancpaul.

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

Grazie ai post qui l’ho usato in questo modo:

 SomeClass = function(arg1, arg2) { // ... } ReflectUtil.newInstance('SomeClass', 5, 7); 

e implementazione:

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