Выбрать главу

Можно было бы подсчитать это число и без использования 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

Историческая ожидаемая продолжительность жизни