Выразительный JavaScript. Современное веб-программирование
Қосымшада ыңғайлырақҚосымшаны жүктеуге арналған QRRuStore · Samsung Galaxy Store
Huawei AppGallery · Xiaomi GetApps

автордың кітабын онлайн тегін оқу  Выразительный JavaScript. Современное веб-программирование

 

Марейн Хавербеке
Выразительный JavaScript. Современное веб-программирование. 3-е издание
2020

Переводчик Е. Сандицкая

Литературный редактор Е. Рафалюк-Бузовская

Художник В. Мостипан

Корректоры О. Андриевич, Н. Гринчик, Е. Павлович



 

Марейн Хавербеке

Выразительный JavaScript. Современное веб-программирование. 3-е издание . — СПб.: Питер, 2021.

 

ISBN 978-5-4461-1226-5

© ООО Издательство "Питер", 2021

 

Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.

 

Введение

Мы думаем, что создаем систему для своих собственных целей. Мы считаем, что формируем ее по своему образу... Но на самом деле компьютер не похож на нас. Это проекция очень малой части нас самих: части, которая отвечает за логику, порядок, ясность и соблюдение правил.

Эллен Ульман. Рядом с машиной: технофилия и ее недостатки

Это книга об обучении компьютеров. Сегодня компьютер встречается чаще, чем отвертка, но он гораздо сложнее, и заставить его делать то, что нам нужно, не всегда легко.

Если вы хотите, чтобы компьютер выполнил стандартную и понятную задачу, такую как просмотр электронной почты или выполнение функций калькулятора, то достаточно открыть соответствующее приложение и приступить к работе. Но для уникальных или открытых задач, скорее всего, приложения не найдется.

И здесь нам на помощь приходит программирование. Программирование — это процесс создания программы, набора точных инструкций, говорящих компьютеру, что делать, поскольку компьютер — тупое и педантичное устройство, программирование которого большей частью утомительное и досадное занятие.

К счастью, если вы способны это преодолеть и, возможно, даже получать удовольствие от строгого мышления в терминах, понятных тупой машине, программирование может принести пользу. Оно позволяет вам за считаные секунды делать вещи, выполнение которых вручную может длиться бесконечно. Это способ заставить ваш компьютер сделать то, что он не мог делать раньше. А еще — отличная тренировка абстрактного мышления.

Программирование большей частью выполняется на языках программирования. Язык программирования — это искусственный язык, созданный для написания инструкций для компьютеров. Интересно, что самый эффективный из найденных нами способов общения с компьютером мы в значительной мере заимствовали из того, как мы общаемся друг с другом. Подобно человеческим языкам, компьютерные языки позволяют разными способами сочетать слова и фразы, что дает возможность выражать все новые и новые понятия.

Было время, когда языковые интерфейсы, такие как командные строки BASIC и DOS 1980-х и 1990-х годов, являлись основным способом взаимодействия с компьютерами. Затем они повсеместно были заменены визуальными интерфейсами — такие интерфейсы проще освоить, хотя они предоставляют меньше свободы. Но компьютерные языки все еще сохраняются — надо только знать, где искать. Один из таких языков, JavaScript, встроен во все современные браузеры и, таким образом, доступен практически на любом устройстве.

Эта книга познакомит вас с данным языком, и вы узнаете, какие полезные и забавные вещи можно делать с его помощью.

О программировании

В этой книге я не только объясню JavaScript, но и познакомлю вас с основными принципами программирования. Программировать, оказывается, трудно. Основные правила просты и понятны, но программы, построенные на основе таких правил, со временем становятся достаточно сложными, вводят собственные правила и собственную сложность. Вы сами создаете свой лабиринт, в котором потом будет легко заблудиться.

Временами чтение этой книги будет вам даваться с трудом. Если вы новичок в программировании, то вам встретится много незнакомого материала, который придется переварить. Многие его части будут сочетаться между собой, так что вам потребуется усвоить эти дополнительные связи.

Вам придется приложить определенные усилия. Пытаясь изо всех сил следовать книге, не спешите с выводами о своих способностях. С вами все в порядке — вам только нужно во всем разобраться. Сделайте перерыв, перечитайте материал и убедитесь, что, читая, вы понимаете примеры программ и упражнений. Обучение — трудная работа, но все, что вы изучите, будет принадлежать вам и облегчит последующее обучение.

Когда активная деятельность перестает приносить пользу, начинайте тихо собирать информацию; когда сбор информации перестает приносить пользу, ложитесь спать.

Урсула К. Ле Гуин. Левая рука Тьмы

В программе есть много всего. Это и кусок текста, набранный программистом; и движущая сила, которая заставляет компьютер делать то, что он делает; это и данные в памяти компьютера, но одновременно и управление действиями, выполняемыми в той же памяти. Попытки провести аналогии между программами и уже знакомыми нам объектами, как правило, терпят неудачу. Одна из приблизительно подходящих — это машина: в ней обычно задействовано много отдельных частей. И чтобы понять принцип их работы, нужно рассмотреть, как они взаимодействуют, обеспечивая работу целого.

Компьютер — физическая машина, играющая роль хозяина для этих нематериальных машин. Сами компьютеры могут выполнять только до глупого простые вещи. Причина, по которой они так полезны, заключается в том, что они делают это поразительно быстро. Программа может изобретательно комбинировать невероятное количество простых действий и таким образом делать очень сложные вещи.

Программа — это мысленная конструкция. Построить ее ничего не стоит, и она ничего не весит, она легко растет под нашими пальцами, печатающими на клавиатуре.

Но без должного внимания размер и сложность программы выходят из-под контроля, что способно привести в замешательство даже ее создателя. Держать программы под контролем — главная проблема программирования. Пока программа работает, все отлично. Искусство программирования — это умение контролировать сложность. Покорить великолепную программу — значит сделать ее простой в своей сложности.

Некоторые программисты считают, что лучше всего справиться с проблемой, если использовать в программах лишь небольшой набор хорошо понятных приемов. Они разработали строгие правила («рекомендуемые методы»), предписывающие формы, которые должны иметь программы, и советуют тщательно придерживаться их, чтобы оставаться в безопасной зоне.

Это не только скучно, но и неэффективно. Новые проблемы часто требуют новых решений. Сфера программирования молода и все еще быстро развивается, и она достаточно разнообразна, чтобы в ней оставалось место для совершенно разных подходов. При разработке программ сделано много ужасных ошибок; вам следует двигаться вперед и тоже совершать ошибки, чтобы в них разбираться. Понимания того, как выглядит хорошая программа, можно достичь на практике, но не выучить, руководствуясь списком правил.

Почему так важен язык программирования

Поначалу, когда компьютеры только появились, языков программирования не существовало. Программы выглядели примерно так:

00110001    00000000    00000000

00110001    00000001    00000001

00110011    00000001    00000010

01010001    00001011    00000010

00100010    00000010    00001000

01000011    00000001    00000000

01000001    00000001    00000001

00010000    00000010    00000000

01100010    00000000    00000000

Эта программа складывает числа от 1 до 10 и выводит результат: 1 + 2 + ... +  + 10 = 55. Она могла бы работать на простой гипотетической машине. Для программирования первых компьютеров требовалось установить в правильное положение множество переключателей или же пробить отверстия в картонных перфокартах и скормить их компьютеру. Представляете, какой утомительной была эта процедура и сколько в ней возникало ошибок! Даже написание простых программ требовало большого умения и дисциплины, а о сложных нечего было и думать.

Конечно, программист, вводивший вручную такие тайные коды в виде битов (единиц и нулей), в глубине души начинал чувствовать себя могущественным волшебником. В смысле удовлетворения от работы это чего-то да стоит.

Каждая строка предыдущей программы содержит одну инструкцию. По-русски это можно описать так.

1. Сохраните номер 0 в ячейке памяти 0.

2. Сохраните номер 1 в ячейке памяти 1.

3. Сохраните значение из ячейки памяти 1 в ячейке памяти 2.

4. Вычтите число 11 из значения в ячейке памяти 2.

5. Если значение в ячейке памяти 2 является числом 0, продолжайте выполнять инструкции, начиная с пункта 9.

6. Прибавьте значение из ячейки памяти 1 к значению из ячейки памяти 0.

7. Прибавьте число 1 к значению из ячейки памяти 1.

8. Продолжайте выполнение инструкций, начиная с пункта 3.

9. Выведите значение из ячейки памяти 0.

Это уже гораздо понятнее, чем суп из битов, но все еще маловразумительно. Гораздо удобнее использовать в инструкциях имена вместо чисел и номеров ячеек памяти:

Установить "total" в 0.

  Установить "count" в 1.

[loop]

  Установить "compare" в "count".

  Вычесть 11 из "compare".

  Если "compare" равно нулю, перейти на [end].

  Добавить "count" к "total".

  Добавить 1 к "count".

  Перейти на [loop].

[end]

  Вывести "total".

