【问题标题】:Why is the wrapper function required for the predicate function in reduce?为什么reduce中谓词函数需要包装函数?
【发布时间】:2019-02-09 22:06:53
【问题描述】:

我正在玩 Array.reduce 和 Set 之间的交互,我注意到以下奇怪的行为。

通常这是有效的:

console.log(
  Set.prototype.add.call(new Set(), 1, 0, [])
);
// Set { 1 }

但如果我将其与 reduce 结合使用,以下内容将不起作用:

console.log(
  [1,2,3].reduce(Set.prototype.add.call, new Set())
);
// TypeError: undefined is not a function
//     at Array.reduce (<anonymous>)

但是,如果我将谓词函数包装在包装器中,这将起作用:

console.log(
  [1,2,3].reduce((...args) => Set.prototype.add.call(...args), new Set())
);
// Set { 1, 2, 3 }

我在不同的 JS 引擎(Chrome 和 Safari)上进行了尝试,得到了相同的结果,因此它可能不是特定于引擎的行为。这同样适用于 Map 对象。我无法弄清楚为什么会这样。

【问题讨论】:

  • 你想达到什么目的?
  • @guest271314 我想,将reduceSet 一起使用,而不将额外的函数传递给reduce 的第一个参数
  • @user1251380 请注意Set.prototype.add.call === Function.prototype.call - .call 如何依赖调用上下文(this)才有意义(如答案所示)。 只有 Set.prototype.add 需要添加集合的调用上下文。
  • @CertainPerformance 仍然没有关注。为什么不直接将.reduce() 的结果传递给new Set()?鉴于示例输入 Set 是不必要的。
  • @guest271314 OP 询问为什么 [1,2,3].reduce(Set.prototype.add.call, new Set()) 会抛出异常,而 [1,2,3].reduce((..args) =&gt; Set.prototype.add.call(..args), new Set()) 不是等价的。

标签: javascript node.js ecmascript-5


【解决方案1】:

如果没有包装,您的 Set.prototype.add.call 将失去其 this 值(应该是 Set.prototype.add 函数,而是设置为 undefined)。

试试这个:

[1,2,3].reduce(Set.prototype.add.call.bind(Set.prototype.add), new Set());

http://speakingjs.com/es5/ch01.html#_extracting_methods

【讨论】:

    【解决方案2】:

    实际上有 两个 部分的脚本需要正确的调用上下文(或this 值)才能正常工作。您已经知道的第一部分是,您需要使用新创建的Set 的调用上下文调用Set.prototype.add,将Set 作为第一个参数传递给.call

    // works:
    Set.prototype.add.call(new Set(), 1, 0, []);
    // works, args[0] is the new Set:
    [1,2,3].reduce((..args) => Set.prototype.add.call(..args), new Set());
    

    但另一个问题是 .call 需要使用适当的调用上下文来调用。 Set.prototype.add.callFunction.prototype.call指的功能相同:

    console.log(Set.prototype.add.call === Function.prototype.call);

    Function.prototype.call 调用的函数基于其调用上下文。例如

    someObject.someMethod.call(< args >)
    

    函数的调用上下文是函数调用中最后一个 . 之前的所有内容。因此,对于上述情况,.call 的调用上下文是someObject.someMethod。这就是.call 知道要运行哪个函数的方式。如果没有调用上下文,.call 将无法工作:

    const obj = {
      method(arg) {
        console.log('method running ' + arg);
      }
    };
    
    // Works, because `.call` has a calling context of `obj.method`:
    obj.method.call(['foo'], 'bar');
    
    const methodCall = obj.method.call;
    // Doesn't work, because methodCall is being called without a calling context:
    methodCall(['foo'], 'bar');

    上面的 sn-p 中的错误有点误导。 methodCall 一个函数 - 特别是 Function.prototype.call - 它只是没有调用上下文,因此会引发错误。此行为与以下 sn-p 相同,其中 Function.prototype.call 在没有调用上下文的情况下被调用:

    console.log(typeof Function.prototype.call.call);
    Function.prototype.call.call(
      undefined,
    );

    希望这应该清楚地表明,在使用 .call 时,您需要在正确的调用上下文中使用它,否则它将失败。所以,回到原来的问题:

    [1,2,3].reduce(Set.prototype.add.call, new Set());
    

    失败,因为 reduce 的内部调用 Set.prototype.add.call 没有调用上下文。它类似于此答案中的第二个 sn-p - 就像将 Set.prototype.add.call 放入独立变量中,然后调用。

    // essential behavior of the below function is identical to Array.prototype.reduce:
    Array.prototype.customReduce = function(callback, initialValue) {
      let accum = initialValue;
      for (let i = 0; i < this.length; i++) {
        accum = callback(accum, this[i]);
        // note: "callback" above is being called without a calling context
      }
      return accum;
    };
    
    // demonstration that the function works like reduce:
    // sum:
    console.log(
      [1, 2, 3].customReduce((a, b) => a + b, 0)
    );
    // multiply:
    console.log(
      [1, 2, 3, 4].customReduce((a, b) => a * b, 1)
    );
    
    // your working Set code:
    console.log(
      [1,2,3].customReduce((...args) => Set.prototype.add.call(...args), new Set())
    );
    // but because "callback" isn't being called with a calling context, the following fails
    // for the same reason that your original code with "reduce" fails:
    [1,2,3].customReduce(Set.prototype.add.call, new Set());

    相比之下,

    (..args) => Set.prototype.add.call(..args)
    

    有效(在.reduce.customReduce 中),因为.call 是使用Set.prototype.add 的调用上下文调用的,而不是先保存在变量中(这会丢失调用上下文)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-02
      • 2018-11-20
      • 2021-01-05
      • 1970-01-01
      • 2020-03-10
      • 2014-05-20
      • 1970-01-01
      相关资源
      最近更新 更多