# Функции, области видимости ## Trivia ### Повторяющиеся действия. Как можно было заметить, компьютеры сильны именно в однотипных задачах, и делают простые задачи очень быстро. Связанные друг с другом однотипные задачи обычно повторяются в циклах (однотипные операции над массивами данных, статистические задачи, отрисовка повторяющихся данных на экране и так далее). Так же есть задачи *по требованию*, которые могут пригодится в любом месте кода. Например: `prompt`, `alert`, `Math.random` и прочие встроенные **функции**, которые являются *подпрограммами*, содержащими в себе программный код, вызываемый для решения определенной задачи. Я думаю понятно, что данные возможности *не являются* возможностями аппаратуры, а воплощены на программном уровне. ### DRY **Don't repeat yourself**. Один из основопологающих принципов разработки. Суть в том, что в процессе программирования вы должны минимизировать повторяющиеся *части кода*, которые делают почти одинаковые задачи; так как копипаста в коде приводит к дублированию отладки, да и вообще некрасиво это :-) ### KISS **Keep It Simple, Stupid**. Решайте задачи самым простым способом. > Отладка кода вдвое сложнее, чем его написание. Так что если вы пишете код настолько умно, насколько можете, то вы по определению недостаточно сообразительны, чтобы его отлаживать. > — *Brian W. Kernighan*. ### DRY > KISS Зачастую эти принципы противоречат друг другу; уменьшение объема кода требует более мощных и сложнее отлаживаемых средств языка; однако в долгосрочной перспективе принцип **DRY** полезней, чем простота кода (**KISS**). ### Пример ```javascript var surname = prompt("Введите фамилию","") if (surname === null || surname === ""){ surname = "Иванов" } var name = prompt("Введите имя","") || "Иван" var fathername = prompt("Введите отчество","") || "Иванович" ``` Это наш пример, который спрашивает у пользователя ФИО ИЛИ берет эти параметры по умолчанию. Как видите, алгоритм ввода ФИО однотипен, и его неплохо было бы выделить в **функцию**. Ко всему прочему, несмотря на эквивалентность алгоритма, `surname` вводится кодом, отличающимся от ввода `name` и `fathername`, что усложняет модификацию и отладку кода. ### Задание Порассуждаем о функциях, какие свойства должны быть у них, что бы они обеспечивали прозрачную работу в комбинации с другим кодом и не имели непредсказуемых побочных эффектов для кода, который их использует. # Ниже спойлер, имейте совесть :-D. Не омрачайте задание подглядыванием ответов. ![СПОЙЛЕР](http://www.websoldier.ru/wp-content/uploads/2015/02/kak-sdelat-spojler-dlya-sajta.jpg) ![СПОЙЛЕР](http://www.websoldier.ru/wp-content/uploads/2015/02/kak-sdelat-spojler-dlya-sajta.jpg) ![СПОЙЛЕР](http://www.websoldier.ru/wp-content/uploads/2015/02/kak-sdelat-spojler-dlya-sajta.jpg) ![СПОЙЛЕР](http://www.websoldier.ru/wp-content/uploads/2015/02/kak-sdelat-spojler-dlya-sajta.jpg) ## Функции **Функция** - подпрограмма, которая принимает определенные параметры при вызове, выполняет определенный код, и возвращает выполнение кода в место вызова, *опционально* (не обязательно) вернув результат работы в место вызова. Свойства **функции**, которые сделали её такой полезной для написания программ: - **Вызов**. Функция может быть вызвана, код функции выполнится в другом месте, после выполнения функции выполнение кода продолжается с места вызова. ```javascript function d() { debugger; } alert("before d"); d() alert("after d"); d() ``` Для входа и выхода из функции используются `F11` и `Shift-F11` в **Developer Tools** при **пошаговой отладке** - **Область видимости**. Так как функция не может "знать", из какого контекста она вызывается, то нет возможности знать заранее, совпадают ли имена переменных в функции и вне её. Таким образом вероятны *побочные эффекты* - непредсказуемые изменения переменных во внешнем коде, которые могут вызвать неправильную работу кода в целом; ```javascript var surname = "Петров"; function readSurname() { surname = prompt("Введите фамилию","") if (surname === null || surname === ""){ surname = "Иванов" } } alert(surname); readSurname(); alert(surname); ``` Для решения этой проблемы используется концепция *области видимости* - **правильно** объявленная переменная в функции (через `var`) существует только в функции и создаются каждый раз при вызове функции; внешние же переменные с таким же именем остаются нетронутыми ```javascript var surname = "Петров"; function readSurname() { var surname = prompt("Введите фамилию","") // ТУТ if (surname === null || surname === ""){ surname = "Иванов" } } alert(surname); readSurname(); alert(surname); ``` - **Параметры** Функция должна уметь получить те или иные данные для своего выполнения. Например встроенные функции `confirm`, `prompt`, `alert`. **Задание**: Каковы параметры и какой у них смысл в вышеуказанных встроенных функциях? ```javascript var surname = "Петров"; function readWithDefault(promptText, promptDefault, somethingDefault) { var something = prompt(promptText,promptDefault) if (something === null || something === ""){ something = somethingDefault; } alert("something: " + something); } alert(surname); readWithDefault("Введите фамилию","","Иванов"); ``` - **Возвращаемое значение**. Обратите внимание на то, что **функции** можно использовать как переменные в выражениях, однако не всегда это имеет смысл. Более того, функции нельзя присвоит значение, однако можно *прочесть* её значение. ```javascript var surname = "Петров"; function readWithDefault(promptText, promptDefault, somethingDefault) { var something = prompt(promptText,promptDefault) if (something === null || something === ""){ something = somethingDefault; } return something; } alert(surname); var name = readWithDefault("Введите имя","","Иван"); alert(name); ``` ## Определение и выполнение функций Обратите внимание, что первый `alert` происходит ДО включения пошаговой отладки. Это говорит о том, что *определение функции* **НЕ** вызывает её. Код функции работает только после вызова, который происходит по d(). Для вызова надо указать в коде имя функции и скобки после имения (с параметрами или без оных) ```javascript function d() { debugger; } alert("before d"); d() alert("after d"); ``` Определение начинается с ключевого слова `function`, после которого идет имя функции и параметры в скобках через запятую. Далее идет блок кода функции в фигурных скобках. В отличие от `if`, `else` и циклов, фигурные скобки **обязательны**. ### Именование функций Как и переменным, функциям нужно давать осмысленные названия. Только учтите, что переменные - *существительные* кода, а функции - *глаголы* кода. ### Выполнение функций. Когда в коде упоминается имя функции со скобками и, возможно, параметрами происходят следующие действия: - вычисляются выражения в скобках. В функцию попадают уже *значения* выражений. - создается новая область видимости, в которую попадают параметры и их значения. Вам не нужно определять переменные для параметров. - начинается выполнение кода в фигурных скобках определения функции. Все переменные, определенные через `var` попадают в локальную область видимости функции, не перекрывающую внешнюю область видимости. - Код выполняется до выполнения `return` или окончания кода функции (закрывающей фигурной скобки). `return` прерывает выполнение функции, более того, с помощью `return` происходит возврат значения функции, которое подставляется на место вызова функции. Таким образом функция ведет себя как *переменная* для чтения. Если функция ничего не возвращает, то, на самом деле, она возвращает `undefined` ```javascript function sqr(a){ alert("Вы передали:" + a); return a*a; alert("Этот код не выполнится"); } var sqr1 = sqr(5) var otherVar = 2; alert("Сумма квадратов: " + (sqr1 + sqr(otherVar + otherVar))); ``` ## Параметры функции и возвращаемое значение ### Параметры (аргументы) Параметры функции перечисляются в скобках после имени через запятую. Параметры - это переменные области видимости функции, в которые попадают вычисленные значения, передаваемые при **вызове**. Таким образом функции получают данные из внешнего кода. В **Javascript** количество параметров при определении и при вызове может отличаться. В таком случае непереданные параметры равны `undefined`: ```javascript debugger; function add(a,b) { a = a || 0; b = b || 0; return a + b; } alert(add()) alert(add(1)); alert(add(2,3)); ``` Если же параметров больше, чем указано в определении функции, то ошибки тоже не происходит. Для доступа к остальным полям существует **псевдомассив** `arguments`. ```javascript debugger; function add(a,b) { console.log(arguments) a = a || 0; b = b || 0; return a + b; } alert(add(4,5,6)) alert(add(4,5,6,7)); prompt("Введите число", "0"); prompt("Введите число"); ``` **Задание** Используя перебор массива `arguments` циклом `for`, сделайте функцию, которая складывает любое количество параметров ### Возвращаемое значение Для возврата значения используется `return`. У него три основных свойства: - Собственно возврат значения во внешний код. Выражение после `return` *вычисляется в контексте функции*: ```javascript debugger; function add(a,b) { return a + b; } alert(add(3,4)) ``` после чего *значение* попадает в место, где функция была вызвана (в `alert`) - Прекращение выполнения функции - `return` без параметра возвращает *ничего*, т. е. `undefined`: ```javascript debugger; function bigAndWeirdFunction() { var somethingBad = Math.random() > 0.5; if (somethingBad){ alert("Something bad happens"); return; } alert("All OK!"); } bigAndWeirdFunction(); bigAndWeirdFunction(); bigAndWeirdFunction(); ``` ### `console.log` и `return` При отладке вы видите в одной консоли *вычисленное значение выражения* (например `2 + 2` или `prompt("Введите число")`) и вывод `console.log`. `console.log` *просто выводит текст* в консоль, как `document.write` - в окно браузера, далее вы с этим ничего не можете сделать (почти). **Выражение** же может быть вставлено в код и являться частью другого выражения: ```javascript 2 + 2 var a = 2 + 2 prompt("Введите число"); var num = prompt("Введите число"); var b; b = console.log(a); //неработает, метод log объекта console возвращает undefined, т. е. ничего b = a; //работает ``` Что бы отличить результат выражения от вывода console.log, отметьте что возле значения выражения есть знак <.