Примечание
ЭТО АРХИВНАЯ ВЕРСИЯ КУРСА!
Материалы предназначаются для пересдающих дисциплину "ОП.04 - Основы алгоритмизации и программирования" в соответствии с учебными планами СПО годов набора ДО 2023-го.
Материалы были перенесены со старого сайта с минимальной доработкой, поэтому не все возможности курса могут работать как ожидается, где-то может слететь форматирование.
Домашние задания в рамках курса проверяться не будут!
ОП.04 - 12 - Функции и аргументы. Область видимости и контекст выполнения. Понятие рекурсии
Код примера для практической работы
Функции и аргументы
Зачастую нам надо повторять одно и то же действие во многих частях программы. Например, необходимо красиво вывести сообщение при приветствии посетителя, при выходе посетителя с сайта, ещё где-нибудь. Чтобы не повторять один и тот же код во многих местах, придуманы функции.
Функция — фрагмент программного кода, к которому можно обратиться из другого места программы вызвав его. В большинстве случаев с функцией связывается идентификатор, но многие языки допускают и безымянные функции. Во многих источниках (особенно старых), функции называли также подпрограммами.
Функции являются основными «строительными блоками» программы. Примеры встроенных функций вы уже видели — это alert(), prompt() и confirm(). Но можно создавать и свои.
Для создания функций мы можем использовать объявление функции.
Пример объявления функции:
function имя(параметры) {
// код, также называемый "телом функции"
}
Как видно из примера, вначале идёт ключевое слово function, после него имя функции, затем список параметров (их еще называют аргументами, но некоторую разницу этих понятий мы еще затронем) в круглых скобках через запятую (он вполне может быть и пустым) и, наконец, код функции, также называемый «телом функции», внутри фигурных скобок.
[!INFO]
Пример для пониманияВ целях соблюдения личной гигиены, обычно утром и вечером нам необходимо из раза в раз выполнять одно и то же действие — чистить зубы. Это действие можно рассмотреть в качестве функции, для успешного выполнения которой нам необходимо два аргумента — зубная щетка и зубная паста. При этом значения аргументов (цвет щетки или вкус пасты) могут быть различны а в каких-то ситуациях даже отсутствовать.
Давайте попробуем задать простую функцию, которая может выполняться без аргументов:
function sayHi() {
console.log( 'Привет, мир!' );
}
Если запустить этот код, то ничего не произойдет, т.к. простого объявления функции явно недостаточно — её необходимо вызвать. Наша новая функция может быть вызвана по своему имени: sayHi().
Например:
function sayHi() {
console.log( 'Привет, мир!' );
}
sayHi();
console.log( 'Какое-то другое сообщение!' );
sayHi();
Вызов sayHi() выполняет код функции. Здесь мы увидим сообщение дважды.
Этот пример явно демонстрирует одно из главных предназначений функций: избавление от дублирования кода.
Если понадобится поменять сообщение или способ его вывода — достаточно изменить его в одном месте: в функции, которая его выводит.
Аргументы и параметры
Мы можем передать внутрь функции любую информацию, используя параметры.
В нижеприведённом примере функции передаются два параметра: from и text. Значение, передаваемое в качестве параметра функции, называется аргументом.
function showMessage(from, text) { // параметры: from, text
console.log(from + ': ' + text);
}
showMessage('Аня', 'Привет!'); // Аня: Привет!
showMessage('Вася', 'И тебе привет!'); // Вася: И тебе привет!
Когда функция вызывается с соответствующими аргументами, переданные значения используются в теле функции.
Другими словами:
- Параметр — это переменная, указанная в круглых скобках в объявлении функции.
- Аргумент — это значение, которое передаётся функции при её вызове.
Мы объявляем функции со списком параметров, затем вызываем их, передавая аргументы.
Рассматривая приведённый выше пример, мы могли бы сказать, что функция showMessage объявляется с двумя параметрами, затем вызывается с двумя аргументами: 'Аня' и 'Привет!'.
Значения по умолчанию
Если при вызове функции аргумент не был указан, то его значением становится undefined.
Например, вышеупомянутая функция showMessage(from, text) может быть вызвана с одним аргументом:
showMessage("Аня");
Это не приведёт к ошибке. Такой вызов выведет "Аня: undefined". В вызове не указан параметр text, поэтому предполагается, что text === undefined.
Если мы хотим задать параметру text значение по умолчанию, мы должны указать его после =:
function showMessage(from, text = "текст не добавлен") {
console.log( from + ": " + text );
}
showMessage("Аня"); // Аня: текст не добавлен
Теперь, если параметр text не указан, его значением будет "текст не добавлен".
В данном случае "текст не добавлен" это строка, но на её месте могло бы быть и более сложное выражение, которое бы вычислялось и присваивалось при отсутствии параметра, даже другая функция.
Возврат значения
Функция может вернуть результат, который будет передан в вызвавший её код.
Простейшим примером может служить функция сложения двух чисел:
function sum(a, b) {
return a + b;
}
let result = sum(1, 2);
console.log( result ); // 3
Директива return может находиться в любом месте тела функции. Как только выполнение доходит до этого места, функция останавливается, и значение возвращается в вызвавший её код (присваивается переменной result выше).
Вызовов return может быть несколько, например, в зависимости от условия:
function checkAge(age) {
if (age >= 18) {
return true;
} else {
return confirm('А родители разрешили?');
}
}
let age = prompt('Сколько вам лет?', 18);
if ( checkAge(age) ) {
alert( 'Доступ получен' );
} else {
alert( 'Доступ закрыт' );
}
Возможно использовать return и без значения. Это приведёт к немедленному выходу из функции.
Например:
function checkAge(age) {
if (age >= 18) {
return true;
} else {
return confirm('А родители разрешили?');
}
}
let age = prompt('Сколько вам лет?', 18);
function showMovie(age) {
if ( !checkAge(age) ) {
return;
}
// не выполнится если checkAge(age) вернет false
alert( "Вам показывается кино" );
}
В коде выше, если checkAge(age) вернёт false, showMovie не выполнит alert.
Пустой return и undefined
Результат функции с пустым return или без него — undefined
Если функция не возвращает значения, это всё равно, как если бы она возвращала undefined:
function doNothing() { /* пусто */ }
alert( doNothing() === undefined ); // true
Пустой return аналогичен return undefined:
function doNothing() {
return;
}
alert( doNothing() === undefined ); // true
Перевод строки между return и его значением
Для длинного выражения в return может быть заманчиво разместить его на нескольких отдельных строках, например так:
return
(some + long + expression + or + whatever * f(a) + f(b))
Код не выполнится, потому что интерпретатор JavaScript подставит точку с запятой после return.
Для него это будет выглядеть так:
return;
(some + long + expression + or + whatever * f(a) + f(b))
Таким образом, это фактически стало пустым return.
[!DANGER]
Никогда не добавляйте перевод строки междуreturnи его значением!
Если мы хотим, чтобы возвращаемое выражение занимало несколько строк, нужно начать его на той же строке, что и return.
Или, хотя бы, поставить там открывающую скобку, вот так:
return (
some + long + expression
+ or +
whatever * f(a) + f(b)
)
И тогда всё сработает, как задумано.
Область видимости и контекст выполнения
Область видимости — часть программы, в пределах которой идентификатор, объявленный как имя некоторой программной сущности (обычно — переменной, типа данных или функции), остаётся связанным с этой сущностью, то есть позволяет посредством себя обратиться к ней.
Говорят, что идентификатор объекта «виден» в определённом месте программы, если в данном месте по нему можно обратиться к данному объекту. За пределами области видимости тот же самый идентификатор может быть связан с другой переменной или функцией, либо быть свободным (не связанным ни с какой из них). Область видимости может, но не обязана совпадать с областью существования объекта, с которым связано имя.
Контекст выполнения — это концепция, описывающая окружение, в котором производится выполнение кода программы. Код всегда выполняется внутри некоего контекста, будь то вся программа, программный модуль, конкретная функция, цикл или какое-либо конкретное выражение.
Как правило, контекст выполнения в программировании довольно тесно связан с областью видимости переменных и функций. Далее мы рассмотрим основные виды контекстов на примере области видимости переменных.
Для простоты, на данном этапе мы будем разделять только глобальный и локальный контексты, определяя их по области видимости переменных.
Локальные переменные (локальный контекст)
Переменные, объявленные внутри функции, видны только внутри этой функции.
Например:
function sayHi() {
let message = 'Привет, мир!'; // локальная переменная
console.log( message );
}
sayHi(); // Привет, мир!
console.log( message ); // <-- будет ошибка, т.к. переменная видна только внутри функции
Внешние переменные (глобальный контекст)
У функции есть доступ к внешним переменным, например:
let userName = 'Вася';
function sayHi() {
let message = 'Привет, ' + userName;
console.log(message);
}
sayHi(); // Привет, Вася
Функция обладает полным доступом к внешним переменным и может изменять их значение.
Например:
let userName = 'Вася';
function sayHi() {
userName = "Петя"; // изменяем значение внешней переменной
let message = 'Привет, ' + userName;
console.log(message);
}
console.log( userName ); // Вася перед вызовом функции
sayHi();
console.log( userName ); // Петя, значение внешней переменной было изменено функцией
Внешняя переменная используется, только если внутри функции нет такой же локальной.
Если одноимённая переменная объявляется внутри функции, тогда она перекрывает внешнюю. Например, в коде ниже функция использует локальную переменную userName. Внешняя будет проигнорирована:
let userName = 'Вася';
function sayHi() {
let userName = "Петя"; // объявляем локальную переменную
let message = 'Привет, ' + userName; // Петя
console.log(message);
}
console.log( userName ); // Вася перед вызовом функции
// функция создаст и будет использовать свою собственную локальную переменную userName
sayHi();
console.log( userName ); // Вася, не изменилась, функция не трогала внешнюю переменную
Переменные, объявленные снаружи всех функций, такие как внешняя переменная userName в вышеприведённом коде — называются глобальными. Глобальные переменные видимы для любой функции (если только их не перекрывают одноимённые локальные переменные).
Желательно сводить использование глобальных переменных к минимуму. В современном коде обычно мало или совсем нет глобальных переменных. Хотя они иногда полезны для хранения данных, которые могут потребоваться в любой части проекта.
Выбор имени функции
Функция по своему смыслу — это действие (а переменная — значение). Поэтому имя функции обычно является глаголом. Оно должно быть кратким, точным и описывать действие функции, чтобы программист, который будет читать код, получил верное представление о том, что делает функция.
Как правило, используются глагольные префиксы, обозначающие общий характер действия, после которых следует уточнение. Обычно в командах разработчиков действуют соглашения, касающиеся значений этих префиксов.
Например, функции, начинающиеся с "show" обычно что-то показывают.
Функции, начинающиеся с…
"get…"— возвращают значение,"calc…"— что-то вычисляют,"create…"— что-то создают,"check…"— что-то проверяют и возвращают логическое значение, и т.д.
Примеры таких имён:
showMessage(..)// показывает сообщениеgetAge(..)// возвращает возраст (получая его каким-то образом)calcSum(..)// вычисляет сумму и возвращает результатcreateForm(..)// создаёт форму (и обычно возвращает её)checkPermission(..)// проверяет доступ, возвращаяtrueилиfalse
Благодаря префиксам, при первом взгляде на имя функции становится понятным, что делает её код, и какое значение она может возвращать.
[!TIP]
Одна функция — одно действие.Функция должна делать только то, что явно подразумевается её названием. И это должно быть одним действием. Два независимых действия обычно подразумевают две функции, даже если предполагается, что они будут вызываться вместе (в этом случае мы можем создать третью функцию, которая будет их вызывать).
Анонимные функции на примере «стрелочных функций»
Анонимная функция — особый вид функций, которые объявляются в месте использования и не получают уникального идентификатора для доступа к ним.
Несмотря на то что в языке JavaScript можно создать функцию без имени, для того чтобы как-то её вызвать, необходимо использовать т.н. «функциональные выражения», которые выходят несколько за рамки данного курса, но в языке JavaScript существует и ещё один очень простой и лаконичный синтаксис для создания функций, применимый для ситуаций в которых анонимную функцию нужно создать и выполнить «здесь и сейчас», а результат сразу присвоить переменной — это «стрелочные функции».
Синтаксис выглядит следующим образом:
let переменная = (аргумент1, аргумент2, ...аргументN) => выражение;
Это создаёт функцию, которая принимает аргументы, затем вычисляет выражение в правой части с переданными аргументами и сразу же возвращает результат, присваивая переменной.
let sum = (a, b) => a + b;
console.log( sum(1, 2) ); // 3
Эта стрелочная функция представляет собой более короткую форму следующей анонимной функции, полученной при помощи т.н. «функционального выражения»:
let sum = function(a, b) {
return a + b;
};
console.log( sum(1, 2) ); // 3
Как вы можете видеть, (a, b) => a + b задаёт функцию, которая принимает два аргумента с именами a и b. И при выполнении она вычисляет выражение a + b и возвращает результат.
При этом, если у нас только один аргумент, то круглые скобки вокруг параметров можно опустить, сделав запись ещё короче:
let double = n => n * 2;
console.log( double(3) ); // 6
Если аргументов нет, круглые скобки будут пустыми, но они должны присутствовать:
let sayHi = () => console.log("Hello!");
sayHi();
Иногда нам нужна более сложная функция, с несколькими выражениями и инструкциями. Это также возможно, нужно лишь заключить их в фигурные скобки. При этом важное отличие — в том, что в таких скобках для возврата значения нужно использовать return (как в обычных функциях):
let sum = (a, b) => { // фигурная скобка, открывающая тело многострочной функции
let result = a + b;
return result; // если мы используем фигурные скобки, то нам нужно явно указать "return"
};
console.log( sum(1, 2) ); // 3
Рекурсия
В программировании рекурсия, или же рекурсивная функция — это такая функция, которая вызывает саму себя.
В процессе выполнения задачи в теле функции могут быть вызваны другие функции для выполнения подзадач. Частный случай — когда функция вызывает сама себя. Это как раз и называется рекурсией. Рекурсия полезна в ситуациях, когда задача может быть естественно разделена на несколько аналогичных, но более простых задач. Или когда задача может быть упрощена до несложных действий.

