JS · Урок 8

Функции, условия и циклы

Теперь научимся программировать: принимать решения, повторять действия и собирать логику в функции.

Условия: if / else

Программа принимает решения по условию:

const age = 17;

if (age >= 18) {
  console.log("Добро пожаловать!");
} else if (age >= 14) {
  console.log("Нужно разрешение родителей");
} else {
  console.log("Страница доступна взрослым");
}
💡
Операторы сравнения: === строгое равенство, !==, >, <, >=, <=. Логические связки — && (и), || (или), ! (не).

Тернарный оператор

Короткая запись «если — иначе» за одну строку:

const time = 20;
const greeting = time < 12 ? "Доброе утро" : "Добрый день/вечер";

Циклы

Циклы повторяют код. Считаем от 1 до 5:

for (let i = 1; i <= 5; i++) {
  console.log("Раз " + i);
}

let n = 0;
while (n < 3) {
  console.log("привет");
  n++;
}

Чаще всего цикл нужен, чтобы перебрать массив:

const langs = ["HTML", "CSS", "JS"];

for (const lang of langs) {
  console.log("Изучаю: " + lang);
}

for (let i = 0; i < langs.length; i++) {
  console.log(i + ": " + langs[i]);   // с индексами
}

Функции

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

function add(a, b) {
  return a + b;
}

const sum = add(2, 3);   // 5

const multiply = (a, b) => a * b;  // стрелочная функция
multiply(4, 5);                   // 20
ℹ️
Стрелочные функции () => {} — современный стиль короче: без function и return, если тело в одну строку.

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

Современный код часто использует встроенные методы:

const nums = [1, 2, 3, 4];

const doubled = nums.map(n => n * 2);      // [2,4,6,8]
const even = nums.filter(n => n % 2 === 0);  // [2,4]
const total = nums.reduce((acc, n) => acc + n, 0); // 10

Живой пример: цикл + условие

Классика: вывести числа от 1 до 15, где кратные 3 — как «Fizz», кратные 5 — как «Buzz»:

switch — выбор из многих вариантов

Когда веток больше двух-трёх и они сравнивают по одному значению, switch читается лучше цепочки if:

const role = "admin";

switch (role) {
  case "admin":
    console.log("Полный доступ");
    break;
  case "moderator":
    console.log("Права модератора");
    break;
  default:
    console.log("Гость");
}
⚠️
Без break выполнение «провалится» в следующий case — это популярный источник скрытых багов.

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

&& и || возвращают не только true/false, а само значение — если результат уже ясен, остальное не вычисляется:

const user = { name: "Аня" };
const name = user.name || "Гость";   // если пусто — подставится "Гость"

const isAdmin = true;
isAdmin && console.log("Кнопка «Удалить» показана"); // выполнится только если true

console.log("" || "текст");   // "текст"
console.log(0 && "никогда"); // 0 (0 — «ложное» значение)
💡
«Ложные» значения в JS: false, 0, "", null, undefined, NaN. Всё остальное — «истинное», включая пустые массивы и объекты.

break и continue в циклах

Цикл можно досрочно остановить или пропустить итерацию:

for (let i = 1; i <= 6; i++) {
  if (i === 3) continue;  // пропустить тройку, цикл идёт дальше
  if (i === 5) break;     // остановиться на пятёрке
  console.log(i);             // 1, 2, 4
}
ℹ️
continue — «следующая итерация», break — «хватит». break работает и в switch.

Область видимости переменных

let и const живут только внутри своего блока { ... }, а var «вылезает» за его пределы — отсюда баги:

if (true) {
  let a = 1;        // только внутри условия
  var b = 2;        // утечёт наружу!
}
console.log(a);  // ReferenceError: a is not defined
console.log(b);  // 2 — работает, но почему? var не знает блока
⚠️
Именно поэтому var не используют: его «весёлая» область видимости порождает труднонаходимые ошибки. Современный код — только const/let.

Объект Math

Стандартный набор математических функций — без подключения библиотек:

Math.round(4.6);     // 5 — округление
Math.floor(4.9);     // 4 — вниз
Math.ceil(4.1);      // 5 — вверх
Math.random();          // число от 0 до 1
Math.max(1, 9, 4);  // 9
Math.min(1, 9, 4);  // 1

const dice = Math.floor(Math.random() * 6) + 1; // кубик от 1 до 6
💡
Формула «случайного целого от A до B»: A + Math.floor(Math.random() * (B - A + 1)). Выигрывает в лотереях казино и играх на сайтах.

Цикл по объекту: for...in и Object.keys

Объекты перебираются иначе, чем массивы. for...in идёт по ключам, а утилиты Object.* отдают ключи, значения или пары массивом:

const config = { theme: "dark", lang: "ru" };

for (const key in config) {
  console.log(key, config[key]);   // theme dark, lang ru
}

Object.keys(config);      // ["theme", "lang"]
Object.values(config);    // ["dark", "ru"]
Object.entries(config);   // [["theme","dark"],["lang","ru"]]
💡
Object.entries + destructuring — самый элегантный перебор: for (const [key, value] of Object.entries(config)).

Параметры по умолчанию и rest

Функции могут подставлять значения, если аргумент не передан, и «собирать» все лишние аргументы в массив:

function greet(name = "Гость") {
  return `Привет, ${name}!`;
}
greet();                    // "Привет, Гость!"

function sumAll(...nums) {   // rest-параметр
  return nums.reduce((acc, n) => acc + n, 0);
}
sumAll(1, 2, 3, 4);   // 10

