Цикл for и изменение переменных
Транскрипт урока
Этот урок будет быстрым и простым, так что пристегнитесь.

Вызовем функцию факториала с циклом while:

const factorial = (n) => {
  let counter = 1;
  let result  = 1;

  while (counter <= n) {
    result = result * counter;
    counter = counter + 1;
  }

  return result;
}
Когда мы работаем с переменными, мы часто поступаем так: меняем их значения, прибавляя к ним сколько-нибудь или умножая их на что-то. Или просто прибавляем или вычитаем единицу.

Как и во многих других языках программирования в JavaScript есть для этого упрощенные формы.

Вместо result = result * counter вы можете сказать result *= counter. Результат будет тот же самый, это просто способ сократить запись. Точно так же вы можете поступить со знаками плюс, минус и остатком от деления:

result *= counter;    // то же, что result = result * counter
result += counter;    // то же, что result = result + counter
result -= counter;    // то же, что result = result - counter
result %= counter;    // то же, что result = result % counter
result /= counter;    // то же, что result = result / counter
Добавление единицы к переменной — тоже очень типичная операция, поэтому вместо counter = counter + 1 можно записать counter++. Так же для минуса — counter = counter - 1 равносильно counter--. Это операторы инкрементирования и декрементирования.

Есть два вида, проще их понять на примере:

// Postfix
let a = 3;
let b;
b = a++;    // b = 3, a = 4

// Prefix
let a = 3;
let b;
b = ++a;    // b = 4, a = 4
Если вы поставите ++ после имени переменной — это постфиксная нотация — то фактическое сложение произойдёт после того, как значение вернётся. Вот почему b тут 3: оно получает значение перед тем как меняется a.

Если вы поставите ++ перед именем переменной — это префиксная нотация — то фактическое сложение произойдёт перед тем, как значение вернётся. Вот почему b тут 4: оно получает значение после того как меняется a.

Но в конце в обоих случаях a становится 4.

Это обновлённая функция факториала:

const factorial = (n) => {
  let counter = 1;
  let result  = 1;

  while (counter <= n) {
    result *= counter;
    counter++;
  }

  return result;
}
Здесь не имеет значения, используем мы префикс или постфикс когда инкрементируем counter, потому что значение не хранится больше нигде.

Этот код немного проще и короче. Иметь цикл — этот повторяющийся код — со счётчиком контролирующим повторения — распространённый в программировании приём. Поэтому кроме цикла while существует цикл for. В нем есть встроенные счетчики.

Это та же функция факториала, но с циклом for вместо цикла while:

const factorial = (n) => {
  let result  = 1;

  for (let counter = 1; counter <= n; counter++) {
    result *= counter;
  }

  return result;
}
Здесь три момента:

  1. Инициализация счётчика.
  2. Условие цикла. Так же как и в цикле while, этот цикл будет повторяться пока это условие истинно.
  3. Обновление счётчика. Как менять счётчик в каждом шаге.
А затем следует тело, код, который должен повторяться. Нам не нужно менять счётчик в теле, потому что он будет меняться, благодаря этому выражению сверху.

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

let x = 5;
let y = 10;

console.log(x++ + ++y);
Как видите, этот код заставляет думать, так как кроме арифметических выражений, мы имеем дело с побочными эффектами.

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

x += 1; // x = x + 1;
Что гораздо проще и понятнее. Да, не получится записать выражение в одну строку, но сам код будет очевидным и без сюрпризов.

Соответствующее правило в eslint: https://eslint.org/docs/rules/no-plusplus
Switch
Конструкция switch может заменить собой несколько условий if. Вот пример обычного условия с if:

let answer;

if (num === 1) {
  answer = "One";
} else if (num === 2) {
  answer = "Two";
} else {
  answer = "Nothing";
}
А вот как его можно переписать с помощью switch:

switch(num) {
  case 1:  // if (num === 1)
    answer = "One";
    break;

  case 2:  // if (num === 2)
    answer = "Two";
    break;

  default:
    answer = "Nothing";
    break;
}
break необходим, чтобы выйти из блока switch. Если break отсутствует, то выполнение пойдёт ниже по следующим случаям, игнорируя проверки. break также можно использовать в циклах for для мгновенного выхода из цикла.

Если в примере выше убрать все break'и, а num будет равен 1, то выполнятся все строки:

answer = "One";
answer = "Two";
answer = "Nothing";
Так что в итоге answer будет иметь значение "Nothing".

Несколько значений case можно группировать.

switch(num) {
  case 1:  // if (num === 1)
    answer = "One";
    break;

  case 2:  // if (num === 2)
  case 3:  // if (num === 3)
  case 4:  // if (num === 4)
    answer = "Two to four";
    break;

  default:
    answer = "Nothing";
    break;
}
Выводы
Арифметические шорткаты:

