繁体   English   中英

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

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

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

编辑:添加有关我特定问题的更多详细信息。 我正在创建一个Client类,其中包含一个socket(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是一个自定义唯一标识符属性,与此处的答案基本相同。

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

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

otherObj.otherFn(args)

然后简单地将该对象替换为上下文:

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() 这是缓慢而危险的。 但是,在这种情况下,您可能不必担心跨站点脚本,至少,因为所有变量都包含在函数中。 实际上,JavaScript没有本机功能太糟糕了,但我认为这样的情况我们有eval

证明它有效:

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

如果将函数绑定到对象并且在绑定函数的任何位置使用,则可以调用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"

在调用函数时,可以解决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
}

这些天你可以使用rest参数

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

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

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

在上面的示例中, messagebroadcast将在实例化时始终绑定到新的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函数,并且能够检索原始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;
};

现在,您可以使用以下命令检索调用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);
}

暂无
暂无

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

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