Advanced JavaScript Functions

Function Declaration vs.
Function Expression vs.
Function Constructor

Function Declaration vs. Function Expression vs. Function Constructor

Function declaration


			function add(x,y){
				return x+y
			}
		
Semicolons are not required after function declaration (though they did not hurt.)

Function expression


			const add = function(x,y){
				return x+y
			};
		
Semicolons are required after function expressions!
function name can be omitted in function expressions to create anonymous function.
a function expression can be immediately invoked (i.e. as IIFE)

Function expression


			const f = function add(x,y){
				return x+y
			};

			console.log("f(2,3):", f(2,3));		// f(2,3):5
			console.log("add(2,3):", add(2,3)); // ReferenceError: add is not defined
		
A function name can be given in function expression. But the function name is scoped to function body! Could be useful for recursion or debugging purposes.

Expression vs. declaration

Hoisting
Function declarations are hoisted
Function expressions are not hoisted
It is a good practise always to declare the variables (including functions) first, before using them.
Following this practise you will not have to wander what is hoisted and what is not.

Function constructor


			const sumLog = new Function('a', 'b', 'console.log(`a + b = ${a+b}`)');

			sumLog(2,4);
		
Do not use this way to create a function (unless you know exactly why you need it)!
Reference: Function Constructor @MDN

Conditional function definition

the strict ES2015 behaviour


			if (0){
				function add(x,y){
					x+y;
				};
			};

			console.log("add:", add); // undefined
		
Not browser compatible! Some browser would defined the function, because the function declaration is hoisted.

Conditional function definition

Safer way:


			var add;
			if (0){
				// function expressions are not hoisted
				add = function(x,y){
					x+y;
				};
			};

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

Scope

Scope

The context in which values and expressions are "visible," or can be referenced.
Scopes can be layered in a hierarchy, so that child scopes have access to parent scopes, but not vice versa.
Each function in JavaScript creates its own scope!
Inner Scope can use the names in Outer Scope
Outer Scope can not see the names defined in Inner Scope
If same name is defined in both Scopes, then the Inner Scope name shadows the Outer Scope name

Example


			let x = 1, y=1; // x and y in global scope

			function foo(){
				let x = 2, y=2;  // x and y in foo scope

				function bar(){
					let y = 3;  // y in bar scope
					x = 3;     // uses x in foo scope
					console.log(`x,y in bar: ${x}, ${y}`);
				}

				bar();
				console.log(`x,y in foo: ${x}, ${y}`);
			}

			foo();
			console.log(`x,y in global: ${x}, ${y}`);

			// OUTPUT:
			//x,y in bar: 3, 3
			//x,y in foo: 3, 2
			//x,y in global: 1, 1
		

References

Scope notes in JS_Beginners Course
let vs. const vs. var in JS_Beginners Course
Functions basics in JS_Beginners Course

A note on Programming Paradigms

A note on Programming Paradigms

Overview

Programming paradigms are a way to classify programming languages based on their features.
Languages can be classified into multiple paradigms.

Common programming paradigms

Procedural
Imperative programming with procedure calls
Object-oriented
Programming by defining objects that send messages to each other. Objects have their own internal (encapsulated) state and public interfaces
Functional
Programming with function calls that avoid any global state
Logic
Programming by specifying a set of facts and rules. An engine infers the answers to questions.

Functions as first-class citizens

Functions as first-class citizens

From its creation back in 1995, JavaScript supports function as first-class citizens (objects)

JavaScript's C-like syntax, including curly braces and the clunky for statement, makes it appear to be an ordinary procedural language. This is misleading because JavaScript has more in common with functional languages like Lisp or Scheme than with C or Java
What that means?
A function can be treated as object, expression or statement.
A function can be passed as argument to other functions.
A function can be the return value of the return operator.

example - function passed as argument


			function callback() {
				console.log(`I will be called!`);
			}

			function caller( f ){
				console.log(`This is the CALLER`);
				f();
			}

			// we pass the callback function to caller
			caller( callback );

			// This is the CALLER
			// I will be called!
		

