Мы уже знакомы со способом называть что-то, используя константы. Например, это константа pi
со значением 3.14
.
const pi = 3.14;
После этой строки, каждый раз, когда мы видим pi
, мы знаем, что её значение 3.14
. Она называется константой, потому что, ээм, она неизменна, постоянна. После этой строки pi
всегда будет 3.14
, это никогда не изменится. Именно поэтому по аналогии с бумагой я использую ручку.
Это может показаться ограничением, но вообще — это довольно хорошее свойство. Изменение того, что мы уже создали — сложная задача. Представьте, что пишете код, используя константу, в значении которой не уверены.
Тем не менее, иногда вам может потребоваться изменить уже существующие данные или, другими словами, их значения. Допустим, вы хотите повторить что-то 5 раз. Один способ — выполнять повторы, отсчитывая до пяти, а затем остановиться. Для этого вам потребуется что-нибудь для хранения счётчика этого меняющегося числа. Константа не будет в таком случае работать — она неизменна, вы не можете изменить её значение после того, как уже создали её.
Поэтому в JavaScript и многих других языках программирования существует идея переменной. Можно представить ее в виде такой же бумажки, на которой имя написано ручкой, но значение — карандашом. В любой момент можно заменить значение на другое.
Можно посчитать факториал с помощью переменной вот так:
let factorial = 1;
factorial = factorial * 2; // 2
factorial = factorial * 3; // 6
factorial = factorial * 4; // 24
factorial = factorial * 5; // 120
Создать переменную — просто, она выглядит как константа, только вместо const
мы пишем let
. Мы позволяем ей быть чем-то и это не навсегда.
Затем мы изменяем значение factorial
. Мы бы такого не смогли сделать, если бы factorial
был константой. Эта строка означает "изменить значение переменной факториал, на результат умножения факториала на 2". Теперь JavaScript умножает factorial
на 2 и хранит этот результат в переменной factorial
. Раньше factorial
был 1, а теперь это 2.
Мы повторяем это ещё трижды, каждый раз умножая полученное значение на следующее целое число: на 3, на 4 и на 5. Это то, что вы возможно делаете в уме, когда умножаете числа. Вероятно, вы не думали об этом так чётко, но это неплохой способ описания процесса вычисления, если бы вам было нужно объяснить его.
Идея использования счётчика для повторения чего-то множество раз — распространённая в программировании, и большинство языков программирования имеют для этого "циклы". Давайте рассмотрим один тип цикла — "цикл while". Это блок кода, который повторяется, пока удовлетворяется какое-то условие.
Представьте фермера, который работает от рассвета до заката. Другими словами, он работает пока солнце в небе. Вы можете записать:
while (sun is up) {
work
}
Конечно, это не настоящий JavaScript, это просто чтобы показать идею. Эта строка "work" будет повторяться снова и снова, пока солнце над горизонтом. Это значит после каждого повторения нам нужно проверять, действительно ли солнце в небе, и остановиться если это не так. Другими словами: проверить — исполнить, проверить — исполнить, и так далее.
Вот функция факториала с переменными и циклом вместо рекурсии.
const factorial = (n) => {
let counter = 1;
let result = 1;
while (counter <= n) {
result = result * counter;
counter = counter + 1;
}
return result;
}
О-о, что тут происходит? Во-первых, мы создали две переменные: одна для счётчика, чтобы считать от 1 до верхнего предела, а вторая для текущего результата.
Затем начинается главная часть: цикл while, который повторяется, пока счётчик меньше или равен n — числу, переданному в эту функцию. Код, который повторяется, простой: мы меняем значения наших двух переменных. Текущий результат умножается на счётчик, а счётчик увеличивается на 1.
В какой-то момент это условие — "счётчик меньше или равен n" — станет ложным, цикл больше не будет повторяться, а программа перейдёт к следующему этапу — return result
. К этому моменту результат станет ответом, потому что за время всех повторов в цикле, результат умножался на 1, затем на 2, 3 и так далее, пока не достиг значения n, каким бы оно ни было.
Давайте посмотрим, что компьютер делает шаг за шагом, когда мы вызываем факториал 3.
n
counter
, установить значение 1result
, установить значение 1n
, поэтомуresult
на counter
и положить ответ — 1 — в result
counter
и положить ответ — 2 — в counter
counter
— 2, это меньше или равно n
, поэтомуresult
на counter
и положить ответ — 2 — в result
counter
и положить ответ — 3 — в counter
counter
— 3, это меньше или равно n
, поэтомуresult
на counter
и положить ответ — 6 — в result
counter
и положить ответ — 4 — в counter
counter
— 4, это не меньше и не равно n
, поэтому остановить повтор и перейти к следующей строкеresult
— 6Компьютер выполняет такие операции в миллиарды раз быстрее, но по сути это выглядит именно так. Общее название такого вида сформулированных повторений — "итерация". Наша программа использует итерацию, чтобы рассчитать факториал.
В прошлый раз мы рассматривали итеративный процесс с рекурсией, а в этот — итеративный процесс без рекурсии.
Оба используют технику итерации, но с рекурсивными вызовами нам не нужно менять значения, мы просто передаём новые значения в следующий вызов функции. А эта функция факториала не имеет рекурсивных вызовов вообще, поэтому все трансформации должны происходить внутри единственного экземпляра, единственной функциональной коробки. У нас нет выбора, кроме как менять значения содержимого.
Стиль программирования, который вы видели в предыдущих уроках, называется "декларативным". Сегодняшний стиль, с изменением значений, называется "императивным".
Сравните рекурсивный и нерекурсивный факториалы:
const recursiveFactorial = (n) => {
if (n === 1) {
return 1;
}
return n * recursiveFactorial(n-1);
}
const factorial = (n) => {
let counter = 1;
let result = 1;
while (counter <= n) {
result = result * counter;
counter = counter + 1;
}
return result;
}
Эта рекурсивная функция — декларативная — она как описание факториала. Она объясняет, что такое факториал.
Это нерекурсивная итеративная функция, и она императивная — она описывает, что делать, чтобы найти факториал.
Слово декларативный происходит от латинского "clarare" — разъяснять, заявлять, делать объявление. Вы разъясняете: я хочу, чтобы факториал n был n умножить на факториал n-1.
Слово императивный происходит от латинского "imperare", что значит "командовать". Вы приказываете чётко передвигаться по шагам — умножать это на это, пока идёт отсчёт и запоминать какие-то числа.
Декларативное — это что. Императивное — это как.
Писать декларативный код, в целом, лучший подход. Ваш код будет легче читать, понимать и делать что-то новое опираясь на него. Некоторые языки провоцируют вас использовать тот или иной подход, а некоторые вообще не оставляют выбора.
Но в итоге вам нужно будет научиться оценивать, когда императивный подход принесет больше проблем чем решений.
Следить за изменениями – сложно, и буквально несколько переменных могут сделать систему очень сложной для понимания.
От изменения состояния появляется гора багов, а оператор присваивания (assignment statements), который создает изменения, часто является причиной всего зла во вселенной.
Кроме let
существует другой способ определения переменных: var a = 5
. Этот способ был единственным до появления стандарта ES6, но в современном JavaScript он является устаревшим, и let
полностью заменил его.
let
и var
по-разному влияют на видимость переменной, и использование var
сегодня нежелательно. let
был создан как более правильная альтернатива старому способу.
Наделать ошибок очень легко, когда приходится справляться с переменными, изменять их, отслеживать и всякое такое. Особенно в зацикленном процессе. Прекрасный способ разобраться с тем, что происходит — использовать простейшую технику для отладки — console.log
. Вспомните, эта функция выводит в консоль то, что ей передают.
Например, я пытаюсь разобраться, что происходит внутри цикла while:
while (counter <= n) {
result = result * result;
counter = counter + 1;
}
Добавлю сюда console.log:
while (counter <= n) {
result = result * result;
counter = counter + 1;
console.log(result)
}
Теперь, каждый раз, когда повторяется этот блок кода, переменная result
будет выводиться на экран. Давайте посмотрим:
1
1
1
(Я запускаю функцию с n
равным 3)
В каждом шаге result
— это 1. Не верно — result
должен возрастать на каждом шаге… Ок, значит вероятная проблема, в строке, где result
меняется. Так и есть! У меня result = result * result
, а мне нужно умножить result
на counter
, а не на result
.
1
2
6
Теперь всё работает! Теперь я вижу шаги и последний шаг выдал верный ответ: 3! это 6. Используйте console.log
абсолютно везде. Это ваш лучший друг :-)
Поскольку цикл while только проверяет состояние, мы можем создать бесконечные циклы, если сделаем состояние всегда истинным.
while (10 > 5) {
console.log("Ten is still larger than 5");
}
Этот код будет выводить на экран "Десять всё ещё больше чем 5" пока не сгорит вселенная или пока вы не закроете программу, ну или пока компьютер не израсходует всю свою память — что бы ни случилось первым. Поскольку состояние 10 > 5
всегда истинно.
Есть еще более простой способ сделать бесконечный цикл — while (true) { ... }
. При этом true
всегда истинный.
Иногда ваши циклы будут бесконечными, даже если вы этого не планировали. Это общая проблема, и она указывает только на то, что внутри цикла вы забыли изменить проверяемую деталь. Например, если я удалю строку counter = counter + 1
из цикла в сегодняшнем уроке:
while (counter <= n) {
result = result * result;
}
… у меня получится бесконечный цикл: counter никогда не меняется, поэтому если counter <= n
, условие всегда будет истинно.
Переменные подобны константам, но вы можете изменить их значения в любой момент.
let age = 21;
age = 22; // now age is 22
age = age + 10; // now age is 32
Циклы — это повторяющиеся блоки кода. Цикл while — это блок, повторяющийся пока какое-то состояние истинно.
while (condition) {
do_stuff;
}
Вот факториал функции с циклом while:
const factorial = (n) => {
let counter = 1;
let result = 1;
while (counter <= n) {
result = result * counter;
counter = counter + 1;
}
return result;
}
Идея: сделать counter
= 1, затем умножать result
на counter
повторно, пока идёт отсчёт до n
(число, передаваемое функции). Когда counter
станет больше, чем n
— остановиться. К тому моменту result
будет ответом.
Это итерация — сформулированное повторение кода. Разные языки по-разному выполняют итерацию. Цикл while — это один из способов, который предлагает JavaScript.
Сравните рекурсивный факториал (из 9 урока) и нерекурсивный факториал (из сегодняшнего):
const recursiveFactorial = (n) => {
if (n === 1) {
return 1;
}
return n * recursiveFactorial(n-1);
}
const factorial = (n) => {
let counter = 1;
let result = 1;
while (counter <= n) {
result = result * counter;
counter = counter + 1;
}
return result;
}
Эта рекурсивная функция — декларативная — она как бы определение (трактование, характеристика) факториала. Она декларирует, что такое факториал.
Эта нерекурсивная итеративная функция — императивная — описание того, что нужно делать, чтобы найти факториал.
Слово декларативный происходит от латинского "clarare" — разъяснять, заявлять, делать объявление. Вы разъясняете: я хочу, чтобы факториал n был n раз факториалом n-1.
Слово императивный происходит от латинского "imperare", что значит "командовать". Вы приказываете чётко передвигаться по шагам — умножать это на это, пока идёт отсчёт и запоминаются какие-то числа.
Декларативное — это что. Императивное — это как.
Писать декларативный код, в целом, лучший выбор. Ваш код будет легче читать, понимать и делать что-то новое опираясь на него. Но иногда у вас нет выбора.
От изменения состояния* появляется гора багов, а инструкции (операторы) присваивания (assignment statements), которые создают изменения, часто являются коренными причинами всего зла во вселенной.
Поэтому, когда дело доходит до инструкций присваивания, действуйте осторожно.
Вам ответят команда поддержки Хекслета или другие студенты.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт