Функции в JavaScript

Въведение

Въведение

"Really understanding functions in JavaScript is the single most important weapon you can wield"
"Secrets of the JavaScript Ninja"
©2016 by Manning Publications Co

Какво са функциите?

Последователност от действия изпълняващи дадена задача, които обединяваме под общо име.
Пример: кулинарна рецепта
Действията се изпълняват след указване на името а резултата може да зависи от допълнителни параметри.
Палачинки (с мляко) vs. Палачинки (с вода)
В програмирането, може да разглеждаме функциите като под-програми, който извършват самостоятелно дадени действия.

Защо да използваме функции?

Избягваме повторението на код (DRY принцип).
Отделяне на концептуално свързани действия в едно цяло.
По-добро структуриране на програмата.
Увеличават изразителната сила на езика.

Функции или Процедури?

В програмирането, понятията функция и процедура се разграничават.
Под процедура се разбира просто последователност от дадени действия, която не връща резултат.
Под функция - последователност от действия, на която се подават входни данни и връща изходен резултат.
В JavaScript, чрез функции можем да реализираме както процедури, така и функции.

Дефиниция на функция (Function Definition)

Дефиниция на функция (Function Definition)

Вариант 1: Function Declarations


			function <име>( <списък параметри> ) {
				<тяло на функцията>
			}
		
<име>
Име на функцията, съобразено с правилата за идентификатори в JS. (tool: variable name validator)
<списък параметри>
Нула или повече параметъра разделени със запетая. Кръглите скоби са задължителни дори и ако функцията няма параметри.
<тяло на функцията>
Изрази, разделени с ';'
Забележете, че след декларацията на функцията не e необходимо да се слага ';', но не пречи!

Function Declarations - Примери


			//функцията няма параметри
			function signOutMsg(){
				confirm("Прекратяване на сесията?");
			}
		


			// функцията декларира 2 параметъра
			function sum(x, y){
				console.log( x + y );
			}
		

Вариант 2: Function Expression


			var functionVar = function(<списък параметри>){
				<тяло на функцията>
			};
		
Тук се създава анонимна функция, която е достъпна чрез променливата functionVar в която сме я съхранили.
Реално, се извършват две действия:
1. var functionVar = undefined // compile time
2. functionVar = function(...){...}; // run time
Тъй като тук дефинираме функцията чрез израз (оператора за присвояване), то задължително трябва да сложим ';' в края.

Параметри на функция

Параметрите на една функция са локални променливи които не декларираме експлицитно с var или let.
Имената на параметри трябва да отговарят на правилата за именуване на променливи в JavaScript.
Параметрите получават стойност при извикването (изпълнението) на функцията.

извикване на функция (Function Invocation)

Function Invocation

За да се изпълнят действията зададени в една функция, то тя трябва да се "извика".
Синоними:
Извикване, стартиране, изпълнение на функция
Function call, invocation, execution.

Синтаксис


			<име на функция>(<списък аргументи>);
		
<име на функция>
името на функцията, която желаем да изпълним.
<списък аргументи>
стойности, които автоматично ще се подадат на параметрите.
Между името на функцията и отварящата скоба '(' не се оставя спейс!

			greet(); // извикване на функцията greet, без аргументи
			greet('Ada', 'Byron'); // извикване на функцията greet с 2 аргумента
		

Пример


			function greet(userName){
				console.log(`Hello ${userName}. Nice to see you.`);
			}

			// едва при извикването ще се изпълнят действията, дефинирани във функцията

			// извикване на функцията greet() с аргумент "Ada"
			greet("Ada");
			// извикване на функцията greet() с аргумент "John"
			greet("John");
		

Предаване на аргументи

Съпоставянето (предаването) на стойности между параметър и аргумент е в зависимост от последователността на изписване. Тоест - първия параметър приема стойността на първия аргумент, втория параметър - на втория аргумент и т.н
argument-parameters mapping
  • x = 2
  • y = 3

Предаване на аргументи

Най-общо, при предаването на стойности на параметрите, това което JS интерпретатора прави, е да създаде локални за функцията променливи и да им присвои зададените като аргументи стойности.

			// декларация на функцията sum():
			function sum(x, y){
				// let x = 2, y = 3; => това се прави от JS интерпретатора при извикването на функцията
				console.log( x + y);
			}

			// извикване на функцията sum():
			sum(2, 3); // 5
		