example - function passed as argument


			const greeting = function(){
				alert("Welcome");
			}

			// SetTimeout will call the greeting after 4s
			setTimeout(greeting, 4000);
		

example

function as return value


			const greeting = function(name){
				return function(){
					alert("Welcome, "+ name);
				}
			}

			setTimeout(greeting("Ada"), 4000);
		

example

currying


			const sum = function(x){
				return function(y){
					return x+y;
				}
			}
			console.log( "sum:", sum(2)(3) );
		

Pros and Cons

Pros
The new (ES2015) arrow functions syntax make JavaScript a language suited for lambda-calculus (functional programming paradigm)
Cons
Not intuitive for persons used with classical procedural languages.

test yourself

Immediately-invoked function expressions (IIFE)

Immediately-invoked function expressions (IIFE)

IIFEImmediatelyInvokedFunctionExpression

IIFE is a pattern which allows to encapsulate our program, so that it will not export any global variable.
When a function will be used only once (usually to scope local variables), we do not need to define it first.
The definition itself will expose a global variable (function name).
It's more efficient to invoke it as an anonymous function.

				( function(){} )();
			
function(){} is an anonymous function declaration!
( function(){} ) is an anonymous function expression!
We can only invoke function expressions.

				(function(x,y){
					console.log( x+y );
				})(2,3);
			

is the same as


				(function(x,y){
					console.log( x+y );
				}(2,3));
			

Notes on IIFE and ES6

After the introduction of block scope in ES6, we can use instead of IIFE, just a block, to encapsulate our program.

				{
					// if you use only let and const to declare variables,
					// your program will not export any global variables!

					let x = 2;
					let y = 3;

					// other code
				}
			

Closures

Closures

Allows a function which is executed outside its outer scope, to access the values of these scoped variables.
In other words, a closure is observed, when an inner function closes over (captures, remembers) the variables from its outer lexical scope when it is called outside that scope.
Reference: Closures @MDN

			function outer(){
				const x = 5;
				function inner(){
					console.log(x);
				}

				return inner;
			}

			const f = outer();
			f(); // f can access the local x !
		

Common problem - "no closure in loop"


			var cats = [];

			for (var i = 0; i < 3; i++) {
				cats[i] = function(){
					console.log(`Cat ${i} is ready!`);
				}
			}

			cats[0](); //Cat 3 is ready!
			cats[1](); //Cat 3 is ready!
			cats[2](); //Cat 3 is ready!
		

the value of 'i' in the cats[i] function body is determined when the function is invoked!

"closures in loops" - IIFE solution


				var cats = [];

				for (var i = 0; i < 3; i++) {
					(function(i){
						cats[i] = function(){
							console.log(`Cat ${i} is ready!`);
						}
					})(i)
				}

				cats[0](); //Cat 0 is ready!
				cats[1](); //Cat 1 is ready!
				cats[2](); //Cat 2 is ready!
			

The value of i - which is a parameter to the IIFE is preserved in cats[i] functions, when they are called outside the IIFE scope.

"closures in loops" - Factory solution


					var cats = [];

					function catMaker(i){
						return function(){
							console.log(`Cat ${i} is ready!`);
						}
					}

					for (var i = 0; i < 3; i++) {
						cats[i] = catMaker(i);
					}

					cats[0](); //Cat 0 is ready!
					cats[1](); //Cat 1 is ready!
					cats[2](); //Cat 2 is ready!
			

"closures in loops" - "let" solution

Definitely, this is the simplest solution :)


			var cats = [];

			for (let i = 0; i < 3; i++) {
				cats[i] = function(){
					console.log(`Cat ${i} is ready!`);
				}
			}

			cats[0](); //Cat 0 is ready!
			cats[1](); //Cat 1 is ready!
			cats[2](); //Cat 2 is ready!
		

common problem - "timeout problem"


			var colors = ["red", "green", "blue"];

			for (var i = 0; i < colors.length; i++) {
				setTimeout(function(){
					changeBG("box", colors[i]);
				}, 1000)
			}

			function changeBG(id, color){
				var node = document.getElementById(id);
				node.style.background = color;
			}
		

