有没有可能调用function.apply而不改变上下文?
在一些Javascript代码(特别是node.js)中,我需要在不改变上下文的情况下调用具有未知参数集的函数。 例如:
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); }
编辑:添加更多关于我的具体问题的细节。 我正在创build一个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
是一个自定义的唯一标识符属性,它与在此处find的答案基本相同。
“ this
” 是对你的函数的上下文的引用。 这确实是重点。
如果你的意思是在这样一个不同的对象的上下文中调用它:
otherObj.otherFn(args)
然后简单地将该对象replace为上下文:
otherObj.otherFn.apply(otherObj, args);
应该是这样的。
如果我正确理解你的话
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()
。 这是缓慢和危险的。 但是,在这种情况下,您可能不必担心跨站脚本,至less所有variables都包含在函数中。 真的,JavaScript不具备本地function,但我想这是我们有eval
。
certificate它的工作原理:
function showArgs() { for (x in arguments) {console.log(arguments[x]);} } showArgs.invoke(['foo',/bar/g]); showArgs.invoke([window,[1,2,3]]);
Firefox控制台输出:
-- [12:31:05.778] "foo" [12:31:05.778] [object RegExp] [12:31:05.778] [object Window] [12:31:05.778] [object Array]
简单地说,就是把这个分配给你想要的,这就是otherFn :
function fn() { var args = Array.prototype.slice.call(arguments); otherFn.apply(otherFn, args); }
如果将函数绑定到一个对象,并且在任何地方使用了绑定函数,则可以使用null调用apply,但仍然可以获得正确的上下文
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"
在调用函数时,可以解决JavaScript中可能发生的上下文更改的一种方法是,如果您需要这些方法是对象构造函数的一部分,那么可以在不打算意思是父对象,通过有效地创build一个本地私有variables来存储原始的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 }
我不打算接受这个答案,因为我仍然希望有更适合的东西。 但是,到目前为止,基于对这个问题的反馈,我现在正在使用这个方法。
对于任何将调用Client.prototype.addListener
或Client.prototype.removeListener
,我都将下面的代码添加到它们的构造函数中:
class ExampleClass constructor: -> # ... for name, fn of this this[name] = fn.bind(this) if typeof(fn) == 'function' message: (recipient, body) -> # ... broadcast: (body) -> # ...
在上面的示例中, message
和broadcast
将在实例化时始终绑定到新的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
将永远不会成功工作,因此,在实例化对象时,我将该方法绑定一次。
由于您似乎想要使用Javascript 1.8.5中定义的bind
函数,并且能够检索传递绑定函数的原始对象,所以我build议重新定义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"};
经过很长时间,我才想起这个问题。 现在回想起来,我认为我真正想要完成的事情与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
只需将属性直接推送到函数的对象,并用它自己的“上下文”来调用它。
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); }