const nums = [1, 2, 3];
Math.max(...nums);         // 3 — распаковка массива в аргументы
ℹ️
Разница на память: ... перед параметром функции — «rest» (собрать), перед массивом в вызове — «spread» (распаковать).

Ранний выход (early return)

Стиль «охрана у входа» ускоряет чтение кода: редкие случаи проверяем в начале и выходим, основную логику оставляем без каскада вложенных if:

function moveUser(user, where) {
  if (!user) return;                 // нет пользователя — дальше не идём
  if (!where) return "Куда едем?";

  // основная логика без лишней вложенности
  return `Перемещаем ${user.name} в ${where}`;
}
💡
Чем меньше вложенных блоков, тем легче код читать. Валидации в начале + «основной путь» без отступа — признак зрелого стиля.

Замыкания: функция внутри функции

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

function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
counter();   // 1
counter();   // 2
counter();   // 3 — count жив внутри
⚠️
Замыкание «хранит» переменную, а не её значение. Если внешние переменные меняются, внутренняя функция увидит новые значения на момент вызова.

Обработка ошибок: try / catch

Код, который может упасть (запрос к сети, работа с файлом), оборачивают в try, а реакцию на падение кладут в catch:

try {
  JSON.parse("это не json");
} catch (err) {
  console.error("Не удалось разобрать данные:", err.message);
} finally {
  console.log("Выполнится в любом случае");
}
💡
Catch перехватывает ошибку, и скрипт не «умирает». Но ловите только то, что реально может выстрелить: оборачивание всего подряд прячет баги.

forEach: прогулка по массиву

Выполнить функцию для каждого элемента без ручного цикла:

const fruits = ["яблоко", "банан", "киви"];

fruits.forEach(fruit => {
  console.log(fruit.toUpperCase());
});

findIndex: позиция первого совпадения

Найти индекс элемента, который удовлетворяет условию:

const users = [{name: "Аня"}, {name: "Боря"}, {name: "Вера"}];

const index = users.findIndex(u => u.name === "Боря");
// index === 1;  если не найден, вернётся -1

every и some: все или некоторые

Проверки, распространяющиеся на всю коллекцию:

const scores = [8, 9, 10, 7];

scores.every(s => s >= 5);  // true  — все прошли
scores.some(s => s === 10); // true  — десяточка есть

Русская сортировка: localeCompare

Стандартная сортировка по юникоду разбросает ё по-своему:

const names = ["Влад", "Аня", "Ёжик", "Боря"];

names.sort((a, b) => a.localeCompare(b, "ru"));
// ["Аня", "Боря", "Влад", "Ёжик"] — по правилам языка

Сортировка чисел

По умолчанию sort сравнивает строки — числа нуждаются в компараторе:

const nums = [10, 2, 33];
nums.sort();               // [10, 2, 33]  ой
nums.sort((a, b) => a - b); // [2, 10, 33] 

Почему нельзя === массивам

Массивы сравниваются по ссылке, а не по содержимому:

const a = [1, 2];
const b = [1, 2];
a === b;  // false — разные «контейнеры»

// сравниваем содержимое по одному элементу:
a.length === b.length && a.every((v, i) => v === b[i]);

Копии массива: slice и spread

Не мутируйте исходник из-за рефакторинга — сначала скопируйте:

const source = [1, 2, 3];
const copy1  = source.slice();
const copy2  = [...source];      // spread, без вложений

copy1.push(4);
source;  // [1, 2, 3] — цел

Array.isArray

Как отличить массив от объекта — и почему typeof не поможет:

typeof [];   // "object"  (мешает)
Array.isArray([]);        // true
Array.isArray({});        // false

Порядок чисел подряд

Массив номеров из «дырявого» конструктора строим честно:

const pages = Array.from({ length: 5 }, (_, i) => i + 1);
// [1, 2, 3, 4, 5]

Строка в массив символов

Два способа разгрызть строку:

"привет".split("");  // ["п", "р", ...]
[..."привет"];           // то же, но с эмодзи работает лучше

"а,б,в".split(",");     // ["а", "б", "в"]

map с индексом

Второй аргумент — порядковый номер текущего элемента:

const list = ["a", "b"];

list.map((item, i) => `${i + 1}. ${item}`);
// ["1. a", "2. b"]

break для быстрого выхода

Нашли нужное — цикл можно останавливать:

const ids = [10, 20, 30, 40];
let found = null;

for (const id of ids) {
  if (id === 30) {
    found = id;
    break;              // дальше не шагаем
  }
}

Цепочки: filter().map()

Обработку удобно выстраивать цепочкой вызовов:

const orders = [120, 45, 300, 80];

const big = orders
  .filter(x => x >= 100)
  .map(x => x * 1.3);   // [156, 390]

Среднее по оценкам

Маленький этюд метода reduce — сумма, а затем деление:

const marks = [4, 5, 3, 5];

const avg = marks.reduce((sum, m) => sum + m, 0) / marks.length;
// 4.25
💡
Не пугайтесь reduce — это обычный «накопитель». На досуге попрактикуйтесь: сложить, посчитать, собрать.

Тренировка

  1. Напишите функцию isEven(n), возвращающую true для чётных чисел.
  2. Выведите через for строку из 5 «⭐» (соберите в переменную).
  3. Добавьте if, который скажет вам, можно ли покупать «взрослые» продукты по возрасту.
  4. Через map увеличьте все цены массива на 10%.