javascript – 为什么谓词函数所需的包装函数在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的调用上下文调用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需要使用approprite调用上下文来调用. Set.prototype.add.call引用与Function.prototype.call相同的函数:

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

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

someObject.someMethod.call(< args >)

函数的调用上下文是在final之前的所有内容.在函数调用中.因此,对于上述内容,.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)

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

转载注明原文:javascript – 为什么谓词函数所需的包装函数在reduce中? - 代码日志