Особености

Ако борят на параметрите не съответства на броя на аргументите, JavaScript не връща грешка!

			function sum(x, y){
				// var x = 2, y = undefined;
				console.log( x + y);
			}

			sum(2); // NaN
		

			function sum(x){
				// var x = 2;
				console.log( x ); // 2
				console.log( x + y); // ReferenceError: y is not defined
				// ако не използваме y във функцията, няма да има грешка.
			}

			sum(2, 3);
		

Return Statement

Return Statement

Всяка функция в JavaScript връща стойност.
На мястото на извикването на функцията ще се подаде връщаната от функцията стойност.
Ако във функцията не сме използвали оператора return, то функцията връща undefined!
За да определим каква да бъде връщаната стойност използваме оператора return.
Или казано по-конкретно, извикването на функция: funcName() е операция (expression, както 2+2), която винаги връща стойност.

			function funcName(){
				// some code
				return <израз>; // exit
			}

			let res = funcName();
		
<израз> трябва да бъде изчислим.
Именно изчислената стойност ще се замести на мястото на извикване на функцията.
След оператора return се излиза от тялото на функцията. Тоест, нито един израз във функцията, след return няма да се изпълни! .

				function f(){
					console.log("start");
					return true;
					console.log("end"); // никога няма да се изпълни!
				}

				f();

				// "start"
			

Example


			function f(){
				console.log("start");
				return true;
				console.log("end"); // никога няма да се изпълни!
			}

			f();
		

Обхват на променливите (Variables Scope)

Обхват на променливите (Variables Scope)

Въведение

Обхват (scope) на една променлива наричаме областта от кода, в която може да достъпим дадената променлива.
В JavaScript различаваме 2 вида scope:
global
local
*ES6 въвежда и block scope (чрез let и const), разгледан в следващите слайдове.

			// x is defined in global scope
			let x = 5;

			function foo() {
				// y is defined in local for 'foo()' scope
				let y = 10;
				console.log(`x in foo: ${x}`); // x in foo: 5
				console.log(`y in foo: ${y}`); // y in foo: 10
			}

			foo();

			console.log(`x in global: ${x}`); // x in global: 5
			console.log(`y in global: ${y}`); // ReferenceError: y is not defined
		

Global Scope

Когато скрипта се изпълнява от браузър, глобалният обхват (global scope) е всичкия код принадлежащ към дадената страница. Ако имаме няколко включени в дадена страница JS файла, то всички те формират global scope.
Променлива, която не е декларирана в тялото на която и да е функция, се нарича глобална и може да бъде достъпена от всяко едно място в global scope. Т.е. от всеки един JS файл, включен в същия HTML файл.

			var x = 1
		

Global Scope


			// x е глобална променлива
			var x = 5;
		

			<body>
				<!-- ... -->
				<script type="text/javascript" src="lib.js"></script>
				<script type="text/javascript">
					console.log(`x = ${x}`)
					// x = 5
				</script>
			</body>
		

Local Scope

Тялото на всяка една функция формира local scope.
Всяка променлива, декларирана с var, let или const, в тялото на една функция се нарича локална и е видима само в тялото на функцията (не може да бъде достъпена извън тялото на функцията).

			function f(){
				// firstName e локална за f():
				var firstName = "ada";

				console.log( firstName );
			}

			f(); // "ada"
			console.log( firstName ); // ReferenceError: firstName is not defined
		

Shadowing

Локални променливи, чието име съвпада с променливи от външния scope, припокриват, засенчват (shadowing) външните променливи в рамките на локалния scope.

Shadowing


			var firstName = "Ada";

			function foo(){
				var firstName = "Turing"
				console.log("foo() firstName: ", firstName);
			}
			foo(); // "Turing"

			console.log("global firstName: ", firstName); // "Ada"
		

Локалната променлива firstName не променя стойността на глобалната променлива firstName!

За JavaScript това са 2 отделни променливи!

Внимавайте за следният възможен бъг:


				var firstName = "Ada";

				function foo(){
					// тук променяме глобалната променлива!!!
					firstName = "Turing"
					console.log("foo() firstName: ", firstName);
				}
				foo();

				console.log("global firstName: ", firstName);
			

