[英]Why is the wrapper function required for the predicate function in reduce?
I was playing around with the interaction between Array.reduce and Set and I noticed the following strange behavior. 我正在玩Array.reduce和Set之间的交互,我注意到以下奇怪的行为。
Normally this works: 通常这有效:
console.log( Set.prototype.add.call(new Set(), 1, 0, []) ); // Set { 1 }
But if I were to combine that with reduce, the following does not work: 但是,如果我将它与reduce结合使用,则以下方法不起作用:
console.log( [1,2,3].reduce(Set.prototype.add.call, new Set()) ); // TypeError: undefined is not a function // at Array.reduce (<anonymous>)
However if I were to wrap the predicate function in a wrapper, this will work: 但是,如果我将谓词函数包装在包装器中,这将起作用:
console.log( [1,2,3].reduce((...args) => Set.prototype.add.call(...args), new Set()) ); // Set { 1, 2, 3 }
I tried this on different JS engines (Chrome and Safari) and got the same result so its probably not an engine specific behavior. 我在不同的JS引擎(Chrome和Safari)上尝试了这个并获得了相同的结果,因此它可能不是特定于引擎的行为。 The same applies to a Map object as well.
这同样适用于Map对象。 What I can't figure out is why that is the case.
我无法弄清楚的是为什么会这样。
Without wrapping, your Set.prototype.add.call
lose its this
value (wich should be Set.prototype.add
function, but instead is set to undefined
). 如果没有换行,你的
Set.prototype.add.call
就会失去它的this
值(应该是Set.prototype.add
函数,而是设置为undefined
)。
Try this: 试试这个:
[1,2,3].reduce(Set.prototype.add.call.bind(Set.prototype.add), new Set());
See http://speakingjs.com/es5/ch01.html#_extracting_methods 请参阅http://speakingjs.com/es5/ch01.html#_extracting_methods
There are actually two parts of the script that need the proper calling context (or this
value) in order to work properly. 实际上,脚本中有两部分需要正确的调用上下文(或
this
值)才能正常工作。 The first part, which you've already figured out, is that you need to call Set.prototype.add
with a calling context of the newly created Set
, by passing that Set
as the first argument to .call
: 您已经想到的第一部分是,您需要使用新创建的
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());
But the other issue is that the .call
needs to be called with the approprite calling context. 但另一个问题是
.call
需要使用approprite调用上下文来调用。 Set.prototype.add.call
refers to the same function as Function.prototype.call
: Set.prototype.add.call
引用与Function.prototype.call
相同的函数:
console.log(Set.prototype.add.call === Function.prototype.call);
The function that Function.prototype.call
calls is based on its calling context. Function.prototype.call
调用的函数基于其调用上下文。 For example 例如
someObject.someMethod.call(< args >)
The calling context of a function is everything that comes before the final .
函数的调用上下文是在final之前的所有内容
.
in the function call. 在函数调用中。 So, for the above, the calling context for
.call
is someObject.someMethod
. 因此,对于上述内容,
.call
的调用上下文是someObject.someMethod
。 That's how .call
knows which function to run. 这就是
.call
知道运行哪个函数的方式。 Without a calling context, .call
won't work: 没有调用上下文,
.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');
The error in the snippet above is somewhat misleading. 上面代码段中的错误有点误导。
methodCall
is a function - specifically Function.prototype.call
- it just doesn't have a calling context, so an error is thrown. methodCall
是一个函数 - 特别是Function.prototype.call
- 它只是没有调用上下文,因此抛出一个错误。 This behavior is identical to the below snippet, where Function.prototype.call
is being called without a calling context: 此行为与下面的代码段相同,其中在没有调用上下文的情况下调用
Function.prototype.call
:
console.log(typeof Function.prototype.call.call); Function.prototype.call.call( undefined, );
Hopefully this should make it clear that when using .call
, you need to use it with the proper calling context, or it'll fail. 希望这应该清楚地表明,当使用
.call
,你需要使用正确的调用上下文,否则它将失败。 So, to get back to the original question: 那么,回到最初的问题:
[1,2,3].reduce(Set.prototype.add.call, new Set());
fails because the internals of reduce
calls Set.prototype.add.call
without a calling context. 失败,因为
reduce
的内部调用Set.prototype.add.call
没有调用上下文。 It's similar to the second snippet in this answer - it's like if Set.prototype.add.call
is put into a standalone variable, and then called. 它类似于这个答案中的第二个片段 - 就像将
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());
In contrast, the 相比之下,
(..args) => Set.prototype.add.call(..args)
works (in both .reduce
and .customReduce
) because the .call
is being called with a calling context of Set.prototype.add
, rather than being saved in a variable first (which would lose the calling context). 作品(在这两个
.reduce
和.customReduce
),因为.call
被调用的调用上下文Set.prototype.add
,而不是被保存在一个变量第一(这会失去调用上下文)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.