Можно было бы подсчитать это число и без использования reduceAncestors
. Но разделение общего подхода (обход древа) и конкретного случая (подсчёт ДНК) позволяет нам писать более понятный код и использовать вновь части кода для других задач. Например, следующий код выясняет процент известных предков данного человека, доживших до 70 лет.
function countAncestors(person, test) {
function combine(person, fromMother, fromFather) {
var thisOneCounts = test(person);
return fromMother + fromFather + (thisOneCounts ? 1 : 0);
}
return reduceAncestors(person, combine, 0);
}
function longLivingPercentage(person) {
var all = countAncestors(person, function(person) {
return true;
});
var longLiving = countAncestors(person, function(person) {
return (person.died - person.born) >= 70;
});
return longLiving / all;
}
console.log(longLivingPercentage(byName["Emile Haverbeke"]));
// → 0.145
Не нужно относиться к таким расчётам слишком серьёзно, так как наш набор содержит произвольную выборку людей. Но код демонстрирует, что reduceAncestors
– полезная часть общего словаря для работы со структурой данных типа фамильного древа.
Связывание
Метод bind
, который есть у всех функций, создаёт новую функцию, которая вызовет оригинальную, но с некоторыми фиксированными аргументами.
Следующий пример показывает, как это работает. В нём мы определяем функцию isInSet
, которая говорит, есть ли имя человека в заданном наборе. Для вызова filter
мы можем либо написать выражение с функцией, которое вызывает isInSet
, передавая ей набор строк в качестве первого аргумента, или применить функцию isInSet
частично.
var theSet = ["Carel Haverbeke", "Maria van Brussel",
"Donald Duck"];
function isInSet(set, person) {
return set.indexOf(person.name) > -1;
}
console.log(ancestry.filter(function(person) {
return isInSet(theSet, person);
}));
// → [{name: "Maria van Brussel", …},
// {name: "Carel Haverbeke", …}]
console.log(ancestry.filter(isInSet.bind(null, theSet)));
// → … тот же результат
Вызов bind
возвращает функцию, которая вызовет isInSet
с первым аргументом theSet
, и последующими аргументами такими же, какие были переданы в bind
.
Первый аргумент, который сейчас установлен в null
, используется для вызовов методов – так же, как было в apply
. Мы поговорим об этом позже.
Итог
Возможность передавать вызов функции другим функциям – не просто игрушка, но очень полезное свойство JavaScript. Мы можем писать выражения «с пробелами» в них, которые затем будут заполнены при помощи значений, возвращаемых функциями.
У массивов есть несколько полезных методов высшего порядка – forEach
, чтобы сделать что-то с каждым элементом, filter
– чтобы построить новый массив, где некоторые значения отфильтрованы, map
– чтобы построить новый массив, каждый элемент которого пропущен через функцию, reduce
– для комбинации всех элементов массива в одно значение.
У функций есть метод apply
для передачи им аргументов в виде массива. Также у них есть метод bind
для создания копии функции с частично заданными аргументами.
Упражнения
Свёртка
Используйте метод reduce
в комбинации с concat
для свёртки массива массивов в один массив, у которого есть все элементы входных массивов.
var arrays = [[1, 2, 3], [4, 5], [6]];
// Ваш код тут
// → [1, 2, 3, 4, 5, 6]
Разница в возрасте матерей и их детей
Используя набор данных из примера, подсчитайте среднюю разницу в возрасте между матерями и их детьми (это возраст матери во время появления ребёнка). Можно использовать функцию average
, приведённую в главе.
Обратите внимание – не все матери, упомянутые в наборе, присутствуют в нём. Здесь может пригодиться объект byName
, который упрощает процедуру поиска объекта человека по имени.
function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}
var byName = {};
ancestry.forEach(function(person) {
byName[person.name] = person;
});
// Ваш код тут
// → 31.2
Историческая ожидаемая продолжительность жизни