firstName в тялото на функцията НЕ Е декларирана чрез var/let или const и така тя се счита за глобална променлива!

Разлики между let, const и var

Разлики между let, const и var

let/const vs. var: scoping

ES6 въвежда в JavaScript и block scope за променливи дефинирани чрез ключовите думи let и const
block е всяка част от кода между { и }

		  {
			var x = 2;           // x e глобална променлива
			let y = 4;           // y e видима само в текущия блок
			const alpha = 2.34;  // alpha e видима само в текущия блок
		  }

		  console.log(`x=${x}`);          // x=2
		  console.log(`y=${y}`);          // error: y is not defined
		  console.log(`alpha=${alpha}`);  // error: alpha is not defined
	  

let/const vs. var: scoping - пример


		let status = "none",
			userAge = 32;

		if(userAge>=18){
			//тук не променяме глобалната променлива status, а създаваме нова променлива видима единствено в този блок
			let status = "Adult"
		};

		console.log(`status = ${status}`);
		// status = none
	

let/const vs. var: redeclaring

Друга разлика между let/cont и var е че ако една променлива вече е декларирана, то при повторна декларация (redeclaring) в съшия скоуп чрез let или const ще възникне грешка.
При ре-деклариране чрез var не би възникнала грешка.
Добра практика е да предпочитаме използването на let/const за деклариране на променливи, вместо var!

Особености на const

Чрез const създаваме променливи, които не бихме искали да ре-дефинираме по погрешка..

			const GOLDEN_RATIO =  1.618;
			GOLDEN_RATIO = 2; //TypeError: Assignment to constant variable.
		
Променлива създадена чрез const задължително трябва да бъде инициализирана:

			const GOLDEN_RATIO; // SyntaxError: Missing initializer in const declaration
		

Особености на const

const не гарантира създаването на константи в буквалния смисъл. А единствено, че при опит за re-assign ще възникне грешка!
Ако стойността на const променливата е съставна, т.е обект, то ние можем да променим някое негово пропърти без да възникне грешка.

			const someArray = [3.14];

			// тук не re-assign-ваме променливата someArray, а променяме стойност в самия масив
			someArray[0] = 99;
			console.log( someArray[0] ); // 99

			const someObject = {
				"pi": 3.14,
			}

			// тук не re-assign-ваме someObject, а променяме пропърти в самия обект:
			someObject.pi = 4;
			console.log( someObject.pi ); // 4

			// тук правим опит за re-assign:
			someArray = 4;      // TypeError: Assignment to constant variable.
			someObject = {};    // TypeError: Assignment to constant variable.
		

Анонимни функции (Anonymous functions)

Анонимни функции

Функция, която няма име се нарича анонимна функция!
Обикновено, анонимните функции се използват като стойности при предаване на аргументи на друга функция, в return изрази, пропъртита на обекти (методи)).
Фактът че в JavaScript функциите са стойности ги определя като "first-class citizens" (first-class function).

Анонимна функция като стойност на променлива (function expression):


			var foo = function(){
				console.log("I am foo!");
			}

			foo();
			// "I am foo!"
		

Анонимна функция като елемент на масив:


			let foobar = [
				function () {
					console.log(`Foo`);
				},
				function () {
					console.log(`Bar`);
				}
			]

			foobar[0](); // 'Foo'
			foobar[1](); // 'Bar'
		

Functions as First-Class Citizens

Functions as First-Class Citizens

Свойството на функциите в JavaScript (анонимни или не) - да бъдат използвани като стойности, т.е. да бъдат записвани в променливи, да бъдат подавани като аргументи на функции или да бъдат връщани като резултат от функция, ги определя като first-class functions
Темата за "first-class functions" се разглежда задълбочено в JavaScript - Advanced with React курса. Но преди да навлезете в тази изключително важна за JavaScript тема е необходимо да усвоите добре основните, разгледани досега.

Функция като стойност на параметър (callback functions)

Често в JavaScript се налага дадена функция да бъде подадена като аргумент на друга функция, за да бъде изпълнена от тази функция:

				function caller(callback){
					console.log("caller will call the callback:")
					callback();
				}

				function callback1(){
					console.log("I'm the callback1 function!")
				}

				function callback2(){
					console.log("I'm the callback2 function!")
				}


				caller(callback1);
				caller(callback2);
			

example