"closures in loops - timeout IIFE fix"


			var colors = ["red", "green", "blue"];

			for (var i = 0; i < colors.length; i++) {
				(function(i){
					setTimeout(function(){
						changeBG("box", colors[i]);
					}, 1000*i)
				})(i);
			}

			function changeBG(id, color){
					var node = document.getElementById(id);
					node.style.background = color;
			}
		

setTimeout() is invoked immediately 3 times. The delay is for the inner function. If we pass same delay, they will be invoked simultaneously after the delay. So, we need to change the delay, as well, i.e. i*1000

"closures in loops - timeout separate call fix"


					var colors = ["red", "green", "blue"];

					for (var i = 0; i < colors.length; i++) {
							doTimeout(i);
					}

					function doTimeout(i){
							setTimeout(function(){
									changeBG("box", colors[i]);
							}, i*1000)
					}

					function changeBG(id, color){
							var node = document.getElementById(id);
							node.style.background = color;
					}
			

Test it!

Recursion

Recursion

A recursive function is a function which calls itself (directly or indirectly)
Each call creates a separate function stack!
Recursion can be endless if we do not take care when it has to stop to call itself. I.e. a recursive function must have a base case - a condition that makes the function to return a value, instead of calling itself.
Recursion_visual_factoriel

Factorial - formula


			n! = (n-1)!*n, if n > 0
			n! = 1,        if n = 0
		

Reference: Факториел @wikipedia

Factorial - recursive demo


			function factorial(n){
				if(n===0){ // recursion end condition
					return 1
				}else{
					return n*factorial(n-1) // recursive call
				}
			}

			factorial(3);
		
Note that we can use the ternary operator to shorten the code above

			function fact(n) {
				return n===0? 1: n*factorial(n-1);
			}
		

Factorial - execution stack diagram

Factorial - Task

Write down a function which uses the iterative approach (i.e. - loop) to solve the Factorial problem

Recursive Power Calculation


			baseexp = base * baseexp - 1
			base0 = 1
		

			function power(base, exponent) {
				if (exponent === 0)
					return 1;
				else
					return base * power(base, exponent - 1);
			}

			console.log(power(2, 10));
		

			// same with ternary operator
			function power(base, exponent) {
				return exponent === 0 ? 1 : base * power(base, exponent - 1);
			}

			console.log(power(2, 10));
		

Power Calculator - Task

Write down a function which uses the iterative approach (i.e. - loop) to solve the Power problem

Traversing the DOM - TASK

apply backgroundColor:"red" for each element in #startNode, which has class "red"
the code given bellow tries to solve the problem, but it has bugs. Can you fix them.

			<!DOCTYPE html>
			<html lang="en">

			<head>
				<meta charset="UTF-8">
				<meta name="viewport" content="width=device-width, initial-scale=1.0">
				<title>TASJ_DOM_Travers</title>
			</head>

			<body>
				<p>TASK: apply backgroundColor:"red" for each elelement in #startNode, which has class "red"</p>
				<p>paragraph 2</p>
				<ul id="startNode">
					<li>
						<ol>
							<li class="red">make me red</li>
							<li>do not make me red</li>
						</ol>
					</li>
					<li class="red">make me red</li>
				</ul>

				<ol>
					<li class="red">do not make me red</li>
				</ol>


				<script>
					function doSomething(node) {
						if (node.classList.contains("red")) {
							node.style.backgroundColor = "red";
							console.log(node.className);
						}
					};

					// HW: fix it
					function traverseDOM(node) {
						// get all HTML child nodes of node
						let children = node.children;

						// loop over children:
						for (let i = 0; i < children.length; i++) {
							let el = children[i];

							// do something with each child element:
							doSomething(el);

							traverseDOM(el);
						}

					}

					let startNode = document.querySelector("#startNode");

					traverseDOM( startNode );

				</script>
			</body>

			</html>
		

Traversing the DOM Element Nodes - Solution 1

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

Traversing the DOM Element Nodes - Solution 2

