Перейти к основному содержимому
Блокчейн22ГЛАВА 22Условия и циклы: как программа принимает решенияif

Условия и циклы: как программа принимает решения

Прочитано0%
Квизы0/5
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

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

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

Эта глава разбирает их до последнего символа: где ставятся скобки и почему, чем && отличается от &, почему switch без break проваливается в следующую ветку, чем for..of отличается от for..in, и почему цикл на var печатает три тройки, а на let — ноль, один, два. Все результаты и все сообщения об ошибках сняты с живого запуска Node.js 24 и TypeScript 6.

if: развилка из одного вопроса​

Самая простая форма выбора — «если условие истинно, выполни блок».

const height = 152340;

if (height > 0) {
console.log('нода что-то знает о цепочке');
}

Разбор по частям — здесь важен каждый символ:

Нажми на часть выражения

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

if (height > 0)
console.log('первая'); // это внутри условия
console.log('вторая'); // а это уже снаружи, выполнится всегда

Ошибки не будет. Программа просто будет работать неправильно — самая дорогая разновидность бага.

Полная форма развилки добавляет вторую ветку:

if (height > 0) {
console.log('цепочка есть');
} else {
console.log('высота нулевая');
}
ОДНА ВЕТКА ИЗ ДВУХ, НИКОГДА ОБЕкод вышеbalance>= pricetrueветка iffalseветка elseдальшепосле развилки пути снова сходятся в одну дорогу
Условие — это развилка: выполняется ровно одна из веток, никогда обе. После развилки пути снова сходятся в одну дорогу.

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

Из чего собирают условие: сравнения и логика​

Условие — это выражение, дающее true или false. Собирают его из сравнений и логических операторов.

Сравнения:

ОператорЧитаетсяПример
===строго равноstatus === 'confirmed'
!==строго не равноstatus !== 'unknown'
> <больше, меньшеheight > 0
>= <=больше или равно, меньше или равноamount >= fee

Про == и != помним из прошлой главы: они приводят типы перед сравнением, и в новом коде их не пишут, кроме идиомы value == null.

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

ОператорЧитаетсяРезультат
&&Иистинно, когда истинны обе стороны
два знака | подрядИЛИистинно, когда истинна хотя бы одна сторона
!НЕпереворачивает логическое значение
if (res.ok && data.height > 0) { /* обе стороны истинны */ }
if (!res.ok) { /* res.ok ложно */ }

Два свойства логических операторов, которые важнее, чем кажутся.

Первое: вычисление короткое. В выражении a && b правая часть вообще не вычисляется, если левая ложна: результат уже известен. То же с ИЛИ: правая часть не вычисляется, если левая истинна. Это не оптимизация, а часть языка, на которой держится очень частый приём — защита от обращения к отсутствующему полю:

if (tx.sender && tx.sender.address === recipient) { /* безопасно */ }

Если tx.sender отсутствует, до второй части дело не дойдёт, и TypeError: Cannot read properties of undefined не случится.

Второе: && и || возвращают не логическое значение, а один из операндов. 0 || 'запасное' даёт строку, а не true. Именно поэтому они работают как «значение по умолчанию» — и именно поэтому для этой цели чаще нужен ??, который не считает ноль пустотой.

Порядок вычисления: сначала сравнения, потом &&, потом ||, а ! вычисляется раньше всего. Когда сомневаешься — ставь круглые скобки. Лишние скобки ничего не стоят, а неверный порядок ищется полдня.

Важно

Не путай && с & и || с |. Одинарные знаки — это побитовые операторы, они работают с двоичным представлением чисел и в условиях почти всегда означают опечатку. Особенно коварно &: выражение if (a & b) иногда даёт правильный результат случайно, потому что 1 & 1 равно 1, и ошибка живёт в коде месяцами.

Лестница else if и почему лучше ранний выход​

Когда вариантов больше двух, ветки выстраивают лестницей:

if (status === 'confirmed') {
console.log('в блоке');
} else if (status === 'unconfirmed') {
console.log('в очереди');
} else if (status === 'unknown') {
console.log('нода про неё не слышала');
} else {
console.log('неизвестный статус: ' + status);
}

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

