# Функции, области видимости ## Зачем? ### Повторяющиеся действия. Как можно было заметить, компьютеры сильны именно в однотипных задачах, и делают простые задачи очень быстро. Связанные друг с другом однотипные задачи обычно повторяются в циклах (однотипные операции над массивами данных, статистические задачи, отрисовка повторяющихся данных на экране и так далее). Так же есть задачи *по требованию*, которые могут пригодится в любом месте кода. Например: `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.bugaga.ru/uploads/posts/2013-07/1372949635_spoyler-10.jpg) ![СПОЙЛЕР](http://www.bugaga.ru/uploads/posts/2013-07/1372949635_spoyler-10.jpg) ![СПОЙЛЕР](http://www.bugaga.ru/uploads/posts/2013-07/1372949635_spoyler-10.jpg) ![СПОЙЛЕР](http://www.bugaga.ru/uploads/posts/2013-07/1372949635_spoyler-10.jpg) ## Функции **Функция** - подпрограмма, которая принимает определенные параметры при вызове, выполняет определенный код, и возвращает выполнение кода в место вызова, *опционально* (не обязательно) вернув результат работы в место вызова. ### Свойства функций, ...которые сделали её такой полезной для написания программ: #### Вызов. Функция может быть вызвана любое количество раз из разных несвязанных между собой мест кода, код функции выполнится, после чего выполнение кода продолжится с места вызова: ```javascript var callCounter = 0; function ourFunction() { alert("Вызов № " + callCounter); callCounter ++; } console.log(callCounter); ourFunction() console.log(callCounter); //тут еще может быть много кода, но мы можем опять воспользоваться функцией когда захотим: ourFunction() console.log(callCounter); ``` Вызов происходит при наличии скобок после имени функции. Если скобок нет - вызов не происходит и это имеет совершенно другой смысл. Для входа и выхода из функции используются `F11` и `Shift-F11` в **Developer Tools** при **пошаговой отладке** #### Область видимости. Так как функция не может "знать", из какого контекста она вызывается, то нет возможности знать заранее, совпадают ли имена переменных в функции и вне её. Таким образом вероятны *побочные эффекты* - непредсказуемые изменения переменных во внешнем коде, которые могут вызвать неправильную работу кода в целом. Побочные эффекты возникают при совпадении внутренних и внешних переменных: ```javascript var surname = "Петров"; function readSurname() { surname = prompt("Введите фамилию","") //тут мы портим внешнюю переменную surname, и это нехорошо if (surname === null || surname === ""){ surname = "Иванов" } } console.log(surname); readSurname(); console.log(surname); ``` Для решения этой проблемы используется концепция *области видимости* - **правильно** объявленная переменная в функции (через `var`) существует только в функции и создается каждый раз при вызове функции; внешние же переменные с таким же именем остаются нетронутыми ```javascript var surname = "Петров"; function readSurname() { var surname = prompt("Введите фамилию","") // тут мы ничего не портим, эта переменная НЕ ЯВЛЯЕТСЯ внешней переменной surname if (surname === null || surname === ""){ surname = "Иванов" } } console.log(surname); readSurname(); console.log(surname); ``` #### **Параметры**. Функция должна уметь получить те или иные данные для своего выполнения. Например встроенные функции `confirm`, `prompt`, `alert`. **Задание**: Каковы параметры и какой у них смысл в вышеуказанных встроенных функциях? ```javascript var name = "Yohan" function greet(name){ alert("Hello, " + name); } greet(name) greet("John") greet("Paul") console.log(name) ``` #### **Возвращаемое значение**. Обратите внимание на то, что **функции** можно использовать как переменные в выражениях, однако не всегда это имеет смысл. Более того, **результату** функции нельзя присвоить значение, однако можно *прочесть* результат, вызвав функцию. **Задание**: какие из функций `prompt`, `confirm` и `alert` возвращают значения, а какие - нет? ```javascript function random5(){ return Math.random()*5; } alert(random5()); var someRandomValueFromZeroToFive = random5(); ``` ## Определение и выполнение функций Обратите внимание, что первый `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; //работает function myLowerCase(str) { console.log(str.toLowerCase()); //это просто пишет текст в консоли. } function rightUpperCase(str) { return str.toUpperCase(); //это работает правильно } var lowerCase = myLowerCase("AbCdEf") //не работает. var upperCase = rightUpperCase("AbCdEf") //работает ``` Что бы отличить результат выражения от вывода console.log, отметьте что возле значения выражения есть знак `<`. ## Область видимости Как было указано выше, переменные, объявленные с `var` внутри функции, являются незаметными для окружающего кода и перекрывают совпадающие переменные внутри функции, оставляя невредимыми внешние переменные: ```javascript var a = 5; alert("global a: " + a); function someFunctionWithA(){ var a = "string"; alert("function scope a: " + a); } alert("global a after function declaration" + a); someFunctionWithA() alert("global a after function execution" + a); ``` Область видимости создается **каждый раз** при **вызове** функции: ```javascript debugger; function add(a,b) { var result = a + b; return result; } add(1,2) add(5,6) ``` Как видите, переменные `a`,`b` и `result` каждый раз имеют разные значения. При вызове область видимости создается, по выходу из функции - удаляется (*не всегда*). ### Глобальная область видимости Если переменная создается **без** `var` в *любом* месте кода, в том числе в функции, она является глобальной, т. е. видимой везде. В ES5 это значит что любая переменная без `var` попадает в объект `window`. В ES6 это вызывает ошибку. ```javascript debugger; function add(a,b) { result = a + b; return result; } result = add(1,2) alert(result); add(5,6) alert(result); ``` Как видно в примере выше, мы не можем расчитывать на целостность переменной `result`, пользуясь функцией `add`. Использование глобальных переменных в большинстве случаев неоправдано; они нужны в-основном только для каких-то общих данных для чтения. Например `Math.PI` является глобальной переменной, доступной только на чтение; то есть *константой*. Ваши же переменные будут доступны и на запись, будьте аккуратны используя их. **Общее правило**: всегда ставьте `var`. ### Вложенные функции и их области видимости ```javascript var a = "0"; var b = "0"; var c = "0"; function level1(){ var b = "1"; var c = "1"; function level2(){ var c = "2"; console.log("Level 2 scope: a: " + a + " b: " + b + " c: " + c); } level2(); console.log("Level 1 scope: a: " + a + " b: " + b + " c: " + c); } level1(); console.log("Level 0 scope: a: " + a + " b: " + b + " c: " + c); ``` Проанализируйте вывод кода выше. Самая вложенная функция `level2` видит переменные своей области видимости (`c`), потом ищет значение на уровень выше (для переменной `b`), и на уровень еще выше (для `a`). Промежуточная функция `level1` *ничего* не знает о переменных в `level2`, но видит свою область видимости и глобальную. Глобальная же имеет свои переменные `a`, `b`, `c` в первозданном виде. ```javascript var a = "0"; var b = "0"; var c = "0"; function level1(){ var b = "1"; var c = "1"; var d = "1"; function level2(){ var c = "2"; var e = "2"; console.log("Level 2 scope: a: " + a + " b: " + b + " c: " + c + " d: " + d + " e: " + e); d = "2"; } console.log("Level 1 before level2, scope: a: " + a + " b: " + b + " c: " + c + " d: " + d + " e: " + e); level2(); console.log("Level 1 after level2, scope: a: " + a + " b: " + b + " c: " + c + " d: " + d + " e: " + e); } level1(); console.log("Level 0 scope: a: " + a + " b: " + b + " c: " + c + " d: " + d + " e: " + e); ``` Данный пример иллюстрирует отсутствие переменных `e` в глобальной области видимости и `level1`, переменной `d` - в глобальной области видимости. Переменная `d` попадает из `level1` в `level2`. ## Функции высшего порядка. ### Функция как тип данных. Функции в **JS** являются *типом данных*, наряду с числами и строками. Определение функции является выражением и вычисляется как значение типа данных `function`: ```javascript function a(){ } alert(typeof a); ``` Набор операций с функциями невелик, в отличие от строк их нельзя конкатенировать, нельзя складывать и умножать как числа; однако их можно присваивать переменным и вызывать. **JS** позволяет создавать функции без названия: ```javascript a(); function a(){ console.log('declared func'); } someFuncVariable() var someFuncVariable = function (){ console.log('anon func'); } someFuncVariable() var b = a; a = null; a() a = b a() ``` ### Знакомство: ООП в функциональном стиле. . Как видите, функция - такой же тип данных, как и остальные, однако этот тип имеет другой набор допустимых операций; в основном функции создают, передают и запускают. Так как ассоциативные массивы в **JS** могут хранить любой тип данных, то функции тоже могут быть элементами объектов. Таким образом реализуется **ООП** в **JS**: ```javascript var rectangle = { x: 0, y: 0, w: 100, h: 100, color: "black", draw: function(/* this */){ console.log("I'm drawing a rectangle with coordinates " + this.x + "x" + this.y + " and dimensions " + this.w + 'x' + this.h + " in " + this.color + " color"); }, setColor: function(/* this, */ color){ this.color = color; this.draw(); } } rectangle.draw(); ``` `this` позволяет функциям-полям объектов получить доступ к другим полям объекта (`x`, `y` и другие в примере выше) ### Функции высшего порядка **Функциями высшего порядка** называют функции, которые оперируют другими функциями - принимают их в качестве параметров или возвращают как результат выполнения. Такой подход позволяет произвести *инъекцию своего кода*. Например, все реализации алгоритма сортировки сравнивают разные сортируемые элементы, при этом для работы алгоритма *вовсе не обязательно* знать структуру сортируемых данных; достаточно просто знать, какой элемент из двух *больше* или *меньше*. Функция, передаваемая в качестве параметра другой функции для последующего вызова называется `callback`. ```javascript var arrayOfNumbers = [4,18,10,2,-1,100, 0, 0.5]; arrayOfNumbers.sort(); //сортирует, используя обычное строковое сравнение `<` и `>` function numberSort(a, b){ var result = a > b ? 1 : -1; console.log("Нас вызвали для сравнения " + a + " и " + b + ". Результат будет " + result); return result; } arrayOfNumbers.sort(numberSort); //сортировка по числовому значению ``` Первый `sort` выше сортирует, используя знаки `<` для элементов массива, интерпретируя элементы как строки; Второй `sort` принимает в качестве параметра функцию, которая вызывается многократно внутри `sort` для некой пары сортируемых элементов. Пара выбирается согласно логике алгоритма сортировки; выбор же, кто из этих двух элементов больше, а кто - меньше, возлагается на переданную функцию `numberSort`, которая должна вернуть 1 если `а` считается больше `b` и -1 в обратном случае. В случае равенства `a` и `b` - возвращается 0, однако это можно не использовать Таким же образом мы можем отсортировать по тому или иному критерию массив объектов (ассоциативных массивов), например: ```javascript var persons = [ {name: "Иван", age: 17}, {name: "Мария", age: 35}, {name: "Алексей", age: 73}, {name: "Яков", age: 12}, ] persons.sort(function(a,b){ //сортируем по возрасту if (a.age > b.age){ return 1; } return -1; }); persons.sort(function(a,b){ //сортируем по имени if (a.name > b.name){ return 1; } return -1; }); ``` Рассмотрим пример: ```javascript function intPrompt(message, defaultValue) { do { var value = prompt(message, defaultValue) } while(value !== null && isNaN(+value) || !Number.isInteger(+value)) return value } function gamePrompt(message, defaultValue) { do { var value = prompt(message, defaultValue) } while(value !== null && !(value == 'камень' || value == 'ножницы' || value == 'бумага')) return value } ``` Далее идет **общее** решение ввода с валидацией: ```javascript function validatedPrompt(message, defaultValue, validator) { do { var value = prompt(message, defaultValue); } while( value !== null && !validator(value)); return value; } debugger; alert(validatedPrompt("number", "", function(value) { return !isNaN(+value) && Number.isInteger(+value) })) alert(validatedPrompt("камень-нжнцы-бмг", "", function(value) { return ["камень", "ножницы", "бумага"].indexOf(value.toLowerCase()) > -1; })) ``` В этом примере код валидации выделен в функцию обратного вызова, а общее решение циклического ввода находится в функции `validatedPrompt` ## Для чего используются функции. - Для избавления повторяющихся кусков кода. **DRY** - Для структуризации и задания имени какой-либо последовательности операций. Например зачастую в начале работы кода запускают однократно функцию `init` (имя приведено для примера), которая выполняется *один* раз, т. е. не уменьшает объем кода. Однако, таким образом, все действия, которые относятся к подготовке программного окружения, заносятся в отдельный блок кода, что более наглядно - Функции обратного вызова используются для внедрения кода, как в случае с `sort` и `validatedPrompt` - Функции обратного вызова используются для того, что бы отказаться от опроса (poll) и перейти к событийной архитектуре (push), т. е. вместо постоянной проверки произошло то или иное событие или нет - происходит вызов callback когда это событие произошло. ```javascript console.log("Начал"); setTimeout(function(){ console.log("Отложил на 5 сек"); }, 5000); console.log("Закончил"); ``` В этом примере, вместо того, что бы засекать время и постоянно в цикле проверять, прошел ли нужный промежуток времени (5 секунд), используется встроенная функция setTimeout, которая запускает ваш код через определенное время. Код предоставляется в форме функции, время вторым параметром в миллисекундах. - Функции используются для создания обособленной области видимости, что бы не нарушать окружающее пространство имен: ```javascript (function(){ var a = 5; var b = "100500"; })() ``` в данном примере создается функция и тут же вызывается. Функция не сохраняется ни в какой из переменных, а значит вы не сможете её вызвать более чем один раз. Единственная цель такой функции (**Self-Invoked Function**) - создать свою собственную область видимости, в которой можно оперировать любыми именами переменных не опасаясь побочных эффектов и влияния на переменные окружающего кода. Просто блок кода со своими именами.