【问题标题】:Javascript pass array of arguments using `apply()`, but keep `this` reference from `call()`Javascript 使用 `apply()` 传递参数数组,但保留 `call()` 中的 `this` 引用
【发布时间】:2012-05-29 07:21:30
【问题描述】:

我需要结合 JavaScript 的 call()apply() 方法的强大功能。我遇到的问题是call() 保留了对this 的正确引用,但是当我需要它作为函数参数发送时,将我拥有的参数数组作为数组发送。 apply() 方法在使用数组时将参数发送给函数就好了,但我不知道如何向它发送对 this 的正确引用,call() 方法似乎自然可以访问。

下面是我拥有的代码的简化版本,它可能看起来毫无用处,但它是一个很好的理解要点的方法:

// AN OBJECT THAT HOLDS SOME FUNCTIONS
var main = {};
main.the_number = 15;
main.some_function = function(arg1, arg2, arg3){
    // WOULD VERY MUCH LIKE THIS TO PRINT '15' TO THE SCREEN
    alert(this.the_number);
    // DO SOME STUFF WITH THE ARGUMENTS
    ... 
};
// THIS STORES FUNCTIONS FOR LATER.
//  'hub' has no direct knowledge of 'main'
var hub = {};
hub.methods = [];
hub.methods.push(main.some_function);
hub.do_methods = function(arguments_array){
    for(var i=0; i<this.methods.length; i++){
        // With this one, '15' is printed just fine, but an array holding 'i' is 
        //  just passed instead if 'i' itself
        this.methods[i].call(arguments_array);   
        // With this one, 'i' is passed as a function argument, but now the
        //  'this' reference to main is lost when calling the function 
        this.methods[i].apply(--need a reference to 'main' here--, arguments_array); 
    }
}

【问题讨论】:

  • apply 的第一个参数将this 设置为您传递的任何内容...如果您想要this == main,那么只需将main 作为第一个参数传递。
  • @Snuffleupagus - 不幸的是,“hub”没有直接了解“main”

标签: javascript call apply


【解决方案1】:

什么? Apply passes the scope as well...

method.apply(this, [args]);

编辑:

在您的代码中,您在包含范围内定义了主对象,因此您可以简单地执行;

this.methods[i].call(main);

this.methods[i].apply(main, [args]);

【讨论】:

  • 我可能遗漏了一些东西,但在这段代码的上下文中,我看不到如何获得需要传递给“应用”的“this”。
  • 我很确定,如果我在你的答案中执行代码,它将传递对 'hub' 而不是 'main' 的引用
  • 不幸的是,'hub' 没有直接了解 'main'
  • 呼叫无权访问它。用作this 的对象是您传递给它的任何内容。在您的情况下,它是一个数组。
  • 没问题,很高兴能帮上忙!您需要做的就是将main 保存在hub 中。然后你可以把它传回函数。
【解决方案2】:

当使用applycall 时,第一个参数是您希望将this 设置为的值。

call 接受参数列表:

.call(main, i, j)

apply 接受一个数组或参数:

.apply(main, [i, j])

所以,在这一行:

this.methods[i].call([i]); 

这会将[i] 传递为thisthis.methods[i] 内。

你可能想这样做:

this.methods[i].call(main, i);

这将调用this.methods[i],将this 设置为main,并将i 传递给它。

或者:

this.methods[i].call(main, arguments_array);

这将调用this.methods[i],将this设置为main,并将arguments_array的元素作为参数传递。

