Почему функция обертки требуется для функции предиката в Redu? - PullRequest
0 голосов
/ 10 февраля 2019

Я играл с Array.reduce и Set, и заметил следующее странное поведение.

Обычно это работает:

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) и получил тот же результат, так что это, вероятно, не специфическое поведение двигателя.То же самое относится и к объекту карты.Я не могу понять, почему это так.

Ответы [ 2 ]

0 голосов
/ 11 февраля 2019

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

0 голосов
/ 10 февраля 2019

Без переноса ваш Set.prototype.add.call потеряет значение this (которое должно быть Set.prototype.add функцией, но вместо этого установлено на undefined).

Попробуйте это:

[1,2,3].reduce(Set.prototype.add.call.bind(Set.prototype.add), new Set());

См. http://speakingjs.com/es5/ch01.html#_extracting_methods

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...