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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 15:57:46  来源:igfitidea点击:

Is it possible to call function.apply without changing the context?

javascriptnode.jsfunction

提问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 thisas the first argument. I'd like to pass argsto 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 clientListenerIdis 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 thisis not going to mean the parent object, by effectively creating a local private variable to store the original thisidentifier.

您可以解决调用函数时 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.addListeneror Client.prototype.removeListener, I did added the following code to their constructor:

对于将调用Client.prototype.addListeneror 的任何类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, messageand broadcastwill always be bound to the new ExampleClassprototype object when it's instantiated, allowing the addListenercode 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 removeListenerwould 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 bindfunction as it is defined in Javascript 1.8.5, and be able to retrieve the original thisobject you pass the bind function, I recommend redefining the Function.prototype.bindfunction:

由于您似乎想要使用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 bindfunction 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);
}