У лестницы есть неприятное свойство: она растёт вправо, когда условия вкладываются друг в друга.

// так пишут первые две недели
function describe(tx) {
if (tx) {
if (tx.status) {
if (tx.status === 'confirmed') {
return 'в блоке';
}
}
}
return 'непонятно';
}

Тот же код с ранним выходом (в англоязычной литературе — guard clause, «сторожевая проверка»):

function describe(tx) {
if (!tx) return 'непонятно';
if (!tx.status) return 'непонятно';
if (tx.status !== 'confirmed') return 'непонятно';
return 'в блоке';
}

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

Тернарный оператор: развилка внутри выражения​

Когда развилка нужна прямо внутри выражения, пишут тернарный оператор — единственный оператор языка с тремя частями:

const h = 7;
console.log(h % 2 === 0 ? 'чётная' : 'нечётная'); // нечётная
Нажми на часть выражения

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

const label = `транзакция ${confirmed ? 'подтверждена' : 'в очереди'}`;

Вкладывать тернарные операторы друг в друга технически можно, но читать такое невозможно. Два уровня — уже повод переписать на if или switch.

Отвечено вопросов: 0 из 4
Почему фигурные скобки у if лучше писать всегда, даже для одной строки?
Что делает короткое вычисление в выражении tx.sender && tx.sender.address?
Чем ранний выход лучше вложенных условий?
В чём принципиальная разница между if и тернарным оператором?
Отвечено верно: 0 из 4

switch и знаменитое проваливание​

Когда одно значение сравнивают с набором вариантов, пишут switch. Живой запуск — и результат, который удивляет всех:

switch (2) {
case 1: console.log('один');
case 2: console.log('два');
case 3: console.log('три'); break;
default: console.log('другое');
}
два
три

Напечаталось две строки. Причина: switch не выбирает ветку, он ищет точку входа и дальше выполняет всё подряд до первого break или до конца. Ветка case 2 подошла, начали печатать с неё, case 3 не имеет break перед собой — и выполнилась тоже. Это поведение называется проваливанием (fall-through), и оно не ошибка языка, а осознанная возможность: иногда несколько значений обрабатывают одинаково.

SWITCH ИЩЕТ ТОЧКУ ВХОДА, А НЕ ВЫБИРАЕТ ВЕТКУswitch (status)status === 'pending'case 'new'мимо: не совпалоcase 'pending'вход сюдаcase 'mined'выполнится тоже!defaultсюда уже не дойдётв консоли две строки:ждём подтверждениязаписано в блокbreak — это выход; без него выполнение течёт вниз
switch ищет точку входа, а не выбирает ветку. Без break выполнение продолжается вниз через следующие case — в этом смысл записи нескольких case подряд.

Правильная форма — с break в каждой ветке:

switch (status) {
case 'confirmed':
console.log('в блоке');
break;
case 'unconfirmed':
case 'unknown': // намеренное проваливание: два значения, одна обработка
console.log('ещё не в блоке');
break;
default:
console.log('неизвестный статус');
}

Сравнение в switch строгое, как ===. Строка '1' не подойдёт к ветке case 1, и TypeScript про это скажет заранее:

error TS2678: Type '"1"' is not comparable to type '1'.

Перевод: «тип "1" несопоставим с типом 1». В чистом JavaScript такая ветка просто никогда не сработает, молча.

Цикл for: три части в скобках​

Цикл повторяет блок кода. Классическая форма выглядит страшно ровно до того момента, пока не разберёшь её на части.

for (let i = 0; i < 3; i++) {
process.stdout.write(i + ' ');
}
// 0 1 2
Нажми на часть выражения

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

Имя счётчика традиционно i — от английского index. Вложенные циклы используют j, потом k. Это одно из немногих мест, где однобуквенное имя считается хорошим стилем.

i++ — сокращение для i = i + 1. Есть и i--, и i += 2 (увеличить на два), и i *= 2.

ТРИ ЧАСТИ ЗАГОЛОВКА ЦИКЛАfor (let i = 0; i < 3; i++)началопроверкашаг1. начало2. проверка3. тело цикла4. шагусловие ложно — выход из циклакольцо: проверка — тело — шагзабыли шаг — условие никогда не станет ложным, цикл вечный
Три части заголовка цикла и порядок их выполнения: начало один раз, дальше кольцо «проверка — тело — шаг».