Теперь вы видите, как работает программа? В первых двух строках двум ячейкам памяти присваиваются начальные значения: total будет использоваться для получения результата вычислений, а в count будет записано число, с которым мы сейчас работаем.

Наиболее странно, пожалуй, выглядят строки, где используется compare. Программа хочет узнать, равно ли значение count 11, чтобы решить, следует ли прекратить работу. Поскольку наша гипотетическая машина довольно примитивна, она может только проверить, равно ли число нулю, и на основе этого принять решение. Поэтому программа задействует ячейку памяти с именем compare для вычисления значения count - 11 и принимает соответству­ющее решение. Следующие две строки добавляют к результату значение count и увеличивают count на 1 каждый раз, когда программа решает, что значение count еще не равно 11.

Вот та же программа на JavaScript:

let total = 0, count = 1;

while (count <= 10) {

  total += count;

  count += 1;

}

console.log(total);

// → 55

В этой версии добавилось еще несколько улучшений. Самое главное, теперь не нужно указывать способ, которым программа должна перепрыгивать назад и вперед, — об этом заботится конструкция while. Программа продолжает выполнять расположенный под while блок (заключенный в фигурные скобки), пока выполняется заданное в while условие. Это условие count <= 10, что означает «count меньше или равно 10». Нам больше не нужно создавать временное значение и сравнивать его с нулем, что было просто неинтересной подробностью. Одна из возможностей языков программирования заключается в том, что они сами позаботятся о неинтересных для нас деталях.

В конце программы, после того как выполнена конструкция while, для вывода результата используется операция console.log.

Наконец, вот как могла бы выглядеть программа, если бы в нашем распоряжении были удобные операции range и sum, которые соответственно создавали бы коллекцию чисел в заданном диапазоне и вычисляли ее сумму:

console.log(sum(range(1, 10)));

// → 55

Мораль этой истории в том, что одна и та же программа может быть создана длинной или короткой, нечитаемой и читаемой. Первая версия нашей программы была совершенно непонятной, тогда как последняя написана почти на обычном английском языке: «запишите (log) сумму (sum) чисел в диапазоне (range) от 1 до 10». (В следующих главах вы научитесь определять операции, такие как sum и range.)

Хороший язык программирования помогает программисту, позволяя ему говорить о действиях, которые должен выполнить компьютер, на более высоком уровне. Язык программирования дает возможность опустить детали, предоставляет удобные строительные блоки (такие как while и conso­le.log), позволяет определять собственные строительные блоки (такие как sum и range) и легко компоновать их.

Что такое JavaScript

JavaScript появился в 1995 году как способ программирования веб-страниц в браузере Netscape Navigator. С тех пор язык был принят во всех остальных ведущих графических браузерах. Это сделало возможным применение современных веб-приложений — приложений, с которыми можно взаимодействовать напрямую, не перезагружая страницу при каждом действии. JavaScript также используется на более традиционных сайтах для выполнения различных интерактивных «умных» действий.

Важно отметить, что JavaScript никогда не имел ничего общего с языком программирования под названием Java. Похожее название было продиктовано не здравым смыслом, а маркетинговыми соображениями. Когда появился JavaScript, язык Java активно продавался и приобретал популярность. Кто-то решил, что будет хорошей идеей бесплатно прокатиться на волне чужого успеха. Теперь приходится за это расплачиваться.

После того как JavaScript прижился за пределами Netscape, был написан стандартный документ. Он оговаривал, как должен работать JavaScript, чтобы различные программы, утверждающие, что поддерживают JavaScript, в действительности имели в виду один и тот же язык. Это так называемый стандарт ECMAScript, по названию организации Ecma International, которая выполнила стандартизацию. На практике термины ECMAScript и JavaScript взаимозаменяемы — представляют собой два названия одного и того же языка.

О JavaScript рассказывают ужасные вещи. И многие из них правда. Когда мне впервые пришлось написать кое-что на JavaScript, он вызвал у меня отвращение. Он принимал почти все, что я вводил, но интерпретировал все совершенно не так, как я имел в виду. Во многом это было связано с тем, что я, конечно, тогда понятия не имел, что делаю, но здесь есть и реальная проблема: JavaScript смехотворно либерален в отношении того, что он позволяет делать. Идея такого подхода заключалась в том, чтобы облегчить программирование на JavaScript для новичков. В действительности это главным образом затрудняет поиск проблем в программах, поскольку система не указывает вам на них.

Однако у такой гибкости есть свои преимущества. Она оставляет место для множества приемов, которые невозможны в более жестких языках, и, как мы увидим (например, в главе 10), ее можно использовать для преодоления отдельных недостатков JavaScript. Хорошо изучив язык и поработав с ним некоторое время, я по-настоящему полюбил JavaScript.

JavaScript претерпел несколько версий. Версия ECMAScript 3 широко поддерживалась во времена, когда JavaScript только находился на пути к своему господству, примерно между 2000 и 2010 годами. В это время велась работа над амбициозной версией 4, в которой планировался ряд радикальных улучшений и расширений языка. Такое радикальное изменение живого, широко используемого языка оказалось политически трудным, и в 2008 году работы над версией 4 были прекращены, что привело к появлению в 2009 году гораздо менее амбициозной версии 5, которая внесла лишь отдельные бесспорные улучшения. Затем в 2015 году вышла версия 6, серьезное обновление, включающее в себя некоторые идеи, запланированные для версии 4. С тех пор каждый год появляются небольшие обновления.

Тот факт, что язык развивается, означает, что браузеры должны постоянно идти с ними в ногу, и если вы используете старый браузер, он может не поддерживать все функции. Разработчики языка стараются не вносить никаких изменений, которые могли бы нарушить работоспособность существующих программ, поэтому старые программы на новых браузерах по-прежнему будут работать. В книге я использую версию JavaScript 2017 года.

Браузеры не единственные платформы, где применяется JavaScript. Некоторые базы данных, такие как MongoDB и CouchDB, используют JavaScript в качестве языка сценариев и запросов. Ряд платформ для программирования настольных ПК и серверов, в частности проект Node.js (тема главы 20), предоставляют среду для программирования на JavaScript вне браузера.

Код и что с ним делать

Код — это текст, из которого состоит программа. Большинство глав данной книги содержат довольно много кода. Я считаю, что его чтение и написание являются неотъемлемой частью обучения программированию. Постарайтесь не просто просматривать примеры, а внимательно читать, чтобы понять каждый из них. Сначала это может быть медленно, код будет выглядеть запутанным, но я обещаю, что вы быстро освоитесь. То же самое касается упражнений. Не думайте, что вы все поняли, пока действительно не напишете рабочее решение.

Я советую вам проверять свои решения упражнений в реальном интерпретаторе JavaScript. Таким образом вы получите немедленную обратную связь — работает ли то, что вы сделали, и я надеюсь, у вас появится соблазн экспериментировать и выходить за рамки упражнений.

Самый простой способ выполнить пример кода из этого издания и поэкспериментировать с ним — найти его в онлайн-версии книги по адресу https://eloquentjavascript.net. На данной странице можно щелкнуть на любом примере кода, чтобы отредактировать его, запустить и увидеть результат, который он выдает. Чтобы поработать с упражнениями, перейдите по адресу https://eloquentjavascript.net/code, где размещен начальный код для каждого упражнения, что позволяет взглянуть на решения.

Если вы хотите выполнять программы, описанные в этой книге, вне ее сайта, то потребуется определенная осторожность. Многие примеры являются независимыми и должны работать в любой среде JavaScript. Но код из последних глав часто написан для конкретной среды (браузера или Node.js) и может выполняться только там. Кроме того, во многих главах описаны более крупные программы, и приведенные там фрагменты кода зависят друг от друга или от внешних файлов. «Песочница», встроенная в сайт книги, содержит ссылки на ZIP-файлы. Они включают в себя все сценарии и файлы данных, необходимые для выполнения кода данной главы.

Обзор этой книги

Эта книга делится на три большие части. В первых 12 главах обсуждается язык JavaScript. Следующие семь глав посвящены браузерам и тому, как JavaScript используется для их программирования. Наконец, две главы посвящены Node.js, еще одной среде для программирования на JavaScript.

На протяжении всей книги вам встретится пять глав проектов, в которых описаны более крупные примеры программ, чтобы вы могли почувствовать вкус настоящего программирования. В порядке их появления мы будем работать над созданием робота доставки, языка программирования, игровой платформы, растрового графического редактора и динамического сайта.

Языковая часть книги начинается с четырех глав, которые познакомят вас с основной структурой языка JavaScript. Вы узнаете, что такое управляющие структуры (такие как ключевое слово while, уже встречавшееся вам во введении), функции (написание собственных строительных блоков) и структуры данных. После этого вы сможете писать простейшие программы. Далее, в главах 5 и 6, описаны способы использования функций и объектов, позволяющие писать более абстрактный код и контролировать его сложность.

После главы первого проекта будет продолжена языковая часть книги — следующие главы посвящены обнаружению и исправлению ошибок, регулярным выражениям (важный инструмент для работы с текстом), модульности (еще одна защита от сложности) и асинхронному программированию (работа с событиями, которые длятся какое-то время). Первую часть книги завершает глава второго проекта.