b *= a;    // same as b = b * a
b += a;    // same as b = b + a
b -= a;    // same as b = b - a
b %= a;    // same as b = b % a
Операторы инкрементирования и декрементирования:

// Postfix
let a = 3;
let b;
b = a++;    // b = 3, a = 4

// Prefix
let a = 3;
let b;
b = ++a;    // b = 4, a = 4
Пример цикла:

const factorial = (n) => {
  let result  = 1;

  // initialization↓    condition↓     update↓
  for (let counter = 1; counter <= n; counter++) {
    result *= counter;
  }

  return result;
}
Тут следует упомянуть о том, что все 3 выражения в цикле for не обязательны.

Например, в блоке инициализации не требуется определять переменные:

let counter = 1;
for (; counter <= n; counter++) {
  // любой код
}
Как и блок инициализации, блок условия не обязателен. Если пропустите это выражение, вы должны быть уверены, что прервете цикл где-то в теле, а не создадите бесконечный цикл.

for (let counter = 1;; counter++) {
  if (counter <= n) break;
  // любой код
}
Вы можете пропустить все 3 блока. Снова убедитесь, что используете break, чтоб закончить цикл, а также изменить счётчик так, чтоб условие для break было истинно в нужный момент.

let counter = 1;
for (;;) {
  if (counter >= n) break;
  // любой код
  counter++;
}
Обратите внимание на то, что если внутри тела цикла использовать оператор return, то выполнение цикла будет прервано и функция вернет значение.

// Функция должна посчитать сумму всех чисел от 1 до n
const sum = (n) => {
  let result = 0;
  for (let counter = 1; counter <= n; counter++) {
    return 10; // return прерывает цикл

    result += counter;
  }

  return result;
}

sum(5); // 10
sum(20); // 10
sum(50); // 10
Тесты
Пройти тест
Я написал рекурсивную функцию, запустил ее, но программа зависла: она не останавливается и работает, кажется, бесконечно. В чем, скорее всего, причина этой проблемы?

Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Может ли функция сначала вызвать другую функцию, а потом вызвать саму себя?
Неверно
Верно!
Дальше
Проверить
Завершить тест
Дан следующий код:

const factorial = (n) => {
if (n === 1) {
  return 1;
  } else {
return n * factorial(n);
  }
};

const result = factorial(12);

Что можно сказать о нем?
Неверно
Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Возможно ли написать рекурсивную функцию, которая вычисляет сумму серии чисел? (например, сумму чисел от 1 до 1000)
Неверно
Верно!
Дальше
Проверить
Завершить тест
Возможно ли наличие нескольких терминальных условий?
Верно!
Неверно
Дальше
Проверить
Завершить тест
Проанализируйте определения функций sum1 и sum2:

const sum1 = (n) => {
  if (n === 1) {
    return 1;
  }

  return n + sum1(n - 1);
};

const sum2 = n => (n === 1) ? 1 : n + sum2(n - 1);

Эти функции совершают одни и те же операции, просто в sum2 использован "укороченный" синтаксис. Верно ли это утверждение?
Неверно
Верно!
Дальше
Проверить
Завершить тест
Функция-однострочник sum принимает целое положительное число n и возвращает сумму всех чисел, входящих в интервал [0, n]:

const sum = n => (n === 0) ? 0 : n + sum(n - 1);

Есть ли в этом определении терминальное условие? Выберите правильное утверждение
Неверно
Неверно
Верно!
Дальше
Проверить
Завершить тест
Ниже приведено определение функции product, которая принимает на вход целое положительное число n, меньшее или равное 5, и возвращает произведение всех чисел, входящих в интервал [n, 5].

const product = (n) => {
  // if (n === 5) {
  // return n;
  // }

  return n * product(n + 1);
};

/*
* вычисление: 2 * 3 * 4 * 5 * 6 * 7 * ...
* RangeError: Maximum call stack size exceeded
*/
product(2);

Однако её вызов приводит к ошибке. Почему функция работает некорректно?
Верно!
Неверно
Неверно
Неверно
Дальше
Проверить
Завершить тест
Пройти еще раз
Пройти еще раз
Пройти еще раз
Пройти еще раз
Упражнение
Напишите функцию isPrime(). Она принимает число и возвращает true, если число является простым, и false в ином случае.

Простое число — целое положительное число, имеющее ровно два различных натуральных делителя — единицу и самого себя. Например, 7 — простое число, потому что делится без остатка только на 1 и на себя. 2017 — другое простое число.

Используйте цикл for и арифметические шорткаты.

isPrime(1);     // false
isPrime(7);     // true
isPrime(10);    // false
(Число 1 не считаем простым).

Подсказки