for..of: перебор элементов, а не индексов​

Классический for нужен, когда важен номер. Когда важны сами элементы — пишут for..of, и он короче и безопаснее:

const txs = ['a', 'b', 'c'];
for (const t of txs) {
process.stdout.write(t + ' ');
}
// a b c

В переменную t попадает элемент, а не индекс. Объявляют её через const: на каждом проходе создаётся новое имя, переприсваивать его не нужно.

Если номер всё-таки нужен, элементы берут вместе с индексом:

for (const [i, t] of txs.entries()) {
process.stdout.write(`${i}:${t} `);
}
// 0:a 1:b 2:c

Квадратные скобки слева — это деструктуризация: entries() отдаёт пары «индекс, значение», а запись [i, t] разбирает пару на две переменные. Подробно разберём в главе про коллекции.

for..of работает не только с массивами, а со всем, что язык считает перебираемым: строки (по символам), Map, Set. С обычным объектом — не работает, и вот живая ошибка при попытке перебрать число:

TypeError: 5 is not iterable

Перевод: «5 не является перебираемым». TypeScript скажет то же самое заранее и подробнее:

error TS2488: Type '5' must have a '[Symbol.iterator]()' method that returns an iterator.

for..in: ключи объекта, и почему им не перебирают массивы​

Похожая по написанию конструкция с совершенно другим смыслом:

const obj = { height: 5, id: 'x' };
for (const k in obj) {
process.stdout.write(k + '=' + obj[k] + ' ');
}
// height=5 id=x

for..in перебирает ключи объекта. Для перебора полей это правильный инструмент. Но у него есть ловушка: применённый к массиву, он выдаёт индексы, причём строками:

const a = ['x', 'y'];
for (const i in a) console.log(typeof i, i);
// string 0
// string 1

Индекс '0' — строка. Попытка сложить его с числом даст склейку, а не сумму. Плюс for..in обходит и унаследованные свойства, что в редких случаях приносит лишние ключи.

Правило без исключений: массивы перебирают через for..of или методы массива, объекты — через for..in или Object.keys. Разница между of и in — два символа, а последствия разные, поэтому проговори её вслух: of — значения, in — ключи.

ЧТО ПОПАДАЕТ В ПЕРЕМЕННУЮconst coins = ['btc', 'eth', 'ton'];forпеременнаяiполучает0, 1, 2счётчик, индексfor..ofпеременнаяcoinполучает'btc', 'eth'…сам элементfor..inпеременнаяkeyполучает'0', '1', '2'ключ, СТРОКАwhileпеременная—получаетничегосчитаешь самнужен и индекс, и элемент — for..of вместе с .entries()по массиву for..in не ходят: ключи приходят строками
Четыре формы цикла и то, что попадает в переменную: for — счётчик, for..of — элемент, for..in — ключ, while — ничего, условие проверяешь сам.

while и do..while: цикл без счётчика​

Когда заранее неизвестно, сколько будет проходов, пишут while — «пока условие истинно».

let n = 3;
while (n > 0) {
process.stdout.write(n + ' ');
n--;
}
// 3 2 1

Условие проверяется перед каждым проходом, поэтому тело может не выполниться ни разу. У do..while проверка стоит в конце, и тело гарантированно выполняется хотя бы один раз:

let m = 0;
do {
process.stdout.write('раз ');
m++;
} while (m < 1);
// раз

do..while встречается редко, но его стоит узнавать в чужом коде.

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

let n = 3;
while (n > 0) {
console.log(n); // n никогда не уменьшается — программа зависнет навсегда
}

Node не выдаст ошибку. Программа просто перестанет отвечать, процессор загрузится на сто процентов, и остановить её придётся вручную — в терминале сочетанием Ctrl+C.

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

let attempts = 0;
while (attempts < 20) {
const status = await fetchStatus(id);
if (status === 'confirmed') break;
attempts++;
await sleep(3000);
}
Важно

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

break, continue и метки​

