为什么reduce中的predicate函数需要wrapper函数?

Why is the wrapper function required for the predicate function in reduce?

我正在尝试 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 对象。我想不通的是为什么会这样。

如果不换行,您的 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

实际上脚本的 两个 部分需要正确的调用上下文(或 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.call指的是与Function.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');

上面代码片段中的错误有点误导。 methodCall 一个函数——特别是Function.prototype.call——它只是没有调用上下文,所以会抛出一个错误。此行为与以下代码段相同,其中 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 没有调用上下文。它类似于此答案中的第二个片段 - 就像 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 的调用上下文调用的,而不是先保存在变量中(这会丢失调用上下文)。