Марейн Хавербеке - Выразительный JavaScript
Итог
Эта глава показала вам, как писать собственные функции. Когда ключевое слово function используется в виде выражения, возвращает указатель на вызов функции. Когда оно используется как инструкция, вы можете объявлять переменную, назначая ей вызов функции.
// Создаём f со ссылкой на функцию
var f = function(a) {
console.log(a + 2);
};
// Объявляем функцию g
function g(a, b) {
return a * b * 3.5;
}
Ключевой момент в понимании функций – локальные области видимости. Параметры и переменные, объявленные внутри функции, локальны для неё, пересоздаются каждый раз при её вызове, и не видны снаружи. Функции, объявленные внутри другой функции, имеют доступ к её области видимости.
Очень полезно разделять разные задачи, выполняемые программой, на функции. Вам не придётся повторяться, функции делают код более читаемым, разделяя его на смысловые части, так же, как главы и секции книги помогают в организации обычного текста.
Упражнения
Минимум
В предыдущей главе была упомянута функция Math.min, возвращающая самый маленький из аргументов. Теперь мы можем написать такую функцию сами. Напишите функцию min, принимающую два аргумента, и возвращающую минимальный из них.
console.log(min(0, 10));
// → 0
console.log(min(0, -10));
// → -10
Рекурсия
Мы видели, что оператор % (остаток от деления) может использоваться для определения того, чётное ли число (% 2). А вот ещё один способ определения:
• Ноль чётный.
• Единица нечётная.
• У любого числа N чётность такая же, как у N - 2.
Напишите рекурсивную функцию isEven согласно этим правилам. Она должна принимать число и возвращать булевское значение.
Потестируйте её на 50 и 75. Попробуйте задать ей -1. Почему она ведёт себя таким образом? Можно ли её как-то исправить?
console.log(isEven(50));
// → true
console.log(isEven(75));
// → false
console.log(isEven(-1));
// → ??
Считаем бобы
Символ номер N строки можно получить, добавив к ней .charAt(N) ("строчка".charAt(5)) – схожим образом с получением длины строки при помощи .length. Возвращаемое значение будет строковым, состоящим из одного символа (к примеру, "к"). У первого символа строки позиция 0, что означает, что у последнего символа позиция будет string.length - 1. Другими словами, у строки из двух символов длина 2, а позиции её символов будут 0 и 1.
Напишите функцию countBs, которая принимает строку в качестве аргумента, и возвращает количество символов “B”, содержащихся в строке.
Затем напишите функцию countChar, которая работает примерно как countBs, только принимает второй параметр — символ, который мы будем искать в строке (вместо того, чтобы просто считать количество символов “B”). Для этого переделайте функцию countBs.
4. Структуры данных: объекты и массивы
Два раза меня спрашивали: «Скажите, м-р Бэббидж, а если вы введёте в машину неправильные данные, получится ли правильный ответ?». Непостижима та путаница в головах, которая приводит к таким вопросам.
Чарльз Бэббидж, «Отрывки из жизни философа» (1864)Числа, булевские значения и строки – кирпичики, из которых строятся структуры данных. Но нельзя сделать дом из одного кирпича. Объекты позволяют нам группировать значения (в том числе и другие объекты) вместе – и строить более сложные структуры.
Написание программ, которым мы до сего момента занимались, сильно затруднял тот факт, что они работали только с простыми данными. Эта глава добавит вам в инструментарий понимание структур данных. К её концу вы будете знать достаточно для того, чтобы начать писать полезные программы.
Глава пройдётся по более-менее реалистичному примеру программирования, вводя понятия по мере необходимости. Код примеров будет строиться из функций и переменных, которые мы определяли ранее.
Белка-оборотень
Иногда, обычно между восемью и десятью часами вечера, Жак против своей воли превращается в небольшого грызуна с пушистым хвостом.
С одной стороны, Жак рад, что он не превращается в классического волка. Превращение в белку влечёт меньше проблем. Вместо того, чтобы волноваться о том, не съешь ли ты соседа (это было бы неловко), он волнуется, как бы его не съел соседский кот. После того, как он дважды просыпался на очень тонкой ветке в кроне дуба, голый и дезориентированный, он приучился запирать окна и двери в своей комнате на ночь, и класть несколько орешков на пол, чтобы чем-то занять себя.
Так решаются проблемы с котом и дубом. Но Жак всё ещё страдает от своего заболевания. Нерегулярные обращения наводят его на мысль, что они должны быть чем-то вызваны. Сначала он думал, что это происходит только в те дни, когда он прикасался к деревьям. Он перестал это делать, и даже стал избегать подходить к ним. Но проблема не исчезла.
Перейдя к более научному подходу, Жак решил вести ежедневный дневник всего, чем он занимался, записывая туда, обращался ли он в белку. Так он надеется сузить круг вещей, приводящих к трансформации.
Сперва он решил разработать структуру данных для хранения этой информации.
Наборы данных
Для работы с куском данных нам вначале нужно найти способ представлять их в памяти машины. К примеру, нам нужно запомнить коллекцию чисел:
2, 3, 5, 7, 11
Можно поиграть со строками – строки могут быть любой длины, в них можно поместить много данных, и использовать для представления этого набора “2 3 5 7 11”. Но это неудобно. Нам нужно будет как-то вынимать оттуда числа или вставлять новые в строку.
К счастью, JavaScript предлагает тип данных специально для хранения последовательностей чисел. Он называется массивом (array), и записывается, как список значений в квадратных скобках, разделённых запятыми:
var listOfNumbers = [2, 3, 5, 7, 11];
console.log(listOfNumbers[1]);
// → 3
console.log(listOfNumbers[1 - 1]);
// → 2
Запись для получения элемента из массива тоже использует квадратные скобки. Пара скобок после выражения, содержащая внутри ещё одно выражение, найдёт в массиве, который задан первым выражением, элемент, порядковый номер которого задан вторым выражением.
Номер первого элемента – ноль, а не один. Поэтому первый элемент можно получить так: listOfNumbers[0]. Если вы раньше не программировали, придётся привыкнуть к такой нумерации. Но она имеет давнюю традицию, и всё время, пока её последовательно соблюдают, она прекрасно работает.
Свойства
Мы видели много подозрительных выражений вроде myString.length (получение длины строки) и Math.max (получение максимума) в ранних примерах. Эти выражения используют свойства величин. В первом случае, мы получаем доступ к свойству length (длина) переменной myString. Во втором — доступ к свойству max объекта Math (который является набором функций и переменных, связанных с математикой).
Почти у всех переменных в JavaScript есть свойства. Исключения — null и undefined. Если вы попробуете получить доступ к несуществующим свойствам этих не-величин, получите ошибку:
null.length;
// → TypeError: Cannot read property 'length' of null
Два основных способа доступа к свойствам – точка и квадратные скобки. value.x и value[x] получают доступ к свойству value – но не обязательно к одному и тому же. Разница в том, как интерпретируется x. При использовании точки запись после точки должна быть именем существующей переменной, и она таким образом напрямую вызывает свойство по имени. При использовании квадратных скобок выражение в скобках вычисляется для получения имени свойства. value.x вызывает свойство под именем “x”, а value[x] вычисляет выражение x и использует результат в качестве имени свойства.
Если вы знаете, что интересующее вас свойство называется “length”, вы пишете value.length. Если вы хотите извлечь имя свойства из переменной i, вы пишете value[i]. А поскольку свойство может иметь любое имя, для доступа к свойству по имени “2” или “Jon Doe” вам придётся использовать квадратные скобки: value[2] или value["John Doe"]. Это необходимо даже когда вы знаете точное имя свойства, потому что ни “2”, ни «John Doe» не являются допустимыми именами переменных, поэтому к ним нельзя обратиться при помощи записи через точку.
Элементы массива хранятся в свойствах. Так как имена этих свойств – числа, и нам часто приходится получать их имена из значений переменных, нужно использовать квадратные скобки для доступа к ним. Свойство length массива говорит о том, сколько в нём элементов. Имя этого свойства – допустимое имя переменной, и мы его знаем заранее, поэтому обычно мы пишем array.length, потому, что это проще, чем писать array["length"].