Два слова управляют ходом цикла изнутри:

  • break — выйти из цикла целиком, немедленно.
  • continue — пропустить остаток тела и перейти к следующему проходу.

Живой запуск, где есть оба:

for (let i = 0; i < 5; i++) {
if (i === 2) continue; // двойку пропускаем
if (i === 4) break; // на четвёрке выходим
process.stdout.write(i + ' ');
}
// 0 1 3

Разбор вывода: 0 напечаталось, 1 напечаталось, на 2 сработал continue и печать пропустилась, 3 напечаталось, на 4 сработал break и цикл закончился. Числа 4 в выводе нет — break сработал до печати.

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

outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) continue outer; // перейти к следующему i
process.stdout.write(`${i}${j} `);
}
}
// 00 10 20

Метки — редкий инструмент, и злоупотреблять ими не стоит. Но узнавать в чужом коде надо: конструкция outer: перед for выглядит загадочно, пока не знаешь, что это просто имя.

Ловушка var в цикле: три тройки​

Один из самых известных примеров в истории JavaScript. Два почти одинаковых цикла, разный результат — живой вывод:

const fns = [];
for (var i = 0; i < 3; i++) fns.push(() => i);
console.log(fns.map(f => f())); // [ 3, 3, 3 ]

const fns2 = [];
for (let i = 0; i < 3; i++) fns2.push(() => i);
console.log(fns2.map(f => f())); // [ 0, 1, 2 ]

Объяснение: var создаёт одну переменную на всю функцию. Все три функции запомнили одно и то же имя, а к моменту вызова цикл уже закончился и в этом имени лежит 3. let создаёт новую переменную на каждом проходе — каждая функция запомнила свою.

Это ещё одна причина, по которой var в новом коде не пишут, и повод один раз посмотреть на пример своими глазами: пересказ такого поведения запоминается хуже, чем вывод [ 3, 3, 3 ] на собственном экране.

Отвечено вопросов: 0 из 5
В каком порядке выполняются части заголовка for (let i = 0; i < 3; i++)?
Чем for..of отличается от for..in?
switch(2) с ветками case 1, case 2 без break, case 3 с break напечатал две строки. Почему?
Почему цикл на var с тремя замыканиями печатает [3, 3, 3]?
Что обязательно должно быть у цикла, ожидающего подтверждения транзакции?
Отвечено верно: 0 из 5

Методы массива вместо циклов​

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

console.log([1, 2, 3].map(x => x * 2)); // [ 2, 4, 6 ]
console.log([1, 2, 3].filter(x => x > 1)); // [ 2, 3 ]
console.log([1, 2, 3].reduce((a, b) => a + b, 0)); // 6
  • map — «преобразуй каждый элемент», возвращает новый массив той же длины.
  • filter — «оставь подходящие», возвращает новый массив, обычно короче.
  • reduce — «сверни в одно значение», возвращает что угодно: сумму, объект, строку.
  • forEach — «сделай что-нибудь для каждого», ничего не возвращает.

Правило выбора: если из списка получается другой список — map или filter; если одно значение — reduce; если нужен побочный эффект вроде печати — forEach или обычный цикл.

У forEach есть важное ограничение, которое ловят все: из него нельзя выйти досрочно. break внутри него не компилируется, а return выходит только из текущего вызова, не из перебора:

[1, 2].forEach(x => {
if (x === 1) return; // это не «выйти из цикла», а «закончить обработку одного элемента»
console.log('видно', x);
});
// видно 2

Единица пропущена, но перебор продолжился. Если нужен настоящий досрочный выход — пиши for..of с break.

Отвечено вопросов: 0 из 3
Какой метод выбрать, чтобы из списка сумм получить список тех же сумм, умноженных на курс?
Какой метод вернёт одно число — сумму всех комиссий?
Почему из forEach нельзя выйти досрочно?
Отвечено верно: 0 из 3

Что добавляет TypeScript к условиям и циклам​

Синтаксис тот же — в этих конструкциях TypeScript не добавляет ни одного нового символа. Он добавляет проверки, и их три вида.

Первая: тип элемента известен внутри цикла.