【讨论】:

  • 在上面代码的上下文中,我如何进入'main'以便我可以通过它来申请? (假设 'hub' 没有直接了解 'main'?
  • @ChrisDutrow:hub 需要“直接了解”main。它无法“弄清楚”你想使用main
  • 我很抱歉我的例子有点弱。我无法更改参数的传递方式,它们必须作为数组传递并由函数解释为普通参数参数。我相应地更改了示例。
  • @ChrisDutrow:那么您想使用.apply 而不是.call。无论哪种方式,hub 都需要了解 main 才能使其正常工作。
【解决方案3】:

只需将父对象的引用附加到函数对象:

main.some_function = function () {
    //...
}
main.some_function.parent = main;

// now some_function.parent holds a ref to main

或者,如果您愿意,也可以使用闭包进行救援:在定义 main.some_function 时包含对 main 的引用:

// self-execution function the returns a function with `that` (i.e., main) in scope
main.some_function = (function(that) {
    return function() {
        alert(that.the_number);
    }
})(main);

【讨论】:

    【解决方案4】:

    当我阅读前面答案中的 cmets 时,我认为您需要某种绑定。您可以使用 jQuery 或类似的库,也可以自己实现:

    var bound_some_function = function(){
        return main.some_function.apply(main, arguments);
    }
    // ...
    hub.methods.push(bound_some_function);
    // ...
    this.methods[i].apply(null /*whathever, has no effect*/, arguments_array);
    

    绑定函数的通用定义如下所示:

    function bind(fun, scope){
        return function(){
            return fun.apply(scope, arguments);
        };
    }
    

    如需进一步了解绑定,请搜索“Javascript 函数绑定”。有很多关于它的文章。

    【讨论】:

      【解决方案5】:

      我之前的信息不正确(在我删除的答案中),因为应用不会对绑定函数产生影响(如上面的答案);

      只需添加一个简单的代码即可证明:

      /*Lets suppose we have a class "MessageReader" that has a method printMessage
       * that for some reason needs to receive the message to be printed from a function,
       * not directly a value.*/
      function MessageReader() {
          this.value = "This is NOT a message and is not supposed to be readed!";
      }
      /*getMessage needs to return a string that will be printed!*/
      MessageReader.prototype.printMessage = function(getMessage) {
          console.log(getMessage.apply(this, []));
      }
      
      function Message(value) {
          this.value = value;
      }
      Message.prototype.getMessage = function() {
          return this.value;
      }
      
      
      var myMessageReader = new MessageReader();
      var myMessage1 = new Message("This is a simple message");
      var myMessage2 = new Message("This is another simple message");
      
      /*This will print the wrong message:*/
      myMessageReader.printMessage(myMessage1.getMessage);
      
      /*But this works!*/
      myMessageReader.printMessage(myMessage2.getMessage.bind(myMessage2));
      

      【讨论】:

      • 如果您之前的答案是一个答案,那么您应该使用更新的信息对其进行编辑而不是删除它。在这个新答案中,您对已删除答案的引用对任何人都没有帮助,因为他们看不到它...它已已删除,对吗?
      【解决方案6】:

      .call().apply() 方法在处理 this 方面没有区别。来自 MDN 文档:

      虽然此函数的语法与 apply() 的语法几乎相同,但根本区别在于 call() 接受参数列表,而 apply() 接受单个参数数组。

      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call

      不幸的是,使用 call/bind/apply 调用函数并维护它的原始 this 引用是不可能的,但是,因为这看起来很丑陋,有一个解决方法可以传递

      function applyOriginalThis(fn, parametersArray)
      {
          var p = parametersArray;
      
          switch (p.length)
          {
              case 0: fn(); break;
              case 1: fn(p[0]); break;
              case 2: fn(p[0], p[1]); break;
              case 3: fn(p[0], p[1], p[2]); break;
              case 4: fn(p[0], p[1], p[2], p[3]); break;
              case 5: fn(p[0], p[1], p[2], p[3], p[4]); break;
              case 6: fn(p[0], p[1], p[2], p[3], p[4], p[5]); break;
              case 7: fn(p[0], p[1], p[2], p[3], p[4], p[5], p[6]); break;
              case 8: fn(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); break;
              case 9: fn(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8]); break;
              default: throw "Too many parameters.";
          }
      }
      

      当然,这仅适用于您希望支持的参数数量。从好的方面来说,采用过多参数的函数是code smell。另外,如果我没记错的话,这种类型的代码模式在 AngularJS 1.x 中被用作.apply() 的性能优化(现在找不到参考)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-05
        • 1970-01-01
        • 2010-12-31
        • 1970-01-01
        • 2014-11-27
        • 2011-01-02
        • 2019-11-22
        • 1970-01-01
        相关资源
        最近更新 更多