35

在一些 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);
}

编辑:添加有关我的具体问题的更多详细信息。我正在创建一个客户端类,其中包含一个套接字(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基本相同。

4

10 回答 10

8

如果我理解正确:

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

火狐控制台输出:

--
[12:31:05.778] "foo"
[12:31:05.778] [object RegExp]
[12:31:05.778] [object Window]
[12:31:05.778] [object Array]
于 2013-08-17T17:34:45.180 回答
7

Simply put, just assign the this to what you want it to be, which is otherFn:

function fn() {
    var args = Array.prototype.slice.call(arguments);
    otherFn.apply(otherFn, args);
}
于 2015-08-21T15:11:17.190 回答
6

' this'对函数上下文的引用。这才是重点。

如果您的意思是在这样的不同对象的上下文中调用它:

otherObj.otherFn(args)

然后只需将该对象替换为上下文:

otherObj.otherFn.apply(otherObj, args);

应该是这样的。

于 2012-09-09T02:28:18.540 回答
5

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

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"
于 2016-10-27T06:17:50.697 回答
1

解决调用函数时 JavaScript 中可能发生的上下文更改的一种方法是,如果您需要它们能够在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
}
于 2014-02-21T00:30:58.873 回答
1

这些天你可以使用休息参数

function fn(...args) {
    otherFn(...args);
}

唯一的缺点是,如果你想在 中使用一些特定的参数fn,你必须从 中提取它args

function fn(...args) {
    let importantParam = args[2]; //third param
    // ...
    otherFn(...args);
}

这是一个尝试的示例(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));

于 2019-08-03T13:29:41.397 回答
0

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:

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:

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"};
于 2012-09-10T22:27:44.547 回答
0

我不会接受这个作为答案,因为我仍然希望有更合适的东西。但这是我现在根据迄今为止对这个问题的反馈使用的方法。

对于将调用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) ->
     # ...

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

于 2012-09-09T12:40:40.693 回答
0

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.

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
于 2015-11-30T22:49:19.510 回答
-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);
}
于 2012-09-09T09:33:06.600 回答