const arr: number[] = [1, 2, 3];
for (const a of arr) { a.toUpperCase(); }
error TS2339: Property 'toUpperCase' does not exist on type 'number'.

Компилятор знает, что в a число, и метод строки у него вызвать не даст.

Вторая: сужение типа внутри ветки. После проверки компилятор считает значение уточнённым:

function label(value: string | number): string {
if (typeof value === 'number') {
return value.toFixed(2); // здесь value — точно число
}
return value.toUpperCase(); // а здесь — точно строка
}

Это называется сужением (narrowing) и работает с typeof, сравнением с литералом, Array.isArray, проверкой поля через in. Никакого кода в собранный JavaScript при этом не добавляется — вся работа происходит на этапе компиляции.

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

function label(h: number): string {
if (h > 0) { return 'есть'; }
}
error TS2366: Function lacks ending return statement and return type does not include 'undefined'.

Перевод: «в функции нет завершающего return, а тип результата не включает undefined». Это ровно тот случай, когда лестница if забыла последний else, — и TypeScript ловит его до запуска.

Ещё одна полезная мелочь: случайное присваивание вместо сравнения внутри условия TypeScript тоже не пропускает, если переменная объявлена через const:

error TS2588: Cannot assign to 's' because it is a constant.
Отвечено вопросов: 0 из 3
Сколько новых символов добавляет TypeScript в синтаксис условий и циклов?
Что такое сужение типа?
error TS2366: Function lacks ending return statement. О чём говорит эта ошибка?
Отвечено верно: 0 из 3

Типичные ошибки: текст, перевод, причина, починка​

СообщениеПереводЧто произошлоКак чинить
TypeError: 5 is not iterable5 не перебираемоеВ for..of подставили не массив и не строкуПроверить, что перебирается: Array.isArray(value)
error TS2488: Type '5' must have a '[Symbol.iterator]()' methodТип не является перебираемымТо же самое, но поймано до запускаИсправить тип значения в for..of
error TS2339: Property 'toUpperCase' does not exist on type 'number'У числа нет такого методаВнутри цикла к элементу применили метод другого типаПосмотреть тип элементов массива
error TS2366: Function lacks ending return statementНет завершающего returnОдна из веток условия ничего не возвращаетДобавить последний else или return в конце
error TS2678: Type '"1"' is not comparable to type '1'Строка несопоставима с числомВ switch сравнивают значения разных типовПривести значение или исправить case
error TS2588: Cannot assign to 's' because it is a constantПрисваивание константеВ условии написали = вместо ===Исправить на сравнение
Программа зависла, ошибки нет—Бесконечный цикл: условие никогда не станет ложнымCtrl+C, затем добавить изменение переменной или счётчик попыток
Цикл ничего не напечатал—Условие ложно с самого началаПроверить начальное значение счётчика и знак сравнения
Пропущен последний элемент—Условие i < arr.length - 1 вместо i < arr.lengthПроверить границу: индексы идут от 0 до length - 1

Тренажёр: конструкции этой главы​

Цель: скорость ≥ 110 зн/мин, точность ≥ 90%

if (!res.ok) throw new Error(`нода ответила ${res.status}`);
клавиша ``клавиша 11клавиша 22клавиша 33клавиша 44клавиша 55клавиша 66клавиша 77клавиша 88клавиша 99клавиша 00клавиша --клавиша ==клавиша ⌫⌫клавиша TabTabклавиша QQклавиша WWклавиша EEклавиша RRклавиша TTклавиша YYклавиша UUклавиша IIклавиша OOклавиша PPклавиша [[клавиша ]]клавиша \\клавиша Caps LockCaps Lockклавиша AAклавиша SSклавиша DDклавиша FFклавиша GGклавиша HHклавиша JJклавиша KKклавиша LLклавиша ;;клавиша ''клавиша EnterEnterклавиша ShiftShiftклавиша ZZклавиша XXклавиша CCклавиша VVклавиша BBклавиша NNклавиша MMклавиша ,,клавиша ..клавиша //клавиша ShiftShiftклавиша CtrlCtrlклавиша WinWinклавиша AltAltклавиша ПробелПробелклавиша AltAltклавиша WinWinклавиша MenuMenuклавиша CtrlCtrl
следующая: I — средний правой руки