[!INFO]
Пример для пониманияРекурсию также можно сравнить с матрёшкой. Первая кукла самая большая, за ней идёт точно такая же кукла, но поменьше. Суть матрёшки состоит в том, что вы можете открывать её и доставать из неё точно такую же куклу, только немного меньше. Такой продолжительный процесс длится до тех пор, пока вы не дойдёте до последней куклы. Так выглядит визуальная репрезентация рекурсии.
Например, при возведении числа в степень мы берём число, умножаем его на себя несколько раз. Эту операцию можно представить в виде:
// 2^5 = 2 * 2 * 2 * 2 * 2
//
// 1 шаг: 2
// 2 шаг: 2 * 2
// 3 шаг: 2 * 2 * 2
// 4 шаг: 2 * 2 * 2 * 2
// 5 шаг: 2 * 2 * 2 * 2 * 2
//
// Какой по счёту шаг — столько и умножений.
Но это же можно представить в виде нескольких последовательных умножений на 2:
// 2^5 = ((((2 * 2) * 2) * 2) * 2)
//
// 1 шаг: 2
// 2 шаг: 2 * 2 (результат 1-го шага * 2)
// 3 шаг: 4 * 2 (результат 2-го шага * 2)
// 4 шаг: 8 * 2 (результат 3-го шага * 2)
// 5 шаг: 16 * 2 (результат 4-го шага * 2)
//
// Для получения нового результата
// мы берём предыдущий и умножаем его на 2.
При таком представлении всё возведение в степень — это лишь умножение предыдущего результата на 2:
// 2^n = 2^(n-1) * 2
// Значение степени двойки —
// это предыдущее значение, умноженное на 2.
Именно такие задачи называются рекурсивными — когда часть условия ссылается на всю задачу в целом (или похожую на неё). У рекурсии 2 составляющие: повторяющиеся операции и базовый случай.
Базовый случай — это условие, при выполнении которого рекурсия заканчивается и функция больше не вызывает саму себя.
Вот пример кода того, как можно реализовать функцию обратного отсчёта с использованием рекурсии:
function countDown(n) {
console.log(n);
if(n > 1){
countDown(n - 1) // вызов рекурсии
} else {
return true // основное действие
}
}
countDown(5)
// 5
// 4
// 3
// 2
// 1
Проще говоря, рекурсия в рамках вызова countDown(5) делает то же, что и код ниже:
countDown(5) {
countDown(4) {
countDown(3) {
countDown(2) {
countDown(1) {
return
}
return
}
return
}
return
}
return
}
Из-за повторяющихся операций рекурсия схожа с циклом. Их часто считают взаимозаменяемыми, но это всё же не совсем так.
[!INFO]
Не приведёт ли рекурсивная функция к бесконечному циклу?Да, такой исход вполне возможен. Однако, как и у функции
forилиwhile, рекурсию можно прервать условиемbreak, чтобы функция перестала вызывать саму себя.Стоит ли использовать рекурсии вместо обычных циклов?
Оба этих метода одинаково эффективны для решения задач, однако выбор одного из них зависит от типа проблемы, поставленной перед вами. Рекурсии эффективны тогда, когда вы работаете с данными, которые слишком сложны, чтобы пройтись по ним с помощью обычных циклов.
Давайте решим одну и ту же задачу с использованием цикла и рекурсии, чтобы увидеть разницу в подходах. Будем писать функцию для нахождения факториала.
Факториал — функция, определённая на множестве неотрицательных целых чисел. Название происходит от лат. factorialis — действующий, производящий, умножающий; обозначается , произносится эн факториал. Факториал натурального числа определяется как произведение всех натуральных чисел от до включительно.
.
Например,
Для принимается в качестве соглашения, что .
Факториал с помощью цикла
Сперва решим задачу нахождения факториала с помощью цикла.
function factorial(n) {
let result = 1
for (let i = 0; i < n; i++) {
result *= i + 1
}
return result
}
console.log(factorial(5)) // 120
В этой функции мы используем цикл, чтобы умножить каждое число на результат предыдущего умножения. То же самое мы можем сделать и рекурсивно.
Факториал с помощью рекурсии
Для расчёта факториала рекурсивно мы создадим функцию, в которой в первую очередь опишем базовый случай, а уже потом — повторяющиеся действия.
function factorial(n) {
if (n <= 1) {
return 1
}
return n * factorial(n - 1)
}
console.log(factorial(5)) // 120
[!TIP]
Хорошим правилом при работе с рекурсией считается первым делом описывать базовый случай и только потом — всё остальное. Это позволяет сделать работу с рекурсией понятнее.
Что почитать по теме
- Современный учебник JavaScript - Функции
- Современный учебник JavaScript - Function Expression
- Современный учебник JavaScript - Стрелочные функции, основы
- Современный учебник JavaScript - Рекурсия и стек
- Дока - Саша Беспоясов - Рекурсия
- NOP::Nuances of Programming - Артур Хайбуллин - Простыми словами о рекурсии
- CSSSR - МАКСИМ ВИСЛОГУРОВ - Рекурсия для неискушённых
- Хекслет - R. D. - Что такое рекурсия, рекурсивный и итеративный процесс в программировании
- W3Schools - JavaScript Function Definitions
- W3Schools - JavaScript Function Parameters
- W3Schools - JavaScript Function Invocation
- W3Schools - JavaScript Scope
- W3Schools - JavaScript Arrow Function
- JavaScript Tutorial - JavaScript Recursive Function