Во второй части, в главах с 13-й по 19-ю, описаны инструменты, к которым имеет доступ браузер с поддержкой JavaScript. Вы научитесь отображать элементы на экране (главы 14 и 17), реагировать на ввод данных пользователем (глава 15) и обмениваться ими по сети (глава 18). В данной части также содержатся две главы проектов.

После этого в главе 20 описывается Node.js, а в главе 21 создается небольшой сайт с использованием указанного инструмента.

Условные обозначения

Моноширинным шрифтом в этой книге набраны элементы программ — иногда это самостоятельные фрагменты, а иногда упоминания в тексте частей ближайшей программы. Программы (некоторые из них вы уже видели) написаны следующим образом:

function factorial(n) {

  if (n == 0) {

    return 1;

  } else {

    return factorial(n - 1) * n;

  }

}

Иногда, чтобы показать, что программа выводит на выходе, ожидаемый вывод записывается после нее с двумя косыми чертами и стрелочкой в начале строки:

console.log(factorial(8));

// → 40320

Удачи!

1. Значения, типы и операторы

Под поверхностью машины движется программа. Без усилий она расширяется и сжимается. Находясь в великой гармонии, электроны рассеиваются и собираются. Формы на мониторе — лишь рябь на воде. Суть остается скрытой внутри...

Мастер Юан-Ма. Книга программирования

Внутри компьютерного мира существуют только данные. Вы можете читать данные, изменять данные, создавать новые данные, но с тем, что не является данными, вы ничего не можете сделать. Все эти данные хранятся в виде длинных последовательностей битов, и, таким образом, принципиально все данные представляют собой одно и то же.

Биты — это что угодно, что может принимать два значения, обычно описываемые как ноль и единица. Внутри компьютера биты представлены как высокий или низкий электрический заряд, сильный или слабый сигнал, блестящая или тусклая точка на поверхности компакт-диска. Любая дискретная информация может быть представлена в виде последовательности нулей и единиц — следовательно, в битах.

Например, можно выразить в битах число 13. Это похоже на представление десятичного числа, но вместо десяти разных цифр у нас есть только две, и вес каждой цифры, если смотреть справа налево, увеличивается в два раза. Вот биты, образующие число 13, с показанным под ними весом цифр:

  0     0     0     0    1    1    0    1

128    64    32    16    8    4    2    1

Таким образом, получаем двоичное число 00001101. Его ненулевые цифры соответствуют 8, 4 и 1, что в сумме дает 13.

Значения

Представьте себе море битов, океан битов. В энергозависимом хранилище данных (рабочей памяти) обычного современного компьютера более 30 миллиардов бит. В энергонезависимом хранилище (на жестком диске или его эквиваленте), как правило, их на несколько порядков больше.

Чтобы иметь возможность работать с таким количеством битов и не потеряться среди них, нужно разделить их на куски, представляющие отдельные фрагменты информации. В среде JavaScript эти куски называются значения­ми. Все значения состоят из битов, но все они играют разные роли. Каждое значение имеет тип, который определяет его роль. Одни значения являются числами, другие — фрагментами текста, третьи — функциями и т.д.

Чтобы создать значение, достаточно просто вызвать его по имени. Это удобно — вам не приходится собирать строительные материалы для значений или платить за них. Вы просто вызываете его, и — вжжух — оно у вас есть. Разумеется, значения создаются из ничего. Каждое должно храниться где-то, и если вы хотите использовать огромное количество значений одновременно, то вам может не хватить памяти. К счастью, это становится проблемой, только если все значения нужны вам одновременно. Как только значение больше не используется, оно исчезает, оставляя после себя свободные биты для повторного применения в качестве строительного материала следующему поколению значений.

В этой главе представлены атомарные элементы программ JavaScript, то есть простые типы значений и операторы, которые взаимодействуют с ними.

Числа

Значения числового типа, что неудивительно, являются числовыми значениями. В программе JavaScript они записываются так:

13

Если использовать это в программе, то в памяти компьютера появится битовая комбинация, соответствующая числу 13.

Для хранения одиночного числового значения в JavaScript применяется фиксированное количество битов — 64. Существует много вариантов того, что можно сделать с 64 битами, и это означает, что количество чисел, которые могут быть представлены таким образом, ограничено. Имея N десятичных цифр, можно представить 10N чисел. Аналогично, имея 64 двоичных числа, можно представить 264 различных чисел, что составляет около 18 квинтиллионов (18 с 18 нулями). Это много.

Раньше компьютерная память была гораздо меньше, и люди старались использовать для представления чисел группы из 8 или 16 бит. Такие маленькие числа было легко случайно переполнить — в итоге получалось число, которое не укладывалось в данное количество битов. Сегодня даже компьютеры, умещающиеся в кармане, имеют достаточно памяти, поэтому можно безбоязненно использовать 64-битные блоки, и остается беспокоиться о переполнении только при работе с действительно астрономическими числами.

Однако не все целые числа, которые меньше 18 квинтиллионов, соответствуют числу JavaScript. Такие биты также хранят и отрицательные числа, поэтому один бит указывает знак числа. Еще бόльшая проблема заключается в необходимости представления нецелых чисел. Для этого несколько битов используется для хранения положения десятичной точки. Фактическое максимальное целое число, которое может быть сохранено, не превышает 9 квадриллионов (15 нолей), что все еще довольно много.

Дробные числа записываются с помощью точки:

9.81

Для очень больших или очень маленьких чисел также можно использовать экспоненциальную запись с добавлением буквы е (от слова «экспонента»), за которой следует экспонента числа:

2.998e8

Это означает 2,998 × 108 = 299 800 000.

Вычисления с целыми (integer) числами, меньшими, чем вышеупомянутые 9 квадриллионов, гарантированно всегда будут точными. К сожалению, о вычислениях с дробными числами этого, как правило, сказать нельзя. Подобно тому как число π (пи) не может быть точно выражено конечным числом десятичных цифр, многие числа частично теряют точность, когда для их хранения доступно только 64 бита. Это досадно, но на практике вызывает проблемы только в специфических ситуациях. Об этом важно помнить и рассматривать дробные цифровые числа как приблизительные, а не как точные значения.

Арифметика

Основное, что обычно делают с числами, — выполняют арифметические действия. Арифметические операции, такие как сложение или умножение, принимают два числовых значения и производят из них новое число. Вот как они выглядят в JavaScript:

100 + 4 * 11

Символы + и * называются операторами. Первый из них обозначает сложение, второй — умножение. Если поместить оператор между двумя значения­ми, то он будет применен к ним и будет создано новое значение.

Но значит ли этот пример «прибавить 4 к 100 и умножить результат на 11», или же умножение выполняется до сложения? Как вы уже, возможно, догадались, сначала выполняется умножение. Но, как и в математике, эту последовательность можно изменить, заключив операцию сложения в скобки.

(100 + 4) * 11

Для вычитания предназначен оператор -, а деление выполняется с помощью оператора /.

При использовании нескольких операторов без скобок порядок их выполнения определяется приоритетом операторов. Как видно в примере, умножение выполняется перед сложением. Оператор / имеет тот же прио­ритет, что и *. Аналогичное правило действует для + и -. Если несколько операторов с одинаковым приоритетом появляются рядом, как в выражении 1 - 2 + 1, то они применяются слева направо: (1 - 2) + 1.

Пускай эти правила приоритета вас не беспокоят. Если сомневаетесь, просто добавьте скобки.

Есть еще один арифметический оператор, который не так легко узнать. Символ % используется для представления операции остатка от деления. X % Y — это остаток от деления X на Y. Например, 314 % 100 равно 14, а 144 % 12 равно 0. Приоритет оператора остатка такой же, как и у умножения и деления. Данный оператор также часто называют делением по модулю.

Специальные числа

В JavaScript есть три специальных значения, которые считаются числами, но ведут себя не как обычные числа.

Первые два — это Infinity и -Infinity, представляющие положительную и отрицательную бесконечность. Infinity - 1 — это все еще Infinity и т.д. Однако не стоит слишком доверять вычислениям на основе бесконечности. Подобное не имеет математического смысла и быстро приведет к следу­ющему специальному числу: NaN.

NaN означает «не число», хотя это значение числового типа. Такой результат можно получить, если, например, попытаться вычислить 0/0 (разделить ноль на ноль), Infinity - Infinity или выполнить любую другую числовую операцию, которая не дает осмысленного результата.

Строки

Следующий основной тип данных — это строка. Строки используются для представления текста. Их записывают, заключая содержимое в кавычки.

`В море`

"На волнах океана"

'Плавает в океане'

Для обозначения строк можно задействовать одинарные, двойные или обратные кавычки — лишь бы их вид в начале и конце строки совпадал.

