2012-09-09 99 views
28

在一些Javascript代碼(特別是node.js)中,我需要調用帶有未知參數集的函數而不更改上下文。例如:是否可以在不改變上下文的情況下調用function.apply?

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

的問題在上面的是,當我打電話apply,我通過傳遞this作爲第一個參數改變上下文。我想通過args到被稱爲的函數,而不是更改被調用函數的上下文。我基本上要做到這一點:

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

編輯:添加關於我的具體問題的更多細節。我正在創建一個Client類,其中包含有關連接的其他信息的套接字(socket.io)對象。我通過客戶端對象本身暴露套接字的事件偵聽器。

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] 

注意clientListenerId是一個自定義的唯一標識符的屬性,本質上是一樣的the answer found here

+0

你問的是如何獲得對全局上下文的引用嗎? – SLaks

+0

你有沒有試過把第一個參數留空?只要它不是必需的論點,那就應該有效。 –

+0

@SLaks - no,因爲'otherFn'將屬於另一個對象,但是該對象將根據何時調用'fn'而有所不同。 –

回答

6

'this'對函數的上下文的引用。這確實是重點。

如果你的意思是它調用不同對象的像這樣的背景下:

otherObj.otherFn(args) 

然後簡單地替換該對象在上下文:

otherObj.otherFn.apply(otherObj, args); 

這應該是它。

+1

問題是,在我有權訪問'otherFn'的時候,我不知道它屬於什麼對象。有沒有辦法確定它的當前綁定,如果我只有一個函數的參考? –

+0

也許我正在尋找[Function.constructor](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/constructor)... –

+1

不。如果你有一個原始函數,你不知道它從哪裏來。它本來可以綁定任何東西,或者什麼也不做。 –

-1

只需將屬性直接推送到函數的對象,並用它自己的「上下文」調用它。

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

如果函數是對象的原型函數,它似乎仍然是個問題。屬性然後在對象上,而不是函數本身。傳遞'otherFn'作爲上下文不允許我訪問對象的屬性。 (我試過沒有成功。) –

+0

你的意思是這樣的? HTTP://的jsfiddle。淨/ K79pp/<--- –

+0

不完全。更像是這樣的:http://jsfiddle.net/K79pp/2/你可以看到對'foo()'和'bar()'的引用無法找到,因爲該函數被用作上下文,實際上我們希望'otherClass'被用作上下文。 –

0

我不打算接受這個答案,因爲我仍然希望有更適合的東西。但是,到目前爲止,我現在使用的方法是基於對這個問題的反饋。

對於將調用Client.prototype.addListenerClient.prototype.removeListener任何一類,我下面的代碼添加到其構造函數:

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

    message: (recipient, body) -> 
    # ... 

    broadcast: (body) -> 
    # ... 

在上面的例子中,messagebroadcast將永遠被綁定到新ExampleClass原型對象當它被實例化時,允許我的原始問題中的代碼addListener工作。

我敢肯定,一些你想知道我爲什麼不只是做類似如下:

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

的問題是,每次.bind()被稱爲,這是一個新的對象。這樣就意味着符合下列條件:

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

因此,removeListener永遠不會成功當對象實例化的工作,因此我的綁定方法一次。

+0

Downvoter:爲什麼downvote?我只是分享了我所做的解決我自己的問題,很高興知道你的推理。 –

+0

我也是。雖然你的問題是基於對「這個」如何工作的誤解,但你的回答是完全正確的。使用'.bind'(或者像下劃線的'_.bindAll'這樣的包裝器)在你想要自己傳遞一個函數的特定用例中是正確的,但是所有對它的調用都被視爲方法特定「父」對象的調用。 –

0

因爲你似乎因爲它是在Javascript 1.8.5定義要使用的bind功能,並能夠檢索您通過綁定功能原來this對象,我建議重新定義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; 
}; 

現在你可以檢索你叫bind功能的原始上下文:

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"}; 
+0

不能使用.apply()而沒有指定上下文確實是Javascript的主要設計缺陷(知道在Python中使用簡單且頻繁的* args/** kwargs)。好主意將函數參數傳遞給.bind()。謝謝。 –

+1

用本地函數Mu seems似乎是一個非常糟糕的主意。 – linuxdan

6

如果我理解正確:

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

PHP有這個功能,call_user_func_array。不幸的是,JavaScript在這方面缺乏。看起來你使用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 + ');'); 
} 

是的,我知道。沒人喜歡eval()。這是緩慢而危險的。但是,在這種情況下,您可能不必擔心跨站點腳本,至少所有變量都包含在函數中。真的,JavaScript不具備本地功能,但我想這是我們有eval的情況。

證明了它的工作原理:

function showArgs() { 
    for (x in arguments) {console.log(arguments[x]);} 
} 

showArgs.invoke(['foo',/bar/g]); 
showArgs.invoke([window,[1,2,3]]); 

Firefox的控制檯輸出:

可以解決背景下,可以在JavaScript中發生變化時,函數調用
-- 
[12:31:05.778] "foo" 
[12:31:05.778] [object RegExp] 
[12:31:05.778] [object Window] 
[12:31:05.778] [object Array] 
+2

這是實際回答問題的唯一回答。其他人都認爲OP要麼希望獲取上下文(他們沒有),要麼沒有上下文,或者他們只是不瞭解'Function.apply'(他們明確地這樣做) )。謝謝你是唯一理解這個問題並說不,沒有什麼像PHP的'call_user_func_array'。 – JMTyler

1

的一種方式,是如果您需要它們能夠在上下文中操作的對象的構造函數的一部分使用方法,其中this不會意味着父對象,通過有效地創建一個本地私有變量來存儲原始的this確定河

我承認 - 樣的範圍在JavaScript中大多數討論 - 這是不完全清楚,所以在這裏是如此,我如何做一個例子:

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

簡單地說,只是分配這個什麼你希望它是,這是otherFn

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

我很長一段時間後,也提醒這個問題。現在回想起來,我認爲我真正想在這裏完成的事情與React庫的自動綁定工作方式類似。

實際上,每個函數被調用一個包裹綁定功能:

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 
2

如果綁定的功能,一個對象,你到處使用綁定功能,可以調用帶空適用,但仍然得到正確的上下文

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" 
相關問題