На самом деле есть две части скрипта, которые нуждаются в правильном контексте вызова (или 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
необходимо вызывать с подходящим контекстом вызова.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
, вместо того, чтобы сначала сохранять его в переменной (что потеряло бы вызывающий контекст).