Между кавычками можно поместить практически все что угодно — из всего этого JavaScript сделает строковое значение. Но есть несколько символов, которые усложняют дело. Очевидно, что трудно разместить внутри строки текст в кавычках. Переводы строки (символы, которые создаются при нажатии клавиши Enter) могут быть включены без экранирования только в том случае, если строка заключена в обратные одиночные кавычки (\').

Чтобы сделать возможной вставку в строку подобных символов, используется следующая запись: всякий раз, когда внутри кавычек встречается обратная косая черта (\), это означает, что следующий символ имеет специальное значение. Это называется экранированием символа. Кавычка, перед которой стоит обратная косая черта, не завершает строку, а становится ее частью. Если после обратной косой черты стоит символ n, он интерпретируется как перевод строки. Аналогично t после обратной косой черты означает символ табуляции. Рассмотрим следующую строку:

"Это первая строка,\nа это вторая"

На самом деле здесь содержится следующий текст:

Это первая строка,

а это вторая

Конечно, бывают ситуации, когда нужно, чтобы обратная косая черта в строке была просто обратной косой, а не специальным кодом. Если две обратные косые черты идут подряд, они сливаются и в результирующем строковом значении останется только одна. Например, строку «Символ новой строки записывается как "\n".» можно записать так:

"Символ новой строки записывается как \"\\n\"."

Внутри компьютера строки также представляются в виде последовательности битов. Способ, которым JavaScript это делает, основан на стандарте Unicode. Данный стандарт присваивает число любому символу, который вам когда-либо понадобится, включая буквы греческого, арабского, японского, армянского и других алфавитов. Если у нас есть число для каждого символа, то строку можно описать последовательностью чисел.

Именно это и делает JavaScript. Но здесь есть сложность: в представлении JavaScript каждый элемент занимает 16 бит, что позволяет описать до 216 различных символов. Но в Unicode определено больше символов — на данный момент примерно вдвое больше. Поэтому некоторые символы, такие как многочисленные смайлики, занимают в строках JavaScript два «знакоместа». Мы вернемся к этому в главе 5.

Строки нельзя делить, умножать или вычитать, но оператор + к ним применим. Он не складывает, а объединяет — выполняет конкатенацию, склеивает две строки. Следующее выражение создает строку «конкатенация»:

"кон" + "кате" + "на" + "ция"

Для строковых значений существует ряд функций (методов), которые можно использовать для выполнения с ними других операций. Подробнее об этом я расскажу в главе 4.

Строки, заключенные в одинарные и двойные кавычки, ведут себя практически одинаково. Единственное различие состоит в том, какой тип кавычек нужно экранировать внутри них. Строки, заключенные в обратные кавычки, обычно называемые литералами шаблонов, позволяют выполнять еще несколько хитрых трюков. В них можно не только разделять строки, но и встраивать другие значения.

`половина от 100 равна ${100/2}`

Если записать что-то внутри литерала шаблона ${}, то будет вычислен результат, а затем он будет преобразован в строку и вставлен на это место. В данном примере получится «половина от 100 равна 50».

Унарные операции

Не все операторы обозначаются одним символом. Отдельные записываются словами — как, например, оператор typeof, результатом которого является строковое значение, соответствующее типу значения, предоставленного оператору.

console.log(typeof 4.5)

// → number

console.log(typeof "x")

// → string

Мы будем использовать console.log в примерах кода, чтобы показать, что хотим видеть результат вычисления чего-либо. Подробнее об этом читайте в следующей главе.

Все остальные представленные операторы работают с двумя значениями, но typeof принимает только одно. Операторы, задействующие два значения, называются бинарными, а операторы, принимающие одно значение, — унарными. Оператор «минус» можно использовать в качестве как бинарного, так и унарного оператора.

console.log(- (10 - 2))

// → -8

Логические значения

Часто полезно иметь значение, которое различает только две возможности, такие как «да» и «нет» или «включено» и «выключено». Для этой цели в JavaScript есть логический (булев) тип. Он имеет только два значения: «истина» и «ложь», которые записываются соответственно в виде слов true и false.

Сравнение

Вот один из способов получить логическое значение:

console.log(3 > 2)

// → true

console.log(3 < 2)

// → false

Знаки > и < являются традиционными обозначениями операций «больше» и «меньше» соответственно. Это бинарные операторы. Результатом их применения будет логическое значение, которое показывает, истинно ли данное выражение.

Строки также можно сравнивать аналогичным способом.

console.log("Арбуз" < "Яблоко")

// → true

Строки сравниваются просто по алфавиту, но не совсем так, как вы ожидаете увидеть в словаре: заглавные буквы всегда «меньше» строчных, поэтому "Z" < "a", а неалфавитные символы (!, - и т.д.) тоже участвуют в сравнении. При сравнении строк JavaScript перебирает символы слева направо, последовательно сравнивая их коды Unicode.

Есть и другие подобные операторы: >= (больше или равно), <= (меньше или равно), == (равно) и != (не равно).

console.log("Хочется" != "Колется")

// → true

console.log("Груша" == "Вишня")

// → false

В JavaScript существует только одно значение, которое не равно самому себе, и это NaN («не число»).

console.log(NaN == NaN)

// → false

Предполагается, что NaN обозначает результат бессмысленных вычислений и как таковой он не равен результату любых других бессмысленных вычислений.

Логические операторы

Существует также ряд операций, которые применяются к самим логическим значениям. JavaScript поддерживает три логических оператора: «И», «ИЛИ» и «НЕ». Они могут быть использованы для «рассуждения» о логических значениях.

Оператор && представляет логическое «И». Это бинарный оператор, и его результат истинен, только если истинны оба заданные ему значения.

console.log(true && false)

// → false

console.log(true && true)

// → true

Оператор || обозначает логическое «ИЛИ». Его результатом является истина, если истинно хотя бы одно из переданных ему значений.

console.log(false || true)

// → true

console.log(false || false)

// → false

Операция «не» обозначается восклицательным знаком (!). Это унарный оператор, который меняет заданное значение на противоположное: результатом !true будет false, а результатом !falsetrue.

При совместном использовании логических, арифметических и других операторов не всегда очевидно, когда нужны скобки. На практике обычно достаточно знать, что из уже рассмотренных нами операторов || имеет самый низкий приоритет, затем идет &&, потом операторы сравнения (>, == и т.д.), а затем остальные. Такой порядок был выбран для того, чтобы в типичных выражениях, подобных представленному ниже, требовалось как можно меньше скобок:

1 + 1 == 2 && 10 * 10 > 50

Последний логический оператор, о котором я расскажу, не унарный и не двоичный, а троичный, то есть работает с тремя значениями. Он записывается с помощью вопросительного знака и двоеточия, например:

console.log(true ? 1 : 2);

// → 1

console.log(false ? 1 : 2);

// → 2

Этот оператор называется условным (или иногда просто троичным, поскольку он единственный такой оператор в языке). Значение слева от знака вопроса «выбирает», какое из двух других значений станет результатом операции. Когда первое значение истинно, будет выбрано второе значение, а когда оно ложно — третье.

Пустые значения

Есть два специальных значения, обозначаемые как null и undefined, которые используются для описания отсутствия осмысленного значения. Сами по себе они являются значениями, но не несут никакой информации.

Результатом многих операций языка, не дающих осмысленного значения (некоторые из них мы увидим позже), часто выступает undefined просто потому, что необходимо выдать какое-то значение.

Разница между undefined и null появилась случайно в ходе разработки JavaScript, и, как правило, она не имеет значения. В тех случаях, когда вам действительно важны эти значения, я рекомендую рассматривать их как взаимозаменяемые.

Автоматическое преобразование типов

Во введении я упоминал, что JavaScript совершает все возможное, чтобы выполнить практически любую программу, которую вы ему предлагаете, даже если эта программа делает странные вещи. Это хорошо видно в следующих выражениях:

console.log(8 * null)

// → 0

console.log("5" — 1)

// → 4

console.log("5" + 1)

// → 51

console.log("five" * 2)

// → NaN

console.log(false == 0)

// → true

Когда оператор применяется к «неправильному» типу значения, JavaScript незаметно для вас преобразует это значение в нужный тип, используя набор правил, которые часто не соответствуют вашим желаниям или ожиданиям. Это называется приведением типов. null в первом выражении заменяется на 0, а "5" во втором выражении превращается в 5 (строка становится числом). Однако в третьем выражении оператор + пытается сначала выполнить конкатенацию строк, а затем сложение чисел, поэтому 1 преобразуется в "1" (число — в строку).

Когда что-то, что нельзя преобразовать в число очевидным способом (например, "пять" или undefined), все же преобразуется в число, получается значение NaN. Результатом последующих арифметических операций с NaN будет NaN. Поэтому, если вы обнаружите данное значение там, где его не должно быть, ищите случайные преобразования типов.

При сравнении значений одного и того же типа с использованием оператора == результат легко предсказуем: это должно быть true, когда значения одинаковы, если только они не равны NaN. Но если типы различаются, то JavaScript задействует сложный и запутанный набор правил, чтобы определить, что делать. В большинстве случаев он просто пытается преобразовать одно из значений в тип другого. Но если с любой стороны от оператора появляется null или undefined, то результатом будет true, только если обе стороны равны null или же обе равны undefined.

console.log(null == undefined);

// → true

console.log(null == 0);

// → false

Такое поведение часто бывает полезным. Если вы хотите проверить, это определенное значение или же null или undefined, то можете сравнить его с null с помощью оператора == (или !=).

Но как быть, если вы хотите убедиться, что какое-либо значение точно равно false? Выражения типа 0 == false и "" == false являются истинными вследствие автоматического преобразования типов. На тот случай, если вы не хотите, чтобы выполнялись какие-либо преобразования типов, есть два дополнительных оператора: === и !==. Первый из них проверяет, точно ли значение равно другому, а второй — точно ли оно не равно. Таким образом, "" === false будет ложным, как и ожидалось.

В целях защиты от неожиданных преобразований типов я рекомендую использовать трехсимвольные операторы сравнения. Но если вы уверены, что типы с обеих сторон будут одинаковыми, то проблем с применением более коротких операторов не возникнет.

Упрощенное вычисление логических операторов

Логические операторы && и || по-разному обрабатывают значения разных типов. Они преобразуют значение с левой стороны от оператора в логический тип, чтобы решить, что делать, но в зависимости от оператора и результата преобразования они возвращают либо исходное левое значение, либо правое значение.

Например, оператор || вернет значение, расположенное слева от него, если оно может быть преобразовано в true, в противном случае он вернет значение, расположенное справа. Этот эффект ожидаем, если оба значения являются логическими либо аналогичными значениями других типов.

console.log(null || "user")

// → user

console.log("Agnes" || "user")

// → Agnes

Мы можем использовать подобную функциональность как способ вернуться к значению по умолчанию. Если у нас есть значение, которое может быть пустым, то мы можем поставить после него || и затем — заменяющее значение. Если исходное значение может быть преобразовано в false, то мы получим вместо него замену. Согласно правилам преобразования строк и чисел в логические значения, 0, NaN и пустая строка ("") эквивалентны false, тогда как все остальные значения — true. Таким образом, 0 || -1 даст -1, а "" || "!?""!?".

Оператор && работает похожим образом, но наоборот. Когда значение слева от него может быть преобразовано в false, то возвращается это значение; в противном случае он возвращает значение, расположенное справа.

Другим важным свойством двух данных операторов является то, что их правая часть вычисляется только при необходимости. Например, в случае true || X, независимо от того, что такое X — даже если какое-то ужасное выражение, — результатом будет true и X никогда не будет вычислен. То же самое относится к false && X, чьим результатом является false: X будет игнорироваться. Это называется упрощенным вычислением.

Условный оператор работает аналогичным образом. Из второго и третьего значений вычисляется только то, которое было выбрано.

Резюме

В этой главе мы рассмотрели четыре типа значений JavaScript: числа, строки, логические и неопределенные значения.

Данные значения создаются путем ввода их имени (true, null) или собственно значения (13, "abc"). Значения можно использовать совместно и преобразовывать одно в другое с помощью операторов. Мы познакомились с бинарными операторами для выполнения арифметических действий (+, -, *, / и %), конкатенацией строк (+), операторами сравнения (==, !=, ===, !==, <, >, <=, >=) и логическими операторами (&&, ||), а также с несколькими унарными операторами (- для отрицательного числа, ! для логического отрицания и typeof — для поиска типа значения) и троичным оператором (?:) для выбора одного из двух значений в зависимости от третьего значения.

Это дает нам достаточно информации для использования JavaScript в качестве карманного калькулятора, но не более того. В следующей главе мы будем объединять выражения в простейшие программы.

2. Структура программы

Сердце мое сияет ярко-красным светом под тонкой, прозрачной кожей, и приходится вколоть мне десять кубиков JavaScript, чтобы вернуть меня к жизни (я хорошо реагирую на токсины в крови). От этой фигни у вас враз жабры побледнеют!

_why, Почемучкин (трогательный) гайд по Ruby

В этой главе мы приступим к тому, что действительно можно назвать программированием. Мы расширим наше владение языком JavaScript за пределы уже знакомых нам существительных и отрывков предложений до такой степени, что сможем выразить осмысленную прозу.

Выражения и инструкции

В главе 1 мы создали значения и применили к ним операторы для получения новых значений. Создание таких значений — суть любой программы на JavaScript. Но эта суть, чтобы быть полезной, должна быть представлена в виде более обширной структуры. Именно этим мы сейчас и займемся.

Фрагмент кода, в котором создается значение, называется выражением. Каждое значение, написанное буквально (например, 22 или "psychoanalysis"), является выражением. Выражение, заключенное в скобки, также является выражением, как и бинарный оператор, применяемый к двум выражениям, или унарный оператор, применяемый к одному.

Именно в этом частично заключается красота языкового интерфейса. Выражения могут содержать в себе другие выражения, подобно предложениям человеческого языка: сложное предложение включает в себя простые и т.д. Это позволяет создавать выражения, которые описывают сколь угодно сложные вычисления.

Если выражение соответствует фрагменту предложения, то инструкция JavaScript соответствует полному предложению. Программа представляет собой последовательность инструкций.

Простейший вид инструкции — это инструкция, после которой стоит точка с запятой. Вот программа:

1;

!false;

Но это бесполезная программа. Выражение может просто создавать значение, которое затем будет использовано кодом, заключающим в себе это выражение. Инструкция же самодостаточна, она чего-то стоит, только если влияет на мир. Инструкция может выводить что-то на экран — это считается изменением мира — или изменить внутреннее состояние машины таким образом, что это повлияет на следующие инструкции. Такие изменения называются побочными эффектами. Операторы в предыдущем примере просто создают значения 1 и true, а затем сразу отбрасывают их. Это никак не влияет на окружа­ющий мир. Если вы запустите эту программу, ничего заметного не произойдет.

Иногда JavaScript позволяет не ставить точку с запятой в конце инструкции. В других случаях она там необходима, иначе следующая строка будет считаться частью предыдущей инструкции. Правила, по которым можно не ставить точку с запятой, довольно сложны, и легко допустить ошибку. Поэтому в данной книге в конце каждой инструкции, где должна стоять точка с запятой, она будет там стоять. Я рекомендую вам поступать так же — по крайней мере пока вы не познакомитесь ближе с последствиями пропуска точек с запятой.

Привязки

Как программа поддерживает внутреннее состояние? Как она может что-то запомнить? Мы видели, как создаются новые значения из старых, но при этом старые не меняются, а новое должно быть сразу же использовано, иначе оно тут же пропадет. Чтобы получать и сохранять значения, в JavaScript есть штуки, называемые привязками или переменными:

let caught = 5 * 5;

Это второй вид инструкции. Специальное (ключевое) слово let указывает на то, что в данном предложении будет определена привязка. Дальше следует имя привязки и, если мы хотим сразу присвоить ей значение, оператор = и выражение.

В предыдущей инструкции была создана привязка с именем caught и задействована для захвата числа, полученного путем умножения 5 на 5.

После того как привязка определена, ее имя можно использовать как выражение. Значением такого выражения является значение, которое привязка содержит в данный момент. Например:

let ten = 10;

console.log(ten * ten);

// → 100

Если привязка указывает на значение, это не означает, что она всегда связана с ним. К уже существующей привязке можно в любое время применить оператор =, чтобы отменить привязку к текущему значению и сделать так, чтобы она указывала на новое значение.

let mood = "легкое";

console.log(mood);

// → легкое

mood = "тяжелое";

console.log(mood);

// → тяжелое

Привязки лучше представлять себе не как коробки, а как щупальца: привязки не содержат значения, а захватывают их. Две привязки могут ссылаться на одно и то же значение. Программа имеет доступ только к тем значениям, на которые есть ссылки. Если вам нужно что-то запомнить, вы отращиваете «щупальце», чтобы удерживать это, или захватываете его одним из уже существующих «щупалец».

Рассмотрим еще один пример. Чтобы запомнить количество долларов, которые вам должен Луиджи, вы создаете привязку. А когда он вам вернет 35 долларов, вы дадите этой привязке новое значение.

let luigisDebt = 140;

luigisDebt = luigisDebt — 35;

console.log(luigisDebt);

// → 105

Когда вы создаете привязку, не давая ей значения, щупальцу нечего схватить и оно повисает в воздухе. Если вы попробуете узнать значение пустой привязки, то получите значение undefined.

Один оператор let может определять несколько привязок. Определения должны разделяться запятыми.

let one = 1, two = 2;

console.log(one + two);

// → 3

Вместо let для создания привязок можно также использовать слова var и const.

var name = "Аида";

const greeting = "Привет, ";

console.log(greeting + name);

// → Привет, Аида

Первый вариант, var (сокращенное variable), — это способ объявления привязок в версиях JavaScript до 2015 года. В следующей главе мы еще вернемся к нему и рассмотрим, чем именно он отличается от let. Пока запомните, что в основном он делает то же самое, что и let, но в данной книге мы будем его использовать редко, потому что некоторые его свойства способны внести путаницу.

Слово const происходит от constant. Оно определяет постоянную привязку, указывающую на одно и то же значение на протяжении всей своей жизни. Это полезный вид привязок, так как позволяет дать имя значению, чтобы впоследствии можно было легко к нему обращаться.

Имена привязок

Именем привязки может быть любое слово. В имя привязки могут входить цифры — например, catch22 является допустимым именем, — но имя не должно начинаться с цифры. В имени привязки может присутствовать знак доллара ($) или подчеркивания (_), но не должно быть других знаков препинания или специальных символов.

Слова со специальным значением, такие как let, являются ключевыми словами и не могут служить именами привязок. Есть также ряд слов, которые зарезервированы для использования в будущих версиях JavaScript и которые также нельзя задействовать в качестве имен привязок. Полный список ключевых и зарезервированных слов довольно длинный.

break case catch class const continue debugger default

delete do else enum export extends false finally for

function if implements import interface in instanceof let

new package private protected public return static super

switch this throw true try typeof var void while with yield

Не трудитесь запоминать этот список. Если вы попробуете создать привязку, именем которой является зарезервированное слово, возникнет непредвиденная синтаксическая ошибка.

Окружение

Множество привязок и их значений, которые существуют в данный момент времени, называется окружением. При запуске программы это окружение не является пустым. Оно всегда содержит привязки, выступающие частью языкового стандарта, а также, в большинстве случаев, привязки, которые обеспечивают способы взаимодействия с окружающей системой. Например, в браузере есть функции для взаимодействия с загруженным в данный момент сайтом и для чтения информации, вводимой с помощью мыши и клавиатуры.

Функции

Многие значения, представляемые в окружении по умолчанию, имеют function. Функция — это часть программы, обернутая в значение. Такие значения могут применяться для выполнения обернутой программы. Например, в окружении браузера привязка prompt содержит функцию, которая выводит небольшое диалоговое окно с запросом на ввод данных пользователем. Эта функция используется так:

prompt("Введите пароль");

Выполнение функции называется ее вызовом или применением. Чтобы вызвать функцию, можно также поставить скобки после выражения, которое производит значение функции. Как правило, вы будете напрямую использовать имя привязки, содержащей функцию. Значения в скобках передаются программе, находящейся внутри функции. В данном примере функция prompt применяет строку, которую мы ей передаем, в качестве текста, выводимого в диалоговом окне. Значения, передаваемые функциям, называются аргументами. Разным функциям могут потребоваться разное количество и разные типы аргументов.

В современном веб-программировании функция prompt не используется — в основном потому, что не позволяет управлять внешним видом получаемого диалогового окна, но она может быть полезной в учебных программах и экспериментах.

Функция console.log

В предыдущих примерах я применял для вывода значений функцию console.log. Большинство систем JavaScript (включая все современные браузеры и Node.js) предоставляют функцию console.log, которая передает аргументы на какое-нибудь устройство вывода текста. В браузерах этот вывод попадает в консоль JavaScript. По умолчанию данная часть интерфейса браузера скрыта, но в большинстве браузеров она открывается, если нажать F12 или, на Mac, COMMAND-OPTION-I. Если это не сработает, найдите в меню пункт Developer Tools (Инструменты разработчика) или аналогичный.

Несмотря на то что в именах привязок нельзя использовать точки, в conso­le.log точка есть. Потому что console.log не является простой привязкой. В действительности это выражение, которое извлекает свойство log из значения, хранящегося в привязке console. О том, что именно это значит, вы узнаете в главе 4.

Возвращение значений

Отображение диалогового окна или вывод текста на экран являются побочными эффектами. Многие функции полезны именно благодаря побочным эффектам, которые они производят. Кроме того, функции могут создавать значения, и в этом случае они полезны и без побочных эффектов. Например, функция Math.max принимает любое количество числовых аргументов и возвращает наибольшее из них.

console.log(Math.max(2, 4));

// → 4

О функции, которая создает значение, говорят, что она возвращает это значение. Все, что создает значение, в JavaScript является выражением, а значит, вызовы функций могут применяться в выражениях большего размера. В следующем примере вызов функции Math.min, выступающей противоположностью Math.max, используется как часть выражения сложения:

console.log(Math.min(2, 4) + 100);

// → 102

В следующей главе вы узнаете, как писать собственные функции.

Последовательность выполнения

Если программа содержит более одной инструкции, то они выполняются так, как если бы это был рассказ: по порядку, сверху вниз. В следующем примере программа состоит из двух инструкций. Первая предлагает пользователю ввести число, а вторая, которая выполняется после первой, выводит квадрат этого числа.

let theNumber = Number(prompt("Введите число"));

console.log("Это число является квадратным корнем из " +

            theNumber * theNumber);

Функция Number преобразует значение в число. Это преобразование необходимо, поскольку результатом prompt является строковое значение, а нам нужно число. Для строкового и логического типов существуют аналогичные функции String и Boolean соответственно.

Вот довольно простое схематическое представление линейной последовательности выполнения программы:

Условное выполнение

Не каждая программа представляет собой прямую дорогу. Например, мы можем создать перекресток, на котором программа будет выбирать нужный путь в зависимости от ситуации. Это называется условным выполнением.

В JavaScript условное выполнение создается с помощью ключевого слова if. В простейшем случае мы хотим, чтобы некоторый код выполнялся тогда и только тогда, когда выполняется определенное условие. Например, мы можем захотеть, чтобы квадрат введенного числа выводился только в том случае, если пользователь ввел именно число.

let theNumber = Number(prompt("Введите число"));

if (!Number.isNaN(theNumber)) {

  console.log("Это число является квадратным корнем из " +

              theNumber * theNumber);

}

После такого изменения программы если вы введете слово «попугай», то ничего не будет выведено.

Ключевое слово if выполняет или не выполняет инструкцию в зависимости от значения логического выражения. Решающее выражение записывается после ключевого слова и помещается в скобки, за которыми следует выполняемая инструкция.

Функция Number.isNaN — это стандартная функция JavaScript, возвраща­ющая true только в том случае, если переданный ей аргумент имеет значение NaN. Функция Number возвращает значение NaN, если передать ей строку, которая не представляет собой корректную запись числа. Таким образом, условие можно прочитать как «сделать это, только если theNumber является числом».

Инструкция, которая стоит после if в этом примере, заключена в фигурные скобки ({ и }). Фигурные скобки можно использовать для того, чтобы сгруппировать любое количество операторов в один оператор, называемый блоком. В данном случае скобки можно было бы пропустить, поскольку они содержат только один оператор; но, чтобы избежать необходимости каждый раз думать о том, нужны ли они, большинство программистов JavaScript используют скобки для каждой обернутой инструкции, как здесь. В данной книге мы по большей части будем придерживаться этого соглашения, за исключением отдельных однострочных инструкций.

if (1 + 1 == 2) console.log("Это выражение истинно");

// → Это выражение истинно

Часто желательно иметь не только код, который выполняется при определенном условии, но и код, обрабатывающий другой вариант. Такой альтернативный путь представлен второй стрелкой на рисунке. Для создания двух отдельных, альтернативных путей выполнения программы совместно с if используют ключевое слово else.

let theNumber = Number(prompt("Введите число"));

if (!Number.isNaN(theNumber)) {

  console.log("Это число является квадратным корнем из " +

              theNumber * theNumber);

} else {

  console.log("Эй, почему вы не ввели число?");

}

Если у вас есть более двух вариантов выбора, то можно «связать» вместе несколько пар if/else, например:

let num = Number(prompt("Введите число"));

if (num < 10) {

  console.log("Маленькое");

} else if (num < 100) {

  console.log("Среднее");

} else {

  console.log("Большое");

}

Программа сначала проверит, меньше ли num, чем 10. Если это так, то она выберет данную ветку, выведет слово «маленькое» и завершится. Если это не так, то программа перейдет к ветви else, внутри которой есть второе условие if. Если это второе условие (<100) выполняется, то есть число находится в диапазоне от 10 до 100, то будет выведено слово «среднее». Если же это не так, то будет выбрана вторая и последняя ветвь else.

Схема выполнения данной программы выглядит примерно так:

Циклы while и do

Рассмотрим программу, которая выводит все четные числа от 0 до 12. Вот один из способов написать такую программу:

console.log(0);

console.log(2);

console.log(4);

console.log(6);

console.log(8);

console.log(10);

console.log(12);

Это работает, но идея написания программы состоит в том, чтобы работы было меньше, а не больше. Если бы нам понадобилось вывести все четные числа до 1000, то такой подход был бы непригоден. Нам нужен способ выполнить фрагмент кода несколько раз. Такой вариант последовательности выполнения называется циклом.

Зацикливание последовательности выполнения позволяет вернуться в некоторую точку программы, где мы уже были, и повторить ее из текущего состояния программы. Если объединить это с привязкой, которая умеет считать, то получим что-то вроде этого:

let number = 0;

while (number <= 12) {

  console.log(number);

  number = number + 2;

}

// → 0

// → 2

// ... и так далее

Инструкция, начинающаяся с ключевого слова while, создает цикл. За словом while следует выражение в скобках, а затем инструкция — точно так же, как в if. Цикл продолжает выполнять эту инструкцию до тех пор, пока выражение выдает значение, которое при преобразовании в логический тип равно true.

Привязка number показывает, как привязка может управлять ходом выполнения программы. При каждом повторном выполнении цикла number получает значение, которое на 2 больше предыдущего. В начале каждого повтора значение number сравнивается с числом 12, чтобы определить, следует ли завершить работу программы.

Теперь в качестве примера, который действительно делает что-то полезное, мы можем взять программу, вычисляющую и показывающую значение 210 (от 2 в степени 10). Мы используем две привязки: одну для отслеживания результата и одну для подсчета того, как часто результат умножался на 2. Цикл проверяет, достигла ли вторая привязка числа 10, и, если нет, обновляет обе привязки.

let result = 1;

let counter = 0;

while (counter < 10) {

  result = result * 2;

  counter = counter + 1;

}

console.log(result);

// → 1024

Счетчик также мог бы начинаться с 1 и проверяться на <= 10, но по причинам, которые станут ясными в главе 4, лучше привыкать к отсчету от 0.

Цикл do — это управляющая структура, похожая на цикл while. Он отличается только одним моментом: цикл do всегда выполняет свое тело хотя бы один раз и проверяет, нужно ли остановиться, только после первого выполнения. Результат того, что проверка выполняется после тела цикла, виден в следующем примере.

let yourName;

do {

  yourName = prompt("Кто вы?");

} while (!yourName);

console.log(yourName);

Эта программа предлагает вам ввести имя. Она будет спрашивать имя снова и снова, пока не получит что-то, что не является пустой строкой. Применение оператора ! преобразует значение в логический тип перед его отрицанием, а все строки, кроме "", преобразуются в true. Это означает, что цикл будет выполняться, пока вы не введете непустое имя.

Код с отступами

В примерах я добавляю пробелы перед инструкциями, которые являются частью более крупной инструкции. Эти пробелы необязательны — компьютер отлично примет программу и без них. Более того, даже разрывы строк в программах необязательны. При желании вы можете написать программу в виде одной длинной строки.

Роль таких отступов внутри блоков состоит в том, чтобы выделить структуру кода. Если в коде внутри одних блоков открываются другие блоки, может быть трудно понять, где заканчивается один блок и начинается другой. С правильными отступами визуальная форма программы соответствует форме блоков внутри нее. Мне нравится использовать два пробела для каждого нового блока, но у каждого свои вкусы — некоторые ставят четыре пробела, другие задействуют символы табуляции. Главное, чтобы у каждого нового блока были отступы одинакового размера.

if (false != true) {

  console.log("Это имеет смысл.");

  if (1 < 2) {

    console.log("Ничего удивительного.");

  }

}

Большинство программ — редакторов кода помогают автоматически создавать для новых строк отступы правильного размера.

Циклы for

Многие циклы следуют шаблону, показанному в примерах с while: сначала создается привязка-счетчик для контроля за выполнением цикла. Затем идет цикл while, обычно с проверочным выражением, в котором проверяется, достиг ли счетчик конечного значения. В конце тела цикла счетчик изменяется, чтобы отслеживать состояние цикла.

Из-за распространенности этого шаблона в JavaScript и аналогичных языках есть несколько более короткая и общая форма цикла — for.

for (let number = 0; number <= 12; number = number + 2) {

  console.log(number);

}

// → 0

// → 2

// ... и так далее

Эта программа в точности эквивалентна предыдущему примеру с печатью четных чисел. Единственное изменение заключается в том, что все инструкции, связанные с состоянием цикла, сгруппированы после ключевого слова for.

В скобках после for должны содержаться две точки с запятой. Перед первой выполняется инициализация цикла, обычно путем определения привязки. Вторая часть — это выражение, которое проверяет, следует ли продолжить выполнение цикла. В последней части обновляется состояние цикла после каждой итерации. В большинстве случаев эта запись короче и понятнее, чем конструкция while.

Вот код, в котором вычисляется 210 с использованием for вместо while:

let result = 1;

for (let counter = 0; counter < 10; counter = counter + 1) {

  result = result * 2;

}

console.log(result);

// → 1024

Принудительный выход из цикла

Ситуация, когда условие продолжения цикла равно false, не единственный вариант завершения цикла. Существует специальная инструкция, называемая break, которая позволяет немедленно «выпрыгнуть» из замкнутого цикла.

В следующей программе показано, как работает инструкция break. Программа находит первое число, которое больше или равно 20 и при этом делится на 7.

for (let current = 20; ; current = current + 1) {

  if (current % 7 == 0) {

    console.log(current);

    break;

  }

}

// → 21

Использование оператора остатка от деления (%) — простой способ проверить, делится ли данное число на другое число. Если это так, то остаток от их деления равен нулю.

Конструкция for в указанном примере не имеет части, в которой проверяется условие выхода из цикла. Это означает, что цикл никогда не закончится, если только не выполнится расположенная внутри инструкция break.

Если удалить оператор break или случайно написать условие завершения, которое всегда равно true, то программа застрянет в бесконечном цикле. Такая программа никогда не закончит работу, что обычно имеет плохие последствия.

Ключевое слово continue похоже на break в том смысле, что оно тоже влия­ет на выполнение цикла. Когда в теле цикла встречается слово continue, управление «выпрыгивает» из него и выполнение цикла продолжается со следующей итерации.

Быстрое обновление привязок

В программах, особенно в циклах, часто требуется обновить привязку, присвоив ей новое значение, основанное на предыдущем значении этой же привязки.

counter = counter + 1;

В JavaScript есть для этого короткая запись:

counter += 1;

Аналогичные сокращения существуют и для других операторов — например, result *= 2 для удвоения result или counter -= 1 для уменьшения на единицу.

Это позволяет еще немного сократить пример со счетчиком.

for (let number = 0; number <= 12; number += 2) {

  console.log(number);

}

Для записей типа counter += 1 и counter -= 1 есть еще более короткие эквиваленты: counter++ и counter--.

Диспетчеризация по значению с помощью switch

Часто бывает, что код выглядит так:

if (x == "value1") action1();

else if (x == "value2") action2();

else if (x == "value3") action3();

else defaultAction();

Существует конструкция, называемая switch, которая предназначена для выражения такой диспетчеризации более непосредственным способом. К сожалению, синтаксис, который используется для этого в JavaScript (и унаследован от семейства языков программирования C/Java), немного неуклюжий — иногда цепочка операторов if выглядит лучше. Вот пример:

switch (prompt("Какая сейчас погода?")) {

  case "дождь":

    console.log("Не забудьте взять зонтик.");

    break;

  case "солнечно":

    console.log("Одевайтесь легко.");

  case "облачно":

    console.log("Пойдите погуляйте.");

    break;

  default:

    console.log("Неизвестная погода!");

    break;

}

Внутри блока switch можно поместить любое количество вариантов case. Программа начнет выполнение с той метки, которая соответствует значению, заданному в switch, или с default, если подходящего значения не найдено. Программа будет выполнять все инструкции подряд, даже «сквозь» другие метки, пока не достигнет инструкции break. В некоторых случаях, таких как вариант "солнечно" в нашем примере, это можно использовать, чтобы один и тот же код выполнялся для нескольких вариантов (рекомендуется сходить погулять как в солнечную, так и в облачную погоду). Но будьте осторожны — об отсутствии break легко забыть, и тогда программа будет выполнять код, который не должен выполняться.

Использование прописных букв

В именах привязок не должно быть пробелов, однако часто бывает полезно использовать в них несколько слов, чтобы четко описать, что представляет данная привязка. Это, в сущности, ваш выбор — как писать имя привязки, состоящее из нескольких слов:

fuzzylittleturtle

fuzzy_little_turtle

FuzzyLittleTurtle

fuzzyLittleTurtle

Первый вариант может быть трудночитаемым. Мне больше нравится вариант с подчеркиваниями, хотя такой текст не очень удобно набирать. Стандартные функции JavaScript и большинство программистов на JavaScript используют стиль, показанный в нижней строке, — пишут с заглавной буквы каждое слово, кроме первого. К таким мелочам нетрудно привыкнуть, а код со смешанными стилями именования может быть неудобно читать, поэтому мы тоже будем следовать данному соглашению.

В некоторых случаях, таких как с функцией Number, первая буква привязки также является большой. Это было сделано для того, чтобы пометить указанную функцию как конструктор. Что такое конструктор, вы узнаете в главе 6. Пока что не стоит беспокоиться о таком явном отсутствии согласованности.

Комментарии

Часто сам код не передает всю информацию, которую хотелось бы передавать посредством программы читателям-людям, или же передает ее настолько загадочно, что не все могут ее понять. В других случаях просто хочется включить в программу некоторые сопутствующие мысли. Именно для этого нужны комментарии.

Комментарий представляет собой текст, являющийся частью программы, но полностью игнорируемый компьютером. В JavaScript есть два способа написания комментариев. Для того чтобы написать однострочный комментарий, можно использовать два символа косой черты (//), после которых пишется сам текст комментария.

let accountBalance = calculateBalance(account);

// Зеленая дыра; цепляясь бездумно,

accountBalance.adjust();

// За серебро травы, на дне река журчит,

let report = new Report();

// И солнце юное с вершины недоступной

addToReport(accountBalance, report);

// Горит. То тихий лог, где пенятся лучи.

// (Артюр Рембо. Уснувший в ложбине. Пер. Г. Петникова)

Комментарий // действует только до конца строки. Если же заключить текст между /* и */, то он будет полностью игнорироваться независимо от того, содержит ли он разрывы строк. Это полезно для добавления блоков информации о файле или части программы.

/*

  Я впервые обнаружил, что это число написано на обороте старой записной

  книжки. С тех пор оно мне часто попадалось среди номеров телефонов

  и штрихкодов продуктов, которые я покупал. Похоже, я ему понравился,

  поэтому я решил его оставить.

*/

const myNumber = 11213;

Резюме

Теперь вы знаете, что программа состоит из инструкций, которые сами по себе тоже иногда состоят из инструкций. Инструкции, как правило, содержат выражения, которые сами по себе тоже могут состоять из меньших выражений.

Инструкции, размещаемые последовательно, одна за другой, образуют программу, выполняемую сверху вниз. Эту последовательность выполнения можно изменять, используя условные (if, else и switch) и циклические (while, do и for) инструкции.

Для хранения именованных фрагментов данных применяются привязки, они полезны для отслеживания состояния программы. Множество определенных привязок называется окружением. Любая система JavaScript всегда помещает в окружение ряд полезных стандартных привязок.

Функции — это специальные значения, в которых инкапсулирована часть программы. Чтобы вызвать функцию, нужно написать functionName(argument1, argument2). Такой вызов функции является выражением и может возвращать значение.

Упражнения

Если вы не знаете, как проверить свои решения упражнений, обратитесь к разделу «Введение».

Каждое упражнение начинается с описания задачи. Прочитайте это описание и попробуйте выполнить упражнение. Если у вас возникнут проблемы, почитайте подсказки в конце книги. Полные решения упражнений не включены в данную книгу, но вы найдете их в Интернете по адресу https://eloquentjavascript.net/code. Если вы хотите чему-то научиться на этих упражнениях, я рекомендую смотреть решения только после выполнения упражнения или по крайней мере после того, как в результате долгих и настойчивых попыток его решить у вас не возникнет легкая головная боль.

Построение треугольника в цикле

Напишите цикл, который делает семь вызовов console.log и выводит следующий треугольник:

#

##

###

####

#####

######

#######

Возможно, вам будет полезно узнать: чтобы получить длину строки, нужно написать после нее .length.

let abc = "abc";

console.log(abc.length);

// → 3

FizzBuzz

Напишите программу, в которой с помощью console.log выводятся все числа от 1 до 100 с двумя исключениями. Для чисел, кратных 3, вместо числа выводится "Fizz", а для чисел, кратных 5 (но не 3), — "Buzz".

Когда это заработает, измените программу так, чтобы она печатала "FizzBuzz" для чисел, которые делятся и на 3, и на 5 (и по-прежнему печатайте "Fizz" или "Buzz" для чисел, кратных только одному из них).

На самом деле такой вопрос задают на собеседованиях и, по утверждениям, отсеивают на нем значительную долю кандидатов в программисты. Поэтому если вы решили эту задачу, то стоимость вашего труда резко возросла.

Шахматная доска

Напишите программу, которая создает строку, представляющую сетку 8 × 8, используя для разделения строк символы новой строки. В каждой позиции сетки стоит либо пробел, либо символ "#". Эти символы должны располагаться в шахматном порядке.

Передавая данную строку в console.log, вы должны получить что-то вроде этого:

# # # #

# # # #

# # # #

# # # #

# # # #

# # # #

# # # #

# # # #

Если вы уже написали программу, которая генерирует этот узор, определите привязку size = 8 и измените программу так, чтобы она работала для любого size, выводя сетку заданных ширины и высоты.

3. Функции

Люди считают, что компьютерные науки — это искусство для гениев. В реальности все наоборот — просто множество людей делают вещи, одну на основе другой, будто строят стену из маленьких камушков.

Дональд Кнут

Функции — это хлеб с маслом программирования на JavaScript. Концепция представления части программы в виде значения имеет множество применений. Она позволяет нам структурировать большие программы, уменьшить количество повторений, присваивать имена подпрограммам и изолировать эти подпрограммы одну от другой.

Наиболее очевидное применение функций — определение нового словаря. Создавать новые слова в прозе — обычно признак дурного стиля. Но в программировании это необходимо.

Словарный запас взрослого человека составляет в среднем около 20 000 слов. Редкий язык программирования насчитывает 20 000 встроенных команд. Но их доступный словарный запас, как правило, точнее определен и, следовательно, менее гибок, чем в человеческом языке. Поэтому мы обычно вынуждены вводить новые концепции, чтобы не повторяться слишком часто.

Определение функции

Определение функции — это обычная привязка, значением которой является функция. Например, в следующем коде определена привязка square, ссылающаяся на функцию, которая вычисляет квадрат заданного числа:

const square = function(x) {

  return x * x;

};

console.log(square(12));

// → 144

Функция состоит из выражения, начинающегося с ключевого слова function. Каждая функция имеет набор параметров (в данном случае только x) и тело, состоящее из инструкций, которые должны выполняться при вызове функции. Тело функции, созданной таким образом, всегда должно быть заключено в фигурные скобки, даже если оно состоит только из одной инструкции.

Функция может иметь несколько параметров или же не иметь их вообще. В следующем примере функция makeNoise не имеет параметров, тогда как у power их два:

const makeNoise = function() {

  console.log("Эгегей!");

};

makeNoise();

// → Эгегей!

const power = function(base, exponent) {

  let result = 1;

  for (let count = 0; count < exponent; count++) {

    result *= base;

  }

  return result;

};

console.log(power(2, 10));

// → 1024

Отдельные функции, такие как power и square, выдают значение, а другие, такие как makeNoise, — нет; их единственным результатом является побочный эффект. Значение, которое возвращает функция, определяет инструкция return. Когда управление программой доходит до этой инструкции, оно сразу выходит из текущей функции и передает возвращенное значение в код, ее вызвавший. Ключевое слово return без следующего за ним выражения приводит к тому, что функция возвращает undefined. Функции, которые вообще не имеют инструкции return, такие как makeNoise, также возвращают undefined.

Параметры, передаваемые функции, ведут себя как обычные привязки, но их начальные значения задаются вызывающим объектом, а не кодом самой функции.

Привязки и области видимости

Каждая привязка имеет область видимости — это часть программы, в которой видна данная привязка. Для привязок, определенных вне какой-либо функции или блока, область видимости — вся программа, и вы можете ссылаться на такие привязки где хотите. Они называются глобальными.

Но на привязки, созданные как параметры функции или объявленные внутри нее, можно ссылаться только в данной функции, поэтому они называются локальными привязками. Каждый раз, когда вызывается функция, создаются новые экземпляры этих привязок. Так обеспечивается некоторая изоляция между функциями — каждый их вызов действует в собственном маленьком мире (в локальной среде), и часто его легко понять, практически ничего не зная о том, что происходит в глобальной среде.

Привязки, объявленные с помощью ключевых слов let и const, на самом деле являются локальными для блока, внутри которого они объявлены. Поэтому если вы создадите одну из таких привязок внутри цикла, то код до и после данного цикла не сможет его «увидеть». До 2015 года в JavaScript только функции могли создавать новые области видимости, поэтому привязки, созданные в старом стиле с помощью ключевого слова var, видны во всей функции, в которой они появились, либо во всей глобальной области видимости, если они не входят в функцию.

let x = 10;

if (true) {

  let y = 20;

  var z = 30;

  console.log(x + y + z);

  // → 60

}

// привязка y здесь не видна

console.log(x + z);

// → 40

Каждая область видимости может «выглядывать» в область, которая ее окружает, поэтому в данном примере привязка x видна внутри блока. Исключение составляют случаи, когда несколько привязок имеют одно и то же имя — тогда код может видеть только самую внутреннюю из привязок. Например, когда код внутри функции halve ссылается на n, то он видит собственную привязку n, а не глобальную n.

const halve = function(n) {

  return n / 2;

};

let n = 10;

console.log(halve(100));

// → 50

console.log(n);

// → 10

Вложенные области видимости

JavaScript различает не только глобальные и локальные привязки. Внутри блоков и функций можно создавать другие блоки и функции, обра

...