Browse Source

05 functions

Ivan Asmer 8 years ago
parent
commit
5efe8e3504
1 changed files with 571 additions and 0 deletions
  1. 571 0
      05Functions.md

+ 571 - 0
05Functions.md

@@ -0,0 +1,571 @@
+# Функции, области видимости
+
+## 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
+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` в *любом* месте кода, в том числе в функции, она является глобальной, т. е. видимой везде. 
+
+```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()
+```
+
+### Функции высшего порядка
+
+**Функциями высшего порядка** называют функции, которые оперируют другими функциями - принимают их в качестве параметров или возвращают как результат 
+выполнения. Такой подход позволяет произвести *инъекцию своего кода*. Например, все реализации алгоритма сортировки сравнивают разные сортируемые элементы,
+при этом для работы алгоритма *вовсе не обязательно* знать структуру сортируемых данных; достаточно просто знать, какой элемент из двух *больше* или *меньше*.
+
+Функция, передаваемая в качестве параметра другой функции для последующего вызова называется `callback`.
+
+```javascript
+var arrayOfNumbers = [4,18,10,2,-1,100, 0, 0.5];
+arrayOfNumbers.sort(); //сортирует, используя обычное строковое сравнение `<` и `>`
+
+function numberSort(a, b){
+    return a > b ? 1 : -1;
+}
+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(isNaN(+value) || !Number.isInteger(+value) && value !== null);
+    return value;
+}
+
+function validatedPrompt(message, defaultValue, validator)
+{
+    do {
+        var value = prompt(message, defaultValue);
+    } while(!validator(value) && value !== null);
+    return value;
+}
+debugger;
+alert(validatedPrompt("number", "", function(value) {
+            return !isNaN(+value) && Number.isInteger(+value) 
+}))
+
+alert(validatedPrompt("камень-нжнцы-бмг", "", function(value) {
+            return ["камень", "ножницы", "бумага"].indexOf(value.toLowerCase()) > -1;
+}))
+```
+
+## Для чего используются функции.
+
+- Для избавления повторяющихся кусков кода. **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**) - создать свою собственную область видимости, в которой можно оперировать
+любыми именами переменных не опасаясь побочных эффектов и влияния на переменные окружающего кода. Просто блок кода со своими именами.
+