Забележете разликата при подаването на функция като параметър и подаването на резултата от функцията като параметър.

			function caller(f){
				console.log("1")
				f();
			}

			function callback(){
				console.log("2")
			}

			console.log("Feeding the caller() with function declaration")
			caller( callback );

			console.log("Feeding the caller() with function execution")
			caller( callback() );
		

Функция като return value


			function foo(){
				return function(){
					console.log(`I'm the returned function`);
				}
			}

			foo()();

			// foo()() е концептуално еквивалентно на:
			// var bar = foo();
			// bar();
		

Variables Hoisting

Variables Hoisting

Декларирането на променливите в даден scope винаги се изпълнява преди всички изрази, без значение къде във scope се намират декларациите.
2 паса:
Compile time = > декларации
Run time = > изпълнение на програмата

			// JavaScript "знае" за съществуването на x, тъй като декларацията "var x;" вече се е изпълнила! Но не и израза, чрез който записваме 10 в x.

			console.log("x:", x); // undefined

			var x;
			x = 10;
		

			// Примерът е аналогичен на горния!
			console.log("x:", x); // undefined

			var x = 10; // тук имаме декларация, която се е изпълнила в началото и израз, който се изпълнява сега.
		

Function Declaration vs. Function Expression

Когато дефинираме функция чрез декларация (Вариант 1) то декларацията на функцията винаги се извършва в началото на scope.

			// foo() вече е декларирана и JS знае, че е функция!
			console.log( foo() );

			function foo() {
				return "I am foo() and I work!";
			}
		

Function Declaration vs. Function Expression


			// foo() вече е декларирана, но JS не знае все още че е функция!
			console.log( foo() ); //TypeError: foo is not a function

			var foo = function(){
				return "I am foo() and I work!";
			}
		

Immediately Invoked Function Expression

Immediately Invoked Function Expression

IIFE

Единственият начин да се изпълни анонимна функция, която не е присвоена на променлива, е чрез конструкта, наречен IIFE (Immediately Invoked Function Expression).

			(f(){})()
		

			(function(){
				console.log("I am in nowhere, but I exist and work!");
			})();

			// "I am in nowhere, but I exist and work"
		
Обърнете внимание на кръглите скоби с който заграждаме декларацията на функцията (за да я превърнем в изпълним израз), както и на скобите след това, за да изпълним този израз.

IIFE

IIFE намира приложение за създаване на local scope в даден скрипт.

				(function(){
					// нито една от променливите създадени чрез var или let, няма да бъде видима извън тази функция:

					var x = 42;
					console.log(`x in IIFE.js: ${x}`);
				})()
			

				
				
			

References

References

Functions @w3schools
Functions @MDN

Примери и Задачи

Примери и Задачи

Function Definitions - Examples

See the Pen Function Definitions by Iva Popova (@webdesigncourse) on CodePen.

Variables Shadowing - Examples

See the Pen Scope & Shadowing by Iva Popova (@webdesigncourse) on CodePen.

Variables Shadowing - Task

See the Pen JS variables shadowing by Iva Popova (@webdesigncourse) on CodePen.

Calculate Rectangle Area - Task

See the Pen Calculate Rectangle Area - Task by Iva Popova (@webdesigncourse) on CodePen.

logArrayEvenElements() - Example

See the Pen logArrayEvenElements() Example by Iva Popova (@webdesigncourse) on CodePen.

sumArrays - Task

See the Pen sumArrays by Iva E. Popova (@ProgressWWWCourses) on CodePen.

findMaxEven() - Task

See the Pen findMaxEven() Task by Iva Popova (@webdesigncourse) on CodePen.

getHigherScoreIndex - Task

See the Pen HW: Max/Min student score by Iva Popova (@webdesigncourse) on CodePen.

SumEven2DimArrayElements - Task

See the Pen HW: SumEvenArrayElements by Iva Popova (@webdesigncourse) on CodePen.

gameBoardDataStructure - Demo and Task

See the Pen gameBoardDataStructure by Iva Popova (@webdesigncourse) on CodePen.

autoChangeColor - Example

See the Pen autoChangeColor by Iva Popova (@webdesigncourse) on CodePen.

Всички примери в codepen:

codepen колекция с примери и задачи върху темата

These slides are based on

customised version of

Hakimel's reveal.js

framework