javascript 是否可以在不更改上下文的情况下调用 function.apply?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12336044/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it possible to call function.apply without changing the context?
提问by Matt Huggins
In some Javascript code (node.js specifically), I need to call a function with an unknown set of arguments without changing the context. For example:
在某些 Javascript 代码(特别是 node.js)中,我需要调用具有未知参数集的函数而不更改上下文。例如:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(this, args);
}
The problem in the above is that when I call apply
, I'm change the context by passing this
as the first argument. I'd like to pass args
to the function being called withoutchanging the context of the function being called. I essentially want to do this:
上面的问题是,当我调用 时apply
,我通过this
作为第一个参数传递来更改上下文。我想传递args
给被调用的函数而不改变被调用函数的上下文。我基本上想这样做:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(<otherFn's original context>, args);
}
Edit:Adding more detail regarding my specific question. I am creating a Client class that contains a socket (socket.io) object among other info pertaining to a connection. I am exposing the socket's event listeners via the client object itself.
编辑:添加有关我的具体问题的更多详细信息。我正在创建一个客户端类,其中包含一个套接字 (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]
Note that clientListenerId
is a custom unique identifier property that is essentially the same as the answer found here.
请注意,这clientListenerId
是一个自定义唯一标识符属性,它与此处找到的答案基本相同。
回答by Scott Sauyet
'this
' isa reference to your function's context. That's really the point.
' this
'是对函数上下文的引用。这才是重点。
If you mean to call it in the context of a different object like this:
如果您想在不同对象的上下文中调用它,如下所示:
otherObj.otherFn(args)
then simply substitute that object in for the context:
然后只需将该对象替换为上下文:
otherObj.otherFn.apply(otherObj, args);
That should be it.
应该是这样。
回答by Brian McCutchon
If I understand you correctly:
如果我理解正确的话:
changes context
| n | y |
accepts array n | func() | func.call() |
of arguments y | ???????? | func.apply() |
PHP has a function for this, call_user_func_array
. Unfortunately, JavaScript is lacking in this regard. It looks like you simulate this behavior using eval()
.
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 + ');');
}
Yes, I know. Nobody likes eval()
. It's slow and dangerous. However, in this situation you probably don't have to worry about cross-site scripting, at least, as all variables are contained within the function. Really, it's too bad that JavaScript doesn't have a native function for this, but I suppose that it's for situations like this that we have eval
.
是的,我知道。没有人喜欢eval()
。它缓慢而危险。但是,在这种情况下,您可能不必担心跨站点脚本,至少,因为所有变量都包含在函数中。真的,JavaScript 没有用于此的本机函数太糟糕了,但我想它是针对这种情况的,我们有eval
.
Proof that it works:
证明它有效:
function showArgs() {
for (x in arguments) {console.log(arguments[x]);}
}
showArgs.invoke(['foo',/bar/g]);
showArgs.invoke([window,[1,2,3]]);
Firefox console output:
火狐控制台输出:
--
[12:31:05.778] "foo"
[12:31:05.778] [object RegExp]
[12:31:05.778] [object Window]
[12:31:05.778] [object Array]
回答by Dave
Simply put, just assign the this to what you want it to be, which is otherFn:
简单地说,只需将 this 分配给您想要的内容,即otherFn:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(otherFn, args);
}
回答by Blackfire
If you bind the function to an object and you use everywhere the bound function, you can call apply with null, but still get the correct context
如果你将函数绑定到一个对象并且你到处都使用绑定的函数,你可以用 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"
回答by YakovL
These days you can use rest parameters:
这些天你可以使用休息参数:
function fn(...args) {
otherFn(...args);
}
The only downside is, if you want to use some specific params in fn
, you have to extract it from args
:
唯一的缺点是,如果你想在 中使用一些特定的参数fn
,你必须从 中提取它args
:
function fn(...args) {
let importantParam = args[2]; //third param
// ...
otherFn(...args);
}
Here's an example to try (ES next version to keep it short):
这是一个可以尝试的示例(ES 下一个版本以保持简短):
// a one-line "sum any number of arguments" function
const sum = (...args) => args.reduce((sum, value) => sum + value);
// a "proxy" function to test:
var pass = (...args) => sum(...args);
console.log(pass(1, 2, 15));
回答by glenatron
One way that you can work around the change of context that can happen in JavaScript when functions are called, is to use methods that are part of the object's constructor if you need them to be able to operate in a context where this
is not going to mean the parent object, by effectively creating a local private variable to store the original this
identifier.
您可以解决调用函数时 JavaScript 中可能发生的上下文更改的一种方法,如果您需要它们能够在this
不意味着的上下文中操作,则使用属于对象构造函数的一部分的方法父对象,通过有效地创建本地私有变量来存储原始this
标识符。
I concede that - like most discussions of scope in JavaScript - this is not entirely clear, so here is an example of how I have done 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
}
回答by Matt Huggins
I was just reminded of this question after a long time. Looking back now, I think what I was really trying to accomplish here was something similar to how the React library works with its automatic binding.
过了很久才想起这个问题。现在回想起来,我认为我真正想要在这里完成的事情类似于 React 库如何使用其自动绑定。
Essentially, each function is a wrapped bound function being called:
本质上,每个函数都是一个被调用的包装绑定函数:
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
回答by Matt Huggins
I'm not going to accept this as an answer, as I'm still hoping for something more suitable. But here's the approach I'm using right now based upon the feedback on this question so far.
我不会接受这个作为答案,因为我仍然希望有更合适的东西。但这是我目前使用的方法,基于迄今为止对这个问题的反馈。
For any class that will be calling Client.prototype.addListener
or Client.prototype.removeListener
, I did added the following code to their constructor:
对于将调用Client.prototype.addListener
or 的任何类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) ->
# ...
In the above example, message
and broadcast
will always be bound to the new ExampleClass
prototype object when it's instantiated, allowing the addListener
code in my original question to work.
在上面的示例中,message
并且在实例化时broadcast
将始终绑定到新的ExampleClass
原型对象,从而允许addListener
我原始问题中的代码工作。
I'm sure some of you are wondering why I didn't just do something like the following:
我相信你们中的一些人想知道为什么我不只是做以下事情:
example = new ExampleClass
client.addListener('message', example.bind(example))
# ...
client.removeListener('message', example.bind(example))
The problem is that every time .bind( )
is called, it's a new object. So that means that the following is true:
问题是每次.bind( )
调用时,都是一个新对象。所以这意味着以下内容是正确的:
example.bind(example) != example.bind(example)
As such, the removeListener
would never work successfully, thus my binding the method once when the object is instantiated.
因此,removeListener
永远不会成功,因此我在实例化对象时绑定了一次方法。
回答by Blake Regalia
Since you seem to want to be using the bind
function as it is defined in Javascript 1.8.5, and be able to retrieve the original this
object you pass the bind function, I recommend redefining the Function.prototype.bind
function:
由于您似乎想要使用bind
在 Javascript 1.8.5 中定义的函数,并且能够检索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;
};
Now you can retrieve the original context that you called the bind
function with:
现在,您可以使用以下命令检索调用该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"};
回答by Blake Regalia
Just push properties directly to the function's object and call it with it's own "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);
}