简体   繁体   English

为什么谓词函数所需的包装函数在reduce中?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM