Выразительный JavaScript - Марейн Хавербеке
Шрифт:
Интервал:
Закладка:
Итог
Возможность передавать вызов функции другим функциям – не просто игрушка, но очень полезное свойство 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
Историческая ожидаемая продолжительность жизни
Мы считали, что только последнее поколение людей дожило до 90 лет. Давайте рассмотрим этот феномен поподробнее. Подсчитайте средний возраст людей для каждого из столетий. Назначаем столетию людей, беря их год смерти, деля его на 100 и округляя: Math.ceil(person.died / 100).
function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}
// Тут ваш код
// → 16: 43.5
// 17: 51.2
// 18: 52.8
// 19: 54.8
// 20: 84.7
// 21: 94
В качестве призовой игры напишите функцию groupBy, абстрагирующую операцию группировки. Она должна принимать массив и функцию, которая вычисляет группу для элементов массива, и возвращать объект, который сопоставляет названия групп массивам членов этих групп.
Every и some
У массивов есть ещё стандартные методы every и some. Они принимают как аргумент некую функцию, которая, будучи вызванной с элементом массива в качестве аргумента, возвращает true или false. Так же, как && возвращает true, только если выражения с обеих сторон оператора возвращают true, метод every возвращает true, когда функция возвращает true для всех элементов массива. Соответственно, some возвращает true, когда заданная функция возвращает true при работе хотя бы с одним из элементов массива. Они не обрабатывают больше элементов, чем необходимо – например, если some получает true для первого элемента, он не обрабатывает оставшиеся.
Напишите функции every и some, которые работают так же, как эти методы, только принимают массив в качестве аргумента.
// Ваш код тут
console.log(every([NaN, NaN, NaN], isNaN));
// → true
console.log(every([NaN, NaN, 4], isNaN));
// → false
console.log(some([NaN, 3, 4], isNaN));
// → true
console.log(some([2, 3, 4], isNaN));
// → false
6. Тайная жизнь объектов
Проблема объектно-ориентированных языков в том, что они тащат с собой всё своё неявное окружение. Вам нужен был банан – а вы получаете гориллу с бананом, и целые джунгли впридачу.
Джо Армстронг, в интервью Coders at WorkТермин «объект» в программировании сильно перегружен значениями. В моей профессии объекты – стиль жизни, тема священных войн и любимое заклинание, не теряющее своей магической силы.
Стороннему человеку всё это непонятно. Начнём же с краткой истории объектов как концепции в программировании.
История
Эта история, как большинство историй о программировании, начинается с проблемы сложности. Одна из идей говорит, что сложность можно сделать управляемой, разделив её на небольшие части, изолированные друг от друга. Эти части стали называть объектами.
Объект – твёрдая скорлупа, скрывающая липкую сложность внутри, и вместо неё предлагающая нам несколько ручек настройки и контактов (вроде методов), представляющих интерфейс, посредством которого объект нужно использовать. Идея в том, что интерфейс относительно прост, и при работе с ним позволяет игнорировать все сложные процессы, происходящие внутри объекта.
Простой интерфейс может спрятать много сложного.
Для примера представьте объект, обеспечивающий интерфейс к участку экрана. С его помощью можно рисовать фигуры или выводить текст на этот участок, но при этом все детали, касающиеся превращения текста или фигур в пиксели, скрыты. У вас есть набор методов, к примеру drawCircle, и это всё, что вам нужно знать для использования такого объекта.
Такие идеи получили развитие в 70-80 годах, а в 90-х их вынесла на поверхность рекламная волна – революция объектно-ориентированного программирования. Внезапно большой клан людей объявил, что объекты – это правильный способ программирования. А всё, что не имеет объектов, является устаревшей ерундой.
Такой фанатизм всегда приводит к куче бесполезной чуши, и с тех пор идёт что-то вроде контрреволюции. В некоторых кругах объекты вообще имеют крайне плохую репутацию.
Я предпочитаю рассматривать их с практической, а не идеологической точки зрения. Есть несколько полезных идей, в частности инкапсуляция (различие между внутренней сложностью и внешней простотой), которые были популяризованы объектно-ориентированной культурой. Их стоит изучать.
Эта глава описывает довольно эксцентричный подход JavaScript к объектам, и то, как они соотносятся с классическими объектно-ориентированными техниками.
Методы
Методы – свойства, содержащие функции. Простой метод:
var rabbit = {};
rabbit.speak = function(line) {
console.log("Кролик говорит '" + line + "'");
};
rabbit.speak("Я живой.");
// → Кролик говорит 'Я живой.'
Обычно метод должен что-то сделать с объектом, через который он был вызван. Когда функцию вызывают в виде метода – как свойство объекта, например object.method() – специальная переменная в её теле будет указывать на вызвавший её объект.
function speak(line) {
console.log("А " + this.type + " кролик говорит '" + line + "'");
}
var whiteRabbit = {type: "белый", speak: speak};
var fatRabbit = {type: "толстый", speak: speak};
whiteRabbit.speak("Ушки мои и усики, я же наверняка опаздываю!");
// → А белый кролик говорит 'Ушки мои и усики, я же наверняка опаздываю!'
fatRabbit.speak("Мне бы сейчас морковочки.");
// → А толстый кролик говорит 'Мне бы сейчас морковочки.'
Код использует ключевое слово this для вывода типа говорящего кролика.
Вспомните, что методы apply и bind принимают первый аргумент, который можно использовать для эмуляции вызова методов. Этот первый аргумент как раз даёт значение переменной this.
Есть метод, похожий на apply, под названием call. Он тоже вызывает функцию, методом которой является, только принимает аргументы как обычно, а не в виде массива. Как apply и bind, в call можно передать значение this.
speak.apply(fatRabbit, ["Отрыжка!"]);
// → А толстый кролик говорит 'Отрыжка!'
speak.call({type: "старый"}, "О, господи.");
// → А старый кролик говорит 'О, господи.'
Прототипы
Следите за руками.
var empty = {};
console.log(empty.toString);
// → function toString(){…}
console.log(empty.toString());
// → [object Object]
Я достал свойство пустого объекта. Магия!
Ну, не магия, конечно. Я просто не всё рассказал про то, как работают объекты в JavaScript. В дополнение к набору свойств, почти у всех также есть прототип. Прототип – это ещё один объект, который используется как запасной источник свойств. Когда объект получает запрос на свойство, которого у него нет, это свойство ищется у его прототипа, затем у прототипа прототипа, и т. д.