Символы этой главы​

ЗнакНазваниеЧто делает
( )круглые скобкидержат условие у if, while, switch и три части у for
{ }фигурные скобкитело блока: то, что выполняется или повторяется
&&логическое Иистинно, когда истинны обе стороны; правая не вычисляется при ложной левой
два знака | подрядлогическое ИЛИистинно, когда истинна хотя бы одна сторона
!логическое НЕпереворачивает истинность
? :тернарный операторразвилка внутри выражения
%остаток от деленияh % 2 === 0 — проверка на чётность
++ --инкремент, декрементувеличить или уменьшить на единицу
+= *=составное присваиваниеi += 2 — то же, что i = i + 2
: после имени цикламеткаимя цикла для break outer и continue outer
; внутри forразделитель частейровно два внутри круглых скобок

Что должен уметь​

  • Писать if и else с фигурными скобками и объяснять, чем опасно их отсутствие.
  • Собирать условие из сравнений и логических операторов и объяснять короткое вычисление.
  • Отличать && от & и понимать, почему одинарный знак в условии — почти всегда опечатка.
  • Переписывать вложенные условия на ранний выход.
  • Использовать тернарный оператор там, где нужен результат, и не вкладывать его в себя.
  • Писать switch с break и объяснять проваливание, а также применять его намеренно.
  • Разбирать заголовок for на три части и проговаривать порядок выполнения.
  • Выбирать между for, for..of, for..in и while по задаче.
  • Помнить, что for..in по массиву даёт строковые индексы.
  • Ставить у любого цикла ожидания ограничение по числу попыток.
  • Пользоваться break и continue, в том числе с метками во вложенных циклах.
  • Объяснять, почему цикл на var с замыканиями печатает три одинаковых числа.
  • Заменять простые циклы на map, filter, reduce и знать, что из forEach нельзя выйти досрочно.
  • Читать ошибки TS2488, TS2339, TS2366, TS2678 и понимать, какая конструкция их вызвала.
  • Узнавать зависание по бесконечному циклу и останавливать процесс через Ctrl+C.
Углубиться

Почему for..of работает не со всем. Перебор в языке устроен через протокол итератора: значение считается перебираемым, если у него есть метод с ключом Symbol.iterator. У массивов, строк, Map и Set он есть, у обычных объектов — нет. Именно этот метод и упомянут в тексте ошибки TS2488. Свой объект можно сделать перебираемым, добавив такой метод, — на этом же протоколе работает и распространение через ....

Стоимость вложенных циклов. Цикл внутри цикла по одному и тому же списку из n элементов выполняет тело n × n раз. Для сотни элементов это десять тысяч проходов — незаметно. Для десяти тысяч — сто миллионов, и скрипт начинает думать секундами. На чемпионате это встречается в задачах вида «найти пару транзакций с одинаковым адресом»: наивное решение с двумя циклами работает, но не укладывается в лимит, а решение через Map проходит за один проход.

switch и Map. Длинный switch по строковому значению часто заменяют объектом или Map, где ключ — значение, а значение — обработчик. Кода получается меньше, а добавление нового варианта не требует правки условной конструкции.

Исчерпывающая проверка union. Если статус описан union-типом, а в switch разобраны не все варианты, TypeScript может об этом сообщить — для этого в ветке default присваивают значение переменной типа never. При добавлении нового статуса в тип компилятор укажет на этот switch. Приём называется exhaustiveness checking и очень выручает в проектах, где набор состояний растёт.

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

Дальше по теме:

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

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоОсновы JavaScript | Урок 4: Условия и циклыGlo Academy
ВидеоЦиклы в JavaScript — while, do while и for. Прерывание цикла break. Продолжение цикла continue.Александр Ламков — Friendly Frontend
ВидеоЦиклы FOR, WHILE в JS: полный курс с примерами и задачамиPomazkov JS
ВидеоЦиклы FOR и WHILE в JavaScript. Директивы break и continueФрілансер по життю
ВидеоЦикл for в JavaScript. JS для начинающихWebDev с нуля. Канал Алекса Лущенко

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.