È ansible call function.apply senza modificare il context?

In qualche codice Javascript (specificamente node.js), devo call una function con un set di argomenti sconosciuti senza modificare il context. Per esempio:

function fn() { var args = Array.prototype.slice.call(arguments); otherFn.apply(this, args); } 

Il problema di cui sopra è che quando chiamo apply , sto cambiando il context passando this come primo argomento. Vorrei passare args alla function chiamata senza modificare il context della function chiamata. Voglio essenzialmente fare questo:

 function fn() { var args = Array.prototype.slice.call(arguments); otherFn.apply(<otherFn's original context>, args); } 

Modifica: aggiunta di ulteriori dettagli relativi alla mia domanda specifica. Sto creando una class Client che contiene un object socket (socket.io) tra altre informazioni relative a una connessione. Sto esponendo gli ascoltatori degli events della socket tramite l'object client stesso.

 class Client constructor: (socket) -> @socket = socket @avatar = socket.handshake.avatar @listeners = {} addListener: (name, handler) -> @listeners[name] ||= {} @listeners[name][handler.clientListenerId] = wrapper = => # append client object as the first argument before passing to handler args = Array.prototype.slice.call(arguments) args.unshift(this) handler.apply(this, args) # <---- HANDLER'S CONTEXT IS CHANGING HERE :( @socket.addListener(name, wrapper) removeListener: (name, handler) -> try obj = @listeners[name] @socket.removeListener(obj[handler.clientListenerId]) delete obj[handler.clientListenerId] 

Notare che clientListenerId è una properties; di identificatore univoco personalizzato che è sostanzialmente la stessa della risposta trovata qui .

' this ' è un riferimento al context della tua function. Questo è veramente il punto.

Se intendi chiamarlo nel context di un altro object come questo:

 otherObj.otherFn(args) 

quindi semplicemente sostituire l'object in per il context:

 otherObj.otherFn.apply(otherObj, args); 

Quello dovrebbe essere.

Se ti capisco bene:

  changes context | n | y | accepts arrays n | func() | func.call() | of arguments y | ???????? | func.apply() | 

PHP ha una function per questo, call_user_func_arrays . Purtroppo, JavaScript è privo di questo aspetto. Sembra simulare questo comportmento utilizzando eval() .

 Function.prototype.invoke = function(args) { var i, code = 'this('; for (i=0; i<args.length; i++) { if (i) { code += ',' } code += 'args[' + i + ']'; } eval(code + ');'); } 

Si, lo so. Nessuno ama eval() . È lento e pericoloso. Tuttavia, in questa situazione, probabilmente non dovrai preoccuparti dello scripting cross-site, alless perché tutte le variables sono contenute all'interno della function. Davvero, è troppo male che JavaScript non abbia una function nativa per questo, ma suppongo che sia per situazioni come questa che abbiamo eval .

Prova che funziona:

 function showArgs() { for (x in arguments) {console.log(arguments[x]);} } showArgs.invoke(['foo',/bar/g]); showArgs.invoke([window,[1,2,3]]); 

Uscita console Firefox:

 -- [12:31:05.778] "foo" [12:31:05.778] [object RegExp] [12:31:05.778] [object Window] [12:31:05.778] [object Array] 

In poche parole, basta assegnare questo a quello che vuoi che sia, che è otherFn :

 function fn() { var args = Array.prototype.slice.call(arguments); otherFn.apply(otherFn, args); } 

Se si lega la function a un object e si utilizza ovunque la function associata, è ansible call applicare con null ma get comunque il context corretto

 var Person = function(name){ this.name = name; } Person.prototype.printName = function(){ console.log("Name: " + this.name); } var bob = new Person("Bob"); bob.printName.apply(null); //window.name bob.printName.bind(bob).apply(null); //"Bob" 

Il modo in cui è ansible lavorare attorno alla modifica del context che può accadere in JavaScript quando le funzioni sono chiamate è utilizzare methods che fanno parte del constructor dell'object se è necessario che possano operare in un context in cui this non verrà significa l'object padre, creando effettivamente una variabile privata locale per memorizzare l'originale di this identificatore.

Concedo che – come la maggior parte delle discussioni di ambito in JavaScript – questo non è del tutto chiaro, quindi ecco un esempio di come ho fatto questo:

 function CounterType() { var counter=1; var self=this; // 'self' will now be visible to all var incrementCount = function() { // it doesn't matter that 'this' has changed because 'self' now points to CounterType() self.counter++; }; } function SecondaryType() { var myCounter = new CounterType(); console.log("First Counter : "+myCounter.counter); // 0 myCounter.incrementCount.apply(this); console.log("Second Counter: "+myCounter.counter); // 1 } 

Non lo accetterò come una risposta, come sto ancora sperando per qualcosa di più adatto. Ma ecco l'approccio che sto usando ora in base ai feedback di questa domanda finora.

Per each class che chiamerà Client.prototype.addListener o Client.prototype.removeListener , ho aggiunto il seguente codice al constructor:

 class ExampleClass constructor: -> # ... for name, fn of this this[name] = fn.bind(this) if typeof(fn) == 'function' message: (recipient, body) -> # ... broadcast: (body) -> # ... 

Nell'esempio precedente, il message e la broadcast saranno sempre legati al nuovo object prototipo di ExampleClass quando viene istanziato, consentendo il funzionamento del codice addListener nella mia domanda originale.

Sono sicuro che alcuni di voi stanno chiedendo perché non ho fatto qualcosa come il seguente:

 example = new ExampleClass client.addListener('message', example.bind(example)) # ... client.removeListener('message', example.bind(example)) 

Il problema è che each volta viene chiamato .bind( ) , è un nuovo object. Quindi significa che il seguente è vero:

 example.bind(example) != example.bind(example) 

In quanto tale, removeListener non functionrebbe mai, quindi vinco il metodo una volta che l'object viene istanziato.

Dal momento che sembra che desideri utilizzare la function di bind come definito in Javascript 1.8.5 e riuscire a recuperare l'originale di this object che superi la function di bind, suggerisco di ridefinire la function Function.prototype.bind :

 Function.prototype.bind = function (oThis) { if (typeof this !== "function") { throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); /** here's the additional code **/ fBound.getContext = function() { return oThis; }; /**/ return fBound; }; 

Ora è ansible recuperare il context originale che hai chiamato la function di bind con:

 function A() { return this.foo+' '+this.bar; } var HelloWorld = A.bind({ foo: 'hello', bar: 'world', }); HelloWorld(); // returns "hello world"; HelloWorld.getContext(); // returns {foo:"hello", bar:"world"}; 

Mi è stato appena ricordato di questa domanda dopo un lungo periodo di tempo. Guardando indietro ora, penso che quello che stavo davvero cercando di realizzare qui era qualcosa di simile a come la libreria React funziona con il suo legame automatico.

Essenzialmente, each function è una function confezionata che viene chiamata:

 function SomeClass() { }; SomeClass.prototype.whoami = function () { return this; }; SomeClass.createInstance = function () { var obj = new SomeClass(); for (var fn in obj) { if (typeof obj[fn] == 'function') { var original = obj[fn]; obj[fn] = function () { return original.apply(obj, arguments); }; } } return obj; }; var instance = SomeClass.createInstance(); instance.whoami() == instance; // true instance.whoami.apply(null) == instance; // true 

Basta spingere le properties; direttamente all'object della function e chiamarla con il proprio "context".

 function otherFn() { console.log(this.foo+' '+this.bar); // prints: "hello world" when called from rootFn() } otherFn.foo = 'hello'; otherFn.bar = 'world'; function rootFn() { // by the way, unless you are removing or adding elements to 'arguments', // just pass the arguments object directly instead of casting it to Array otherFn.apply(otherFn, arguments); }