Я хотел бы спросить, почему 0 часть аргумента?
Я понимаю всю логику метода reduce и параметр rest, но на самом деле не понимаю важности 0. Заранее большое спасибо!
const sum = (...args) => {
return args.reduce((a,b) => a + b, 0);
}
1 ответ
ECMAScript — это динамически типизированный/нетипизированный/однотипный язык программирования (в зависимости от того, кого вы спросите). Тем не менее, имеет смысл рассмотреть типыздесь.
Я буду использовать синтаксис TypeScript, на самом деле я буду использовать определение из библиотеки TypeScript , но фактический синтаксис не имеет большого значения:
reduce<U>(
callbackfn: (
previousValue: U,
currentValue: T,
currentIndex: number,
array: readonly T[]
) => U,
initialValue: U
): U;
Тот факт, что исходный массив и текущий индекс передаются в функцию обратного вызова, является странностью ECMAScript, которая на самом деле не нужна для понимания того, как работает, поэтому давайте избавимся от них:
reduce<U>(
callbackfn: (
previousValue: U,
currentValue: T,
) => U,
initialValue: U
): U;
Обратите также внимание, что здесь у нас есть два параметра разных типов:
- является типом элемента .
- является типом результата .
Итак, это функция, которая принимает три аргумента:
- Невидимый
this
, который повторяется. - Функция , которая объединяет a и a и создает новый .
- Начальное значение типа
U
.
Концептуально , что делает, так это то, что он «сводит» набор значений к одному значению. Важно понимать, что это единственное значение результата может иметь произвольный тип . Он не обязательно должен быть того же типа, что и тип элемента
Array
.
делает это, используя переданную ему функцию редукции.
Итак, концептуально то, что возвращает
callbackfn(
callbackfn(
callbackfn(
callbackfn(
callbackfn(
callbackfn(
callbackfn(
callbackfn(
initialValue,
this[0]
),
this[1]
),
this[2]
),
this[3]
),
this[4]
),
this[5]
),
this[6]
),
this[7]
)
// and so on …
Или, если мы напишем функцию обратного вызова как бинарный оператор
ω
:
initialValue ω this[0] ω this[1] ω this[2] ω this[3] ω …
Обратите внимание, что функция обратного вызова должна быть левоассоциативной, т.е. эквивалентной
(((((initialValue ω this[0]) ω this[1]) ω this[2]) ω this[3]) ω …)
Многие библиотеки коллекций, и ECMAScript не является исключением, также предоставляют версию, не имеющую начального значения. Во многих библиотеках эта версия имеет другое название (например, в Scala версия с начальным значением называется
foldLeft
, версия без называется
reduceLeft
, в Haskell версия с начальным значением называется
foldl
, версия без называется
foldl1
). В ECMAScript вместо этого используется "перегрузка", т.е. вы можете просто пропустить
initialValue
аргумент.
Если начальное значение опущено, вместо этого мы начинаем с первого элемента коллекции, т.е.
callbackfn(
callbackfn(
callbackfn(
callbackfn(
callbackfn(
callbackfn(
callbackfn(
this[0],
this[1]
),
this[2]
),
this[3]
),
this[4]
),
this[5]
),
this[6]
),
this[7]
)
// and so on …
или записывается как оператор
this[0] ω this[1] ω this[2] ω this[3] ω …
Однако это имеет важное последствие! Наш оператор/функция обратного вызова теперь должен принимать два s и возвращать a, больше нет способа получить тип результата, отличный от типа элемента.
И это имеет еще одно важное последствие: больше не работает с пустой коллекцией.
Таким образом, это две причины, по которым вам нужно указать начальное значение:
- Вы хотите преобразовать тип.
- Вы хотите работать с пустой коллекцией.
В данном конкретном случае первая причина неприменима: функция явно предназначена для уменьшения
Array<number>
к
number
. Но важна вторая причина: вы не хотите заставлять клиентов функции проверять, пуста ли коллекция. Клиент должен иметь возможность позвонить
sum(...someArray)
и не волнуйся ли
someArray
пусто или нет.
Возможность изменить тип на произвольный важна для универсальности . Оказывается, это мощная функция: она может делать все то же, что и вы, перебирая коллекцию. Другими словами: вы можете удалить все
методыArray.prototype
кроме , а также удалить
for … of
а также
for … in
из ECMAScript, и вы по-прежнему можете делать все, что возможно, перебирая коллекцию. (Ну, я думаю, вам понадобится способ добавить что-то в массив, так что давайте продолжим
push
также.)
Вот только пример реализации
Array.prototype.map
с использованием
Array.prototype.reduce
:
Array.prototype.map = function map(callbackFn) {
return this.reduce(
(previousValue, currentValue) =>
previousValue.push(callbackFn(currentValue)),
[]
);
}
Здесь вы видите, что тот факт, что мы можем изменить тип, важен, потому что мы начинаем с
Array<T>
, но мы не хотим
T
в результате мы хотим
Array<U>
. Это также показывает, что утверждение «
reduce
сводит коллекцию к одному значению» не означает, что результатом должно быть простое значение. Значение результата может быть сколь угодно сложным… в данном случае это другой массив.