简体   繁体   English

是否可以在不更改上下文的情况下调用function.apply?

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

In some Javascript code (node.js specifically), I need to call a function with an unknown set of arguments without changing the context. 在一些Javascript代码(具体是node.js)中,我需要使用一组未知的参数调用函数而不更改上下文。 For example: 例如:

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. 上面的问题是,当我调用apply ,我通过将this作为第一个参数传递来更改上下文。 I'd like to pass args to the function being called without changing the context of the function being called. 我想在改变被调用函数的上下文的情况下将args传递给被调用的函数。 I essentially want to do this: 我基本上想要这样做:

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. 我正在创建一个Client类,其中包含一个socket(socket.io)对象以及与连接有关的其他信息。 I am exposing the socket's event listeners via the client object itself. 我通过客户端对象本身公开套接字的事件监听器。

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是一个自定义唯一标识符属性,与此处的答案基本相同。

' this ' is a reference to your function's context. ' this ' 对函数上下文的引用。 That's really the point. 这才是真正的重点。

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. 那应该是它。

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 . PHP有一个函数call_user_func_array Unfortunately, JavaScript is lacking in this regard. 不幸的是,JavaScript在这方面缺乏。 It looks like you simulate this behavior using eval() . 看起来您使用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() . 没有人喜欢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 . 实际上,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: Firefox控制台输出:

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

Simply put, just assign the this to what you want it to be, which is otherFn : 简单地说,只需将其分配给您想要的内容,即otherFn

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

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 如果将函数绑定到对象并且在绑定函数的任何位置使用,则可以调用apply with null,但仍然可以获得正确的上下文

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"

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
}

These days you can use rest parameters : 这些天你可以使用rest参数

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)); 

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.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) ->
     # ...

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. 在上面的示例中, messagebroadcast将在实例化时始终绑定到新的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. 问题是,每次调用.bind( ) ,它都是一个新对象。 So that means that the following is true: 这意味着以下情况属实:

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永远不会成功,因此我在实例化对象时绑定了一次方法。

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

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

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM