Итак, VS Code настроен, первое знакомство с JavaScript прошло успешно и даже познакомились немного с консолью в веб-браузере. Теперь приступим к следующему шагу — рассмотрим чуть подробнее структуру кода программы на JavaScript.
Инструкции
Инструкция в JavaScript — это языковые конструкции (команды), выполняющие какие-либо действия. Например,
alert("Hello, world");
это инструкция JavaScript, выводящая сообщение в окне браузера. Инструкции разделяются точкой с запятой (;). Например:
alert("Hello, world"); alert("Привет, мир!");
здесь две инструкции отделены точкой с запятой. Также инструкции могут располагаться на одной или нескольких строках. Пример многострочной инструкции:
document.write(2 + 3 - 1);
Здесь инструкция располагается сразу на трех строках. В результате, на страницу будет выведен результат — 4. Возможно, что в погоне за скоростью написания кода на JavaScript или просто для удобства, в JavaScript ингода можно не ставить точку с запятой между инструкциями, например, следующий код будет работать как и задумано — выведет два сообщения:
alert("Hello, world") alert("Привет, мир!")
В данном случае происходит «магия» под названием автоматическая вставка точки с запятой. То есть, когда движок JavaScript будет обрабатывать наш код, то он будет выглядеть внутри движка примерно так:
alert("Hello, world"); alert("Привет, мир!");
Однако, не стоит увлекаться такой возможностью. В большинстве случаев, ваш код будет состоять не только из каких-то элементарных инструкций типа alert("hello")
или document.write("hello")
, но и содержать всевозможные циклы, объекты, массивы, которые мы, безусловно, будем изучать. И, если вы будете пренебрегать простым правилом: написал инструкцию — поставь точку с запятой, то с большой долей вероятности вы столкнетесь с ошибками, которые вам будет очень сложно увидеть в коде.
Комментарии
Программы на JavaScript могут насчитывать тысячи различных инструкций, располагаться в десятках отдельных файлах и бывает крайне сложно держать весь этот код в голове и помнить, какая чать кода за что отвечает и как работает. Для таких случаев, в JavaScript используются комментарии.
Комментарий в исходном коде — это специальным образом оформленный текст внутри исходного кода, который никак не исполняется и служит основной цели — сделать код более понятным для человека.
Также, с помощью комментариев можно временно «отключить» часть кода, чтобы он не выполнялся.
В JavaScript используются однострочные и многострочные комментарии. Однострочный комментарий начинается с //. Пример однострочного комментария:
//выводим сообщение пользователю alert('Hello'); let a = 1; //объявляем переменную и присваиваем ей значение 1
здесь мы видим два однострочных комментария. Первый комментарий располагается на отдельной строке, а второй — на строке с кодом JavaScript. Оба эти варианта написания однострочного комментария допустимы.
Иногда бывает необходимо написать комментарий на несколько строк. Например, сделать подробное описание какого-то участка кода. В этом случае можно воспользоваться многострочным комментарием, который начинается с /* и заканчивается на */. Например,
/* Это многострочный комментарий который располагается на трех строках */ alert('Hello');
Весь текст, расположенный между /* и */ будет интерпретирован как многострочный комментарий.
Кстати, в VS Code, чтобы закомментировать одну или несколько строк кода. достаточно их выделить и нажать сочетание клавиш Ctrl+/
Блок инструкций
Блок инструкций — это ещё один «кирпичик» структуры кода. Более подробно мы об этой конструкции ещё поговорим, а пока запомним, что блок инструкций используется для группировки нескольких инструкций и отделяется парой фигурных скобок, например:
alert("JavaScript"); //ниже определяется блок инструкций { document.write("Текст из блока"); alert("hello") }
Блоки инструкций часто используются в программировании, например, когда необходимо выполнить ряд инструкций только в том случае, если выполняется определенное условие.
Итого
Сегодня мы немного подробнее познакомились с языком JavaScript и разобрались с тем, что из себя представляют инструкции и комментарии. Не пренебрегайте явным использованием точки с запятой для отделения одной инструкции от другой и пишите комментарии к своему коду, чтобы можно было легко вспомнить, что вы сделали в том или ином участке кода, например, неделю назад.
Синтаксис JavaScript – это набор правил, как создаются программы JavaScript.
В этом уроке мы рассмотрим базовые лексические структуры языка.
Набор символов
При написании программ на JavaScript используется набор символов Unicode. В отличие от 7-разрядной кодировки ASCII, подходящей только для английского языка, и 8-разрядной кодировки ISO Latin-1, подходящей только для английского и основных западноевропейских языков, 16-разрядная кодировка Unicode поддерживает практически все письменные языки, имеющиеся на планете. Стандарт ECMAScript v3 требует, чтобы реализации JavaScript обеспечивали поддержку стандарта Unicode версии 2.1 или выше, а стандарт ECMAScript v5 требует, чтобы реализации обеспечивали поддержку стандарта Unicode версии 3 или выше.
var str = "hello, world!"; // Используется латиница
var стр = "Привет, мир!"; // Используется кириллица
Пробельные символы
Интерпретатор JavaScript игнорирует все пробельные символы которые могут присутствовать между языковыми конструкциями и воспринимает текст программы как сплошной поток кода.
Кроме того, JavaScript также, по большей части, игнорирует символы перевода строки. Поэтому пробелы и символы перевода строки могут без ограничений использоваться в исходных текстах программ для форматирования и придания им удобочитаемого внешнего вида.
Пробельные символы улучшают читаемость исходного кода, но эти символы, как правило, не нужны для функциональности js-сценария.
Код символа | Название | Сокращение | Описание | Escape последовательность |
---|---|---|---|---|
U + 0009 | Горизонтальная табуляция | <HT> | Перемещает позицию печати к следующей позиции горизонтальной табуляции | t |
U + 000B | Вертикальная табуляция | <VT> | Перемещает позицию печати к следующей позиции вертикальной табуляции | v |
U + 000C | Прогон страницы, смена страницы | <FF> | Выбрасывает текущую страницу и начинает печать со следующей | f |
U + 0020 | Пробел | <SP> | Интервал между буквами | |
U + 00A0 | Неразрывный пробел | <NBSP> | Символ, отображающийся внутри строки подобно обычному пробелу, но не позволяющий разорвать в этом месте строку |
В дополнение к пробельным символам символы конца строки также используются для улучшения читаемости исходного текста. Однако в некоторых случаях символы конца строки могут влиять на выполнение кода JavaScript, поскольку есть несколько моментов, когда их применение запрещено. Символы конца строки также влияют на процесс автоматической установки точки с запятой.
Следующие символы распознаются интерпретаторами JavaScript как символы конца строки:
Код символа | Название | Сокращение | Описание | Escape последовательность |
---|---|---|---|---|
U + 000A | Перевод строки | <LF> | Перемещает позицию печати на одну строку вниз | n |
U + 000D | Возврат каретки | <CR> | Перемещает позицию печати в крайнее левое положение | r |
U + 2028 | Разделитель строк | <LS> | Разделяет строки текста, но не абзацы | |
U + 2029 | Сепаратор абзацев | <PS> | Разделяет абзацы текста |
Точка с запятой
Программа (сценарий) на языке JavaScript представляет собой перечень «инструкций», которые выполняются веб-браузером.
В JavaScript инструкции, как правило, разделяются точкой с запятой (;).
Если несколько инструкций располагаются на одной строке, то между ними следует поставить знак «точка с запятой» (;).
Во многих случаях JavaScript интерпретирует переход на новую строчку как разделитель команд для автоматического ввода точек с запятой (ASI) для завершения инструкций.
Если каждая инструкция размещается на отдельной строке, то разделитель можно не писать:
Одна инструкция может располагаться на нескольких строчках:
В этом случае JavaScript ждёт завершение выражения и поэтому автоматически не вставляет «виртуальную» точку с запятой между строчками.
Тем не менее, рекомендуется всегда добавлять точки с запятой – это позволит избежать побочных эффектов:
Примечание: Хотя точки с запятой в конце инструкций необязательны, рекомендуется всегда добавлять их. Сейчас это правило, которому следуют все большие проекты.
Это правило предотвращает некоторые ошибки, например незавершенный ввод, а также позволяет сжимать кoд за счет удаления пустых мест. Сжатие кода без точек с запятой приводит к синтаксическим ошибкам. Кроме того, наличие точек с запятой препятствует снижению быстродействия, потому что синтаксические анализаторы пытаются исправлять предполагаемые ошибки, добавляя недостающие точки с запятой.
Чувствительность к регистру
Для написания JavaScript-пpoгpaмм используется набор символов Unicode, который включает в себя наборы ASCII и Latin-1 и поддерживается практически всеми языками и платформами.
В JavaScript все элементы, включая имена переменных, функций и операторов, чувствительны к регистру и должны всегда содержать одинаковые наборы прописных и строчных букв. Например, ключевое слово while должно набираться как «while», а не «While» или «WHILE».
Аналогично num, NUM и Num – это три разные переменные:
Комментарии
Комментарии позволяют выделить фрагмент программы, который не выполняется интерпретатором JavaScript, а служит лишь для пояснений содержания программы.
Комментарии в JS могут быть однострочными и многострочными.
Однострочные комментарии начинаются с двойного слэша //
. Текст считается комментарием до конца строки:
Многострочный комментарий начинается с слэша и звездочки (/*)
, а заканчивается ими же в обратном порядке (*/)
. Так можно закомментировать одну и более строк:
Совет: Не пренебрегайте комментариями в своих кодах. Они пригодятся вам при отладке и сопровождении программ. На этапе разработки бывает лучше закомментировать ненужный фрагмент программы, чем просто удалить. А вдруг его придется восстанавливать?
Идентификаторы
Идентификатор — это последовательность букв, цифр, символов подчёркивания (_)
и знаков доллара ($)
. Цифра не может быть первым символом идентификатора, т. к. тогда интерпретатору JavaScript труднее отличать идентификаторы от чисел. Идентификаторы выступают в качестве имён переменных, функций, свойств объекта и т. д.
Для совместимости и простоты редактирования для составления идентификаторов обычно используются только символы ASCII и цифры. Однако в ECMAScript v3 идентификаторы могут содержать буквы и цифры из полного набора символов Unicode. Это позволяет программистам давать переменным имена на своих родных языках и использовать в них математические символы:
var имя = 'Макс';
var Π = 3.14;
Исторически, программисты использовали разные способы объединения нескольких слов для записи идентификаторов. Сегодня есть два устоявшихся негласных стиля: camelCase и snake_case.
В JavaScript наиболее популярным стилем именования идентификаторов, состоящих из нескольких слов, является camelCase – «верблюжья» нотация. Это означает, что первая буква является строчной, а первые буквы всех последующих слов – прописными, например:
var firstSecond;
var myCar = "audi";
var doSomethingImportant;
Хотя это не является требованием, рекомендуется следовать этому правилу, чтобы не отступать от формата встроенных функций и объектов ECMAScript.
Внимание: В JavaScript объединение нескольких слов для записи идентификаторов с применением дефисов запрещено. Они зарезервированы для математических вычитаний.
На заметку: В JavaScript ключевые слова, зарезервированные слова и значения true
, false
и null
не могут быть идентификаторами.
Ключевые и зарезервированные слова
Стандарт ЕСМА-262 определяет набор ключевых слов (keywords), которые не могут использоваться в качестве идентификаторов. Зарезервированные слова имеют определенное значение в языке JavaScript, так как они являются частью синтаксиса языка. Использование зарезервированных слов приведет к ошибке компиляции при загрузке скрипта.
Зарезервированные ключевые слова по версии ECMAScript® 2015
break
case
catch
class
const
continue
debugger
default
delete
do
else
export
extends
finally
for
function
if
import
in
instanceof
new
return
super
switch
this
throw
try
typeof
var
void
while
with
yield
Ключевые слова, зарезервированные на будущее
Кроме того, ЕСМА-262 содержит набор зарезервированных слов (reserved words), которые также нельзя использовать как идентификаторы или имена свойств. За ними сейчас не стоит никакой функциональности, но она может появиться в будущих версиях:
enum
await
В строгом (strict) режиме в этот список добавляются следующие слова:
implements
package
protected
static
interface
private
public
Зарезервированные ключевые слова в версиях ECMAScript® от 1 по 3
abstract
boolean
byte
char
double
final
float
goto
int
long
native
short
synchronized
transient
volatile
В 5-й редакции ECMAScript немного изменены правила употребления ключевых и зарезервированных слов. Как и прежде они не могут быть идентификаторами, но теперь их допустимо использовать как имена свойств в объектах. Тем не менее, для обеспечения совместимости с прошлыми и будущими редакциями ECMAScript всё-же лучше не использовать ключевые и зарезервированные слова как идентификаторы и имена свойств.
Итоги
- Интерпретатор JavaScript игнорирует все пробельные символы которые могут присутствовать между языковыми конструкциями и воспринимает текст программы как сплошной поток кода.
Кроме того, JavaScript также, по большей части, игнорирует символы перевода строки. Поэтому пробелы и символы перевода строки могут без ограничений использоваться в исходных текстах программ для форматирования и придания им удобочитаемого внешнего вида. - Пропуск точек с запятой нельзя признать правильной практикой программирования, и поэтому желательно выработать привычку их использовать.
- В JavaScript все элементы, включая имена переменных, функций и операторов, чувствительны к регистру и должны всегда содержать одинаковые наборы прописных и строчных букв.
- Не пренебрегайте комментариями в своих кодах. Они пригодятся вам при отладке и сопровождении программ. Не переживайте насчет увеличения размера кода, т.к. существуют инструменты сжатия JavaScript, которые, при публикации, легко удалят комментарии.
-
Идентификаторы выступают в качестве имён переменных, функций, свойств объекта и состоят из последовательности букв, цифр, символов подчёркивания
(_)
и знаков доллара($)
. - Ключевые слова JavaScript, применяемые для обозначения элементов синтаксиса языка, а также другие слова, зарезервированные на будущее, нельзя использовать в качестве имен переменных, функций и объектов.
Порой обучение продвигается с трудом. Сложная теория, непонятные задания… Хочется бросить. Не сдавайтесь, все сложности можно преодолеть. Рассказываем, как
Не понятна формулировка, нашли опечатку?
Выделите текст, нажмите ctrl + enter и опишите проблему, затем отправьте нам. В течение нескольких дней мы улучшим формулировку или исправим опечатку
Что-то не получается в уроке?
Загляните в раздел «Обсуждение»:
- Изучите вопросы, которые задавали по уроку другие студенты — возможно, ответ на ваш уже есть
- Если вопросы остались, задайте свой. Расскажите, что непонятно или сложно, дайте ссылку на ваше решение. Обратите внимание — команда поддержки не отвечает на вопросы по коду, но поможет разобраться с заданием или выводом тестов
- Мы отвечаем на сообщения в течение 2-3 дней. К «Обсуждениям» могут подключаться и другие студенты. Возможно, получится решить вопрос быстрее!
Подробнее о том, как задавать вопросы по уроку
В этой статье мы изучим что такое инструкции и чем они отличаются от выражений. Кроме этого разберём как создавать однострочные и многострочные комментарии в JavaScript.
Программы на JavaScript состоят из инструкций. Одна инструкция может располагаться из нескольких строчках.
// инструкция if
if (true) {
console.log('Привет, мир!');
}
Кроме этого на одной строчке может находиться несколько инструкций, если они разделены друг от друга точкой с запятой:
let a = 5; let b = 6; let c = a + b;
В HTML, JavaScript программы выполняются веб-браузером.
JavaScript инструкции состоят из: значений, выражений, ключевых слов и комментариев.
Например, эта инструкция говорит браузеру установить в качестве содержимого страницы <h1>Привет, мир!</h1>
:
document.body.innerHTML = '<h1>Привет, мир!</h1>';
Так что же такое инструкция и чем она отличается от выражения?
Инструкции
В JavaScript есть выражения (на английском expressions). Выражение как уже мы отмечали раньше – это всегда то, что возвращает значение.
Например, выражением является операция присвоения переменной значения:
num = 77
Инструкция (на английском statement) в отличие от выражения – это всегда то, что выполняет определённые действия.
Примеры инструкций:
// создание переменных
let x;
const obj = { y: 5 };
// условная инструкция if
if (x === obj.y) {
console.log('x равно obj.y');
}
// цикл
for (let i = 0; i < 3; i++) {
console.log('значение i: ' + i);
}
Здесь 4 инструкции, каждая из которых выполняет определённые действия:
let x;
– это инструкция, которая выполняет определённые действия; в данном случае создаёт переменную с помощью ключевого словаlet
;const obj = { y: 5 };
– это инструкция, в которой мы создаём переменнуюobj
с помощью ключевого словаconst
и присваиваем ей объект, содержащий одно свойство;if
– это инструкция, которая выполняет действия в фигурных скобках, если условие, указанное в круглых скобках истинно;for
– это инструкция, которая выполняет код в фигурных скобках много раз, в данном случае 3 раза.
Инструкции в JavaScript следует завершать точкой с запятой. Поэтому здесь после первой и второй инструкции поставлены точки с запятой. Блоки кода в условных конструкциях и циклах, которые заканчиваются фигурной скобкой не требуют после себя точку с запятой.
Кроме этого, инструкции также желательно размещать на разных строках.
Наличие точки запятой в конце позволяет очень просто отличить инструкцию от выражения. Исключением здесь является то, что точка с запятой не требуется после закрывающей фигурной скобки.
Но точку с запятой можно опускать в JavaScript, если указывать инструкции на разных строчках. То есть код, приведенный выше можно переписать так:
let x
const obj = { y: 5 }
if (x === obj.y) {
console.log('x равно obj.y')
}
for (let i = 0; i < 3; i++) {
console.log('значение i: ' + i)
}
Обратите внимание, что внутри блоках кода if
и for
тоже указаны инструкции. Но эти инструкции являются выражениями, так как они возвращают значения. То есть выражение в JavaScript может быть инструкцией, но инструкция выражением нет.
Например, строка – это выражение. Но посредством добавления к ней точки с запятой, мы её конвертируем в инструкцию:
'моя строка';
'use strict';
Первая инструкция не имеет какого-то определённого смысла, интерпретатор её просто выполняет. Следующая инструкция 'use strict'
включает строгий режим в JavaScript.
Пример выражения, конвертированного в инструкцию:
let a = 7;
let b = 11;
let c;
c = a + b;
В этом примере на 4 строчке выражение c = a + b
будет являться инструкцией, так как мы к нему добавили точку с запятой. Но если бы мы даже этого не сделали, то это выражение не перестало быть инструкцией. Так как это выражение располагается на отдельной строчке. А как мы знаем, в этом случае точку с запятой можно опускать. Но при этом это выражение не перестало возвращать значение, так как выражение всегда возвращает значение. Но также оно является инструкцией, и это означает, что это выражение выполнится отдельно от других.
Ключевые слова и идентификаторы
Ключевые слова – это зарезервированные слова, которые являются частью синтаксиса языка программирования. Например:
const a = 'hello';
В этом примере используется ключевое слово const
. С помощью него мы можем создавать переменные в JavaScript.
Ключевые слова нельзя использовать для обозначения идентификаторов.
В JavaScript очень много ключевых слов. Вот некоторые из них: await
, break
, catch
, class
, const
, continue
, else
, export
, extends
, for
, function
, if
, let
, return
, static
, this
, throw
, try
, while
.
Идентификатор – это имя, которые мы можем дать переменной, функции, классу и так далее. Составлять имя мы можем из букв, цифр, нижнего подчеркивания и знака доллара.
При этом идентификатор не может начинаться с цифры, а также в качестве него нельзя использовать ключевые слова. Кроме этого, JavaScript чувствителен к регистру.
const a = 5;
const A = 7;
console.log(a); // 5
console.log(A); // 7
Комментарии JavaScript – это подсказки, которые программист может добавить, чтобы облегчить чтение и понимание своего кода. Они полностью игнорируются движками JavaScript.
Есть два способа добавить комментарии к коду:
//
– однострочные комментарии;/* */
– многострочные комментарии;
В JavaScript любая строка, начинающаяся с //
, является однострочным комментарием. Например:
name = 'Bob';
// выведем сообщение в консоль
console.log(`Привет, ${name}`);
Здесь // выведем сообщение в консоль
является комментарием.
Однострочный комментарий можно использовать ещё так:
name = 'Bob';
console.log(`Привет, ${name}`); // выведем сообщение в консоль
В Javascript любой текст между /*
и */
является многострочным комментарием. Например:
/* Этот комментарий
расположен на нескольких строчках */
Комментарии также очень часть используют для отключения кода при отладке. Например, если нам не нужно выполнять какую-то инструкцию, то мы можем её просто закомментировать:
myFn1();
// myFn2();
myFn3();
Это может очень ценным при отладке, потому что вместо удаления некоторого кода мы можем его просто закомментировать.
Рекомендации по именованию переменных, функций и классов
Основные рекомендации:
- имя, состоящее из одного слова, следует писать строчными буквами; например:
article
; - имя, состоящее из нескольких слов, следует писать слитно строчными буквами, кроме букв на стыке слов, их необходимо писать прописными; например:
articleTitle
; - при составлении имени, количество слов в нём не должно быть больше 3; например:
articleDatePublishedon
; - переменные не хранящие функции и свойства объектов, не являющиеся методами должны быть существительными;например:
textComment
; - массивы и коллекции значений следует задавать существительными во множественном числе; например:
lastComments
); - именовать функции и методы объектов необходимо глаголами; например:
getLastArticles
); - именование классов необходимо начинать с прописной буквы; например:
Comments
.
Время на прочтение
15 мин
Количество просмотров 139K
Содержание
- Введение
- Величины, типы и операторы
- Структура программ
- Функции
- Структуры данных: объекты и массивы
- Функции высшего порядка
- Тайная жизнь объектов
- Проект: электронная жизнь
- Поиск и обработка ошибок
- Регулярные выражения
- Модули
- Проект: язык программирования
- JavaScript и браузер
- Document Object Model
- Обработка событий
- Проект: игра-платформер
- Рисование на холсте
- HTTP
- Формы и поля форм
- Проект: Paint
- Node.js
- Проект: веб-сайт по обмену опытом
- Песочница для кода
Сердце моё сияет ярко-красным светом под моей тонкой, прозрачной кожей, и им приходится вколоть мне десять кубиков JavaScript, чтобы вернуть меня к жизни (я хорошо реагирую на токсины в крови). От этой фигни у вас враз жабры побледнеют!
_why, Why’s (Poignant) Guide to Ruby
В этой главе мы начнём заниматься тем, что уже можно назвать программированием. Мы расширим использование языка JavaScript за пределы существительных и фрагментов предложений к более-менее осмысленной прозе.
Выражения и инструкции
В первой главе мы создавали величины и применяли к ним операторы, получая новые величины. Это важная часть каждой программы, но только лишь часть.
Фрагмент кода, результатом работы которого является некая величина, называется выражением. Каждая величина, записанная буквально (например, 22 или “психоанализ”) тоже является выражением. Выражение, записанное в скобках, также является выражением, как и бинарный оператор, применяемый к двум выражениям или унарный – к одному.
Это часть красоты языкового интерфейса. Выражения могут включать другие выражения так же, как сложноподчинённое предложение состоит из простых. Это позволяет нам комбинировать выражения для создания вычислений любой сложности.
Если выражение – это фрагмент предложения, то инструкция – это предложение полностью. Программа – это просто список инструкций.
Простейшая инструкция – это выражение с точкой с запятой после него. Это — программа:
1;
!false;
Правда, это бесполезная программа. Выражение можно использовать только для получения величины, которая может быть использована в другом выражении, охватывающем это. Инструкция стоит сама по себе и её применение изменяет что-то в мире программы. Она может выводить что-то на экран (изменение в мире), или менять внутреннее состояние машины таким образом, что это повлияет на следующие за ним инструкции. Эти изменения называются побочными эффектами. Инструкции в предыдущем примере просто выдают величины 1 и true, и сразу их выбрасывают. Они не оказывают никакого влияния на мир программы. При выполнении программы ничего заметного не происходит.
В некоторых случаях JavaScript позволяет опускать точку с запятой в конце инструкции. В других случаях она обязательно, или следующая строка будет расцениваться как часть той же инструкции. Правила, согласно которым можно или нельзя опускать точку с запятой, довольно сложны и увеличивают вероятность ошибиться. В этой книге мы не будем опускать точку с запятой, и я рекомендую делать так же в своих программах, пока вы не накопите опыт.
Переменные
Как же программа хранит внутреннее состояние? Как она его запоминает? Мы получали новые величины из старых, но старые величины это не меняло, а новые нужно было использовать сразу, или же они исчезали. Чтобы захватить и хранить их, JavaScript предлагает нечто под названием «переменная».
var caught = 5 * 5;
И это даёт нам второй вид инструкций. Специальное ключевое слово (keyword) var
показывает, что в этой инструкции мы объявляем переменную. За ним идёт имя переменной, и, если мы сразу хотим назначить ей значение – оператор = и выражение.
Пример создаёт переменную под именем caught и использует её для захвата числа, которое получается в результате перемножения 5 и 5.
После определения переменной её имя можно использовать в выражениях. Величина переменной будет такой, какое значение в ней сейчас содержится. Пример:
var ten = 10;
console.log(ten * ten);
// → 100
Переменные можно называть любым словом, которое не является ключевым (типа var). Нельзя использовать пробелы. Цифры тоже можно использовать, но не первым символом в названии. Нельзя использовать знаки пунктуации, кроме символов $ и _.
Переменной присваивают значение не навсегда. Оператор = можно использовать на существующих переменных в любое время, чтобы присвоить им новое значение.
var mood = "лёгкое";
console.log(mood);
// → лёгкое
mood = "тяжёлое";
console.log(mood);
// → тяжёлое
Представляйте себе переменные не в виде коробочек, а в виде щупалец. Они не содержат значения – они хватают их. Две переменные могут ссылаться на одно значение. Программа имеет доступ только к значениям, которые они содержат. Когда вам нужно что-то запомнить, вы отращиваете щупальце и держитесь за это, или вы используете существующее щупальце, чтобы удержать это.
Переменные как щупальца
Пример. Для запоминания количества денег, которые вам должен Василий, вы создаёте переменную. Затем, когда он выплачивает часть долга, вы даёте ей новое значение.
var vasyaDebt = 140;
vasyaDebt = vasyaDebt - 35;
console.log(vasyaDebt);
// → 105
Когда вы определяете переменную без присваивания ей значения, щупальцу не за что держаться, оно висит в воздухе. Если вы запросите значение пустой переменной, вы получите undefined.
Одна инструкция var может содержать несколько переменных. Определения нужно разделять запятыми.
var one = 1, two = 2;
console.log(one + two);
// → 3
Ключевые и зарезервированные слова
Слова со специальным смыслом, типа var – ключевые. Их нельзя использовать как имена переменных. Также есть несколько слов, «зарезервированных для использования» в будуших версиях JavaScript. Их тоже нельзя использовать, хотя в некоторых средах исполнения это возможно. Полный их список достаточно большой.
break case catch continue debugger default delete do else false finally for function if implements in instanceof interface let new null package private protected public return static switch throw true try typeof var void while with yield this
Не нужно их запоминать, но имейте в виду, что ошибка может крыться здесь, если ваши определения переменных не работают, как надо.
Окружение
Коллекция переменных и их значений, которая существует в определённый момент, называется окружением. Когда программа запускается, окружение не пустое. Там всегда есть переменные, являющиеся частью программного стандарта, и большую часть времени там есть переменные, помогающие взаимодействовать с окружающей системой. К примеру, в браузере есть переменные и функции для изучения состояния загруженной веб-страницы и влияния на неё, для чтения ввода с мыши и клавиатуры.
Функции
Многие величины из стандартного окружения имеют тип function
(функция). Функция – отдельный кусочек программы, который можно использовать вместе с другими величинами. К примеру, в браузере переменная alert содержит функцию, которая показывает небольшое окно с сообщением. Используют его так:
alert("С добрым утром!");
Диалог alert
Выполнение функции называют вызовом. Вы можете вызвать функцию, записав скобки после выражения, которое возвращает значение функции. Обычно вы напрямую используете имя функции в качестве выражения. Величины, которые можно написать внутри скобок, передаются программному коду внутри функции. В примере, функция alert использует данную ей строку для показа в диалоговом окне. Величины, передаваемые функциям, называются аргументами функций. Функция alert требует один аргумент, но другие могут требовать разное количество аргументов разных типов.
Функция console.log
Функция alert может использоваться как средство вывода при экспериментах, но закрывать каждый раз это окно вам скоро надоест. В прошлых примерах мы использовали функцию console.log для вывода значений. Большинство систем JavaScript (включая все современные браузеры и Node.js) предоставляют функцию console.log, которая выводит величины на какое-либо устройство вывода. В браузерах это консоль JavaScript. Эта часть браузера обычно скрыта – большинство браузеров показывают её по нажатию F12, или Command-Option-I на Маке. Если это не сработало, поищите в меню “web console” или “developer tools”.
В примерах этой книги результаты вывода показаны в комментариях:
var x = 30;
console.log("the value of x is", x);
// → the value of x is 30
Хотя в именах переменных нельзя использовать точку – она, очевидно, содержится в названии console.log. Это оттого, что console.log – не простая переменная. Это выражение, возвращающее свойство log переменной console. Мы поговорим об этом в главе 4.
Возвращаемые значения
Показ диалогового окна или вывод текста на экран – это побочный эффект. Множество функций полезны оттого, что они производят эти эффекты. Функции также могут производить значения, и в этом случае им не нужен побочный эффект для того, чтобы быть полезной. К примеру, функция Math.max принимает любое количество переменных и возвращает значение самой большой:
console.log(Math.max(2, 4));
// → 4
Когда функция производит значение, говорят, что она возвращает значение. Всё, что производит значение – это выражение, то есть вызовы функций можно использовать внутри сложных выражений. К примеру, возвращаемое функцией Math.min (противоположность Math.max) значение используется как один из аргументов оператора сложения:
console.log(Math.min(2, 4) + 100);
// → 102
В следующей главе описано, как писать собственные функции.
prompt и confirm
Окружение браузера содержит другие функции, кроме alert, которые показывают всплывающие окна. Можно вызвать окно с вопросом и кнопками OK/Cancel при помощи функции confirm
. Она возвращает булевское значение – true, если нажато OK, и false, если нажато Cancel.
confirm("Ну что, поехали?");
Функцию prompt
можно использовать, чтобы задать открытый вопрос. Первый аргумент – вопрос, второй – текст, с которого пользователь начинает. В диалоговое окно можно вписать строку текста, и функция вернёт его в виде строки.
prompt("Расскажи мне всё, что знаешь.", "...");
Эти функции нечасто используют, потому что нельзя изменять внешний вид этих окон — но они могут пригодиться для экспериментальных программ.
Управление порядком выполнения программы
Когда в программе больше одной инструкции, они выполняются сверху вниз. В этом примере у программы две инструкции. Первая спрашивает число, вторая, выполняемая следом, показывает его квадрат.
var theNumber = Number(prompt("Выбери число", ""));
alert("Твоё число – квадратный корень из " + theNumber * theNumber);
Функция Number преобразовывает величину в число. Нам это нужно, потому что prompt возвращает строку. Есть сходные функции String и Boolean, преобразующие величины в соответствующие типы.
Простая схема прямого порядка исполнения программы:
Условное выполнение
Выполнять инструкции по порядку – не единственная возможность. В качестве альтернативы существует условное выполнение, где мы выбираем из двух возможных путей, основываясь на булевской величине:
Условное выполнение записывается при помощи ключевого слова if. В простом случае нам нужно, чтобы некий код был выполнен, только если выполняется некое условие. К примеру, в предыдущей программе мы можем считать квадрат, только если было введено именно число.
var theNumber = prompt("Выбери число", "");
if (!isNaN(theNumber))
alert("Твоё число – квадратный корень из " + theNumber * theNumber);
Теперь, введя «сыр», вы не получите вывод.
Ключевое слово if выполняет или пропускает инструкцию, в зависимости от значения булевого выражения. Это выражение записывается после if в скобках, и за ним идёт нужная инструкция.
Функция isNaN
– стандартная функция JavaScript, которая возвращает true, только если её аргумент – NaN (не число). Функция Number возвращает NaN, если задать ей строку, которая не представляет собой допустимое число. В результате, условие звучит так: «выполнить, если только theNumber не является не-числом».
Часто нужно написать код не только для случая, когда выражение истинно, но и для случая, когда оно ложно. Путь с вариантами – это вторая стрелочка диаграммы. Ключевое слово else используется вместе с if для создания двух раздельных путей выполнения.
var theNumber = Number(prompt("Выбери число", ""));
if (!isNaN(theNumber))
alert("Твоё число – квадратный корень из " + theNumber * theNumber);
else
alert("Ну ты что число-то не ввёл?");
Если вам нужно больше разных путей, можно использовать несколько пар if/else по цепочке.
var num = Number(prompt("Выбери число", "0"));
if (num < 10)
alert("Маловато");
else if (num < 100)
alert("Нормально");
else
alert("Многовато");
Программа проверяет, действительно ли num меньше 10. Если да – выбирает эту ветку, и показывает «Маловато». Если нет, выбирает другую – на которой ещё один if. Если следующее условие выполняется, значит номер будет между 10 и 100, и выводится «Нормально». Если нет – значит, выполняется последняя ветка.
Последовательность выполнения примерно такая:
Циклы 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, это решение было бы неприемлемым. Нам нужна возможность повторения. Этот вид контроля над порядком выполнения называется циклом.
Зацикливание даёт возможность вернуться назад к какой-то инструкции и повторить всё заново с новым состоянием программы. Если скомбинировать это с переменной для подсчёта, можно сделать следующее:
var number = 0;
while (number <= 12) {
console.log(number);
number = number + 2;
}
// → 0
// → 2
// … и т.д.
Инструкция, начинающаяся с ключевого слова while
– это цикл. За while следует выражение в скобках, и затем инструкция (тело цикла) – так же, как у if. Цикл выполняет инструкцию, пока выражение выдаёт истинный результат.
В цикле нам нужно выводить значение и прибавлять к нему. Если нам нужно выполнять в цикле несколько инструкций, мы заключаем его в фигурные скобки { }. Фигурные скобки для инструкций – как круглые скобки для выражений. Они группируют их и превращают в единое. Последовательность инструкций, заключённая в фигурные скобки, называется блоком.
Много программистов заключают любое тело цикла в скобки. Они делают это для единообразия, и для того, чтобы не нужно было добавлять и убирать скобки, если приходится изменять количество инструкций в цикле. В книге я не буду писать скобки вокруг единичных инструкций в цикле, так как люблю краткость. Вы можете делать, как угодно.
Переменная number показывает, как переменная может отслеживать прогресс программы. При каждом повторении цикла number увеличивается на 2. Перед каждым повторением оно сравнивается с 12, чтобы понять, сделала ли программа всё, что требовалось.
Для примера более полезной работы мы можем написать программу вычисления 2 в 10 степени. Мы используем две переменные: одну для слежения за результатом, а вторую – для подсчёта количества умножений. Цикл проверяет, достигла ли вторая переменная 10, и затем обновляет обе.
var result = 1;
var counter = 0;
while (counter < 10) {
result = result * 2;
counter = counter + 1;
}
console.log(result);
// → 1024
Можно начинать counter с 1 и проверять его на <=10, но по причинам, которые станут ясны далее, всегда лучше начинать счётчики с 0.
Цикл do похож на цикл while. Отличается только в одном: цикл do всегда выполняет тело хотя бы один раз, а проверяет условие после первого выполнения. Поэтому и тестируемое выражение записывают после тела цикла:
do {
var name = prompt("Who are you?");
} while (!name);
console.log(name);
Эта программа заставляет ввести имя. Она спрашивает его снова и снова, пока не получит что-то кроме пустой строки. Добавление «!» превращает значение в булевское и затем применяет логическое отрицание, а все строки, кроме пустой, преобразуются в булевское true.
Отступы в коде
Вы, наверно, заметили пробелы перед некоторыми инструкциями. В JavaScript это не обязательно – программа отработает и без них. Даже переводы строк не обязательно делать. Можно написать программу в одну строку. Роль пробелов в блоках – отделять их от остальной программы. В сложном коде, где в блоках встречаются другие блоки, может быть сложно разглядеть, где кончается один и начинается другой. Правильно отделяя их пробелами вы приводите в соответствие внешний вид кода и его блоки. Я люблю отделять каждый блок двумя пробелами, но вкусы различаются – некоторые используют четыре, некоторые – табуляцию. Чем больше пробелов использовать, тем заметнее отступ, но тем быстрее вложенные блоки убегают за правый край экрана.
Циклы for
Много циклов строятся по такому шаблону, как в примере. Создаётся переменная-счётчик, потом идёт цикл while, где проверочное выражение обычно проверяет, не достигли ли мы какой-нибудь границы. В конце тела цикла счётчик обновляется.
Поскольку это такой частый случай, в JavaScript есть вариант покороче, цикл for
.
for (var number = 0; number <= 12; number = number + 2)
console.log(number);
// → 0
// → 2
// … и т.д.
Эта программа эквивалентна предыдущей. Только теперь все инструкции, относящиеся к отслеживанию состояния цикла, сгруппированы.
Скобки после for содержат две точки с запятой, разделяя инструкцию на три части. Первая инициализирует цикл, обычно задавая начальное значение переменной. Вторая – выражение проверки необходимости продолжения цикла. Третья – обновляет состояние после каждого прохода. В большинстве случаев такая запись более короткая и понятная, чем while.
Вычисляем 2^10 при помощи for:
var result = 1;
for (var counter = 0; counter < 10; counter = counter + 1)
result = result * 2;
console.log(result);
// → 1024
Хотя я не писал фигурных скобок, я отделяю тело цикла пробелами.
Выход из цикла
Дождаться, пока условие цикла не станет ложным – не единственный способ закончить цикл. Специальная инструкция break
приводит к немедленному выходу из цикла.
В следующем примере мы покидаем цикл, когда находим число, большее или равное 20, и делящееся на 7 без остатка.
for (var current = 20; ; current++) {
if (current % 7 == 0)
break;
}
console.log(current);
// → 21
Простой способ выяснить, делится ли одно число на другое — использовать оператор остатка от деления (%). Если число делится без остатка, тогда остаток будет равен нулю.
Конструкция for не имеет проверочной части – поэтому цикл не остановится, пока не сработает инструкция break.
Если вы не укажете эту инструкцию, или случайно напишете условие, которое всегда выполняется, программа зависнет в бесконечном цикле и никогда не закончит работу – обычно это плохо.
Если вы сделаете бесконечный цикл, обычно через несколько секунд среда исполнения предложит вам прервать его. Если нет, вам придётся закрыть закладку, или даже весь браузер.
Ключевое слово continue
также влияет на исполнение цикла. Когда это слово встречается в цикле, он немедленно переходит на следующую итерацию.
Короткое обновление переменных
Особенно часто в циклах программе нужно обновить переменную, основываясь на её предыдущем состоянии.
counter = counter + 1;
В JavaScript есть для этого короткая запись:
counter += 1;
Подобные записи работают для многих других операторов, к примеру result *= 2 для удвоения, или counter -= 1 для обратного отсчёта.
Это позволяет нам сократить программу вывода чётных чисел:
for (var number = 0; number <= 12; number += 2)
console.log(number);
Для counter += 1 и counter -= 1 есть ещё более короткие записи: counter++ and counter—.
Работаем с переменными при помощи switch
Часто код выглядит так:
if (variable == "value1") action1();
else if (variable == "value2") action2();
else if (variable == "value3") action3();
else defaultAction();
Существует конструкция под названием switch
, которая упрощает подобную запись. К сожалению, синтаксис JavaScript в этом случае довольно странный – часто цепочка if/else выглядит лучше. Пример:
switch (prompt("Как погодка?")) {
case "дождь":
console.log("Не забудь зонт.");
break;
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 и большинство программистов используют последний вариант – каждое слово с большой буквы, кроме первого.
В некоторых случаях, например в случае функции Number, первую букву тоже пишут большой – когда нужно выделить функцию как конструктор. О конструкторах мы поговорим в главе 6. Сейчас просто не обращайте на это внимания.
Комментарии
Часто код не содержит всю информацию, которую хотелось бы передать читателям-людям, или доносит её в непонятном виде. Иногда вы чувствуете поэтическое вдохновение, или просто хотите поделиться мыслями в своей программе. Для этого служат комментарии.
Комментарий – это текст, который записан в программе, но игнорируется компьютером. В JavaScript комментарии можно писать двумя способами. Для однострочного комментария можно использовать два слеша:
var accountBalance = calculateBalance(account);
// Издалека долго
accountBalance.adjust();
// Течёт река Волга
var report = new Report();
// Течёт река Волга
addToReport(accountBalance, report);
// Конца и края нет
Комментарий продолжается только до конца строки. Код между символами /* и */ будет игнорироваться вместе с возможными переводами строки. Это подходит для включения целых информационных блоков в программу:
/*
Этот город – самый лучший
Город на Земле.
Он как будто нарисован
Мелом на стене.
*/
var myCity = ‘Челябинск’;
Итог
Теперь вы знаете, что программа состоит из инструкций, которые сами могут содержать инструкции. В инструкциях содержатся выражения, которые могут состоять из выражений.
Записывая инструкции подряд, мы получаем программу, которая выполняется сверху вниз. Вы можете изменять этот поток выполнения, используя условные (if, else, и switch) операторы и операторы цикла (while, do, и for).
Переменные можно использовать для хранения кусочков данных под определённым названием и для отслеживания состояния программы. Окружение – набор определённых переменных. Системы, исполняющие JavaScript, всегда добавляют несколько стандартных переменных в ваше окружение.
Функции – особые переменные, включающие части программы. Их можно вызвать командой functionName(argument1, argument2). Такой вызов – это выражение, и может выдавать значение.
Упражнения
Если вы не уверены, как выполнять упражнения, обратитесь к «введению».
Каждое упражнение начинается с описания задачи. Прочтите и постарайтесь выполнить. В сложных ситуациях обращайтесь к подсказкам. Готовые решения задач можно найти на сайте книги eloquentjavascript.net/code. Чтобы обучение было эффективным, не заглядывайте в ответы, пока не решите задачу сами, или хотя бы не попытаетесь её решить достаточно долго для того, чтобы у вас слегка заболела голова. Там же можно писать код прямо в браузере и выполнять его.
Треугольник в цикле
Напишите цикл, который за 7 вызовов console.log выводит такой треугольник:
#
##
###
####
#####
######
#######
Будет полезно знать, что длину строки можно узнать, приписав к переменной .length.
var abc = "abc";
console.log(abc.length);
// → 3
FizzBuzz
Напишите программу, которая выводит через console.log все цифры от 1 до 100, с двумя исключениями. Для чисел, нацело делящихся на 3, она должна выводить ‘Fizz’, а для чисел, делящихся на 5 (но не на 3) – ‘Buzz’.
Когда сумеете – исправьте её так, чтобы она выводила «FizzBuzz» для всех чисел, которые делятся и на 3 и на 5.
(На самом деле, этот вопрос подходит для собеседований, и говорят, он позволяет отсеивать довольно большое число кандидатов. Поэтому, когда вы решите эту задачу, можете себя похвалить)
Шахматная доска
Напишите программу, создающую строку, содержащую решётку 8х8, в которой линии разделяются символами новой строки. На каждой позиции либо пробел, либо #. В результате должна получиться шахматная доска.
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
Когда справитесь, сделайте размер доски переменным, чтобы можно было создавать доски любого размера.
JavaScript: Приоритет операций
Посмотрите внимательно на выражение 2 + 2 * 2
и посчитайте в уме ответ.
Правильный ответ: 6
.
Если у вас получилось 8
, то этот урок для вас. В школьной математике мы изучали понятие «приоритет операции». Приоритет определяет то, в какой последовательности должны выполняться операции. Например, умножение и деление имеют больший приоритет, чем сложение и вычитание, а приоритет возведения в степень выше всех остальных арифметических операций: 2 ** 3 * 2
вычислится в 16
.
Но нередко вычисления должны происходить в порядке, отличном от стандартного приоритета. В сложных ситуациях приоритет можно (и нужно) задавать круглыми скобками, точно так же, как в школе, например: (2 + 2) * 2
.
Скобки можно ставить вокруг любой операции. Они могут вкладываться друг в друга сколько угодно раз. Вот пара примеров:
console.log(3 ** (4 - 2)); // => 9
console.log(7 * 3 + (4 / 2) - (8 + (2 - 1))); // => 14
Главное при этом соблюдать парность, то есть закрывать скобки в правильном порядке. Это, кстати, часто становится причиной ошибок не только у новичков, но и у опытных программистов. Для удобства ставьте сразу открывающую и закрывающую скобку, а потом пишите внутреннюю часть. Редактор на нашем сайте (и большинство других редакторов кода) делают это автоматически: вы пишете (
, а редактор сразу добавляет )
. Это касается и других парных символов, например, кавычек. О них — в будущих уроках.
Иногда выражение сложно воспринимать визуально. Тогда можно расставить скобки, не повлияв на приоритет. Например, задание из прошлого урока можно сделать немного понятнее, если расставить скобки.
Было:
console.log(8 / 2 + 5 - -3 / 2); // => 10.5
Стало:
console.log(((8 / 2) + 5) - (-3 / 2)); // => 10.5
Запомните: код пишется для людей, потому что код будут читать люди, а машины будут только исполнять его. Для машин код — или корректный, или не корректный, для них нет «более» понятного или «менее» понятного кода.
Задание
Дано вычисление 70 * 3 + 4 / 8 + 2
.
Расставьте скобки так, чтобы оба сложения (3 + 4
) и (8 + 2
) высчитывались в первую очередь. Выведите на экран результат.
Советы
- Приоритет операторов
11. Числа с плавающей точкой
JavaScript: Числа с плавающей точкой
JavaScript не делает различий между рациональными (0.5) и натуральными числами (10), для него и то, и другое – числа (в других языках это не так). Благодаря этому их можно использовать совместно в любых операциях:
3 * 0.5; // 1.5
Но как бы от нас не скрывали, рациональные числа, в силу своих особенностей, устроены совсем по-другому. Нам, как прикладным программистам, это было бы не особенно важно, если бы не одна деталь. Посмотрите на этот пример:
// Проверьте этот код в консоли браузера
0.2 * 0.2 // 0.04000000000000001
Операция умножения двух рациональных чисел внезапно привела к неточному вычислению результата. Тот же самый результат выдадут и другие языки программирования. Такое поведение обуславливается ограничениями вычислительных мощностей. Объем памяти, в отличие от чисел, конечен (бесконечное количество чисел требует бесконечного количества памяти для своего хранения). И если с натуральными числами эта проблема решается простым ограничением по верхней границе (есть некоторое максимальное число, которое можно ввести), то с рациональными такой финт не пройдет.
// Максимальное возможное целое число
console.log(Number.MAX_SAFE_INTEGER);
9007199254740991
Рациональные числа не выстроены в непрерывную цепочку, между 0.1 и 0.2 бесконечное множество чисел. Соответственно возникает серьезная проблема, а как хранить рациональные числа? Это интересный вопрос сам по себе. В интернете множество статей, посвященных организации памяти в таких случаях. Более того, существует стандарт, в котором описано, как это делать правильно, и подавляющее число языков на него опирается.
Для нас, как для разработчиков, важно понимать, что операции с плавающими числами неточны (эту точность можно регулировать), а значит при решении задач, связанных с подобными числами, необходимо прибегать к специальным трюкам, которые позволяют добиться необходимой точности.
Задание
Вычислите и выведите на экран произведение двух чисел: 0.39 и 0.22
Советы
- Что нужно знать про арифметику с плавающей запятой
12. Бесконечность (Infinity)
JavaScript: Бесконечность (Infinity)
В программировании широко известна ошибка “деление на ноль”. В низкоуровневых языках она приводит к краху программы и необходимости ее перезапуска. Там, где другие падают, JavaScript продолжает работать.
console.log(1 / 0); // ?
Попробуйте выполнить этот код в браузере. На экран выведется Infinity
(бесконечность)! Для тех, кто изучал высшую математику (привет, матан!), в этом нет ничего удивительного. Деление на ноль действительно создает бесконечность. Бесконечность в JavaScript — самое настоящее число, с которым возможно проводить различные операции. В повседневных задачах смысла от этого мало, так как большинство операций с бесконечностью завершаются созданием бесконечности, например, при прибавлении любого числа к бесконечности мы все равно получим бесконечность.
Infinity + 4; // Infinity
Infinity - 4; // Infinity
Infinity * Infinity; // Infinity
Однако есть несколько примеров, где бесконечность нужна. Подробнее этот вопрос рассматривается на Хекслете.
Задание
Распечатайте на экран сумму бесконечностей, поделенную на 10
13. NaN
JavaScript: NaN
Некоторые операции с бесконечностями приводят к странному результату, например, деление бесконечности на бесконечность. В математике такая операция не имеет никакого числового эквивалента. В JavaScript вернется NaN
.
Infinity / Infinity; // NaN
NaN
– специальное значение “не число”, которое обычно говорит о том, что была выполнена бессмысленная операция. Результатом любой операции, в которой участвует NaN
, будет NaN
.
NaN + 1; // NaN
NaN
интересное значение, хотя оно обозначает “не число” — с точки зрения типов, оно является числом. Парадокс. NaN
никогда не является желаемым значением и появляется только в результате ошибок. Если вы его встретили, то нужно отследить момент, в котором выполнилась операция, недопустимая для чисел, и поправить это место.
Задание
Выполните операцию, которая приводит к NaN, и распечатайте её результат на экран с помощью console.log()
.
Советы
- NaN
14. Линтер
JavaScript: Линтер
Теперь, когда мы уже научились писать простые программы, можно немного поговорить о том, как их писать.
Код программы следует оформлять определенным образом, чтобы он был достаточно понятным и простым в поддержке. Специальные наборы правил — стандарты — описывают различные аспекты написания кода. Конкретно в JavaScript самым распространенным стандартом является стандарт от AirBnb.
В любом языке программирования существуют утилиты — так называемые
линтеры. Они проверяют код на соответствие стандартам. В JavaScript это eslint.
Взгляните на пример из предыдущего урока:
console.log(8/2+5 - -3 / 2); // => 10.5
Линтер будет «ругаться» на нарушение сразу нескольких правил:
- space-infix-ops – Отсутствие пробелов между оператором и операндами.
- no-mixed-operators – По стандарту нельзя писать код, в котором разные операции используются в одном выражении без явного разделения скобками.
В прошлом уроке мы сами признали, что такое обилие цифр и символов запутывает, и решили добавить скобки исключительно для удобства чтения:
console.log(((8 / 2) + 5) - (-3 / 2)); // => 10.5
Этот вариант уже не нарушает правил, и линтер будет «молчать».
В упражнении прошлого урока у вас скорее всего получилось так:
console.log(70 * (3 + 4) / (8 + 2));
Есть ли здесь нарушение стандарта?
К сожалению, да. На этот раз операции *
и /
находятся в одном выражении без разделения скобками. Вы можете решить эту проблему, добавив дополнительные скобки. Но в какой-то момент количество скобок может быть уже настолько большим, что код снова станет неудобным и непонятным. В этот момент разумнее будет разделить выражение на отдельные части. Мы научимся это делать в следующих уроках.
no-mixed-operators — лишь одно из большого количества правил. Другие правила описывают отступы, названия создаваемых сущностей, скобки, математические операции, длину строк и множество иных аспектов. Каждое отдельное правило кажется довольно мелким, не очень важным. Но вместе они составляют основу хорошего кода.
Сейчас сайт не будет проверять ваш код линтером, но в ваших будущих практиках на Хекслете и в реальной разработке линтер будет работать и сообщать вам о нарушениях.
Задание
Выведите на экран результат следующего вычисления: «разница между пятью в квадрате и произведением трёх и семи». Расставьте скобки таким образом, чтобы не нарушать правило no-mixed-operators
.
Строки
Текст в программировании называется «строками», и эта тема не так проста, как может показаться. Как вывести фразу, в которой есть и одинарные, и двойные кавычки? Как вообще быть с текстом, ведь компьютер не знает ничего о буквах! Модуль посвящен разным аспектам написания текста – от кавычек и экранирования до кодировки.
15. Кавычки
JavaScript: Кавычки
'Hello'
'Goodbye'
'G'
' '
''
Какие из этих пяти вариантов — строки?
С первыми двумя все понятно, это точно строки, мы уже работали с подобными конструкциями и говорили, что строки – это наборы символов.
Любой одиночный символ в кавычках — это строка. Пустая строка ''
— это тоже строка. То есть строкой мы считаем всё, что находится внутри кавычек, даже если это пробел, один символ или вообще отсутствие символов.
Ранее в уроках мы записывали строки в одинарных кавычках, но это не единственный способ. Можно использовать и двойные:
// Стандарт кодирования airbnb, рекомендует
// использовать, по возможности, одинарные
console.log('Dracarys!');
Представьте, что вы хотите напечатать строчку Dragon’s mother. Апостроф перед буквой s — это такой же символ, как одинарная кавычка. Попробуем:
console.log('Dragon's mother');
// Uncaught SyntaxError: missing ) after argument list
Такая программа не будет работать. С точки зрения JavaScript, строчка началась с одинарной кавычки, а потом закончилась после буквы n. Дальше были символы s mother
без кавычек — значит, это не строка. А потом была одна открывающая строку кавычка, которая так и не закрылась: ');
. Этот код синтаксически некорректен (это видно даже по тому, как подсвечен код).
Здесь нам помогут двойные кавычки. Такой вариант программы отработает корректно:
console.log("Dragon's mother");
Теперь интерпретатор знает, что строка началась с двойной кавычки — значит, и закончиться должна на двойной кавычке. А одинарная кавычка внутри стала частью строки.
Верно и обратное. Если внутри строки мы хотим использовать двойные кавычки, то саму строку надо делать в одинарных. Причем количество кавычек внутри самой строки не важно.
А что, если мы хотим создать такую строку:
Dragon's mother said "No"
В ней есть и одинарные и двойные кавычки. Как быть в этой ситуации? Нужно каким-то образом сказать интерпретатору считать каждую кавычку частью строки, а не началом или концом строки.
Для этого экранируют специальные символы. В нашем случае тот символ, который является признаком конца и начала строки, это либо одинарная кавычка, либо двойная, в зависимости от ситуации. Для экранирования используется обратный слеш .
// Экранируется только ", так как в этой ситуации
// двойные кавычки имеют специальное значение
console.log("Dragon's mother said "No"");
// => Dragon's mother said "No"
Посмотрите внимательно: нам нужно было добавить для двойных кавычек, но не для одинарной (апостроф), потому что сама строка создана с двойными кавычками. Если бы строка создавалась с одинарными кавычками, то символ экранирования нужен был бы перед апострофом, но не перед двойными кавычками.
// не выводится, если после него идет обычный,
// а не специальный символ
console.log("Death is so terribly final");
// => Death is so terribly final
А что, если нужно вывести сам обратный слеш? Точно так же, как и любой другой специальный символ, его надо экранировать самим собой.
console.log("\");
// =>
Вопрос на самопроверку, что выведет этот код?
console.log("\ \ \\ \ '"");
Задание
Напишите программу, которая выведет на экран:
"Khal Drogo's favorite word is "athjahakar""
Программа должна в точности вывести на экран именно эту фразу. Обратите внимание на кавычки в начале и в конце фразы:
"Khal Drogo's favorite word is "athjahakar""
Советы
- Шаблонные строки
16. Экранирующие последовательности
JavaScript: Экранирующие последовательности
Мы хотим показать диалог Матери Драконов со своим ребёнком:
- Are you hungry?
- Aaaarrrgh!
Если вывести на экран строку с таким текстом:
console.log('- Are you hungry?- Aaaarrrgh!');
то получится так:
- Are you hungry?- Aaaarrrgh!
Не то, что мы хотели. Строки расположены друг за другом, а не одна ниже другой. Нам нужно как-то сказать интерпретатору «нажать на энтер» — сделать перевод строки после вопросительного знака. Это можно сделать, используя символ перевода строки: n
.
console.log('- Are you hungry?n- Aaaarrrgh!');
результат:
- Are you hungry?
- Aaaarrrgh!
n
— это специальный символ. В литературе его часто обозначают как LF (Line Feed). Возможно вы сейчас подумали, что это опечатка, ведь здесь мы видим два символа и
n
, но это не так. С точки зрения компьютера — это один невидимый символ перевода строки. Доказательство:
// Мы это не изучали, но вы должны знать правду
// Ниже код, который возвращает длину строки
'a'.length; // 1
'n'.length; // 1 !!!
'nn'.length; // 2 !!!
Почему так сделано? n
— всего лишь способ записать символ перевода строки, но сам перевод строки по своему смыслу – это один символ, правда, невидимый. Именно поэтому и возникла такая задача. Нужно было как-то представить его на клавиатуре. А поскольку количество знаков на клавиатуре ограничено и отдано под самые важные, то все специальные символы реализуются в виде таких обозначений.
Символ перевода строки не является чем-то специфичным для программирования. Все, кто хоть раз печатал на компьютере, использовал перевод строки, нажимая на Enter. Во многих редакторах есть опция, позволяющая включить отображение невидимых символов — с ее помощью можно понять, где они находятся (хотя это всего лишь схематичное отображение, у этих символов нет графического представления, они невидимые):
- Привет!¶ - О, привет!¶ - Как дела?
Устройство, которое выводит соответствующий текст, учитывает этот символ. Например, принтер при встрече с LF протаскивает бумагу вверх на одну строку, а текстовый редактор переносит весь последующий текст ниже, также на одну строку.
n
— это пример экранирующей последовательности (escape sequence). Их ещё называют управляющими конструкциями. Хотя таких символов не один десяток, в программировании часто встречаются всего несколько. Кроме перевода строки, к таким символам относятся табуляция (разрыв, получаемый при нажатии на кнопку Tab) и возврат каретки (только в Windows). Нам, программистам, часто нужно использовать перевод строки n
для правильного форматирования текста.
console.log('Gregor CleganenDunsennPollivernChiswyck');
На экран выведется:
Gregor Clegane
Dunsen
Polliver
Chiswyck
Обратите внимание на следующие моменты:
- Не имеет значения, что стоит перед или после
n
: символ или пустая строка. Перевод будет обнаружен и выполнен в любом случае. - Помните, что строка может содержать один символ или вообще ноль символов. А еще строка может содержать только
n
. Проанализируйте следующий пример:console.log('n'); console.log('Dunsen');
Здесь мы сначала выводим строку «перевод строки», а потом делаем вывод обыкновенной строки. Программа выведет на экран:
Dunsen
Почему перед строкой
Dunsen
появилось две пустые строки, а не одна? Дело в том, что операторconsole.log()
при выводе значения автоматически добавляет в конец символ перевода строки. Таким образом, один перевод строки мы указали явно, передав этот символ экранирующей последовательности аргументом в функцию, а второй перевод строки добавлен самой функцией автоматически.Ещё пример кода:
console.log('Polliver'); console.log('Gregor Clegane'); console.log(); console.log('Chiswyck'); console.log('n'); console.log('Dunsen');
Вывод будет таким:
Polliver Gregor Clegane Chiswyck Dunsen
Сейчас у вас достаточно знаний, чтобы самостоятельно разобраться и понять, почему вывод сформировался именно таким образом.
- Если нам понадобится вывести
n
именно как текст (два отдельных печатных символа), то можно воспользоваться уже известным нам способом экранирования, добавив еще одинв начале. То есть последовательность
\n
отобразится как символыи
n
, идущие друг за другом.
console.log('Joffrey loves using \n');
на экран выйдет:
Joffrey loves using n
Небольшое, но важное замечание про Windows. В Windows для перевода строк по умолчанию используется rn
. Такая комбинация хорошо работает только в Windows, но создаёт проблемы при переносе в другие системы (например, когда в команде разработчиков есть пользователи как Windows, так и Linux). Дело в том, что последовательность rn
имеет разную трактовку в зависимости от выбранной кодировки (рассматривается позже). По этой причине, в среде разработчиков принято всегда использовать n
без r
, так как LF всегда трактуется одинаково и отлично работает в любой системе. Не забудьте настроить ваш редактор на использование n
.
Задание
Напишите программу, которая выводит на экран:
- Did Joffrey agree?
- He did. He also said "I love using n".
При этом программа использует только один console.log()
, но результат на экране должен выглядеть в точности, как показано выше.
Советы
- Обязательно поэкспериментируйте с выводом разных строк на сайте https://repl.it/languages/babel
- История перевода строки
Определения
- Экранирующая последовательность – специальная комбинация символов в тексте. Например,
n
— это перевод строки.
17. Конкатенация
JavaScript: Конкатенация
В веб-разработке программы постоянно оперируют строками. Всё, что мы видим на сайтах, так или иначе представлено в виде текста. Этот текст чаще всего динамический, то есть полученный из разных частей, которые соединяются вместе. Операция соединения строк в программировании называется конкатенацией.
// Оператор такой же, как и при сложении чисел
// но здесь он имеет другой смысл (семантику)
console.log('Dragon' + 'stone');
// => 'Dragonstone'
Склеивание строк всегда происходит в том же порядке, в котором записаны операнды. Левый операнд становится левой частью строки, а правый — правой.
Вот еще несколько примеров:
console.log('Kings' + 'wood'); // => Kingswood
// Обратный порядок слов
console.log('road' + 'Kings'); // => roadKings
// Конкатенировать можно абсолютно любые строки
console.log("King's" + 'Landing'); // => King'sLanding
Как видите, строки можно склеивать, даже если они записаны с разными кавычками.
В последнем примере название города получилось с ошибкой: King’s Landing нужно писать через пробел. Но в наших начальных строках не было пробелов, а пробелы в самом коде слева и справа от символа +
не имеют значения, потому что они не являются частью строк.
Выхода из этой ситуации два:
// Оба способа равнозначны
// Ставим пробел в левой части
console.log("King's " + 'Landing'); // => King's Landing
// Ставим пробел в правой части
console.log("King's" + ' Landing'); // => King's Landing
Пробел — такой же символ, как и другие. Чем больше пробелов, тем шире отступы:
console.log("King's " + ' Landing'); // => King's Landing
console.log("King's " + ' Landing'); // => King's Landing
Задание
Выведите на экран
Winter came for the House of Frey.
используя конкатенацию слов.
Определения
- Конкатенация – операция соединения двух строк. Например,
console.log("King's " + ' Landing');
18. Кодировка
JavaScript: Кодировка
На самом глубоком уровне компьютер оперирует исключительно цифрами 0
и 1
. Это так называемый двоичный код, а единички и нули называются битами, от “binary digit” — «двоичная цифра».
Обычные, привычные нам числа в десятичной системе счисления, закодированы с помощью двоичных чисел:
- 0 ← 0
- 1 ← 1
- 2 ← 10
- 3 ← 11
- 4 ← 100
- 5 ← 101
Но как быть с текстом? Компьютер на самом деле не знает ничего о буквах, знаках пунктуации и прочих текстовых символах. Все эти символы также закодированы числами.
Можно взять английский алфавит и дать каждой букве число, начиная с единицы по порядку:
- a ← 1
- b ← 2
- c ← 3
- d ← 4
- …
- z ← 26
Далее можно научить компьютер понимать эту таблицу и переводить текст в числа и наоборот — числа в текст:
hello
→8
5
12
12
15
7
15
15
4
→good
Подобные таблицы, в которых сопоставляются буквы и числа, называются кодировками. Кроме букв алфавита, в таблицы кодировок входят знаки препинания и другие полезные символы. Вы наверняка сталкивались с кодировками, например, ASCII или UTF-8.
Разные кодировки содержат различное количество символов. Изначально небольших таблиц вроде ASCII было достаточно для большинства задач. Но в ней есть только латинские буквы, несколько простых символов вроде %
и ?
и специальные управляющие символы типа перевода строки.
С распространением компьютеров, разным странам понадобились свои, более широкие таблицы. В том числе для кириллических букв, иероглифов, арабской вязи, дополнительных математических и типографских символов, а впоследствии — даже для эмодзи-смайликов.
Сегодня в большинстве случаев используется один из вариантов юникода — utf-8. Он включает в себя знаки почти всех письменных языков мира. Благодаря этому, письмо, сформированное человеком в Китае на китайском, без проблем можно открыть и увидеть в первозданном виде на компьютере в Финляндии (поймет он его или нет, это уже другой вопрос).
С кодированием текста и кодировками программисты встречаются в своей жизни регулярно. Поддержка юникода у разных языков программирования выполнена на разном уровне. Кроме того, кодировки нужно явно указывать при работе и с базами данных, и с файлами.
Задание
В JavaScript можно «запросить» и вывести на экран любой символ из кодировки ASCII. Например:
console.log(String.fromCharCode(63));
На экран выведется символ с номером 63 — вопросительный знак ?
. Таким способом можно выводить любой символ.
Найдите в интернете таблицу кодов ASCII. Можно использовать запросы типа “ascii codes table” или «коды ascii». Обычно в таких таблицах коды указаны сразу в нескольких системах счисления: десятичной, двоичной, восьмеричной и шестнадцатеричной. Нас интересует десятичный код (dec или decimal).
Используя пример выше и найденную таблицу, выведите на экран символы ~
, ^
и %
(каждый на своей собственной строке).
(Конечно, можно «обмануть» тесты и просто сделать что-то типа console.log('~')
, но так будет совсем неинтересно 🙂
Определения
- Кодировка – набор символов, закодированных с помощью чисел для представления текста в электронном виде.
Переменные в языке JavaScript
Информацию можно помещать в специальные «хранилища» — переменные. Это позволяет переиспользовать уже существующие данные и не дублировать их в разных частях кода. В этом модуле мы разберем как изменять переменные и именовать их, чтобы чтение вашего кода было понятным для любого разработчика. Вы поймете, что придумать название переменной не так-то просто! А еще расскажем, как использовать переменные для упрощения сложных вычислений.
19. Что такое переменная
JavaScript: Что такое переменная
Представьте себе задачу: нам нужно напечатать на экран фразу Father! два раза или даже пять раз. Эту задачу можно решить в лоб:
console.log('Father!');
console.log('Father!');
В простейшем случае так и стоит поступить, но если фраза Father! начнет использоваться чаще, да еще и в разных частях программы, то придется ее везде повторять. Проблемы с таким подходом начнутся тогда, когда понадобится изменить нашу фразу, а такое происходит довольно часто. Нам придется найти все места, где использовалась фраза Father!, и выполнить необходимую замену. А можно поступить по-другому. Вместо копирования нашего выражения достаточно создать переменную с этой фразой.
// greeting - переводится как приветствие
let greeting = 'Father!';
console.log(greeting); // => 'Father!'
console.log(greeting); // => 'Father!'
Переменная указывает на данные, которые были в неё записаны. Благодаря этому, данные можно использовать многократно без необходимости их постоянно дублировать. Сама переменная создается и наполняется данными (инициализируется) с помощью инструкции let greeting = 'Father!'
.
Для имени переменной используется любой набор допустимых символов, к которым относятся буквы английского алфавита, цифры, а также знаки _ и $. При этом цифру нельзя ставить в начале. Имена переменных регистрозависимы, то есть имя hello
и имя heLLo
– это два разных имени, а значит и две переменные. Регистр в JavaScript имеет важное значение, никогда не забывайте про него.
Переменную не обязательно инициализировать данными во время объявления. Иногда бывает нужно ее создать, а наполняться она будет потом:
let greeting;
// Использование
console.log(greeting); // undefined
// Изменение переменной в следующем уроке
Объявленная, но не инициализированная переменная, содержит внутри себя значение undefined
. Это специальное значение, используемое тогда, когда ничего не определено.
Количество создаваемых переменных ничем не ограничено, большие программы содержат десятки и сотни тысяч имен переменных:
let greeting1 = 'Father!';
console.log(greeting1);
console.log(greeting1);
let greeting2 = 'Mother!';
console.log(greeting2);
console.log(greeting2);
Для удобства анализа программы, переменные принято создавать как можно ближе к тому месту, где они используются.
Задание
Создайте переменную с именем motto
и содержимым What Is Dead May Never Die!
. Распечайте содержимое переменной.
Советы
- let
Определения
- Переменная – способ сохранить информацию и дать ей имя для последующего использования в коде.
20. Изменение переменной
JavaScript: Изменение переменной
Само слово “переменная” говорит о том, что ее можно менять. И действительно, с течением времени внутри программы значения переменных могут изменяться.
let greeting = 'Father!';
console.log(greeting);
console.log(greeting);
greeting = 'Mother!';
console.log(greeting);
console.log(greeting);
Имя осталось тем же, но внутри другие данные. Обратите внимание на ключевое различие между объявлением переменной и ее изменением. Ключевое слово let
ставится только при создании переменной, но при изменении оно уже не используется.
Переменные — мощная и в тоже время опасная вещь. Никогда нельзя быть точно уверенным, что записано внутри неё, не проанализировав код, который находится перед переменной. Именно этим занимаются разработчики во время отладки, когда пытаются разобраться почему программа не работает или работает не так, как задумано.
Как вы увидите позже, в JavaScript есть не только переменные. Более того, переменные не так часто используются с целью менять, намного чаще их используют с целью хранить.
Задание
В упражнении определена переменная, внутри которой находится строка. Переопределите значение этой переменной и присвойте ей ту же строку, но в перевернутом виде, т.е. расположите символы первоначальной строки в обратном порядке.
Советы
- Если в редакторе есть запись
// BEGIN
и// END
, то код нужно писать между этими строчками.
Определения
- Переменная – способ сохранить информацию и дать ей имя для последующего использования в коде.
21. Выбор имени переменной
JavaScript: Выбор имени переменной
Представим себе, что программа из прошлого урока выглядит так:
let x = 'Father!';
console.log(x);
console.log(x);
Она по прежнему работает, но в ней изменилось имя переменной на x
. Компьютеру без разницы, как мы называем переменные, это бездушная машина, но вот программистам — нет. Мы гораздо чаще читаем код, чем пишем. Причём не свой, а написанный другими людьми. От качества и понятности имён переменных зависит половина успеха в анализе кода.
Лучше посидеть и придумать название, которое описывает суть, смысл переменной, чем назвать её как попало, а в будущем переделывать. Постарайтесь давать им такие имена, чтобы они были максимально понятны без контекста, без изучения окружающего кода.
Существует общепринятое правило: не используйте транслит для имён, только английский язык. Если вы испытываете сложности с английским, то пользуйтесь переводчиком. Со временем, копаясь в чужом коде, вы сформируете правильные понятия для именования.
Среди разработчиков есть шутка: «самое сложное в программировании — названия переменных и инвалидация кеша». Придумывать названия и правда сложно. Как бы вы назвали переменную, в которой хранится количество неоплаченных заказов от клиентов, имеющих задолженность в предыдущем квартале?
Самопроверка. Придумайте название для переменной, в которой будет храниться «количество братьев и сестёр короля». Запишите его в блокноте или отправьте себе на почту. Не указывайте там ничего, кроме названия переменной. А через несколько уроков мы вернёмся к этой теме 😉
Задание
Создайте переменную, описывающую количество моих братьев, и присвойте ей значение 2. Распечатайте содержимое переменной. Затем сравните свое имя с именем, которое используется в учительском решении.
Советы
- Именование в программировании
- Ошибки в именовании переменных
22. Ошибки при работе с переменными
JavaScript: Ошибки при работе с переменными
Порядок следования инструкций в коде с переменными играет огромное значение. Переменная должна быть определена (но не обязательно инициализированна) до того, как будет использована. Ниже пример ошибки, которую очень часто допускают новички:
// Uncaught ReferenceError: greeting is not defined
console.log(greeting);
let greeting = 'Father!';
Запуск программы с примера выше завершается ошибкой ReferenceError: greeting is not defined. ReferenceError – это ошибка обращения, она означает, что в коде используется имя (говорят идентификатор), которое не определено. Причём в самой ошибке об этом говорят прямо: greeting is not defined, что переводится как greeting не определен. Кроме неправильного порядка определения, в JavaScript встречаются банальные опечатки — как при использовании переменной, так и при её объявлении.
Количество подобных ошибок уменьшается за счет использования правильно настроенного редактора. Такой редактор подсвечивает имена, которые используются без объявления и предупреждает о возможных проблемах.
Еще одна распространенная ошибка — попытаться объявить уже объявленную переменную:
let greeting = 'Father!';
let greeting = 'Father!';
Так делать нельзя. Придётся создать новую переменную.
Задание
Найдите в программе необъявленную переменную и объявите ее, присвоив ей значение ‘Dragon’;
Определения
- Переменная – способ сохранить информацию и дать ей имя для последующего использования в коде.
23. Выражения в определениях
JavaScript: Выражения в определениях
Переменные полезны не только для хранения и переиспользования информации, но и для упрощения сложных вычислений. Давайте рассмотрим пример: нужно перевести евро в рубли через доллары. Подобные конвертации через промежуточную валюту часто делают банки при покупках за рубежом.
Для начала переведем 50 евро в доллары. Допустим, что один евро — 1.25 долларов:
let dollarsCount = 50 * 1.25;
console.log(dollarsCount); // => 62.5
В предыдущем уроке мы записывали в переменную конкретное значение. А здесь let dollarsCount = 50 * 1.25;
справа от знака равно находится выражение. Интерпретатор вычислит результат — 62.5
— и запишет его в переменную. С точки зрения интерпретатора не важно, что перед ним: 62.5
или 50 * 1.25
, для него оба варианта — выражения, которые надо вычислить. И они вычисляются в одно и тоже значение — 62.5
.
Любая строка — выражение. Конкатенация строк — тоже выражение. Когда интерпретатор видит выражение, он обрабатывает его и генерирует результат — значение выражения. Вот несколько примеров выражений, а в комментариях справа от каждого выражения — итоговое значение:
62.5 // 62.5
50 * 1.25 // 62.5
120 / 10 * 2 // 24
'hello' // "hello"
'Good' + 'will' // "Goodwill"
Правила построения кода (грамматика языка) таковы, что в тех местах, где ожидается выражение, можно поставить любое вычисление (не только математическое, но и, например, строковое — как конкатенация), и программа останется работоспособной. По этой причине невозможно описать и показать все случаи использования всех операций.
Программы состоят из множества комбинаций выражений, и понимание этой концепции — один из ключевых шагов на вашем пути.
Основываясь на сказанном выше, подумайте, сработает ли такой код?
let who = "dragon's" + 'mother';
console.log(who);
Запустите его на repl.it и поэкспериментируйте.
Вернемся к нашей валютной программе. Запишем стоимость доллара в рублях как отдельную переменную. Вычислим цену 50 евро в долларах, умножив их на 1.25
. Допустим, что 1 доллар — 60 рублей:
let rublesPerDollar = 60;
let dollarsCount = 50 * 1.25; // 62.5
let rublesCount = dollarsCount * rublesPerDollar; // 3750
console.log(rublesCount);
А теперь давайте добавим к выводу текст с помощью конкатенации:
let rublesPerDollar = 60;
let dollarsCount = 50 * 1.25; // 62.5
let rublesCount = dollarsCount * rublesPerDollar; // 3750
console.log('The price is ' + rublesCount + ' rubles');
The price is 3750 rubles
Любая переменная может быть частью любого выражения. В момент вычисления, вместо имени переменной подставляется её значение.
Интерпретатор вычисляет значение dollarsCount
до того, как эта переменная начнет использоваться в других выражениях. Когда подходит момент использования переменной, Javascript «знает» значение, потому что уже вычислил его.
Задание
Напишите программу, которая берет исходное количество евро, записанное в переменную eurosCount
, переводит евро в доллары и выводит на экран. Затем полученное значение переводит в рубли и выводит на новой строчке.
Пример вывода для 100 евро:
125
7500
Считаем, что:
– 1 евро = 1.25 долларов
– 1 доллар = 60 рублей
24. Переменные и конкатенация
JavaScript: Переменные и конкатенация
Для закрепления предыдущей темы, попробуем использовать переменные с конкатенацией. Синтаксически ничего не меняется: мы умеем конкатенировать (склеивать) две строки:
let what = 'Kings' + 'road';
console.log(what); // => 'Kingsroad'
… а значит сумеем конкатенировать строку и одну переменную, в которой записана строка:
let first = 'Kings';
let what = first + 'road';
console.log(what); // => 'Kingsroad'
… и даже конкатенировать две переменные, в которых записаны строки:
let first = 'Kings';
let last = 'road';
let what = first + last;
console.log(what); // => 'Kingsroad'
Задание
Сайты постоянно посылают письма своим пользователям. Типичная задача — сделать автоматическую отправку персонального письма, где в заголовке будет имя пользователя. Если где-то в базе сайта хранится имя человека в виде строки, то задача генерации заголовка сводится к конкатенации: например, нужно склеить строку Здравствуйте
со строкой, где записано имя.
Напишите программу, которая будет генерировать заголовок и тело письма, используя уже готовые переменные, и выводить получившиеся строки на экран.
Для заголовка используйте переменные firstName
и greeting
, запятую и восклицательный знак. Выведите это на экран в правильном порядке.
Для тела письма используйте переменные info
и intro
, при этом второе предложение должно быть на новой строке.
Результат на экране будет выглядеть так:
Hello, Joffrey! Here is important information about your account security. We couldn't verify you mother's maiden name.
Выполните задание, используя только два console.log()
.
Советы
- Подумайте, с какой строкой и в каком порядке нужно склеивать переменные, чтобы получить такой двустрочный вывод тела письма.
- Помните, что можно создать строку, которая содержит только управляющую последовательность
n
.
25. Именование переменных
JavaScript: Именование переменных
greeting
— пример простого имени, но не все имена так просты. Довольно часто они составные, то есть включают в себя несколько слов. Например, «имя пользователя». В разных языках применяются разные стили кодирования, и имя переменной будет отличаться.
В именовании переменных можно выделить три основных подхода, которые иногда комбинируют друг с другом. Все эти подходы проявляют себя, когда имя переменной состоит из нескольких слов:
- kebab-case — составные части переменной разделяются дефисом. Например:
my-super-var
. - snakecase — для разделения используется подчеркивание. Например: `mysuper_var`.
- CamelCase — каждое слово в переменной пишется с заглавной буквы. Например:
MySuperVar
. - lowerCamelCase — каждое слово в переменной пишется с заглавной буквы, кроме первого. Например:
mySuperVar
.
В Javascript используется CamelCase и его вариация lowerCamelCase, при котором первая буква первого слова — строчная. Именно lowerCamelCase применяется для переменных. Это значит, что имена соединяются друг с другом, при этом все имена кроме первого становятся с заглавной буквы: userName
. С тремя словами это выглядит так: mySuperVariable
.
Задание
Создайте две переменные с именами «первое число» и «второе число» на английском языке, используя lowerCamelCase. Запишите в первую переменную число 11
, во вторую — -100
. Выведите на экран произведение чисел, записанных в получившихся переменных.
Код будет работать с любыми названиями, а наша система всегда проверяет только результат на экране, поэтому выполнение этого задания — под вашу ответственность.
Определения
- Стандарт кодирования – Набор синтакстических и стилистических правил написания кода.
26. Магические числа
JavaScript: Магические числа
Вспомним один из прошлых уроков:
let dollarsCount = 50 * 1.25; // 62.5
let rublesCount = dollarsCount * 60; // 3750
console.log(rublesCount);
С точки зрения профессиональной разработки, такой код «пахнет». Так описывают код, который сложен для понимания. И причина здесь вот в чем: уже сейчас, глядя на число 60
и 1.25
, вы скорее всего задаетесь вопросом: «что это за числа?». А представьте, что будет через месяц! А как его поймет новый программист, не видевший код ранее? В нашем примере контекст восстанавливается благодаря грамотному именованию, но в реальной жизни код значительно сложнее, и догадаться до смысла чисел зачастую невозможно.
Этот «запах» называют Magic Numbers (магические числа). Числа, происхождение которых невозможно понять без глубокого знания происходящего внутри данного участка кода.
Выход из ситуации прост: достаточно создать переменные с правильными именами, как все встанет на свои места.
let dollarsPerEuro = 1.25;
let rublesPerDollar = 60;
let dollarsCount = 50 * dollarsPerEuro; // 62.5
let rublesCount = dollarsCount * rublesPerDollar; // 3750
console.log(rublesCount);
Обратите внимание на следующие детали:
- Именование lowerCamelCase.
- Две новые переменные отделены от последующих вычислений пустой строчкой. Эти переменные имеют смысл и без вычислений, поэтому такое отделение уместно, оно повышает читаемость.
- Получился хорошо именованный и структурированный код, но он длиннее прошлой версии. Так часто бывает, и это нормально. Код должен быть читабельным.
Задание
Вы столкнулись с таким кодом, который выводит на экран общее количество комнат во владении нынешнего короля:
let king = 'King Balon the 6th';
console.log(king + ' has ' + (6 * 17) + ' rooms.');
Как видите, это магические числа: непонятно, что такое 6 и что такое 17. Можно догадаться, если знать историю королевской семьи: каждый новый король получает в наследство все замки от предков и строит новый замок — точную копию родительского.
Эта странная династия просто плодит одинаковые замки…
Избавьтесь от магических чисел, создав новые переменные, и выведите текст на экран.
Получится так:
King Balon the 6th has 102 rooms.
Названия переменных должны передавать смысл чисел, но должны при этом оставаться достаточно короткими и ёмкими для комфортного чтения.
Помните: код будет работать с любыми названиями, а наша система всегда проверяет только результат на экране, поэтому выполнение этого задания — под вашу ответственность.
27. Константы
JavaScript: Константы
Во всем модуле подавляющее большинство примеров кода использовало переменные в качестве имен (псевдонимы) конкретных значений, а не как переменные, которые меняют свое значение со временем.
let dollarsInEuro = 1.25
let rublesInDollar = 60;
let dollarsCount = 50 * dollarsInEuro; // 62.5
let rublesCount = dollarsCount * rublesInDollar; // 3750
console.log(rublesCount);
В программировании принято называть такие имена константами, и многие языки поддерживают константы как конструкцию. JavaScript, как раз, относится к таким языкам, и его стандарты кодирования говорят прямо — если значение не меняется, то мы имеем дело с константой. Перепишем пример выше на использование констант:
const dollarsInEuro = 1.25;
const rublesInDollar = 60;
const euros = 1000;
const dollars = euros * dollarsInEuro; // 1250
const rubles = dollars * rublesInDollar; // 75000
console.log(rubles);
Единственное изменение заключается в том, что ключевое слово let
заменилось на const
, но это только синтаксис. Теперь, если попытаться изменить любую константу, то мы получим сообщение об ошибке. В остальном они используются точно так же, как и переменные.
const pi = 3.14;
pi = 5; // TypeError: Assignment to constant variable.
Зачем такие сложности? Почему бы не оставить только переменные? Даже если бы мы оставили только переменные, то это не отменяет того факта, что они часто использовались бы как константы, более того, код на JavaScript можно и идиоматично писать без использования переменных вообще. Посмотрите на пример из реального кода Хекслета. На текущем этапе вы его врядли поймете, но попробуйте посчитать количество констант и переменных внутри него, вы увидите, что здесь ровно одна переменная, и целая куча констант.
Константы значительно проще для анализа, когда мы видим константу в коде, то нам сразу понятно, что ее значение всегда остается прежним. При использовании констант отсутствует понятие времени. С переменными все не так, мы не можем быть уверены в их значении, приходится анализировать весь код, чтобы понять, как они могли измениться.
Переменные жизненно необходимы только в одном случае (во всех остальных гарантировано можно обойтись без них) – при работе с циклами, до которых мы ещё дойдем.
В дальнейшем мы будем предпочитать константы и использовать переменные только тогда, когда без них никак.
Задание
Создайте константу army
, присвойте ей значение 'the white walkers'
и распечайте её значение на экран.
28. Интерполяция
JavaScript: Интерполяция
В уроке про конкатенацию перед нами стояла задача создать заголовок письма из двух констант и знаков препинания. Вы, скорее всего, решили задачу так:
const firstName = 'Joffrey';
const greeting = 'Hello';
console.log(greeting + ', ' + firstName + '!');
// => 'Hello, Joffrey!'
Это довольно простой случай, но даже здесь нужно приложить усилия, чтобы увидеть, какая в итоге получится строка. Нужно следить за несколькими кавычками и пробелами, и без вглядывания не понять, где что начинается и кончается.
Есть другой, более удобный и изящный способ решения той же задачи — интерполяция. Вот, как это выглядит:
const firstName = 'Joffrey';
const greeting = 'Hello';
// Обратите внимание на ограничители строки, это бектики
// Интерполяция не работает с одинарными и двойными кавычками
console.log(`${greeting}, ${firstName}!`);
// => 'Hello, Joffrey!'
Мы просто создали одну строку и «вставили» в неё в нужные места константы с помощью знака доллара и фигурных скобок ${ }
. Получился как будто бланк, куда внесены нужные значения. И нам не нужно больше заботиться об отдельных строках для знаков препинания и пробелов — все эти символы просто записаны в этой строке-шаблоне.
В одной строке можно делать сколько угодно подобных блоков.
Интерполяция работает только со строками в бэктиках. Это символ `.
Почти во всех языках интерполяция предпочтительнее конкатенации для объединения строк. Строка при этом получается склеенная, и внутри неё хорошо просматриваются пробелы и другие символы. Во-первых, интерполяция позволяет не путать строки с числами (из-за знака +), а во-вторых, так гораздо проще (после некоторой практики) понимать строку целиком.
Задание
Выведите на экран строку Do you want to eat, <name>?
, где вместо <name>
должна использоваться константа stark
. Вывод должен получиться таким:
Do you want to eat, Arya?
Советы
- Шаблонные строки
Определения
- Интерполяция – способ соединения строк через вставку значений переменных в строку-шаблон с помощью фигурных скобок. Например,
`Hi, ${name}!`
.
29. Извлечение символов из строки
JavaScript: Извлечение символов из строки
Иногда нужно получить один символ из строки. Например, если сайт знает имя и фамилию пользователя, и в какой-то момент требуется вывести эту информацию в формате A. Ivanov, то нужно взять первый символ из имени.
const firstName = 'Tirion';
console.log(firstName[0]); // => 'T'
Квадратные скобки с цифрой — это специальный синтаксис извлечения символа из строки. Цифра называется индексом — позицией символа внутри строки. Индексы начинаются с 0 почти во всех языках программирования — поэтому, чтобы получить первый символ, нужно указать индекс 0
. Индекс последнего элемента равен длине строки минус единица:
// Длина строки 6, поэтому последний индекс — это 5
const firstName = 'Tirion';
console.log(firstName[5]); // => 'n'
// Вопрос на самопроверку. Что выведет этот код?
const magic = 'nyou'
console.log(magic[1]); // => ?
Индексом может быть не только конкретное число, но и значение переменной. Вот пример, который приведёт к тому же результату — выводу на экран символа T, но индекс внутри квадратных скобок записан не числом, а константой:
const firstName = 'Tirion';
const index = 0;
console.log(firstName[index]); // => 'T'
Технически можно указать индекс и за пределами слова. Для нашего примера — это числа от 6 и выше. JavaScript не считает такое поведение ошибкой. Обращение по несуществующему индексу вернет значение undefined
.
const firstName = 'Tirion';
console.log(firstName[10]); // => undefined
Задание
Выведите на экран последний символ строки, находящейся в переменной name
Типы данных JavaScript
JavaScript — язык со слабой типизацией и неизменяемыми примитивными типами данных. Что произойдет, если мы попробуем умножить число на строку? Каким образом JavaScript понимает, что за тип данных перед ним? И что делает JavaScript, когда видит несоответствие типов? Ответы на эти вопросы вы найдете в текущем модуле.
30. Типы данных
JavaScript: Типы данных
Что произойдет, если мы попробуем умножить число на строку? JavaScript вернет NaN
(не число) — то самое значение. Оно возникает там, где вместе используются несовместимые значения. В данном случае число и строка:
3 * 'Dracarys'; // NaN
Внутри высокоуровневых языков программирования данные разделяются по типам. Любая строка относится к типу String, а числа — к типу Number и BigInt (очень большие числа). Зачем нужны типы? Для защиты программы от трудноотловимых ошибок. Типы определяют две вещи:
- Возможные (допустимые) значения. Например, числа в JavaScript делятся на два типа: Number и BigInt. Первые — это все числа ниже определенного порога (его можно посмотреть), вторые — выше. Такое разделение связано с техническими особенностями работы аппаратуры.
- Набор операций, которые можно выполнять над этим типом. Например, операция умножения имеет смысл для типа «целые числа». Но не имеет смысла для типа «строки»: умножать слово «мама» на слово «блокнот» — бессмыслица.
JavaScript ведет себя двояко, когда встречается с нарушениями. В некоторых ситуациях, он ругается на недопустимость операции и завершается с ошибкой. В других — программа продолжает работать. В этом случае недопустимая операция возвращает что-то похожее на NaN
, как в примере выше.
Каким образом JavaScript понимает, что за тип данных перед ним? Достаточно просто. Любое значение где-то инициализируется и, в зависимости от способа инициализации, становится понятно, что перед нами. Например, числа — это просто числа без дополнительных символов, кроме точки для рациональных чисел. А вот строки всегда ограничены специальными символами (в JavaScript три разных варианта). Например, такое значение '234'
– строка, несмотря на то, что внутри нее записаны цифры.
JavaScript позволяет узнать тип данных с помощью оператора typeof
:
typeof 3; // 'number'
typeof 'Game'; // 'string'
Типы данных Number, BigInt и String — это примитивные типы, они встроены в сам язык. Помимо них, есть еще несколько примитивных типов данных, которые мы изучим позже, например, Boolean. Кроме того, в JavaScript встроен также составной (набор данных) тип Object (а на его базе массивы, даты и другие). Они рассматриваются на Хекслете.
По-английски строки в программировании называются “strings”, а строчки текстовых файлов — “lines”. Например, в коде выше есть две строчки (lines), но только одна строка (strings). В русском иногда может быть путаница, поэтому во всех уроках мы будем говорить строка для обозначения типа данных «строка», и строчка для обозначения строчек (lines) в файлах.
Задание
Выведите на экран число -0.304
.
Советы
- Литерал
- Статья о дробных числах
Определения
- Тип данных – множество данных в коде (разновидность информации). Тип определяет, что можно делать с элементами конкретного множества. Например, целые числа, рациональные числа, строки — это разные типы данных.
- Примитивные типы данных – простые типы, встроенные в сам язык программирования.
- Строка (string) – тип данных, описывающий набор символов (иными словами — текст); например,
'text'
или"text"
.
31. undefined
JavaScript: undefined
Объявление переменных возможно и без указания конкретного значения. Что будет выведено на экран если её распечатать?
let name;
console.log(name); // ?
На экране появится undefined
, специальное значение особого типа, которое означает отсутствие значения. Undefined активно используется самим JavaScript в самых разных ситуациях, например, при обращении к несуществующему символу строки:
const name = 'Arya';
console.log(name[8]);
Смысл (семантика) значения undefined
именно в том, что значения нет. Однако, ничто не мешает написать такой код:
let key = undefined;
И хотя интерпретатор позволяет такое сделать, это нарушение семантики значения undefined
, ведь в этом коде выполняется присваивание, а значит — подставляется значение.
JavaScript — один из немногих языков, в которых в явном виде присутствует понятие undefined
. В остальных языках его роль выполняет значение null
, которое, кстати, тоже есть в JavaScript.
Вопрос на самопроверку. Почему нельзя объявить константу без указания значения?
Задание
Выведите на экран значение undefined
, не указывая его явно (через присваивание или передав напрямую в console.log()
). Если не догадаетесь, как это сделать, подсмотрите решение учителя.
32. Неизменяемость примитивных типов
JavaScript: Неизменяемость примитивных типов
Что произойдет, если попытаться изменить символ в строке?
let firstName = 'Alexander';
// Код выполнится без ошибок
firstName[0] = 'B';
console.log(firstName); // => "Alexander"
Как это ни странно, но значение переменной firstName
останется прежним, хотя код выполнится без ошибок. Так происходит из-за неизменяемости примитивных типов в JavaScript — язык не дает никакой физической возможности поменять строку. Неизменяемость примитивных типов важна по многим причинам, ключевая — производительность. Но что делать, если нам действительно нужно её изменить? Для этого и существуют переменные:
let firstName = 'Alexander';
// Код выполнится без ошибок
firstName = 'Blexander'
console.log(firstName); // => "Blexander"
Есть большая разница между изменением значения переменной и изменением самого значения. Примитивные типы в JavaScript поменять нельзя (а вот составные можно, подробнее о них уже на самом Хекслете), а заменить значение переменной — без проблем.
Задание
Вам даны три константы с фамилиями разных людей. Составьте и выведите на экран слово из символов в таком порядке:
- Третий символ из первой строки;
- Второй символ из второй строки;
- Четвертый символ из третьей строки;
- Пятый символ из второй строки;
- Третий символ из второй строки;
Попробуйте использовать интерполяцию: внутри фигурных скобок можно помещать не только целые переменные, но и отдельные символы с помощью квадратных скобок.
33. Слабая типизация
JavaScript: Слабая типизация
Нам известно про два разных типа данных: числа и строки. Мы, например, можем складывать числа, потому что операция сложения — это операция для типа «числа».
А что, если применить эту операцию не к двум числам, а к числу и строке?
console.log(1 + '7'); // => '17'
Несмотря на то, что '7'
— это строка, а не число, интерпретатор JavaScript выдал ответ 17
, как если бы мы складывали две строки. Когда JavaScript видит несоответствие типов, он сам пытается преобразовать информацию. В данном случае он преобразовал число 1
в строку '1'
, а потом спокойно сделал конкатенацию '1'
и '7'
.
Не все языки так делают. JavaScript — это язык со слабой типизацией. Он знает о существовании разных типов (числа, строки и др.), но относится к их использованию не очень строго, пытаясь преобразовывать информацию, когда это кажется разумным. Иногда JavaScript даже доходит до крайностей. Большинство выражений, не работающих в других языках, прекрасно работают в JavaScript. Попробуйте выполнить любую арифметическую операцию (кроме сложения), подставив туда строки или любые другие типы данных (кроме ситуации, когда оба операнда – это числа или строки, содержащие только число) — вы увидите, что они всегда будут работать и возвращать NaN
, что довольно логично.
const result = 'one' * 'two';
console.log(result); // => NaN
В языках со строгой типизацией сложить число со строкой не получится.
JavaScript был создан для интернета, а в интернете вся информация — это строки. Даже когда вы вводите на сайте номер телефона или год рождения, на сервер эта информация поступает не как числа, а как строки. Поэтому авторы языка решили, что автоматически преобразовывать типы — правильно и удобно.
Такое автоматическое неявное преобразование типов с одной стороны и правда удобно. Но на практике это свойство языка создает множество ошибок и проблем, которые трудно найти. Код может иногда работать, а иногда не работать — в зависимости от того, «повезло» ли в конкретном случае с автоматическим преобразованием. Программист это заметит не сразу.
В дальнейших заданиях вы будете встречаться с таким поведением не раз. Часто будет возникать вопрос «почему мой код работает не так, как я ожидаю?».
Слабая типизация красной нитью проходит сквозь всю разработку на Javascript.
Задание
Выведите на экран результат выражения: 7 - (-8 - -2)
. Попробуйте сделать число 7 не числом, а строкой. Поэкспериментируйте с другими числами тоже.
Вызов функций
Для выражения любой произвольной операции в программировании существует понятие «функция». Функции — кирпичики, из которых программисты строят системы. В этом модуле мы научимся пользоваться уже созданными функциями. Посмотрим на сигнатуру функции в документации и разберемся, как её использовать. Познакомимся со стандартными библиотеками, которые хранят тысячи функций. Все функции невозможно выучить, но каждый программист должен знать, где искать документацию по ним.
34. Функции и их вызов
JavaScript: Функции и их вызов
Сложение, конкатенация, нахождение остатка от деления и остальные рассмотренные операции – все это довольно базовые возможности языков программирования. Математика не ограничена арифметикой, кроме нее есть и множество других разделов со своими операциями, например, геометрия. То же самое касается и строк: их можно переворачивать, менять регистр букв, удалять лишние символы — и это только самое простое. И, наконец, на более высоком уровне есть прикладная логика конкретного приложения. Программы списывают деньги, считают налоги, формируют отчеты. Количество подобных операций бесконечно и индивидуально для каждой программы. И все они должны быть как-то выражены в коде.
Для выражения любой произвольной операции в программировании существует понятие функция. Функции бывают как встроенные, так и добавленные программистом. С одной встроенной функцией мы уже знакомы, это log()
в вызове console.log()
.
Функции — одна из ключевых конструкций в программировании, без них невозможно сделать практически ничего. Знакомство с ними мы начинаем как можно раньше, так как весь дальнейший материал оперирует функциями по максимуму. Сначала мы научимся пользоваться уже созданными функциями, а уже потом научимся создавать свои собственные.
Начнем с простых функций для работы над строками. Ниже пример вызова функции reverse()
, которая переворачивает строку:
// reverse это функция
import { reverse } from 'hexlet-basics/string';
// Вызов функции reverse с аргументом 'Hello!'
const result = reverse('Hello!');
console.log(result); // => '!olleH'
Лирическое отступление. Первая строчка в этом коде – импорт функции из другого модуля. Импорты и модули изучаются на Хекслете, здесь же они будут присутствовать в задании «как есть», так как без них невозможно использовать функции, определенные в других файлах. Не заморачивайтесь, если вам не понятен смысл этого действия, подробнее о нем можно узнать из курса введение в программирование.
Мы создали константу result
и указали интерпретатору записать в неё результат, возвращаемый функцией reverse()
при её вызове. В этом смысле функции подобны операциям – они всегда возвращают результат своей работы. Запись reverse('Hello!')
означает, что вызывается функция с именем reverse
, в которую был передан аргумент (или параметр) 'Hello!'
. Аргументы нужны функциям для работы так же, как операторам нужны операнды. Функция reverse()
переворачивает именно ту строку, которая передается ей в аргументах.
Вызов функции всегда обозначается скобками ()
, идущими сразу за именем функции. В скобках может быть любое количество аргументов, а иногда — вообще ни одного. Количество зависит от используемой функции, например, функция pow()
принимает на вход два аргумента и возводит число, переданное первым параметром, в степень, переданную вторым параметром.
import { pow } from 'hexlet-basics/math';
const result = pow(2, 3); // 2 * 2 * 2
console.log(result); // => 8
По большому счету, операторы и функции — это одно и то же. Ключевая разница только в том, как они записываются. Если представить сложение как функцию, то она будет выглядеть так:
3 + 5; // 8
sum(3, 5); // 8
// или даже так
// В js такой синтаксис невозможен, но есть языки (lisp)
// где оно выглядит очень похоже (посмотрите курс по Racket)
+(3, 5);
Вопрос на самопроверку. Как узнать, что возвращает вызов console.log
? Проверьте.
Задание
В 7 королевствах жил один человек — Сэм Тарли, который занимался картографией. Он имел доступ к компьютерам и умел программировать, поэтому написал для себя функцию calculateDistance()
. Функция высчитывает расстояние между городами в лигах. Она принимает два строковых параметра (названия городов) и возвращает число (расстояние между ними).
Вот пример использования, где на экран выводится расстояние между городами Lannisport и Bayasabhad:
import { calculateDistance } from 'hexlet-basics/got';
const distance = calculateDistance('Lannisport', 'Bayasabhad');
console.log(distance);
Первая строчка — это специальный код, подключающий функцию calculateDistance()
в вашу программу. Благодаря ей вы можете запускать функцию, не видя её содержимое. Это обычное дело в программировании: вы знаете, что делает функция и как ей пользоваться, но не знаете, как именно она работает внутри.
Воспользуйтесь функцией calculateDistance()
и выведите на экран расстояние между городами Qarth и Vaes Dothrak. Не копируйте пример, а создайте константу с другим именем и напишите код с нуля самостоятельно.
Определения
- Функция – операция, способная принимать данные и возвращать результат; функция вызывается так:
foo()
. - Аргумент – информация, которую функция получает при вызове. Например,
foo(42)
— передача аргумента42
функцииfoo()
35. Математические функции JavaScript
JavaScript: Математические функции JavaScript
Объяснение функций в JavaScript немного осложняется структурой языка. Изначально он появился в браузерах и имел сильно ограниченные возможности по отношению к языкам общего назначения. Со временем все изменилось — JavaScript стал мощным языком, захватившим клиентскую разработку и активно использующимся на сервере. Однако наследие осталось, так как нужно поддерживать обратную совместимость. Поэтому, в некоторых местах есть несостыковки, которые нельзя объяснить системой: на них можно только махнуть рукой и сказать: «Так исторически сложилось».
К подобным «местам» относятся математические функции. В предыдущем задании мы использовали самописную функцию pow()
(но сами ее не писали), а теперь давайте рассмотрим её версию, встроенную в сам язык.
Math.pow(2, 3); // 8
Что такое Math
? Технически — это объект, доступный из любого места программы, но перед тем, как говорить об объектах, нужно проделать очень большой путь. Сейчас достаточно запомнить, что функции для математических операций вызываются через Math.
. Наличие этой приставки никак не влияет на понятие функции, которое мы рассмотрели ранее и будем рассматривать позже.
Полный список функций Math доступен в документации. О том, как её правильно читать, мы поговорим далее.
Задание
Сэм рассчитывал количество вражеских солдат, находящихся в башнях-близнецах, и случайно перепутал знаки операций. Его расчеты оказались верными, не считая того, что результат получился отрицательным. Помогите Сэму найти модуль числа бойцов. Выведите на экран модуль числа, находящегося в переменной soldiersCount
, используя функцию Math.abs(). Эта функция возвращает модуль переданного числа:
Math.abs(-3); // 3
Определения
- Функция – операция, способная принимать данные и возвращать результат; функция вызывается так:
foo()
. - Аргумент – информация, которую функция получает при вызове. Например,
foo(42)
— передача аргумента42
функцииfoo()
36. Сигнатура функции
JavaScript: Сигнатура функции
Функция Math.pow()
(напомним, что функция здесь pow()
, а Math
– объект, о котором мы ничего не знаем), возводящая число в какую-нибудь степень, принимает два параметра: какое число возводить и в какую степень возводить. Если вызывать pow()
без параметров, то вернется NaN
. Функция честно пытается выполнить возведение в степень, но если значение не передано, то интерпретатор автоматически передает ей undefined
. JavaScript заставляет программистов быть более аккуратным, чем остальные языки. В большинстве языков, если передать в функцию меньше параметров, чем она ожидает, то возникнет ошибка, — но только не в JavaScript. NaN
вернется и при передаче любых не числовых значений:
const result = Math.pow(2, 'boom');
console.log(result); // => NaN
Другая функция может иметь другое число параметров и другие типы параметров. Например, может существовать функция, которая принимает три параметра: число, строку и ещё одно число.
Откуда мы знаем, сколько каких параметров нужно функции Math.pow()
и какого типа будет «возврат»? Мы заглянули в сигнатуру этой функции. Сигнатура определяет входные параметры и их типы, а также выходной параметр и его тип. Про функцию Math.pow()
можно почитать в документации. В разделе «Синтаксис» есть такой текст:
Math.pow(base, exponent)
Параметры
base
Основание степени.
exponent
Показатель степени, в которую возводится основание base.
Это сигнатура функции и короткое пояснение на русском языке. Документация позволяет понять, сколько аргументов у функции и какого они типа, возвращает ли что-то функция и если да, то какого типа возвращаемое значение.
Задание
Теперь ваша очередь посмотреть на сигнатуру функции в документации и разобраться, как её использовать. Можете читать документацию на русском языке, но программист обязан уметь читать документацию на английском. Используйте словари или переводчики при необходимости. Лучше сразу привыкать и подтягивать навыки чтения на английском, иначе будут сложности в будущем.
В Math
есть функция ceil()
. Изучите её документацию.
Напишите программу, которая использует функцию Math.ceil()
с константой number
и выводит результат на экран.
Определения
- Сигнатура функции – формальное описание типов аргументов и типа возвращаемого значения функции.
37. Аргументы по умолчанию
JavaScript: Аргументы по умолчанию
Рассмотрим функцию round()
, написанную для этого урока. Она округляет переданное число:
import { round } from 'hexlet-basics/math';
const result = round(10.25, 0); // 10
Мы передали в неё два аргумента: число и точность округления. 0
означает, что округление будет до целого значения, то есть дробная часть просто отбрасывается.
Чаще всего нужно округлять именно до целого числа (а не до десятых, например), поэтому создатели функции round()
сделали второй аргумент необязательным и задали ему внутри функции значение по умолчанию 0
. Значит, можно не указывать второй аргумент, а результат будет тем же:
const result = round(10.25); // 10
Если нужна другая точность, то можно передать аргумент:
// округление до одного знака после запятой
const result = round(10.25, 1); // 10.3
Если функция в JavaScript принимает необязательные аргументы, то они всегда стоят после обязательных. Их количество может быть любым (это зависит от самой функции), но они всегда идут рядом и в конце списка аргументов.
Задание
Округлите число, записанное в переменную number
, до двух знаков после запятой и выведите результат на экран.
Определения
- Аргумент по умолчанию – необязательный аргумент функции.
38. Функции с переменным числом параметров
JavaScript: Функции с переменным числом параметров
Интересная особенность некоторых функций — принимать переменное число аргументов. Речь не идет о значениях по умолчанию. Посмотрите на этот пример:
Math.max(1, 10, 3); // 10
Функция Math.max()
находит максимальное значение среди переданных аргументов. Как вы думаете, сколько аргументов она ожидает на вход? Если открыть документацию этой функции, то мы увидим странную конструкцию:
Math.max([value1[, value2[, ...]]])
Такая запись говорит о том, что эта функция принимает на вход любое число аргументов (и даже может быть вызвана без них). Необязательность передаваемых аргументов описывается скобками [], точно так же описываются и опциональные параметры, у которых есть значения по умолчанию. Возможность передачи любого числа параметров зашита в этой части [, …].
Math.max(1, -3, 2, 3, 2); // 3
Задание
Посчитайте программно (а не в голове) минимальное число среди 3, 10, 22, -3, 0 — и выведите его на экран. Воспользуйтесь функцией Math.min()
, которая работает аналогично Math.max()
.
Определения
- Аргумент по умолчанию – необязательный аргумент функции.
39. Вызов функции — выражение
JavaScript: Вызов функции — выражение
Выражение — это код, который при выполнении программы вычисляется в значение.
Какие из этих фрагментов кода являются выражениями?
42
10 * 45
'Kings ' + 'road'
calculateDistance('Lannisport', 'Bayasabhad')
Числа и математические операции — наверное, самые простые варианты. Выражение 42
вычислится в значение 42
, выражение 10 * 45
— в значение 450
.
Конкатенация строк — тоже выражение, которое вычислится в соответствующее значение (новую строку).
Но вот четвёртый вариант… Это тоже выражение! Мощность и гибкость языка программирования во многом возможна благодаря тому, что вызов функции — это выражение.
Посмотрите на пример:
const distance = calculateDistance('Lannisport', 'Bayasabhad');
В константу distance
записывается результат вычисления выражения. В отличие от операций (например, 10 + 12
), где явно видно, какое вычисление производится, в функциях само вычисление скрыто от нас, и мы видим только результат. Поэтому говорят, что функция «возвращает» значение. Можно применить эту терминологию и к обычным операциям. Например, сказать, что конкатенация двух строк возвращает новую строку.
Что является выражением, а что нет? Сейчас может казаться, что это одна из скучных деталей из учебника по программированию. Но это действительно важный вопрос. Всё, что работает как выражение, может быть использовано в других выражениях, а также во всех местах, где на вход ожидаются выражения. Распознавать выражения в коде — важный навык, необходимый программисту каждый день.
Допустим, у нас есть функция numberOfKnights()
, которая принимает название замка в королевстве и возвращает количество рыцарей в этом замке. Зная, что вызов функции — выражение, можно допустить, что такой код будет работать:
const result = 4 + numberOfKnights('Winterfell');
Почему? Сложение — это выражение, а значит его операндами могут быть другие выражения: выражения 4
и выражения numberOfKnights('Winterfell')
. В итоге получится 4 + какое-то число.
Значит, и такой код будет работать:
const result = numberOfKnights('Winterfell') + numberOfKnights('Oldtown');
Здесь два разных вызова функций, но каждый вызов — выражение, поэтому в итоге получится сложение двух значений — двух чисел (количества рыцарей замка Winterfell и количества рыцарей замка Oldtown).
Как вы увидите в следующих уроках, выражения могут комбинироваться друг с другом в самых причудливых формах. Умея распознавать выражения, вы сможете самостоятельно придумывать новые варианты использования, даже никогда не видев их до этого. В этом и заключается секрет изучения программирования, вместо заучивания конкретных способов работы, мы изучаем принципы по которым работает код.
Задание
Арья собирается в путешествие из Винтерфела в Орлиное гнездо, чтобы навестить Лизу Аррен, но по пути ей нужно заехать к Фреям для совершения акта возмездия. Ей нужно рассчитать общую длину маршрута.
К сожалению, функция calculateDistance()
может вычислять расстояние только между двумя точками. Поэтому придется сначала узнать расстояние от Винтерфелла до замка Фреев, а потом расстояние до Орлиного гнезда.
Названия замков на английском языке:
- Винтерфелл —
Winterfell
- Близнецы (Замок Фреев) —
The Twins
- Орлиное гнездо —
The Eyrie
Выведите на экран полную длину маршрута Арьи. Напомним, что функция calculateDistance()
принимает два аргумента и возвращает число.
40. Аргументы как выражения
JavaScript: Аргументы как выражения
Вспомним код:
import { round } from 'hexlet-basics/math';
const result = round(10.25); // 10
Функция round()
вызывается с аргументом 10.25
.
Мы выяснили, что выражения вычисляются в значения. То есть с точки зрения JavaScript, значения и выражения — это что-то схожее. Поэтому любые значения в программе технически можно заменить выражениями.
При вызове функции можно передать в неё аргументом выражение:
const result = round(8 + 2.25); // 10
Результат будет таким же, как в первом примере, потому что выражение 8 + 2.25
вычислится в значение 10.25
, и с таким аргументом произойдет вызов round()
.
Более того, можно использовать переменные вперемешку со значениями и другими выражениями:
const number = 1.25;
const result = round(number + 7 + 2); // 10
Естественно, это работает не только с числами, а с любыми значениями и выражениями. Например, со строками.
console.log('D' + 'ragon'); // => 'Dragon'
Давайте подытожим. Взгляните на несколько примеров из текущего урока:
// простые вызовы
round(10.25); // 10
console.log('Dragon'); // => 'Dragon'
// выражения в аргументах
round(8 + 2.25); // 10
console.log('Dra' + 'gon'); // => 'Dragon'
// выражения с переменными в аргументах
number = 1.25;
round(number + 7 + 2); // 10
const text = 'Dr';
console.log(text + 'ag' + 'on'); // => 'Dragon'
Заметьте схожесть: во всех вызовах в функции передается какая-то информация, но иногда это простое, «вычисленное» значение (10.25
, 'Dragon'
), а иногда составное выражение — «не вычисленное» значение (8 + 2.25
, number + 7 + 2
, text + 'ag' + 'on'
и т.д.). При этом во всех примерах передаётся один аргумент. Когда аргументов несколько, они обязательно разделяются запятыми.
Задание
Вам доступна функция calculateDistanceBetweenTowns()
. Она принимает один аргумент, в котором должны содержаться названия двух городов через дефис. В ответ она возвращает расстояние между этими городами. Вот пример использования:
const distance = calculateDistanceBetweenTowns('Lannisport-Bayasabhad');
Напишите программу, которая использует функцию calculateDistanceBetweenTowns()
и выводит на экран расстояние между городами, записанными в переменные from
и to
.
41. Вызов функций в аргументах функций
JavaScript: Вызов функций в аргументах функций
Продолжаем тему выражений. Если вызов функции — выражение, значит мы можем вызывать одну функцию в аргументах другой функции (…в вызов функции в вызов функции в вызов… а-а-а!).
import { round } from 'hexlet-basics/math';
const number = -100.234203;
const result = round(Math.abs(number), 2); // 100.23
Мы вызываем функцию round()
и передаем ей два аргумента:
- результат вызова функции
abs()
с аргументомnumber
- число
2
Можно сделать то же самое, но с промежуточными шагами:
const number = -100.234203;
const module = Math.abs(number); // 100.234203
const result = round(module, 2); // 100.23
Какой вариант предпочтительнее? Если вычисление совсем простое и неглубокое (не больше одного вложения функции), то можно смело вкладывать вызов в вызов. В остальных ситуациях предпочтительно разбивать вызовы на промежуточные вычисления.
Причины все те же. Чтение такого кода значительно легче. Во-первых, промежуточные переменные своими названиями отражают суть операций. Во-вторых, такой код легче отлаживать, а промежуточные данные проще исследовать. И в-третьих, глубокие вложенные вызовы сложно читать. В продвинутых языках подобная проблема решается механизмом типа композиции функций, но в JavaScript, к сожалению, подобного нет.
Давайте взглянем на код и попробуем ответить на вопрос: что в каком порядке будет вычисляться?
const number = -100.234203;
const result = round(Math.abs(number), round(2.43));
JavaScript, как и большинство традиционных языков, является языком с жадными вычислениями (в других языках бывают ленивые вычисления). JavaScript пытается вычислить сначала максимально глубокий уровень вызова, затем менее глубокий, и так двигается снизу вверх пока не вычислит всё.
В нашем примере сначала будут вычислены аргументы, а затем получившиеся данные попадут в вызов round()
.
Ситуация с вложенными вызовами функций часто вводит новичков в ступор. Здесь нет никакой магии, нужно просто чуть больше тренировок. Хорошее упражнение — расписывать процесс по шагам на бумажке, симулируя действия компьютера. Вот так:
round(Math.abs(number), round(2.43));
round(100.234203, round(2.43));
round(100.234203, 2);
100.23;
Задание
Для построения генеалогического дерева семьи Старков Сэм написал функцию getParentFor()
, которая возвращает имя родителя, если передать ей первым параметром полное имя ребенка. Вторым параметром функция принимает строчку 'father'
или 'mother
‘. Так функция понимает, кого из родителей возвращать. По умолчанию параметр равен 'mother'
. То есть, если нужно узнать имя матери, то можно не передавать специально 'mother'
, а передать лишь один параметр — имя ребенка.
Напишите программу, которая выводит на экран имя деда Джоффри по материнской линии. Полное имя Джоффри на английском: Joffrey Baratheon.
const mother = getParentFor('Joffrey Baratheon');
console.log(mother); // => 'Cersei Lannister'
Советы
- Злые однострочники
42. Детерминированность
JavaScript: Детерминированность
Независимо от того, какой язык программирования используется, функции внутри него обладают некоторыми фундаментальными свойствами. Зная эти свойства, легче прогнозировать поведение функций, способы их тестирования и место их использования. К таким свойствам относится детерминированность. Функция называется детерминированной тогда, когда для одних и тех же входных аргументов она возвращает один и тот же результат. Например, функция, переворачивающая строку, детерминированная.
import { reverse } from 'hexlet-basics/string';
reverse('cat'); // tac
reverse('cat'); // tac
Сколько бы раз мы её не вызывали, передавая туда значение 'cat'
, она всегда вернет 'tac'
(хотя технически можно написать её и по другому, но смысла в этом никакого, а проблем доставит). В свою очередь функция, возвращающая случайное число, не является детерминированной, так как у одного и того же входа (даже если он пустой, то есть аргументы не принимаются) мы получим всегда разный результат. Насколько он разный – не важно, даже если хотя бы один из миллиона вызовов вернет что-то другое, эта функция автоматически считается недетерминированной.
import { rand } from 'hexlet-basics/math';
rand(); // 0.234111
rand(); // 0.932342
Зачем это нужно знать? Детерминированность серьезно влияет на многие аспекты. Детерминированные функции удобны в работе, их легко оптимизировать, легко тестировать. Если есть возможность сделать функцию детерминированной, не раздумывая делайте её такой.
Задание
Санса хочет повесить на свою дверь просьбу о том, чтобы никто не входил без стука. Она попросила Сэма распечатать лист с надписью “СТУЧАТЬ!”. Помогите Сэму перевести слово в верхний регистр, используя функцию toUpperCase()
, которая принимает на вход строку и возвращает такую же, но со всеми буквами в верхнем регистре. Распечатайте на экран текст, записанный в константу text
, не забыв перевести его в верхний регистр.
import { toUpperCase } from 'hexlet-basics/string';
console.log(toUpperCase('hello')); // => HELLO
Как вы думаете, что вернет функция toUpperCase()
, если передать ей на вход строку HELLO?
Советы
- Детерминированные функции
Определения
- Детерминированность функции – Для одного и того же входа, всегда один и тот же выход
43. Побочные эффекты
JavaScript: Побочные эффекты
console.log()
– обычная функция. Внимание, вопрос: что возвращает функция console.log()
?
Ответ: что бы она не возвращала, это значение никак не используется.
console.log()
выводит что-то на экран, но это не возврат значения — это просто какое-то действие, которое выполняет функция.
Вывод на экран и возврат значения из функции — разные и независимые операции. Технически вывод на экран равносилен записи в файл (немного особый, но всё-таки файл). Для понимания этой темы необходимо немного разобраться в устройстве операционных систем, что крайне важно для программистов.
С точки зрения программы вывод на экран — это так называемый побочный эффект. Побочным эффектом называют действия, которые взаимодействуют с внешним окружением (средой выполнения). К таким действиям относятся любые сетевые взаимодействия, взаимодействие с файловой системой (чтение и запись файлов), вывод информации на экран, печать на принтере и так далее.
Побочные эффекты — один из основных источников проблем и ошибок в программных системах. Код с побочными эффектами сложен в тестировании и ненадежен. При этом без побочных эффектов программирование не имеет смысла. Без них было бы невозможно получить результат работы программы (записать в базу, вывести на экран, отправить по сети и так далее).
Понимание принципов работы с побочными эффектами очень сильно влияет на стиль программирования и способность строить качественные программы. Эта тема полностью раскроется в курсах на Хекслете.
Вопрос для самопроверки. Можно ли определить наличие побочных эффектов внутри функции, опираясь только на её возврат?
Задание
Это задание не связано напрямую с уроком
Выведите на экран значение константы text
после обработки функциями reverse()
и toLowerCase()
.
Зависит ли результат функции от порядка применения функций reverse()
и toLowerCase()
?
Советы
- Побочный эффект
Определения
- Побочный эффект – действие, которое изменяет внешнее окружение (среду выполнения). Например, вывод на экран или отправка письма.
- Чистые функции – Детерминированные функции без побочных эффектов
44. Стандартная библиотека
JavaScript: Стандартная библиотека
JavaScript, как и любой другой язык, поставляется с набором полезных функций. Все вместе они составляют так называемую стандартную библиотеку. В неё обычно входят тысячи функций, которые невозможно выучить — этого и не нужно делать. Подразумевается, что любой программист знает, где искать документацию по ним и примерно представляет себе, чего он хочет достичь. А дальше — дело техники. Если отнять у программистов интернет, то большинство не сможет ничего запрограммировать.
Для новичков эта информация часто выглядит так: «Сходи туда, не знаю куда, принеси то, не знаю что». То есть непонятно, как узнавать про эти функции, когда ты ничего не знаешь вообще. Как ни странно, не существует способа раз и навсегда познать всё, что нужно познать. Любой разработчик в процессе своего профессионального взросления знакомится со всё более интересными функциями, решающими его задачи более элегантно, и таким образом пополняет свой арсенал.
Вот некоторые советы, как узнавать о новых функциях:
- Всегда чётко отслеживайте, с чем вы сейчас работаете (какой тип данных). Почти всегда вы найдете необходимую функцию в соответствующем разделе документации — например, для работы со строками нужно изучать строковые функции.
- Периодически открывайте раздел со стандартными функциями по изучаемой тематике и просто пробегайтесь по ним, изучая сигнатуры и способы использования.
- Чаще читайте чужой код, особенно код библиотек, которые вы используете. Он весь доступен на GitHub.
У JavaScript есть свои особенности по структуре стандартной библиотеки. Так как его код может исполняться в разных средах, таких как серверное окружение или браузер, то возможности стандартной библиотеки сильно зависят от варианта использования. Например, из браузера невозможно выполнять некоторые задачи, которые необходимо уметь выполнять на сервере. Документацию по серверной части необходимо смотреть на сайте https://nodejs.org. Серверные части стандартной библиотеки организованы в модули, у каждого модуля есть своя страница с описанием всех функций, находящихся внутри него. Например, модуль fs необходим для работы с файловой системой, через его функции происходит запись и чтение файлов.
Если говорить про браузер, то там вообще мало что есть. По большей части это какие-то базовые функции, встроенные в сам язык — например те же функции для работы с математикой. Остальные возможности добавляются через использование сторонних библиотек.
Задание
Оператор typeof
позволяет определить тип передаваемого операнда. Название типа возвращается в виде строки. Например, вызов typeof 'go go go'
вернёт строку 'string'
(number — число).
Выведите на экран тип значения константы motto
.
Советы
- Описание строковых функций
Определения
- Стандартная библиотека – набор полезных функций, входящий в комплект поставки языка программирования.
Свойства и Методы
Данные, которыми мы оперируем в своих программах, могут обладать важными свойствами. В JavaScript свойства встроены прямо в язык. Кроме свойств у данных существуют методы — функции, находящиеся внутри свойств. Свойства и методы — такие же выражения, как переменные, константы или вызовы функции, а значит, их можно всячески комбинировать. Глубже эти темы разбираются на отдельных курсах, посвященных объектно-ориентированным возможностям JavaScript. Мы же в этом модуле изучим основы.
45. Свойства
JavaScript: Свойства
Данные, которыми мы оперируем в своих программах, могут обладать важными свойствами — например, у строк есть длина. Как вы увидите далее, это свойство очень важно для реализации алгоритмов, связанных с преобразованием строки (как пример — переворот строки). Как узнать длину строки? Во многих языках длина строки вычисляется с помощью специальной функции и выглядит это примерно так:
import { length } from 'hexlet-basics/string';
const name = 'Robb';
console.log(length(name)); // => 4
В JavaScript свойства встроены прямо в язык. Они указываются через точку сразу после переменной (или константы):
const name = 'Robb';
const len = name.length;
console.log(len); // => 4
Свойства связаны с данными, у которых они берутся. Для примитивных типов все свойства описаны в документации, как например, у строк. При этом у чисел вообще нет свойств.
JavaScript позволяет обращаться к свойствам, которые не существуют (например, при опечатках). В таком случае их значением является undefined
:
const name = 'Robb';
console.log(name.whatIsThat); // => undefined
Вопрос для самопроверки. Что распечает код console.log(name[name.length])
для name
, определенного выше? Почему ответ такой?
Задание
Напечатайте на экран длину строки text
.
Советы
- Ознакомьтесь с документацией String.length. Обратите внимание, что длина строки равна количеству символов в ней. Длина пустой строки
''
равна 0.
46. Методы
JavaScript: Методы
Кроме свойств, у данных существуют методы — функции, находящиеся внутри свойств. С практической точки зрения это значит, что метод работает и вызывается как функция, но делает это как свойство, через точку.
const name = 'Robb';
const upperName = name.toUpperCase();
console.log(upperName); // => 'ROBB'
Встроенные методы всегда оперируют теми данными, с которыми они связаны. Метод .toUpperCase()
возвращает ту же строку, но преобразуя все символы в верхний регистр. Методов у данных обычно значительно больше, чем свойств, например, для строк их несколько десятков. В документации, на первый взгляд, они описаны немного странно: String.prototype.toLowerCase(). Это описание раскрывает некоторые внутренние детали реализации, которые сейчас не важны, да и мы не изучили всей необходимой базы для разговора о прототипах.
Методы есть и у чисел:
const temperature = 22.93;
// Округление до одного знака после запятой
const roundedTemperature = temperature.toFixed(1);
// Метод возвращает строку, которая содержит преобразованное число
console.log(roundedTemperature); // => '22.9'
// Напрямую можно вызывать так
// Скобки нужны обязательно, иначе не заработает
(22.93).toFixed(1); // '22.9'
Хозяйке на заметку. Технически всё несколько сложнее. Методы есть не у самих чисел, а у данных (объектов) типа Number. Числа, записанные в переменные или константы, автоматически преобразуются к данному типу во время обращения к ним, в это время происходит так называемый boxing.
Возникает закономерный вопрос: зачем нужны методы, почему не просто функции? С числами ситуация ещё сложнее. Часть операций реализована в виде методов самих чисел, например, .toFixed()
, а ещё большая часть — в виде методов, доступных через Math
.
Есть две причины почему так сделано:
- Исторически так сложилось. JavaScript разрабатывался слишком быстро и поэтому не все было продумано хорошо.
- Далеко не все функции имеют отношение к конкретному значению. Возьмем для примера
Math.min()
. Эта функция находит минимальное число среди всех, которые ему были переданы. Эту функцию нелогично делать методом конкретного числа, например, так —(1).min()
. Она не имеет никакой связи с конкретным числом.
С другой стороны, функции, работающие с конкретным числом, для единообразия должны быть реализованы как методы. К таким функциям относится получение модуля числа. То есть вместо такого вызова Math.abs(-10)
, логично иметь такой: (-10).abs()
.
Что касается методов в целом, то не все так однозначно. Есть языки, в которых методов нет и там всё прекрасно, есть языки, где методы — это основной способ работы с функциями, но даже в этих языках всегда, наряду с методами, используются обычные функции. JavaScript — язык, в котором прижились оба подхода, в нём активно используются как обычные функции, так и методы. О плюсах и минусах подобных подходов подробно рассказывается в курсах посвященных ООП.
Задание
Приведите строку text
к нижнему регистру и напечатайте её на экран.
47. Неизменяемость
JavaScript: Неизменяемость
Что напечатает на экран последний вызов?
const name = 'Tirion';
console.log(name.toUpperCase()); // => TIRION
console.log(name); // => ?
Ответ на этот вопрос зависит от того, как вы поняли урок про неизменяемость примитивных типов данных. Вызов метода .toUpperCase()
возвращает новое значение, в котором все буквы преобразованы в верхний регистр, но он не меняет (и не может этого сделать) исходную строку. Поэтому внутри константы (или переменной — это не важно) окажется старое значение: 'Tirion'
. Эта логика справедлива для методов всех примитивных типов. Более того, попытка изменить значение свойства этих данных ни к чему не приведет:
const name = 'Tirion';
console.log(name.length); // => 6
name.length = 100;
console.log(name.length); // => 6
Вместо изменения значения можно заменить значение. Для этого понадобятся переменные:
let name = 'Tirion';
name = name.toUpperCase();
console.log(name); // => TIRION
Задание
Данные, вводимые пользователями, часто содержат лишние пробельные символы в конце или начале строки. Обычно их вырезают с помощью метода .trim()
, например, было: ' hellon '
, стало: 'hello'
.
Обновите переменную firstName
записав в неё то же самое значение, но обработанное методом .trim()
. Распечатайте то, что получилось, на экран.
48. Свойства и методы как выражения
JavaScript: Свойства и методы как выражения
Свойства и методы — такие же выражения, как переменные, константы или вызовы функции, а значит, их можно всячески комбинировать.
Использование в операциях:
const name = 'Shaya';
name.length + 5; // 10
`hi, ${name.toUpperCase()}!`; // hi, SHAYA!
Использование в аргументах функций:
const name1 = 'Robb';
const name2 = 'Shaya';
console.log(name2.length); // => 5
console.log(name2.toLowerCase()); // => shaya
console.log(Math.min(name1.length, name2.length)); // => 4
Задание
Выведите на экран первую и последнюю буквы предложения, записанного в константу text
, в следующем формате:
First: N Last: t
Ваша задача извлечь эти символы из строки и вставить в console.log()
, не используя промежуточные переменные.
49. Цепочка вызовов
JavaScript: Цепочка вызовов
У чисел есть метод, который преобразует их в строку:
const peopleCount = 5;
peopleCount.toString(); // '5'
Попробуйте ответить на вопрос, заработает ли следующий код — и если да, то что он напечатает на экран?
const name = 'Tirion';
console.log(name.length.toString());
Синтаксис нескольких подряд идущих точек мы видим впервые, но все операции, которые здесь встречаются, нам знакомы. Всё, что произошло в этом коде — это объединение уже известных возможностей языка. Такое в программировании происходит довольно часто. Даже не зная синтаксиса, можно пробовать комбинировать различные подходы, и есть неплохая вероятность, что они заработают.
Самый простой способ понять как работает этот код — разбить цепочку на отдельные операции:
const name = 'Tirion';
const len = name.length;
console.log(len.toString());
Эти примеры абсолютно эквивалентны. Мы можем выполнять операции последовательно с промежуточным созданием констант, а можем строить непрерывную цепочку из свойств и методов. В цепочках вычисления всегда идут слева направо.
Ещё один пример для закрепления:
const name = 'Tirion';
console.log(name.toUpperCase().toLowerCase());
Подобный код требует небольших умственных усилий. Важно понимать, что .toLowerCase()
применяется к результату вызова функции, которая находится левее. А функция toUpperCase()
возвращает строку. Новички часто делают ошибки в цепочках с методами, забывая ставить вызов:
const name = 'Tirion';
// Этот код отработает неверно!
console.log(name.toUpperCase.toLowerCase());
Продолжая эту идею, возможно строить бесконечно длинные (хотя, в данном случае, бесполезные) цепочки:
// Чему равен результат такого вызова?
console.log(name.toUpperCase().toLowerCase().length.toString().length);
С функциями подобный трюк не сработает, так как при обычном использовании они вкладываются друг в друга f(f(f())), что значительно ухудшает анализ. Но это не значит, что нельзя сделать красиво — можно и даже нужно. В других языках это реализуется через композицию функций или пайплайн-оператор, который, кстати говоря, постепенно начинает использоваться и в самом JavaScript: https://github.com/tc39/proposal-pipeline-operator
Задание
С помощью метода substring()
получите часть предложения, записанного в константу text
, c 4
по 15
символ включительно. Полученную подстроку обработайте методом trim
и выведите на экран длину итоговой подстроки. Выполните эти методы подряд в цепочке без создания промежуточных переменных.
- substring()
- trim()
Определение функций
Определение собственных функций значительно упрощает написание и поддержку программ. Например, умение определять функции позволяет объединять сложные (составные) операции в одну – вся сложность может быть скрыта за одной простой функцией. Научившись писать функции, вы сделаете первый шаг на пути к построению по-настоящему полезных программ. И мы вам в этом поможем. В этом модуле вы создадите свою первую функцию и научитесь давать ей (а заодно переменным и константам) понятные названия.
50. Создание (определение) функции
JavaScript: Создание (определение) функции
Определение собственных функций значительно упрощает написание и поддержку программ. Функции позволяют объединять сложные (составные) операции в одну. Например, отправка письма на сайте – это достаточно сложный процесс, включающий в себя взаимодействие с внешними системами (интернет). Благодаря возможности определять функции, вся сложность может быть скрыта за простой функцией:
import { send } from 'some-email-package';
const email = '[email protected]';
const title = 'Помогите';
const body = 'Я написал историю успеха, как я могу получить скидку?';
// Один маленький вызов — и много логики внутри
send(email, title, body);
Создадим нашу первую функцию. Её задача – вывести на экран следующий текст:
Today is: December 5
// Определение функции
// Определение не вызывает и не выполняет функцию
// Мы лишь говорим, что теперь такая функция существует
const showCurrentDay = () => {
const text = 'Today is: December 5';
console.log(text);
};
// Вызов функции
showCurrentDay();
Для любознательных. Такая функция в JavaScript называется стрелочной. Она появилась со стандартом языка ES6. Далее мы будем работать только со стрелочными функциями.
Определение функции выше состоит из двух частей:
- Присваивание функции константе
- Непосредственно определение функции
Само по себе определение функции – это всё, что находится после присвоения:
// Обратите внимание на стиль
// Пробелы между символами
// Открывающая фигурная скобка в конце той же строчки, где стрелка
// Закрывающая - на своей отдельной строчке в конце
() => {
const text = 'Today is: December 5';
console.log(text);
};
Такое определение создает функцию, но не вызывает её. Поскольку это определение не связывается ни с каким именем (константой), то такой код — бесполезен, хотя и является синтаксически корректным.
Связывание функции с именем является обычным присваиванием. В этом смысле функции ведут себя как обычные данные, которые можно записывать в константы. Технически функцию можно присвоить и переменной, но так как функции неизменяемы, то смысла в этом нет.
const doSomething = /* определение любой функции */;
В отличие от обычных данных, функции выполняют действия, поэтому их имена практически всегда должны быть глаголами: «построить что-то», «нарисовать что-то», «открыть что-то».
Всё, что описывается внутри фигурных скобок {}
, называется телом функции. Внутри тела можно описывать любой код. Считайте, что это маленькая самостоятельная программа, набор произвольных инструкций. Тело выполняется ровно в тот момент, когда запускается функция. Причём каждый вызов функции запускает тело независимо от других вызовов. Кстати, тело может быть пустым:
// Минимальное определение функции, которая ничего не делает
const noop = () => {};
noop(); // вызов есть, а смысла нет
// Такая функция тоже бывает полезна
// Но это относится к продвинутым темам
Понятие «создать функцию» имеет много синонимов: «реализовать», «определить» и даже «заимплементить» (от слова implement). Все они встречаются в повседневной практике на работе.
Задание
Реализуйте функцию printMotto()
, которая печатает на экран фразу Winter is coming.
printMotto(); // => Winter is coming
Важное замечание! В задачах, в которых нужно реализовать функцию, эту функцию вызывать не нужно. Вызывать функцию будут автоматизированные тесты, которые проверяют ее работоспособность. Пример с вызовом выше показан только для того, чтобы вы понимали, как ваша функция будет использоваться.
51. Передача одного аргумента
JavaScript: Передача одного аргумента
Функции без аргументов встречаются редко. Чаще функции принимают на вход данные, как-то их используют и выдают результат обратно. В этом уроке мы познакомимся с определением функций, принимающих на вход один аргумент. Посмотрите на определение ниже:
const showCurrentDay = (text) => {
console.log(`Today is: ${text}`);
};
showCurrentDay('January 29');
Today is: January 29
Теперь понятно, зачем нужны были круглые скобки после имени функции: в них можно указать аргументы (или, что тоже самое, параметры). Технически, параметры функции всегда являются переменными, а не константами. Но лучше относиться к ним как к константам.
Заметьте: мы не определяем переменную text
, но используем её в теле функции. JavaScript работает так: переменная сама создаётся при вызове, и указанное значение (в нашем примере — 'January 29'
) записывается в эту переменную.
Аргументы можно называть как угодно, их имена имеют смысл исключительно в теле функции. Например, если изменить имя аргумента так:
const showCurrentDate = (lala) => {
console.log(`Today is: ${lala}`);
};
const date = 'January 29';
showCurrentDate(date);
то поведение функции не изменится. Это касается как имен внутри функции (lala
), так и снаружи (date
).
Новички иногда пытаются сделать примерно такое определение функции:
const showCurrentDate = ('Today is: December 5') => {
// какой-нибудь код
};
Запустить такой код не получится — он содержит синтаксическую ошибку. Вместо переменной в аргументе написана строка, то есть значение.
Аргумент должен быть переменной, иначе он не сможет быть аргументом, то есть чем-то, что принимает значение при вызове.
Если же вам нужна какая-то информация в функции, и вы заранее знаете какая именно, то аргумент для этого не нужен, ведь мы уже умеем сохранять значения для последующего использования — достаточно создать константу в самом теле:
const showCurrentDate = () => {
const text = 'Today is: December 5';
// какой-нибудь код
};
Задание
Реализуйте функцию printJaimesLine()
, которая принимает один аргумент — строку, и выводит реплику на экран в формате JAIME: переданная_строка
.
Как назвать переменную, которая будет аргументом — решайте сами.
Наша система содержит код, скрытый от вас. В этом упражнении скрыт вызов функции printJaimesLine()
. Так мы проверяем ваше решение.
Вам не нужно самостоятельно вызывать функцию, только определить её. Но для наглядности — вот как наша система вызывает её:
printJaimesLine('Farewell, my friend...');
JAIME: Farewell, my friend...
52. Передача нескольких аргументов
JavaScript: Передача нескольких аргументов
Аргументов может быть несколько. В таком случае в определении функции мы делаем то же самое, что в вызове: просто указываем аргументы через запятую.
Полный пример определения функции с несколькими аргументами и её вызова:
const showCurrentDay = (month, day) => {
console.log(`Today is: ${month} ${day}`);
};
showCurrentDay('January', '29');
Today is: January 29
Главное — помнить, в каком порядке аргументы стоят при определении функции, в таком же порядке они должны передаваться при вызове.
Аргументы можно и не передавать, или передать только часть, тогда значением непереданных аргументов станет undefined
. Это поведение немного необычно, так как в большинстве языков так делать нельзя.
showCurrentDay('January');
// Today is: January undefined
showCurrentDay();
// Today is: undefined undefined
Задание
Сэм составляет множество карт, и ему часто нужно выводить на экран повторяющиеся символы для визуализации маршрутов. Например, так Сэм иллюстрирует узкие дороги между городами:
Meereen =-=-=-=- Myr
А так иллюстрирует широкие трассы:
Vaes Dothrak ======== Vahar
В документации js он нашёл метод String.prototype.repeat()
.
console.log('=-'.repeat(4));
=-=-=-=-
Сэм не очень доволен. Ему нужно нарисовать сотни маршрутов разной длины с разными символами. Неудобно вызывать сотни раз repeat()
внутри вызова console.log()
.
Напишите для Сэма функцию printSeq()
, которая сама выводит на экран получившиеся повторения. Она принимает два аргумента — строку и число, и выводит повторяющуюся строку на экран.
Вот пример того, как Сэм будет использовать написанную вами printSeq()
:
printSeq('=-', 4);
=-=-=-=-
53. Возврат значений
JavaScript: Возврат значений
В модуле «Вызов функций» мы в основном работали с функциями, которые выводят на экран, а не возвращают результат. Честно говоря, вывод на экран — фактически обучающий элемент. В реальном коде на экран никто ничего не выводит (за исключением утилит командной строки). Функции возвращают данные, которые передаются в другие функции.
Научиться писать функции, которые возвращают информацию — первый шаг на пути к построению по-настоящему полезных программ.
Начнем с тривиального примера: создадим и вызовем функцию, которая принимает два числа и возвращает первое число минус второе. Назовём её sub()
, от англ. “subtract” — «вычесть»:
const sub = (a, b) => {
const result = a - b;
return result;
};
const result = sub(10, 7);
console.log(result); // => 3
Возврат значения задаётся специальной инструкцией return
. Cправа от return
помещается выражение. Любое выражение. То есть, мы можем делать вычисления сразу после return
без создания константы result
:
const sub = (a, b) => {
// Сначала вычисляется выражение справа от `return`
// затем получившееся значение возвращается
return a - b;
};
console.log(sub(2018, 1975)); // => 43
Обратите внимание: мы знаем, что вызов функции — выражение, поэтому мы передали вызов одной функции в вызов другой функции — console.log(sub(2018, 1975))
.
Интерпретатор, встречая return
, останавливает дальнейшее выполнение функции и возвращает указанное справа значение в то место, где была вызвана функция.
Посмотрите на эту функцию:
const foo = () => {
return 7;
return 10;
};
console.log(foo());
Что выведется на экран?
Правильный ответ: 7
. Функция всегда будет возвращать только число 7
, так как интерпретатор, наткнувшись на первый return
, остановит выполнение функции. Строчка кода return 10;
никогда не выполнится.
Задание
Сэм создаёт генеалогические деревья разных семей. Ему постоянно приходится рассчитывать количество места, занимаемое именами родителей на экране.
Создайте функцию getParentNamesTotalLength()
для Сэма. Она должна принимать один аргумент — имя ребенка, и возвращать количество символов в именах матери и отца суммарно. Функция не должна выводить ничего на экран, только возвращать число.
Примеры вызова:
getParentNamesTotalLength('Daenerys Targaryen'); // 35
Для получения имён родителей используйте уже существующую функцию getParentFor()
:
- Получение имени матери
getParentFor(child, 'mother')
, гдеchild
— имя ребёнка. - Получение имени отца
getParentFor(child, 'father')
, гдеchild
— имя ребёнка
Вам не нужно вызывать свою функцию, только определить её.
54. Возврат по умолчанию
JavaScript: Возврат по умолчанию
Рассмотрим немного модифицированную функцию из предыдущего урока:
const sub = (a, b) => {
// Полученный результат никак не используется
// и не возвращается наружу
const answer = a - b;
};
const result = sub(10, 7);
console.log(result); // undefined
Несмотря на отсутствие return
внутри функции, console.log()
выведет на экран undefined
. Это стандартное поведение функций в JavaScript, оно существует не просто так. Вызов функции – выражение, а выражения всегда возвращают результат своего выполнения.
Забыть инструкцию return
— частая ошибка новичка. Мы в обучении каждый день сталкиваемся с просьбами о помощи типа «функция правильная, но почему-то не работает». И почти всегда оказывается, что забыт return
, а результат, вместо возврата, просто печатается на экран.
С другой стороны, если написать инструкцию return
без указания выражения после него, то наружу вернется все тот же undefined
. Кажется, что подобный возврат не имеет смысла, но это не так. return
без выражения нередко используют для прерывания вычислений. Подробнее эта тема раскрывается в модуле, посвященному условным конструкциям.
Вопрос для самопроверки. Что возвращает функция console.log()
?
Задание
Это немного странное задание, но для тренировки будет полезным. Реализуйте функцию doNothing()
, которая ничего не делает.
55. Параметры по умолчанию
JavaScript: Параметры по умолчанию
Напомню, что аргумент может быть необязательным. У такого аргумента есть значение по умолчанию.
Например, функция getParentFor()
, которую вы использовали в некоторых упражнениях, принимает имя ребёнка первым аргументом, а вторым — строку 'mother'
или 'father'
. Второй аргумент — необязательный, и если не указывать его при вызове, то автоматически по умолчанию будет использоваться 'mother'
.
Эти два вызова равнозначны:
getParentFor('Jon Snow');
getParentFor('Jon Snow', 'mother');
Каким образом там сделаны аргументы по умолчанию? Давайте заглянем в определение этой функции:
const getParentFor = (child, parent = 'mother') => {
// какой-то код
};
Первый аргумент указан привычно — просто название переменной. Это делает аргумент обязательным.
Второй аргумент указан со значением в формате аргумент = какое_то_значение
. Точно так же, как при создании переменных и констант. Этот фрагмент = какое_то_значение
делает аргумент необязательным, и задаёт ему значение по умолчанию.
Аргументов по умолчанию может быть любое количество, но желательно, чтобы все они были в конце списка аргументов. То есть такие строчки кода будут некорректны:
const getParentFor = (childName = 'Jon', who) => {
const calculate = (a, b = 90, c) => {
const getPrices = (code = 4161, quantity, place) => {
Важно! Значение по умолчанию присваивается аргументу только если при вызове функции для него не было передано значение. Если передать аргумент с любым значением (кроме undefined
), значение по умолчанию использоваться не будет.
getParentFor('Daenerys Targaryen', 'father'); // Aerys II Targaryen
Задание
Реализуйте функцию getCustomParentFor()
, которая принимает два аргумента:
- Строку с именем ребёнка.
- Строку с указанием родителя. Этот аргумент должен по умолчанию быть
'father'
.
Функция должна возвращать имя соответствующего родителя.
Примеры вызова:
getCustomParentFor('Cersei Lannister'); // Tywin Lannister
getCustomParentFor('Daenerys Targaryen', 'mother'); // Rhaella Targaryen
Такой вызов вернёт имя отца.
- Внутри своей функции используйте уже готовую функцию
getParentFor()
. Как она работает, мы подробно разбирали раньше.
56. Именование
JavaScript: Именование
Стиль именования функций в JavaScript такой же, как и стиль именования переменных: lowerCamelCase. Но при выборе самих слов есть важное отличие.
Функция — действие, вызов функции всегда подобен указанию «сходи», «возьми», «напечатай», «положи» и так далее. Вспомните, какие функции были в предыдущих уроках:
showDate()
(«показать дату»)sub()
(subtract — «вычесть»)round()
(«округлить»)getMoney()
(«получить»)
Переменная/Константа — сущность, поэтому мы используем существительные:
child
result
euros
Берите на вооружение следующую структуру: функция — глагол, переменная/константа — существительное.
В уроке про переменные мы просили вас придумать название переменной и записать в блокноте или отправить себе на почту. Найдите это название и посмотрите на него свежим взглядом: оно понятное? Описывает суть однозначно или требует вникания?
Жизнь программиста наполнена такими моментами: открыть старый код и попытаться понять его. Будьте добры к будущему себе и к коллегам, давайте переменным, константам и функциям понятные названия.
Задание
Реализуйте функцию, которая принимает на вход номер кредитки (состоящий из 16 цифр) в виде строки и возвращает его скрытую версию, которая может использоваться на сайте для отображения. Если исходная карта имела номер 2034399002125581, то скрытая версия выглядит так ****5581. Другими словами, функция заменяет первые 12 символов, на звездочки. Количество звездочек регулируется вторым необязательным параметром. Значение по умолчанию — 4.
// Кредитка передается внутрь как строка
getHiddenCard('1234567812345678', 2); // '**5678'
getHiddenCard('1234567812345678', 3); // '***5678'
getHiddenCard('1234567812345678'); // '****5678'
getHiddenCard('2034399002121100', 1); // '*1100'
Полезные методы:
- String.prototype.slice() – извлекает часть строки или как говорят “подстроку”. Первым параметром принимает индекс элемента, с которого надо начинать извлечение, вторым – индекс элемента, до которого извлекаются символы. По умолчанию, извлекается все до конца строки.
// с третьего индекса до конца строки 'java script'.slice(3); // 'a script' // с первого индекса по четвертый 'java script'.slice(1, 4); // 'ava' // можно использовать отрицательные индексы // тогда отсчет берется с конца строки 'java script'.slice(-2); // 'pt'
- String.prototype.padStart() – Дополняет строку “заполнителем” слева, до тех пор пока длина строки не станет равной указанной. Первым параметром функция принимает желаемую длину строки, вторым – заполнитель.
'5'.padStart(2, '*'); // '*5' '10'.padStart(4, '+'); // '++10'
Советы
- Статья про именование в программировании
- Ментальное программирование
57. Упрощенный синтаксис функций
JavaScript: Упрощенный синтаксис функций
По сравнению с некоторыми (в первую очередь функциональными) языками, определение функции в JavaScript выглядит довольно громоздко:
const square = (x) => {
return x ** 2;
};
Здесь используется много дополнительных символов и слово return
. С версии es6, в языке появился альтернативный, сокращенный синтаксис, который, в некоторых ситуациях, значительно упрощает восприятие и сокращает количество кода.
// Требуется немного времени на привыкание к этой форме, но потом вы не сможете без неё жить
const double = (x) => x ** 2;
Отличия от полного определения два: пропали фигурные скобки и инструкция return
. Сокращенная запись функции делает возврат автоматически. Подразумевается, что внутри такой функции ровно одно выражение, которое вычисляется, и её результат сразу возвращается наружу.
Подчеркнём, что отличия исключительно синтаксические, с точки зрения использования различий нет. Пример с двумя аргументами:
Полная версия
const sum = (a, b) => {
return a + b;
};
Сокращенная версия
const sum = (a, b) => a + b;
Обратите внимание на отсутствие фигурных скобок. Разработчики, которые не привыкли использовать такой синтаксис, иногда пишут подобный код const sum = (a, b) => { a + b };
, а потом долго не могут понять, почему он не работает. Ответ очень простой: если стоят фигурные скобки, то это не сокращенная форма, а значит, чтобы функция вернула искомое значение, придётся поставить return
.
Задание
Реализуйте функцию capitalize()
, которая принимает непустую строку и приводит первую букву первого слова к верхнему регистру:
const name = 'arya';
console.log(capitalize(name)); // => Arya
Чтобы получить подстроку (или символ) из строки, используйте метод slice():
'welcome'.slice(2, 5); // lco
Логика
Логические выражения позволяют отвечать на вопросы, которые возникают во время работы программы. Пользователь аутентифицирован? Подписка оплачена? Год високосный? В этом модуле изучаем функции-предикаты – те, которые задают вопрос и отвечают на него – правда это или ложь. Попрактикуемся в написании таких функций и перейдем к более сложным логическим выражениям.
58. Логический тип
JavaScript: Логический тип
Кроме арифметических операций со школы нам известны операции сравнения. Например, 5 > 4
. Это звучит как вопрос: «5 больше 4?». В данном случае ответ «да». В других случаях ответом может быть «нет», например, для 3 < 1
.
Операции сравнения не имеют привязки к числам. Сравнивать можно практически всё что угодно, например, строки. Каждый раз, когда мы входим на какой-то сайт, внутри происходит сравнение введенных логина и пароля с теми, какие есть в базе. И только если они есть, нас пускают вовнутрь (аутентифицируют).
Языки программирования адаптировали все математические операции сравнения практически в неизменном виде. Единственное серьезное отличие – операторы равенства и неравенства. В математике для этого используется обычное равно =
, но в программировании такое встречается не часто. Во многих языках символ =
используется для присваивания значений переменным, поэтому для сравнения взяли ==
или ===
.
Список операций сравнения в JavaScript:
<
меньше<=
меньше или равно>
больше>=
больше или равно===
равно!==
не равно
Небольшая ремарка: для равенства и неравенства также существуют операторы ==
и !=
, которые мы не будем использовать из-за потенциальной опасности. Мы поговорим об этом в будущих уроках.
Логическая операция типа 5 > 4
или password === text
— это выражение, и его результат — специальное значение true
(«истина») или false
(«ложь»). Это новый для нас тип данных — boolean. Он содержит всего лишь два этих значения.
const result = 5 > 4;
console.log(result); // => true
console.log('one' !== 'one'); // => false
Наряду со строками (string), целыми и рациональными числами (number), логический тип (boolean) — это один из примитивных типов данных в JavaScript.
Попробуем написать примитивную функцию, которая принимает на вход возраст ребенка и определяет, младенец ли он. Младенцами считаются дети до года:
const isInfant = (age) => age < 1;
Пользуемся тем фактом, что любая операция — это выражение, поэтому единственной строчкой функции пишем «вернуть то значение, которое получится в результате сравнения age < 1
».
В зависимости от пришедшего аргумента, сравнение будет либо истинным (true
), либо ложным (false
), и return
вернёт этот результат.
const isInfant = (age) => age < 1;
console.log(isInfant(3));
false
А теперь проверим ребенка, которому полгода:
console.log(isInfant(0.5));
true
Задание
Напишите функцию isPensioner()
, которая принимает один параметр — возраст человека, и проверяет, является ли он пенсионным. Пенсионером считается человек, достигший возраста 60 лет и больше.
Примеры вызова:
isPensioner(75); // true
isPensioner(18); // false
Определения
- Логический тип (boolean) – тип данных с двумя возможными значениями: true (истина) и false (ложь).
59. Предикаты
JavaScript: Предикаты
Вспомним функцию isInfant()
из прошлого урока:
const isInfant = (age) => age < 1;
console.log(isInfant(3));
false
Подобные функции называют предикатами. Функции-предикаты (или функции-вопросы) отвечают на какой-то вопрос и всегда (без исключений!) возвращают либо true
, либо false
.
Предикаты во всех языках принято именовать особым образом для простоты анализа. В JavaScript предикаты, как правило, начинаются с префикса is
, has
или can
, но не ограничены этими словами. Примеры:
isInfant()
— «младенец ли?»hasChildren()
— «есть ли дети?»isEmpty()
— «пустой ли?»hasErrors()
— «есть ли ошибки?»
Функция может считаться предикатом только если она возвращает boolean.
Давайте напишем ещё одну функцию-предикат. Она принимает строку и проверяет, является ли она словом 'Castle'
:
const isCastle = (type) => type === 'Castle';
console.log(isCastle('Sea'));
false
Задание
Напишите функцию isMister()
, которая принимает строку и проверяет, является ли она словом 'Mister'
.
Примеры вызова:
isMister('Mister'); // true
isMister('Miss'); // false
Советы
- Именование в программировании
Определения
- Предикат – выражение, отвечающее на вопрос «да» или «нет» с помощью типа boolean.
60. Комбинирование операций и функций
JavaScript: Комбинирование операций и функций
Логические операции — это выражения. Значит, логические операции можно комбинировать с другими выражениями.
Например, мы хотим проверить чётность числа, то есть кратность двум. В программировании используют такой подход:
- проверяют остаток от деления на 2:
- если остаток 0, то число было чётным
- если остаток не 0, то число было нечётным
Остаток от деления — простая, но очень важная концепция в арифметике, алгебре, и даже в теории чисел и криптографии. Идея проста: нужно разделить число на несколько равных групп, и если в конце что-то останется — это и есть остаток от деления.
Делим конфеты поровну между людьми:
- 7 конфет, 2 человека: 2 x 3 + остаток 1. Значит, 7 не кратно 2.
- 21 конфету, 3 человека: 3 x 7 + остаток 0. Значит, 21 кратно 3.
- 19 конфет, 5 человек: 5 x 3 + остаток 4. Значит, 19 не кратно 5.
Оператор %
вычисляет остаток от деления (не путайте с делением):
7 % 2
→1
21 % 3
→0
19 % 5
→4
С помощью него напишем функцию проверки чётности:
const isEven = (number) => number % 2 === 0;
isEven(10); // true
isEven(3); // false
В одном выражении мы скомбинировали логический оператор ===
(проверка равенства) и арифметический оператор %
.
Приоритет арифметических операций выше логических. Значит, сначала вычисляется арифметическое выражение number % 2
, затем результат участвует в логическом сравнении.
По-русски это можно расшифровать так: «вычислить остаток от деления числа number
на 2 и сравнить, равен ли остаток нулю; затем вернуть результат проверки равенства».
Другой пример: напишем функцию, которая принимает строку и проверяет, заглавная ли первая буква.
Алгоритм:
- Получим и запишем в переменную первый символ из строки-аргумента.
- Сравним, равен ли символ своей большой (заглавной) версии.
- Вернём результат.
const isFirstLetterInUpperCase = (string) => {
const firstLetter = string[0];
return firstLetter.toUpperCase() === firstLetter;
}
isFirstLetterInUpperCase('marmont'); // false
isFirstLetterInUpperCase('Robb'); // true
Мы использовали метод toUpperCase()
. Он возвращает строку, на которой он был вызван, в которой все буквы стали заглавными. Мы вызвали его на первом символе строки.
Попробуйте проговорить происходящее по-русски, аналогично тому, как мы расшифровывали процесс в примере с isEven()
в начале урока.
Напомним об извлечении символов из строки с помощью квадратных скобок:
const firstName = 'Alexander';
firstName[0]; // A
Задание
Сэм решил изучить историю Таргариенов со времени первых людей, но книг было много и информация могла находиться в любой из них. К счастью для Сэма, большинство книг были оцифрованы молодыми мейстерами. Он подумал, что неплохо бы написать функцию, которая анализирует тексты на наличие в них упоминания фамилии Таргариенов.
Реализуйте функцию hasTargaryenReference()
, которая принимает на вход строку и проверяет, начинается ли она с Targaryen. Сделать это легко используя метод substring()
, который принимает на вход два параметра:
- Индекс, с которого нужно взять подстроку (включает в подстроку)
- Индекс, до которого нужно взять подстроку (не включает в подстроку)
Этот метод позволяет извлечь начало подстроки такой же длины, как и слово Targaryen, а затем проверить равно ли оно Targaryen. Напомню, что индексы начинаются с нуля.
hasTargaryenReference(''); // false
hasTargaryenReference('Targari'); // false
hasTargaryenReference('targaryen'); // false
hasTargaryenReference('Targaryen'); // true
Советы
- substring()
61. Логические операторы
JavaScript: Логические операторы
Логические выражения могут объединяться друг с другом, создавая все более хитрые проверки. Хороший пример: проверка пароля. Как вы знаете, некоторые сайты при регистрации хотят пароль от 8 до 20 символов в длину. Честно говоря, это странное ограничение, но что поделать. В математике мы бы написали 8 < x < 20
(где x
это длина конкретного пароля), но в JavaScript такой трюк не пройдет. Нам придётся сделать два отдельных логических выражения и соединить их специальным оператором «И»:
Пароль длиннее 8 символов **И** пароль короче 20 символов.
Вот функция, которая принимает пароль и говорит, соответствует ли он условиям, или не соответствует:
const isStrongPassword = (password) => {
const length = password.length;
return length > 8 && length < 20;
};
isStrongPassword('qwerty'); // false
isStrongPassword('qwerty1234'); // true
isStrongPassword('zxcvbnmasdfghjkqwertyui'); // false
&&
– означает «И» (в математической логике это называют конъюнкцией). Всё выражение считается истинным только в том случае, когда истинен каждый операнд — каждое из составных выражений. Иными словами, &&
означает «и то, и другое».
Приоритет этого оператора ниже, чем приоритет операторов сравнения, поэтому выражение отрабатывает правильно без скобок.
Кроме &&
, часто используется оператор ||
— «ИЛИ» (дизъюнкция). Он означает «или то, или другое, или оба». Операторы можно комбинировать в любом количестве и любой последовательности, но когда одновременно встречаются &&
и ||
, то приоритет лучше задавать скобками. Ниже пример расширенной функции определения корректности пароля:
const hasSpecialChars = (str) => /* проверяет содержание специальных символов в строке */;
const isStrongPassword = (password) => {
const length = password.length;
// Скобки задают приоритет. Понятно что к чему относится.
return (length > 8 && length < 20) || hasSpecialChars(password);
};
Другой пример. Мы хотим купить квартиру, которая удовлетворяет условиям: площадь от 100 кв. метров и больше на любой улице ИЛИ площадь от 80 кв. метров и больше, но на центральной улице Main Street
.
Напишем функцию, проверяющую квартиру. Она принимает два аргумента: площадь (число) и название улицы (строку):
const isGoodApartment = (area, street) => {
// Через переменную, чтобы функция была не слишком длинной
const result = area >= 100 || (area >= 80 && street === 'Main Street');
return result;
};
isGoodApartment(91, 'Queens Street'); // false
isGoodApartment(78, 'Queens Street'); // false
isGoodApartment(70, 'Main Street'); // false
isGoodApartment(120, 'Queens Street'); // true
isGoodApartment(120, 'Main Street'); // true
isGoodApartment(80, 'Main Street'); // true
Область математики, в которой изучаются логические операторы, называется булевой алгеброй. Ниже показаны «таблицы истинности» — по ним можно определить, каким будет результат применения оператора:
И &&
A | B | A && B |
---|---|---|
TRUE | TRUE | TRUE |
TRUE | FALSE | FALSE |
FALSE | TRUE | FALSE |
FALSE | FALSE | FALSE |
ИЛИ ||
A | B | A || B |
---|---|---|
TRUE | TRUE | TRUE |
TRUE | FALSE | TRUE |
FALSE | TRUE | TRUE |
FALSE | FALSE | FALSE |
Задание
Джон поручил Сэму реализовать автоматическое распознавание солдат Ланнистеров на видео. Идея автоматизировать дозор крепости казалась ему привлекательной. В процессе работы Сэму понадобилось написать функцию, которая определяет, Ланнистер ли перед ним или нет. Немного подумав, Сэм выделил следующие правила определения Ланнистера:
Если у солдата доспехи красного цвета И нет щита
ИЛИ
если у солдата есть щит с изображением льва
то это Ланнистер.
Напишите функцию isLannisterSoldier()
, которая принимает на вход два аргумента:
- Цвет доспехов (строка). Если доспехи красные, то строка
red
. null
если щита нет. Строкаlion
, если щит есть, и на нём изображен лев.
Функция возвращает true
, если распознан Ланнистер, и false
если не распознан.
Примеры вызова:
isLannisterSoldier('red', 'lion'); // true
isLannisterSoldier('blue', null); // false
Советы
- Булева алгебра
- Конъюнкция
- Дизъюнкция
Определения
- Логические операторы – операторы «И» (&&), ИЛИ (||), позволяющие создавать составные логические условия.
62. Отрицание
JavaScript: Отрицание
Наряду с конъюнкцией (И) и дизъюнкцией (ИЛИ), часто используется операция «отрицание». Отрицание меняет логическое значение на противоположное. В программировании ему соответствует унарный оператор !
.
Если есть функция, проверяющая чётность числа, то с помощью отрицания можно выполнить проверку нечётности:
const isEven = (number) => number % 2 === 0;
isEven(10); // true
!isEven(10); // false
То есть мы просто добавили !
слева от вызова функции и получили обратное действие.
Отрицание — мощный инструмент, который позволяет лаконично выражать задуманные правила в коде без необходимости писать новые функции.
А что если написать так !!isEven(10)
? Внезапно, но код сработает. В логике двойное отрицание подобно отсутствию отрицания вообще.
isEven(10); // true
!isEven(10); // false
!!isEven(10); // true
Задание
Реализуйте функцию isNotLannisterSoldier()
, которая проверяет, что солдат — не Ланнистер. Функция принимает на вход 2 аргумента:
- Цвет доспехов (строка). Например, строку
red
, если доспехи красные. - Изображение на щите. Например, строку
lion
, если щит с изображением льва. Если щита нет, то будет переданnull
.
Вам доступна уже готовая функция isLannisterSoldier()
. Воспользуйтесь ей, чтобы не писать все логические условия заново.
Условия распознавания Ланнистера описаны в прошлом уроке.
Примеры вызова:
isNotLannisterSoldier('red', 'lion'); // false
isNotLannisterSoldier('blue', null); // true
Советы
- Законы Де Моргана
63. Логические операторы
JavaScript: Логические операторы 2
Логические операторы — важная тема, поэтому стоит закрепить её дополнительным примером и упражнением.
Попробуем реализовать функцию, проверяющую год на високосность. Год будет високосным, если он кратен 400 или одновременно кратен 4 и не кратен 100. Как видите, в определении уже заложена вся необходимая логика, осталось только переложить её на код:
const isLeapYear = (year) => year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
isLeapYear(2018); // false
isLeapYear(2017); // false
isLeapYear(2016); // true
Разберём по частям:
- первое условие
year % 400 === 0
: остаток от деления на 400 равен 0, значит, число кратно 400 ||
ИЛИ- второе условие
(year % 4 === 0 && year % 100 !== 0)
year % 4 === 0
: остаток от деления на 4 равен 0, значит, число кратно 4&&
Иyear % 100 !== 0
: остаток от деления на 100 не равен 0, значит, число не кратно 100
Задание
Напишите функцию isNeutralSoldier()
, которая принимает на вход два аргумента:
- Цвет доспехов (строка). Возможные варианты:
red
,yellow
,black
. - Цвет щита (строка). Возможные варианты:
red
,yellow
,black
.
Функция возвращает true
, если цвет доспехов не красный и цвет щита чёрный. В остальных случаях возвращает false
.
Примеры вызова:
isNeutralSoldier('yellow', 'black'); // true
isNeutralSoldier('red', 'black'); // false
isNeutralSoldier('red', 'red'); // false
64. Слабая типизация
JavaScript: Слабая типизация — 2
В модуле «Арифметика» мы затронули тему слабой типизации.
JavaScript — это язык со слабой типизацией. Он знает о существовании разных типов (числа, строки и др.), но относится к их использованию не очень строго, пытаясь преобразовывать информацию, когда это кажется ему разумным.
Особенно много автоматических преобразований происходит при работе с логическими операциями.
Пример:
console.log(0 || 1);
1
Что тут произошло:
Оператор ИЛИ работает так, что его выполнение (слева направо) прерывается и возвращается результат первого аргумента, который можно преобразовать в true
.
Пример:
console.log(0 && 1);
0
Что тут произошло:
Оператор И работает так, что его выполнение (слева направо) прерывается и возвращается результат первого аргумента, который можно преобразовать в false
.
В JavaScript есть два простых правила, по которым происходят преобразования:
0
,''
,undefined
,NaN
,null
приводятся кfalse
. Эти значения называют falsy.- Всё остальное приводится к
true
Этим активно пользуются в разработке, например, для определения значения по умолчанию:
const value = name || '';
// Примеры
234 || ''; // 234
'hexlet' || ''; // 'hexlet'
Но здесь есть потенциальный баг. Если name
может содержать falsy значения и это допустимо, то код выше начнет работать неверно:
// Упс
false || ''; // ''
0 || ''; // ''
undefined || ''; // ''
В одном из уроков мы рассмотрели операторы сравнения ===
и !==
и упомянули, что в JavaScript так же есть операторы ==
и !=
, но их не стоит использовать. Отличия как раз заключаются в преобразовании типов:
console.log('' === false); // => false
console.log('' == false); // => true
Пустая строка и false
— это разные значения, поэтому оператор ===
говорит «ложь! они не равны!».
Но оператор ==
преобразует типы, и с его точки зрения пустая строка и false
равны.
Это преобразование неявное, поэтому по возможности избегайте операторов ==
и !=
.
Вспомните операцию отрицания:
const answer = true;
console.log(!answer); // => false
При двойном отрицании !!
итоговое значение равно начальному:
const answer = true;
console.log(!!answer); // => true
Но здесь дополнительно происходят преобразования типа. Поэтому результатом двойного отрицания всегда будет значение типа boolean. Этим приемом иногда пользуются, чтобы поменять тип данных.
Задание
Реализуйте функцию getLetter()
, которая извлекает из переданной строки указанный символ (по порядковому номеру, а не индексу) и возвращает его наружу. Если такого символа нет, то функция возвращает пустую строку.
Примеры вызова:
const name = 'Hexlet';
// Обычное обращение возвращает undefined
name[10]; // undefined
// 11 символ соответствует 10 индексу
getLetter(name, 11); // ''
getLetter(name, 1); // 'H'
getLetter(name, 0); // ''
getLetter(name, 6); // 't'
Советы
- Boolean
- Извлечение символов из строки
Условные конструкции
Задача функции-предиката — получить ответ на вопрос, но обычно этого недостаточно и нужно выполнить определенное действие в зависимости от ответа. If и Switch – конструкции JavaScript, с помощью которых программист может выбирать необходимое поведение программы в зависимости от разных условий: пропускать одни инструкции и выполнять другие. Их и разберем на практике в этом модуле.
65. Условная конструкция (if)
JavaScript: Условная конструкция (if)
Задача предиката — получить ответ на вопрос, но обычно этого недостаточно и нужно выполнить определенное действие в зависимости от ответа.
Напишем функцию, которая определяет тип переданного предложения. Для начала она будет отличать обычные предложения от вопросительных.
const getTypeOfSentence = (sentence) => {
const lastChar = sentence[sentence.length - 1];
if (lastChar === '?') {
return 'question';
}
return 'general';
};
getTypeOfSentence('Hodor'); // general
getTypeOfSentence('Hodor?'); // question
if
– конструкция языка, управляющая порядком выполнения инструкций. В скобках ей передается выражение-предикат, а затем описывается блок кода в фигурных скобках. Этот блок кода будет выполнен, только если предикат — истина.
Если предикат — ложь, то блок кода в фигурных скобках пропускается, и функция продолжает свое выполнение дальше. В нашем случае следующая строчка кода — return 'general';
— заставит функцию вернуть строку и завершиться.
Как видите, return
может находиться где угодно в функции. В том числе внутри блока кода с условием.
Если в фигурных скобках после if
содержится только одна строчка кода, то фигурные скобки можно не писать и сделать так:
const getTypeOfSentence = (sentence) => {
const lastChar = sentence[sentence.length - 1];
if (lastChar === '?')
return 'question';
return 'general';
};
console.log(getTypeOfSentence('Hodor')); // => general
console.log(getTypeOfSentence('Hodor?')); // => question
Советуем не делать так и всегда писать фигурные скобки. В таком случае явно видно, где начинается и заканчивается тело условия. Код становится более чётким и понятным.
Задание
Реализуйте функцию getSentenceTone()
, которая принимает строку и определяет тон предложения. Если все символы в верхнем регистре, то это вопль — 'scream'
. В ином случае — нормальное предложение — 'general'
.
Примеры вызова:
getSentenceTone('Hello'); // general
getSentenceTone('WOW'); // scream
Алгоритм:
- Сгенерируйте строку в верхнем регистре на основе строки-аргумента с помощью
toUpperCase()
. - Сравните её с исходной строкой:
- Если строки равны, значит, строка-аргумент в верхнем регистре.
- В ином случае — строка-аргумент не в верхнем регистре.
66. else
JavaScript: else
Напишем функцию getTypeOfSentence()
, которая анализирует текст и возвращает описание его тона: для обычных предложений – General sentence, для вопросительных – Question sentence.
getTypeOfSentence('Hodor'); // General sentence
getTypeOfSentence('Hodor?'); // Question sentence
Реализация функции:
const getTypeOfSentence = (sentence) => {
let sentenceType;
// Предикат, проверяющий окончание текста
// Если он оканчивается на символ '?', то вернётся true,
// иначе false
if (sentence.endsWith('?')) {
sentenceType = 'Question';
} else {
sentenceType = 'General';
}
return `${sentenceType} sentence`;
};
Мы добавили ключевое слово else
и новый блок с фигурными скобками. Этот блок выполнится, только если условие в if
— ложь.
Существует два способа оформления конструкции if-else. С помощью отрицания можно изменить порядок блоков:
const getTypeOfSentence = (sentence) => {
let sentenceType;
// Добавилось отрицание
// Содержимое else переехало в if и наоборот
if (!sentence.endsWith('?')) {
sentenceType = 'General';
} else {
sentenceType = 'Question';
}
return `${sentenceType} sentence`;
};
Какой способ предпочтительнее? Человеческому мозгу проще мыслить прямолинейно, а не через отрицание. Старайтесь выбирать проверку, которая не содержит отрицаний, и подстраивайте содержимое блоков под неё.
Задание
Реализуйте функцию buildUrl()
, которая принимает на вход адрес страницы (без указания домена) и имя домена, а возвращает полный url со схемой https.
Примеры вызова:
buildUrl('pages/about', 'hexlet.io'); // 'https://hexlet.io/pages/about'
buildUrl('/pages/about', 'hexlet.io'); // 'https://hexlet.io/pages/about'
// Для главной страницы слэш в конце обязателен
buildUrl('/', 'ru.code-basics.com'); // 'https://ru.code-basics.com/'
buildUrl('', 'ru.code-basics.com'); // 'https://ru.code-basics.com/'
Первым параметром в функцию могут быть переданы адреса с ведущим слешем /
и без него. Задача функции обрабатывать эту ситуацию, чтобы слеш не дублировался.
Для реализации этой функции вам могут понадобиться методы slice() и startsWith().
Определения
- else – способ задать блок кода, который будет выполнен, если условие с
if
не удовлетворено
67. Конструкция else if
JavaScript: Конструкция else if
Функция getTypeOfSentence()
из предыдущего урока различает только вопросительные и обычные предложения. Давайте попробуем добавить поддержку восклицательных предложений:
const getTypeOfSentence = (sentence) => {
const lastChar = sentence[sentence.length - 1];
let sentenceType;
if (lastChar === '!') {
sentenceType = 'exclamation';
} else {
sentenceType = 'normal';
}
if (lastChar === '?') {
sentenceType = 'question';
}
return `Sentence is ${sentenceType}`;
};
getTypeOfSentence('Who?'); // 'Sentence is question'
getTypeOfSentence('No'); // 'Sentence is normal'
getTypeOfSentence('No!'); // 'Sentence is exclamation'
Мы добавили ещё одну проверку (“exclamation” переводится «восклицание»). Технически функция работает, но с точки зрения семантики есть проблемы.
- Проверка на наличие вопросительного знака происходит в любом случае, даже если уже был обнаружен восклицательный знак.
- Ветка
else
описана именно для первого условия, но не для второго.
Правильнее будет воспользоваться ещё одной возможностью условной конструкции:
const getTypeOfSentence = (sentence) => {
const lastChar = sentence[sentence.length - 1];
let sentenceType;
if (lastChar === '?') {
sentenceType = 'question';
} else if (lastChar === '!') {
sentenceType = 'exclamation';
} else {
sentenceType = 'normal';
}
return `Sentence is ${sentenceType}`;
};
getTypeOfSentence('Who?'); // 'Sentence is question'
getTypeOfSentence('No'); // 'Sentence is normal'
getTypeOfSentence('No!'); // 'Sentence is exclamation'
Теперь все условия выстроены в единую конструкцию. else if
— это «если не выполнено предыдущее условие, но выполнено текущее». Получается такая схема:
- если последний символ это
?
, то'question'
- иначе, если последний символ это
!
, то'exclamation'
- иначе
'normal'
Выполнится только один из блоков кода, относящихся ко всей конструкции if
.
Задание
На электронной карте Вестероса, которую реализовал Сэм, союзники Старков отображены зеленым кружком, враги — красным, а нейтральные семьи — серым.
Напишите для Сэма функцию whoIsThisHouseToStarks()
, которая принимает на вход фамилию семьи и возвращает одно из трёх значений: 'friend'
, 'enemy'
, 'neutral'
.
Правила определения:
- Друзья (
'friend'
): ‘Karstark’, ‘Tally’ - Враги (
'enemy'
): ‘Lannister’, ‘Frey’ - Любые другие семьи считаются нейтральными
Примеры вызова:
whoIsThisHouseToStarks('Karstark'); // 'friend'
whoIsThisHouseToStarks('Frey'); // 'enemy'
whoIsThisHouseToStarks('Joar'); // 'neutral'
whoIsThisHouseToStarks('Ivanov'); // 'neutral'
Определения
- else if – способ задать несколько альтернативных условий.
68. Тернарный оператор
JavaScript: Тернарный оператор
Посмотрите на определение функции, которая возвращает модуль переданного числа:
const abs = (number) => {
if (number >= 0) {
return number;
}
return -number;
};
abs(10); // 10
abs(-10); // 10
Можно ли записать её лаконичнее? Что-то вроде return <ответ в зависимости от условия>
? Для этого справа от return должно быть выражение, но if
— это инструкция, а не выражение.
В JavaScript существует конструкция, которая по своему действию аналогична конструкции if-else, но при этом является выражением. Она называется тернарный оператор.
Тернарный оператор — единственный в своем роде оператор, требующий три операнда:
const abs = (number) => {
return number >= 0 ? number : -number;
};
Общий паттерн выглядит так: <predicate> ? <expression on true> : <expression on false>
.
Сокращенный вариант функции abs()
, выглядит так:
const abs = (number) => (number >= 0 ? number : -number);
Обратите внимание на скобки вокруг тернарника. Они не обязательны, но линтер настоятельно рекомендует их ставить, во избежание неоднозначностей.
Давайте перепишем начальный вариант getTypeOfSentence()
аналогично:
Было:
const getTypeOfSentence = (sentence) => {
const lastChar = sentence.slice(-1);
if (lastChar === '?') {
return 'question';
}
return 'normal';
};
Стало:
const getTypeOfSentence = (sentence) => {
const lastChar = sentence.slice(-1);
return (lastChar === '?') ? 'question' : 'normal';
};
getTypeOfSentence('Hodor'); // normal
getTypeOfSentence('Hodor?'); // question
Если вы помните, в чём сила выражений, то вероятно уже догадались, что тернарный оператор можно вкладывать в тернарный оператор. Не делайте этого 🙂 Такой код тяжело и читать, и отлаживать, это очень плохая практика.
Задание
Реализуйте функцию convertText()
, которая принимает на вход строку и, если первая буква не заглавная, возвращает перевернутый вариант исходной строки. Если первая буква заглавная, то строка возвращается без изменений.
Примеры вызова:
convertText('Hello'); // 'Hello'
convertText('hello'); // 'olleh'
// Не забудьте учесть пустую строку!
convertText(''); // ''
Перевернуть строчку можно используя функцию reverse()
. В качестве аргумента в неё нужно передать строку, которую мы хотим перевернуть:
const result = reverse('Hello!');
console.log(result); // => '!olleH'
Есть разные подходы к решению этой задачи. Возможно, вам пригодится метод toUpperCase() и возможность получения символа из строки (например, str[0]
).
Попробуйте написать два варианта функции: с обычным if-else, и с тернарным оператором.
Определения
- Тернарный оператор – Способ превратить простую условную инструкцию в выражение, например,
number >= 0 ? number : -number
.
69. Конструкция Switch
JavaScript: Конструкция Switch
Многие языки в дополнение к условной конструкции if включают в себя switch. Это специализированная версия if, созданная для некоторых особых ситуаций. Например, её имеет смысл использовать там, где есть цепочка if else с проверками на равенство. Например:
if (status === 'processing') {
// Делаем раз
} else if (status === 'paid') {
// Делаем два
} else if (status === 'new') {
// Делаем три
} else {
// Делаем четыре
}
Эта составная проверка обладает одной отличительной чертой: каждая ветка здесь — это проверка значения переменной status
. Switch позволяет записать этот код короче и выразительнее:
switch (status) {
case 'processing': // status == processing
// Делаем раз
break;
case 'paid': // status == paid
// Делаем два
break;
case 'new': // status == new
// Делаем три
break;
default: // else
// Делаем четыре
}
Свитч — довольно сложная конструкция с точки зрения количества элементов, из которых она состоит:
- Внешнее описание, в которое входит ключевое слово
switch
. Переменная, по значениям которой switch будет выбирать поведение. И фигурные скобки для вариантов выбора. - Конструкции
case
иdefault
, внутри которых описывается поведение для разных значений рассматриваемой переменной. Каждыйcase
соответствуетif
в примере выше.default
– это особая ситуация, соответствующая веткеelse
в условных конструкциях. Какelse
, указыватьdefault
не обязательно (но линтер всегда его просит). break
нужен для предотвращения «проваливания». Если его не указать, то после выполнения нужногоcase
выполнение перейдет к следующемуcase
, и так либо до ближайшегоbreak
, либо до конца switch.
Фигурные скобки в switch не определяют блок кода, как это было в других местах. Внутри допустим только тот синтаксис, который показан выше. То есть там можно использовать case
или default
. А вот внутри каждого case
(и default
) ситуация другая. Здесь можно выполнять любой произвольный код:
switch (count) {
case 1:
// Делаем что-то полезное
break;
case 2:
// Делаем что-то полезное
break;
default:
// Что-то делаем
}
Иногда результат, полученный внутри case
, — это конец выполнения функции, содержащей switch. В таком случае его нужно как-то вернуть наружу. Для решения этой задачи есть два способа.
Первый. Создать переменную перед switch, заполнить её в case и затем, в конце, вернуть значение этой переменной наружу.
(count) => {
// Объявляем переменную
let result;
// Заполняем
switch (count) {
case 1:
result = 'one';
break;
case 2:
result = 'two';
break;
default:
result = null;
}
// Возвращаем
return result;
};
Второй способ проще и короче. Вместо создания переменной, case позволяет внутри себя делать обычный возврат из функции. А так как после return
никакой код не выполняется, то мы можем избавиться от break
:
(count) => {
switch (count) {
case 1:
return 'one';
case 2:
return 'two';
default:
return null;
}
};
Switch хоть и встречается в коде, но технически всегда можно обойтись без него. Ключевая польза при его использовании в том, что он лучше выражает намерение программиста, когда нужно проверять конкретные значения переменной. Хотя кода и стало физически чуть больше, читать его легче, в отличие от блоков else if.
Задание
Реализуйте функцию getNumberExplanation()
, которая принимает на вход число и возвращает объяснение этого числа. Если для числа нет объяснения, то возвращается null
:
getNumberExplanation(8); // null
// Объяснения есть только для указанных ниже чисел
getNumberExplanation(666); // 'devil number'
getNumberExplanation(42); // 'answer for everything'
getNumberExplanation(7); // 'prime number'
Советы
- switch
Циклы
Любой код может повторяться десятки, тысячи, миллионы раз. В комбинации с другими известными нам инструментами — переменными и условиями — это открывает множество возможностей по построению программ и сложных систем. Приведем простой пример. Вам нужно найти конкретную фразу в учебнике из 500 страниц. Фразу вы помните, а вот номер страницы нет. Самый простой (и долгий) способ — последовательно просматривать страницы до тех пор, пока не найдете нужную. Для выполнения таких повторяющихся действий и нужны циклы.
70. Цикл While
JavaScript: Цикл While
Программы, которые мы пишем, становятся всё сложнее и объемнее. Они все ещё очень далеки от реальных программ, где количество строк кода измеряется десятками и сотнями тысяч (а иногда и миллионами), но текущая сложность уже способна заставить напрячься людей без опыта. Начиная с этого урока, мы переходим к одной из самых сложных базовых тем в программировании – циклам.
Любые прикладные программы служат очень прагматичным целям. Они помогают управлять сотрудниками, финансами, развлекают в конце концов. Несмотря на различия, все эти программы выполняют заложенные в них алгоритмы, которые очень похожи между собой. Что это такое? Алгоритм — это последовательность действий (инструкций), которая приводит нас к некоему ожидаемому результату. В принципе, это описание подходит под любую программу, но под алгоритмами обычно понимается что-то более специфичное.
Представьте себе, что у нас есть книга и мы хотим найти внутри неё какую-то конкретную фразу. Саму фразу мы помним, но не знаем, на какой она странице. Как найти нужную страницу? Самый простой (и долгий) способ — последовательно просматривать страницы до тех пор, пока мы не найдем нужную. В худшем случае придется просмотреть все страницы, но результат мы всё равно получим. Именно этот процесс и называется алгоритмом. Он включает в себя логические проверки (нашли ли фразу) и перебор страниц. Количество страниц, которое придется посмотреть, заранее неизвестно, но сам процесс просмотра повторяется из раза в раз совершенно одинаковым образом. Для выполнения повторяющихся действий как раз и нужны циклы. Каждый повтор, в таком случае, называется итерацией.
Допустим мы хотим написать функцию, которая выводит на экран все числа от 1 до указанного (через аргументы):
printNumbers(3);
// => 1
// => 2
// => 3
Эту функцию невозможно реализовать уже изученными средствами, так как количество выводов на экран заранее неизвестно. А с циклами это не составит никаких проблем:
const printNumbers = (lastNumber) => {
// i сокращение от index (порядковый номер)
// используется по общему соглашению во множестве языков
// как счетчик цикла
let i = 1;
while (i <= lastNumber) {
console.log(i);
i = i + 1;
}
console.log('finished!');
};
printNumbers(3);
1 2 3 finished!
В коде функции использован цикл while
. Он состоит из трёх элементов:
- Ключевое слово
while
. Несмотря на схожесть с вызовом функций, это не вызов функции. - Предикат. Условие, которое указывается в скобках после
while
. Это условие вычисляется на каждой итерации. - Тело цикла. Блок кода в фигурных скобках. Этот блок аналогичен блоку кода в функциях. Всё, что определено внутри этого блока (константы или переменные), видно только внутри этого блока.
Конструкция читается так: «делать то, что указано в теле цикла, пока истинно условие (предикат) i <= lastNumber
». Разберём работу этого кода для вызова printNumbers(3)
:
// Инициализируется i
let i = 1;
// Предикат возвращает true, поэтому выполняется тело цикла
while (1 <= 3)
// console.log(1);
// i = 1 + 1;
// Закончилось тело цикла, поэтому происходит возврат в начало
while (2 <= 3)
// console.log(2);
// i = 2 + 1;
// Закончилось тело цикла, поэтому происходит возврат в начало
while (3 <= 3)
// console.log(3);
// i = 3 + 1;
// Предикат возвращает false, поэтому выполнение переходит за цикл
while (4 <= 3)
// console.log('finished!');
// На этом этапе i равен 4, но он нам уже не нужен
// функция завершается
Самое главное в цикле — завершение его работы (выход из цикла). Процесс, который порождает цикл, должен в конце концов остановиться. Ответственность за остановку полностью лежит на программисте. Обычно задача сводится к введению переменной, называемой «счётчиком цикла». Сначала счётчик инициализируется, то есть ему задаётся начальное значение. В нашем примере это инструкция let i = 1
, выполняемая до входа в цикл. Затем в условии цикла проверяется, достиг ли счётчик своего предельного значения. И, наконец, счётчик меняет свое значение i = i + 1
.
На этом моменте новички делают больше всего ошибок. Например, случайно забытое увеличение счётчика или неправильная проверка в предикате способны привести к зацикливанию. Это ситуация, при которой цикл работает бесконечно и программа никогда не останавливается. В таком случае приходится её завершать принудительно (кто знает, может быть когда зависают реальные программы, в этот момент внутри них выполняется бесконечный цикл).
const printNumbers = (lastNumber) => {
let i = 1;
// Этот цикл никогда не остановится
// и будет печатать всегда одно значение
while (i <= lastNumber) {
console.log(i);
}
console.log('finished!');
};
В некоторых случаях бесконечные циклы полезны. Здесь мы такие случаи не рассматриваем, но полезно видеть как выглядит этот код:
while (true) {
// Что-то делаем
}
Подводя итог. Когда всё же нужны циклы, а когда можно обойтись без них? Физически невозможно обойтись без циклов тогда, когда алгоритм решения задачи требует повторения каких-то действий, как в примере с книгой, и количество этих операций заранее неизвестно.
Задание
Модифицируйте функцию printNumbers()
так, чтобы она выводила числа в обратном порядке. Для этого нужно идти от верхней границы к нижней. То есть счётчик должен быть инициализирован максимальным значением, а в теле цикла его нужно уменьшать до нижней границы.
Пример вызова и вывода:
printNumbers(4);
4 3 2 1 finished!
Советы
- Цикл while
Определения
- Цикл While – инструкция для повторения кода, пока удовлетворяется какое-то условие.
71. Агрегация данных (Числа)
JavaScript: Агрегация данных (Числа)
Отдельный класс задач, который не может обойтись без циклов, называется агрегированием данных. К таким задачам относятся поиск максимального, минимального, суммы, среднего арифметического и т.п. Их главная особенность в том, что результат зависит от всего набора данных. Для рассчета суммы нужно сложить все числа, для вычисления максимального нужно сравнить все числа.
С такими задачами хорошо знакомы все, кто занимаются числами, например бухгалтеры или маркетологи. Обычно их выполняют в таблицах наподобие Microsoft Excel или Google Tables.
Разберем самый простой пример – поиск суммы набора чисел. Реализуем функцию, которая складывает числа в указанном диапазоне, включая границы. Диапазоном в данном случае называется ряд чисел от какого-то начала до определенного конца. Например, диапазон [1, 10] включает в себя все целые числа от 1 до 10.
sumNumbersFromRange(5, 7); // 5 + 6 + 7 = 18
sumNumbersFromRange(1, 2); // 1 + 2 = 3
// [1, 1] диапазон с одинаковым началом и концом – тоже диапазон
// он в себя включает ровно одно число – саму границу диапазона
sumNumbersFromRange(1, 1); // 1
sumNumbersFromRange(100, 100); // 100
Для реализации этого кода нам понадобится цикл, так как сложение чисел – это итеративный процесс (он повторяется для каждого числа), а количество итераций зависит от размера диапазона. Перед тем, как смотреть код, попробуйте ответьте на вопросы ниже:
- Каким значением инициализировать счетчик?
- Как он будет изменяться?
- Когда цикл должен остановиться?
Попробуйте сначала подумать над этими вопросами, а затем посмотрите код ниже:
const sumNumbersFromRange = (start, finish) => {
// Технически можно менять start
// Но входные аргументы нужно оставлять в исходном значении
// Это сделает код проще для анализа
let i = start;
let sum = 0; // Инициализация суммы
while (i <= finish) { // Двигаемся до конца диапазона
sum = sum + i; // Считаем сумму для каждого числа
i = i + 1; // Переходим к следующему числу в диапазоне
}
// Возвращаем получившийся результат
return sum;
};
Общая структура цикла здесь стандартна. Есть счетчик, который инициализируется начальным значением диапазона, есть сам цикл с условием остановки при достижении конца диапазона, и, наконец, изменение счетчика в конце тела цикла. Количество итераций в таком цикле равно finish - start + 1
. То есть для диапазона от 5 до 7 – это 7 – 5 + 1, то есть 3 итерации.
Главные отличия от обычной обработки связаны с логикой вычислений результата. В задачах на агрегацию всегда есть какая-то переменная, которая хранит внутри себя результат работы цикла. В коде выше это sum
. На каждой итерации цикла происходит её изменение, прибавление следующего числа в диапазоне: sum = sum + i
. Весь процесс выглядит так:
// Для вызова sumNumbersFromRange(2, 5);
let sum = 0;
sum = sum + 2; // 2
sum = sum + 3; // 5
sum = sum + 4; // 9
sum = sum + 5; // 14
// 14 – результат сложения чисел в диапазоне [2, 5]
У переменной sum
есть начальное значение, равное 0. Зачем вообще задавать значение? Любая повторяющаяся операция начинается с какого-то значения. Нельзя просто так объявить переменную и начать с ней работать внутри цикла. Это приведет к неверному результату:
// начальное значение не задано
// js автоматически делает его равным undefined
let sum;
// первая итерация цикла
sum = sum + 2; // ?
В результате такого вызова внутри sum
окажется NaN
, то есть не-число. Оно возникает из-за попытки сложить 2
и undefined
. Значит какое-то значение всё же нужно. Почему в коде выше выбран 0? Очень легко проверить, что все остальные варианты приведут к неверному результату. Если начальное значение будет равно 1, то результат получится на 1 больше, чем нужно.
В математике существует понятие нейтральный элемент операции (у каждой операции свой элемент). Это понятие имеет очень простой смысл. Операция с этим элементом не изменяет то значение, над которым проводится операция. В сложении любое число плюс ноль дает само число. При вычитании – тоже самое. Даже у конкатенации есть нейтральный элемент – это пустая строка: '' + 'one'
будет ‘one’.
Вопрос на самопроверку. Какой нейтральный элемент у операции умножения?
Задание
Реализуйте функцию multiplyNumbersFromRange()
, которая перемножает числа в указанном диапазоне включая границы диапазона. Пример вызова:
multiplyNumbersFromRange(1, 5); // 1 * 2 * 3 * 4 * 5 = 120
multiplyNumbersFromRange(2, 3); // 2 * 3 = 6
multiplyNumbersFromRange(6, 6); // 6
72. Агрегация данных (Строки)
JavaScript: Агрегация данных (Строки)
Агрегация применяется не только к числам, но и к строкам. Это такие задачи, в которых строка формируется динамически, то есть заранее неизвестно, какого она размера и что будет содержать.
Представьте себе функцию, которая умеет «умножать» строку, то есть она повторяет её указанное количество раз:
repeat('hexlet', 3); // 'hexlethexlethexlet'
Принцип работы этой функции довольно простой: в цикле происходит «наращивание» строки указанное количество раз:
const repeat = (text, times) => {
// Нейтральный элемент для строк – пустая строка
let result = '';
let i = 1;
while (i <= times) {
// Каждый раз добавляем строку к результату
result = `${result}${text}`;
i = i + 1;
}
return result;
};
Распишем выполнение этого кода по шагам:
// Для вызова repeat('hexlet', 3);
let result = '';
result = `${result}hexlet`; // hexlet
result = `${result}hexlet`; // hexlethexlet
result = `${result}hexlet`; // hexlethexlethexlet
Задание
Реализуйте функцию joinNumbersFromRange()
, которая объединяет все числа из диапазона в строку:
joinNumbersFromRange(1, 1); // '1'
joinNumbersFromRange(2, 3); // '23'
joinNumbersFromRange(5, 10); // '5678910'
73. Обход строк
JavaScript: Обход строк
Циклы подходят не только для обработки чисел, но и при работе со строками. В первую очередь благодаря возможности получить конкретный символ по его индексу. Ниже пример кода, который распечатывает буквы каждого слова на отдельной строке:
const printNameBySymbol = (name) => {
let i = 0;
// Такая проверка будет выполняться до конца строки
// включая последний символ. Его индекс `length - 1`.
while (i < name.length) {
// Обращаемся к символу по индексу
console.log(name[i]);
i = i + 1;
}
};
const name = 'Arya';
printNameBySymbol(name);
// => 'A'
// => 'r'
// => 'y'
// => 'a'
Самое главное в этом коде, поставить правильное условие в while
. Это можно сделать сразу двумя способами: i < name.length
или i <= name.length - 1
. Оба способа приводят к одному результату.
Задание
Реализуйте функцию printReversedNameBySymbol()
, которая печатает переданное слово посимвольно, как в примере из теории, но делает это в обратном порядке.
const name = 'Arya';
printReversedNameBySymbol(name);
// => 'a'
// => 'y'
// => 'r'
// => 'A'
74. Условия внутри тела цикла
JavaScript: Условия внутри тела цикла
Тело цикла, как и тело функции — это место выполнения инструкций. Значит, мы можем использовать внутри него всё изученное ранее, например — условные конструкции.
Представьте себе функцию, которая считает, сколько раз входит буква в предложение. Пример её работы:
countChars('Fear cuts deeper than swords.', 'e'); // 4
// Если вы ничего не нашли, то результат — 0 совпадений
countChars('Sansa', 'y'); // 0
Перед тем как посмотреть её содержимое, попробуйте ответить на вопросы:
- Является ли эта операция агрегацией?
- Какой будет проверка на вхождение символа?
const countChars = (str, char) => {
let i = 0;
let count = 0;
while (i < str.length) {
if (str[i] === char) {
// Считаем только подходящие символы
count = count + 1;
}
// Счетчик увеличивается в любом случае
i = i + 1;
}
return count;
};
Эта задача является агрегирующей. Несмотря на то, что она считает не все символы, для подсчета самой суммы все равно приходится анализировать каждый символ.
Ключевое отличие этого цикла от рассмотренных в наличии условия внутри тела. Переменная count
увеличивается только в том случае, когда текущий рассматриваемый символ совпадает с ожидаемым.
В остальном — это типичная агрегатная функция, которая возвращает количество нужных символов вызываемому коду.
Задание
Функция из теории учитывает регистр букв. То есть A
и a
с её точки зрения разные символы. Реализуйте вариант этой же функции, так чтобы регистр букв был не важен:
countChars('HexlEt', 'e'); // 2
countChars('HexlEt', 'E'); // 2
75. Формирование строк в циклах
JavaScript: Формирование строк в циклах
Ещё одно использование циклов – формирование строк. Подобная задача нередко встречается в веб-программировании. Она сводится к обычной агрегации с применением интерполяции или конкатенации.
Есть одна задача, крайне популярная среди людей, проводящих собеседования, это переворот строки. Её можно решить множеством разных способов, но именно посимвольный перебор считается самым базовым. Пример работы этой функции:
reverse('Hexlet'); // telxeH
Общая идея переворота состоит в следующем: нужно брать символы по очереди с начала строки и соединять их в обратном порядке. Звучит довольно просто. Давайте проверим:
const reverse = (str) => {
let i = 0;
// Нейтральный элемент для строк это пустая строка
let result = '';
while (i < str.length) {
// Соединяем в обратном порядке
result = `${str[i]}${result}`;
// То же самое через конкатенацию
// result = str[i] + result;
i = i + 1;
}
return result;
};
const name = 'Bran';
reverse(name); // 'narB'
// Проверка нейтрального элемента
reverse(''); // ''
Единственный возможно сложный момент в этом коде – прочувствовать, как собирается сама строка. Так как каждый следующий символ прикрепляется к результирующей строке слева, то, в конечном итоге, строка оказывается перевернута.
Задание
Реализуйте такую же функцию reverse()
, но выполняющую обход строки не с первого элемента по последний, а наоборот, от последнего к первому. Общая структура функции при этом останется такой же. Изменится начальный индекс, условие окончания цикла, сборка новой строки и формирование нового индекса в цикле.
76. Синтаксический сахар
JavaScript: Синтаксический сахар
Подобные конструкции index = index + 1
в JavaScript используются довольно часто, поэтому создатели языка добавили сокращённый вариант записи: index += 1
. Такие сокращения принято называть синтаксическим сахаром, потому что они делают процесс написания кода немного проще и приятнее, «подслащивая» его 🙂
Существуют сокращённые формы для всех арифметических операций и для конкатенации строк:
a = a + 1
→a += 1
a = a - 1
→a -= 1
a = a * 2
→a *= 2
a = a / 1
→a /= 1
a = a + 'foo'
→a += 'foo'
Задание
Реализуйте функцию filterString()
, принимающую на вход строку и символ, и возвращающую новую строку, в которой удален переданный символ во всех его позициях.
Пример вызова:
const str = 'If I look back I am lost';
filterString(str, 'I'); // 'f look back am lost'
filterString(str, 'o'); // 'If I lk back I am lst'
77. Инкремент и декремент
JavaScript: Инкремент и декремент
Из языка Си в JavaScript перекочевали две операции: инкремент ++
и декремент --
, которые очень часто встречаются вместе с циклами. Эти унарные операции увеличивают и уменьшают на единицу число, записанное в переменную:
let i = 0;
i++; // 0
i++; // 1
i--; // 2
i--; // 1
Кроме постфиксной формы, у них есть и префиксная:
let i = 0;
++i; // 1
++i; // 2
--i; // 1
--i; // 0
Кажется, что нет никакой разницы между постфиксной и префиксной формами. Но тут начинаются сложности.
В отличие от всех остальных операций, которые не имеют побочных эффектов и просто возвращают новое значение, инкремент и декремент не только возвращают значение, но и изменяют значение переменной.
При использовании префиксной нотации сначала происходит изменение переменной, а потом возврат.
При использовании постфиксной нотации — наоборот: можно считать, что сначала происходит возврат, а потом изменение переменной.
Правило работает одинаково для инкремента и декремента. Для простоты рассмотрим только инкремент:
let x = 5;
console.log(++x); // => 6
console.log(x); // => 6
console.log(x++); // => 6
console.log(x); // => 7
Что происходит?
- Вывели на экран
++x
. Это префиксный инкремент, поэтому сначала значение переменной увеличилось на 1, потом результат вернулся и вывелся на экран. - Так как значение изменилось,
console.log(x)
вывел 6. - Теперь выводим на экран
x++
. Это постфиксный инкремент, поэтому возвращено значение, содержавшееся в переменной до её увеличения на 1. - Так как значение изменилось,
console.log(x)
вывел 7.
Особенно страшным это становится тогда, когда инкремент вставляют внутрь других операций: x = i++ - 7 + --h
. Понять такой код почти невозможно, и его написание должно рассматриваться как тяжкое преступление.
Например, в языке JavaScript линтер (программа, проверяющая код) сразу начинает ругаться, когда встречает использование инкремента или декремента.
Рекомендации по использованию:
- Никогда не мешайте в рамках одного выражения операции/функции без побочных эффектов с операциями/функциями, обладающими побочными эффектами.
- Используйте инкремент и декремент только там, где нет разницы между префиксным и постфиксным вариантом: отдельно от всего, на своей собственной строчке кода.
Задание
Напишите функцию makeItFunny()
, которая принимает на вход строку и возвращает её копию, у которой каждый n-ный элемент переведен в верхний регистр. n – задается на входе в функцию.
Для определения каждого n-ного элемента понадобится остаток от деления %
. Подумайте, как его можно использовать.
Пример вызова:
const text = 'I never look back';
// Каждый третий элемент
makeItFunny(text, 3); // 'I NevEr LooK bAck'
Советы
- Оператор остатка от деления (%)
78. Возврат из циклов
JavaScript: Возврат из циклов
Работа с циклами обычно сводится к двум сценариям:
- Агрегация. Накопление результата во время итераций и работа с ним после цикла. Переворот строки как раз относится к такому варианту.
- Выполнение цикла до достижения необходимого результата и выход. Например, задача поиска простых чисел. Напомним, что простое число — это число, которое делится без остатка только на себя и на единицу.
Рассмотрим простой алгоритм проверки простоты числа. Будем делить искомое число x
на все числа из диапазона от двух до x - 1
и смотреть остаток от деления. Если в этом диапазоне не найден делитель, который делит число x
без остатка, значит перед нами простое число.
Если задуматься, то можно заметить, что достаточно проверять числа не до x - 1
, а до половины числа. Например, 11 не делится на 2, 3, 4, 5. Но и дальше гарантированно не будет делиться на числа больше своей половины. Значит, можно провести небольшую оптимизацию и проверять деление только до x / 2
.
const isPrime = (number) => {
if (number < 2) {
return false;
}
let divider = 2;
while (divider <= number / 2) {
if (number % divider === 0) {
return false;
}
divider += 1;
}
return true;
}
isPrime(1); // false
isPrime(2); // true
isPrime(3); // true
isPrime(4); // false
Алгоритм построен таким образом, что если во время последовательного деления на числа до x / 2
находится хоть одно, которое делит без остатка, то переданный аргумент — не простое число, а значит дальнейшие вычисления не имеют смысла. В этом месте стоит возврат false
.
И только если цикл отработал целиком, можно сделать вывод, что число — простое, так как не было найдено ни одного числа, которое делит число без остатка.
Задание
Реализуйте функцию hasChar()
, которая проверяет (с учётом регистра), содержит ли строка указанную букву. Функция принимает два параметра:
- Строка
- Буква для поиска
Пример вызова:
hasChar('Renly', 'R'); // true
hasChar('Renly', 'r'); // false
hasChar('Tommy', 'm'); // true
hasChar('Tommy', 'd'); // false
Советы
- Список простых чисел
79. Цикл For
JavaScript: Цикл For
Цикл while
идеален для ситуаций, когда количество итераций неизвестно заранее, например, при поиске простого числа. Когда количество итераций известно, предпочтительнее использовать цикл for
.
Посмотрим реализацию переворота строки через цикл for
:
const reverseString = (str) => {
let result = '';
for (let i = 0; i < str.length; i += 1) {
result = `${str[i]}${result}`;
}
return result;
};
Можно читать так: цикл с индексом i
повторяется пока i < str.length
и после каждого шага увеличивает i
на 1.
В определении цикла for
есть:
- Начальное значение счётчика. Этот код выполняется ровно один раз перед первой итерацией.
- Предикат — условие повторения циклов. Выполняется на каждой итерации. Точно так же как и в
while
- Описание изменения счётчика. Этот код выполняется в конце каждой итерации.
В остальном принцип работы точно такой же, как у цикла while
.
Задание
Сэмвелл обнаружил, что его сообщения перехватываются в замке «Близнецы» и там читаются. Из-за этого их атаки перестали быть внезапными. Немного подумав, он разработал программу, которая бы шифровала сообщения по следующему алгоритму. Она бы брала текст и переставляла в нем каждые два подряд идущих символа.
encrypt('attack'); // 'taatkc'
// Если число символов нечётное
// то последний символ остается на своем месте
encrypt('go!'); // 'og!'
Реализуйте функцию encrypt()
, которая принимает на вход исходное сообщение и возвращает зашифрованное.
Подумайте. Может ли эта функция расшифровать зашифрованное сообщение?