This demo use the method given by Douglas Crockford in "JavaScript: The Good Parts"

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

Traversing ALL of DOM Nodes

This demo use the method given by Douglas Crockford in "JavaScript: The Good Parts"

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

References

Recursion @javascript.info

Functions - Best Practices

Functions - Best Practices

Do One Thing and Do It Well (and Name It Well )

Do one thing per function. Not many things. If you are doing many things, then you should divide it to many functions.
Why?
Ease of debugging
.
If you write a function for each task you do, giving it a good name, you'll not need to put comments.

Exercises

Exercises

countEvenNumbers

generateRandomNumber

SumEven2DimArrayElements

isPalindrome

Solutions

countEvenNumbers

Code


			/* -------------------------------------------------------------------------- */
			/*                           task: countEvenNumbers                           */
			/* -------------------------------------------------------------------------- */
			// Да се дефинира функция countEvenNumbers, която връща броя четни числа в
			// подаденият й масив от числа:
			function countEvenNumbers(arr) {
				let evenCount = 0;
				for (let i = 0; i < arr.length; i++) {
					const number = arr[i];
					if(number%2===0){
						evenCount+=1;
					}
				}

				return evenCount;
			}

			// примерно извикване:
			let evenCount = countEvenNumbers( [1,4,2,3,5] );
			console.log( evenCount );
			// 2
		

generateRandomNumber

Algorithm

Reference: Check this StackOverflow answer

Code


			/* -------------------------------------------------------------------------- */
			/*                            task: generateRandomNumber                      */
			/* -------------------------------------------------------------------------- */
			// Да се дефинира функцията generateRandomNumber(start, end), която генерира
			// цяло случайно число в интервала, зададен чрез параметрите start и end.
			// Hint: използвайте Math.random() функцията за генериране на случайно число.
			function generateRandomNumber(start,end) {
				return Math.floor(Math.random() * (start - end + 1)) + end;
			}
			// примерно извикване:
			let randomNumber = generateRandomNumber(1,100);
			console.log( randomNumber );
			// randomNumber трябва да е цяло число, 1 >= randomNumber <=100

		

SumEven2DimArrayElements

Code


			/* -------------------------------------------------------------------------- */
			/*                                    task                                    */
			/* -------------------------------------------------------------------------- */
			// Да се дефинира функция SumEven2DimArrayElements(), която връща сумата от
			// четните елементи на подаден й двумерен масив.

			let arr = [
				[1,2,3],
				[4,5,6]
			];

			function SumEven2DimArrayElements(arr) {
				let sum = 0;

				for (let i = 0; i < arr.length; i++) {
					const row = arr[i];

					for (let j = 0; j < row.length; j++) {
						const number = row[j];
						if(number%2===0){
							sum+=number;
						}
					}
				}

				return sum;
			}
			// Примерно извикване на функцията:
			let sum = SumEven2DimArrayElements(arr);
			console.log(sum);
			// expected output: 12
		

isPalindrome

Algorithm

code


			/* -------------------------------------------------------------------------- */
			/*                             task: isPalindrome                             */
			/* -------------------------------------------------------------------------- */
			// Да се дефинира функция isPalindrome, която връща "true" ако подадената й
			// като аргумент дума е палиндром, и "false" - ако думата не е палиндром.
			// Палиндром е дума, която се чете по един и същ начин от ляво надясно и от
			// дясно наляво. Пример за палиндром са: мадам, боб, капак.

			function isPalindrome(word) {
				// изчисляваме индекса, който маркира средата на думата:
				let middle = Math.floor(word.length/2);
				// console.log(`middle index of ${word} is: ${middle}`);

				for (let i = 0; i < middle; i++) {
					let j = word.length - 1 - i;
					if(word[i]!==word[j]){
						// the word is not palindrome
						return false
					}
				}
				// the word is palindrome
				return true;
			}

			// примерно извикване:
			console.log( isPalindrome("madam") );// true
			console.log( isPalindrome("test") ); // false
		

These slides are based on

customised version of

Hakimel's reveal.js

framework