我玩弄Array.reduce与集之间的互动,我发现以下奇怪的行为。
通常情况下这个工程:
console.log(
Set.prototype.add.call(new Set(), 1, 0, [])
);
// Set { 1 }
但是,如果我结合起来,与减少,以下不工作:
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对象为好。我想不出就是为什么是这种情况。
实际上有脚本的两个部分需要以正常工作的正确调用上下文(或this
值)。第一部分,你已经想通了,是你需要调用Set.prototype.add
与新创建Set
的呼唤下,通过传递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 >)
函数的调用上下文是在函数调用的最后.
之前出现的一切。因此,针对上述情况,为.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
的调用上下文,而不是被保存在一个变量第一(这会失去调用上下文)。
没有包装,你Set.prototype.add.call
失去this
值(至极应该Set.prototype.add
功能,而是被设置为undefined
)。
尝试这个:
[1,2,3].reduce(Set.prototype.add.call.bind(Set.prototype.add), new Set());