Web Kokua Banner Image

Functions

Functions are one of the fundamental building blocks in JavaScript or any programming language for that matter. A function in JavaScript is similar to a procedure, a set of statements that performs a task or calculates a value, but for a procedure to qualify as a function, it should take some input and return an output where there is some obvious relationship between the input and the output. To use a function, you must define it somewhere in the scope from which you wish to call it.

Calling Functions

A function within scope can be called by simply using its name and parameters if it has any. Functions are hoisted so it doesn't matter where it is called, but good programming practice would be to declare the function in the code before calling it. There are other ways to call functions. There are often cases where a function needs to be called dynamically, or the number of arguments to a function vary, or in which the context of the function call needs to be set to a specific object determined at runtime.

Functions Are Objects

It turns out that functions are themselves objects, and in turn, these objects have methods. The call() and apply() methods can be used to achieve this goal.

Functions & Scope

Functions must be in scope when they are called, but the function declaration can be hoisted (appear below the call in the code). The scope of a function declaration is the function in which it is declared (or the entire program, if it is declared at the top level).

The scope of variables and functions depend upon where they are placed within the hierarchy of the code. Variables and functions declared at the root level of the code are global. The scope of variables and functions declared within a closure are only available within the closure unless they are exposed to the outside.

Function Arguments

The arguments of a function are not limited to strings and numbers. You can pass whole objects to a function. The showProps() function (defined in Working with objects) is an example of a function that takes an object as an argument.

// NUMBER PARAMETER - RETURNS SQUARE OF NUMBER function square(number) { return number * number; } // STRING PARAMETER - RETURNS CONCATENATED TEXT function add_text(start_text) { let additional_text = "mySuffix"; return start_text + additional_text; } // RETURNS AN ARRAY OF CHARACTERS function array_from_text(text1, text2, text3) { let concat_text = text1 + text2 + text3; return concat_text.split(""); } // A FUNCTION WITHIN A FUNCTION - RETURNS ADDED SQUARES function addSquares(a, b) { function square(x) { return x * x; } return square(a) + square(b); }


Expression Function

Function expressions are convenient when passing a function as an argument to another function.

// NUMBER PARAMETER - RETURNS SQUARE OF NUMBER const square = function(number) { return number * number; } // STRING PARAMETER - RETURNS CONCATENATED TEXT const add_text = function(start_text) { let additional_text = "mySuffix"; return start_text + additional_text; } // RETURNS AN ARRAY OF CHARACTERS const array_from_text = function(text1, text2, text3) { let concat_text = text1 + text2 + text3; return concat_text.split(""); } // A FUNCTION WITHIN A FUNCTION - RETURNS ADDED SQUARES const addSquares = function(a, b) { let square = function(x) { return x * x; } return square(a) + square(b); }

Object Parameters

const Employee = {}; Employee.fName = "Dirk"; Employee.lName = "Harriman"; Employee.Position = "Programmer"; Employee.SkillSet = {}; Employee.SkillSet.Languages = ["Javascript", "Java", "C++", "C#", "PHP", "Python"]; /* THE JS OBJECT: { "fName": "Dirk", "lName": "Harriman", "Position": "Programmer", "SkillSet": { "Languages": ["Javascript","Java","C++","C#","PHP","Python"] } } */ function obj_param(obj) { let rStr = ""; rStr = "First Name: " + obj.fName +"<br/>"; rStr += "Last Name: " + obj.lName + "<br/>"; rStr += "Position: " + obj.Position + "<br/>"; rStr += "Skill Set:<br/>" for (let i = 0; i < obj.SkillSet.Languages.length; i++) { rStr += obj.SkillSet.Languages[i] + "<br/>"; } return rStr; } return_string = obj_param(Employee);


Function As A Parameter

// FUNCTION TAKES TWO PARAMETERS - A FUNCTION f, AND AN ARRAY a. function map(f, a) { const result = new Array(a.length); // BUILD A RESULT ARRAY THE SAME SIZE AS THE INPUT ARRAY for (let i = 0; i < a.length; i++) { // LOOP THROUGH INPUT ARRAY a result[i] = f(a[i]); // ASSIGN THE RESULT OF A FUNCTION CALL TO f TO RESULT ARRAY } return result; // REURN THE RESULT ARRAY } // CREATE A FUNCTION f, TO PASSED AS A PARAMETER const f = function (x) { return x * x * x; }; // CREATE AN ARRAY TO BE PASSED AS A PARAMETER const numbers = [0, 1, 2, 5, 10]; // RETURNS AN ARRAY const cube = map(f, numbers);


Recursion

Recursive functions are functions that call themselves. Recursive functions must have an exit condition or they will run until they run out of memory and crash.

Recursion & The Stack

Recursive functions use something called the "call stack". When a program calls a function, that function goes on top of the call stack. This is similar to a stack of books. You add things one at a time. Then, when you are ready to take something off, you always take off the top item.

Recursion uses the stack to keep track of states. Each recursive call that doesn't meet the exit condition will push its state onto the stack. When the exit condition is met, it starts popping the stack.

Recursion Debugging

Recursive functions can be difficult to debug. The easiest way to work through the code is by keeping track of what is on the stack. Consider the following recursive function call.

function loop(x) { // "x >= 5" IS THE EXIT CONDITION if (x >= 5) { return; } // DO STUFF loop(x + 1); // THE RECURSIVE CALL // WHERE THE CODE GOES AFTER EXIT CONDITION IS MET AND return IS CALLED } loop(0);


Iteration Action The Stack
1 The parameter 0 is not greater than or equal to 5, so it is placed on the "call stack" and a recursive call is made to loop(0+1 = 1) 0
2 The parameter 1 is not greater than or equal to 5, so it is placed on the "call stack" and a recursive call is made to loop(1+1 = 2) 1
0
3 The parameter 2 is not greater than or equal to 5, so it is placed on the "call stack" and a recursive call is made to loop(2+1 = 3) 2
1
0
4 The parameter 3 is not greater than or equal to 5, so it is placed on the "call stack" and a recursive call is made to loop(3+1 = 4) 3
2
1
0
5 The parameter 4 is not greater than or equal to 5, so it is placed on the "call stack" and a recursive call is made to loop(4+1 = 5) 4
3
2
1
0
6 The parameter 5 is equal to 5, the exit condition is met, so the return statement is called,
The code upon return that is executed is the code following the recursive call,
In this function there is no code, so the "call stack" is popped until it's empty.
3
2
1
0
7 The "call stack" is popped. 2
1
0
8 The "call stack" is popped. 1
0
9 The "call stack" is popped. 0
10 The "call stack" is empty. (Empty Call Stack)

function fibonacci(num) { if (num < 2) { return num; } else { return fibonacci(num - 1) + fibonacci(num - 2); } } for (let i = 0; i < 10; i++) { rStr += fibonacci(i) +"<br/>"; }


Getting all the nodes of a tree structure (such as the DOM) is easier via recursion

function walkTree(node) { if (node === null) { return; } // DO SOMETHING WITH NODE for (let i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }

Binary Tree

A binary tree is a data structure in which nodes that have lesser value are stored on the left while the nodes with a higher value are stored at the right.


//********************************************************* class Node { constructor(data) { this.data = data; this.left = null; this.right = null; } showData() { return this.data; } } //********************************************************* class BinarySearchTree { //********************************************* constructor() { this.root = null; } //********************************************* insert(data) { var newNode = new Node(data); if (this.root === null) this.root = newNode; else this.insertNode(this.root, newNode); } //********************************************* insertNode(node, newNode) { if (newNode.data < node.data) { if (node.left === null) node.left = newNode; else this.insertNode(node.left, newNode); } else { if (node.right === null) node.right = newNode; else this.insertNode(node.right, newNode); } } //********************************************* remove(data) { this.root = this.removeNode(this.root, data); } //********************************************* removeNode(node, key) { if (node === null) return null; else if (key < node.data) { node.left = this.removeNode(node.left, key); return node; } else if (key > node.data) { node.right = this.removeNode(node.right, key); return node; } else { if (node.left === null && node.right === null) { node = null; return node; } if (node.left === null) { node = node.right; return node; } else if (node.right === null) { node = node.left; return node; } var aux = this.findMinNode(node.right); node.data = aux.data; node.right = this.removeNode(node.right, aux.data); return node; } } //********************************************* inorder(node) { if (node !== null) { this.inorder(node.left); gBinTree += node.data + "<br/>"; //console.log(node.data); this.inorder(node.right); } } //********************************************* preorder(node) { if (node !== null) { gBinTree += node.data + "<br/>"; //console.log(node.data); this.preorder(node.left); this.preorder(node.right); } } //********************************************* postorder(node) { if (node !== null) { this.postorder(node.left); this.postorder(node.right); gBinTree += node.data + "<br/>"; //console.log(node.data); } } //********************************************* findMinNode(node) { if (node.left === null) return node; else return this.findMinNode(node.left); } //********************************************* getRootNode() { return this.root; } //********************************************* search(node, data) { if (node === null) return null; else if (data < node.data) return this.search(node.left, data); else if (data > node.data) return this.search(node.right, data); else return node; } }

var gBinTree = ""; // GLOBAL BINARY TREE VARIABLE function buildTree(result_name) { let result = document.getElementById(result_name); let rStr = ""; var BST = new BinarySearchTree(); rStr += "Inserting Nodes: 15, 25, 10, 7, 22, 17, 13, 5, 9, 27<br/>"; BST.insert(15); BST.insert(25); BST.insert(10); BST.insert(7); BST.insert(22); BST.insert(17); BST.insert(13); BST.insert(5); BST.insert(9); BST.insert(27); // 15 // / \ // 10 25 // / \ / \ // 7 13 22 27 // / \ / // 5 9 17 var root = BST.getRootNode(); rStr += "Root Node: " + root.showData() +"<br/>"; BST.inorder(root); rStr += "In Order Traversal: "+ gBinTree + "<br/>Remove 5<br/>"; BST.remove(5); // 15 // / \ // 10 25 // / \ / \ // 7 13 22 27 // \ / // 9 17 var root = BST.getRootNode(); rStr += "Root Node: " + root.showData() + "<br/>"; gBinTree = ""; // CLEAR GLOBAL BST.inorder(root); rStr += "In Order Traversal: " + gBinTree + "<br/>Remove 7<br/>"; BST.remove(7); // 15 // / \ // 10 25 // / \ / \ // 9 13 22 27 // / // 17 var root = BST.getRootNode(); rStr += "Root Node: " + root.showData() + "<br/>"; gBinTree = ""; // CLEAR GLOBAL BST.inorder(root); rStr += "In Order Traversal: " + gBinTree + "<br/>Remove 15<br/>"; BST.remove(15); // 17 // / \ // 10 25 // / \ / \ // 9 13 22 27 var root = BST.getRootNode(); rStr += "Root Node: " + root.showData() + "<br/>"; gBinTree = ""; // CLEAR GLOBAL BST.inorder(root); rStr += "<br/>In Order Traversal: " + gBinTree +"<br/>"; gBinTree = ""; // CLEAR GLOBAL BST.postorder(root); rStr += "<br/>Post Order Traversal: " + gBinTree +"<br/>"; gBinTree = ""; // CLEAR GLOBAL BST.preorder(root); rStr += "<br/>Pre-Order Traversal:" + gBinTree +"<br/>"; result.innerHTML = rStr; }



Function Scope & Closure

Functions form a scope for variables—this means variables defined inside a function cannot be accessed from anywhere outside the function. The function scope inherits from all the upper scopes. For example, a function defined in the global scope can access all variables defined in the global scope. A function defined inside another function can also access all variables defined in its parent function, and any other variables to which the parent function has access. On the other hand, the parent function (and any other parent scope) does not have access to the variables and functions defined inside the inner function. This provides a sort of encapsulation for the variables in the inner function.

// GLOGAL VARIABLES FOR DEMONSTRATING SCOPE const gNum1 = 20; const gNum2 = 3; const gName = "Dirk"; function gMultiply() { const fNum1 = 2; return gNum1 * gNum2 * fNum1; } function gGetScore() { const gNum1 = 2; const gNum2 = 9; function add() { return `${gName} scored ${gNum1 + gNum2}`; } return add(); } function test_scope(result_name) { let result = document.getElementById(result_name); let rStr = "Multiply: " + gMultiply() + "<br/>"; rStr += "Get Score: " + gGetScore() + "<br/>"; result.innerHTML = rStr; }


Anonymous Function Expression

The following function expression has an anonymous function. An anonymous function has no name assigned to it. This is a function expression, but there are other types of functions without any name. The begging question in that case is how are they called. The answer is that they are invoked immediately.

const square = function (number) { return number * number; }; const x = square(4); // x gets the value 16

IIEF - Immediately Invoked Expression Function

An Immediately Invoked Function Expression (IIFE) is a code pattern that directly calls a function defined as an expression. It looks like this:

(function () { // DO SOMETHING })(); const value = (function () { // DO SOMETHING return someValue; })(); // WITH FUNCTION IN DECLARATION (function () { // CODE HERE })(); // LAMDA FUNCTION (AKA ARROW FUNCTION) (() => { // CODE HERE })(); // ASYNC FUNCTION (async () => { // CODE HERE })();

It is a design pattern which is also known as a Self-Executing Anonymous Function and contains two major parts:

  1. The first is the anonymous function with lexical scope enclosed within the Grouping Operator (). This prevents accessing variables within the IIFE idiom as well as polluting the global scope.
  2. The second part creates the immediately invoked function expression () through which the JavaScript engine will directly interpret the function.
( ◄──────── GROUPING OPERATOR function () { // CODE HERE } ) ◄──────── GROUPING OPERATOR (); ◄──────── IMMEDIATELY INVOKED FUNCTION EXPRESSION

Instead of saving the function in a variable, the function is immediately invoked. This is almost equivalent to just writing the function body, but there are a few unique benefits:


All variables and functions defined within the anonymous function are not available to the code outside of it, effectively closing the code off; sealing itself from the outside world. This is known as closure.

The following code will fail because the function calls are made outside of the closure and they are out of scope at the global level.

(function(){ var foo = 'Hello'; var bar = 'World!' function baz(){ return foo + ' ' + bar; } })(); // THESE ALL THROW EXCEPTIONS: console.log(foo); console.log(bar); console.log(baz());

function check_scope(result_name) { let result = document.getElementById(result_name); let rStr = ""; (function () { var ieVar1 = "Hola"; var ieVar2 = "Aloha"; function Say_Hello() { return ieVar1 + " and " + ieVar2; } })(); if (typeof(ieVar1) == "undefined") { rStr += "ieVar1 is Undefined<br/>" } if (typeof (ieVar2) == "undefined") { rStr += "ieVar2 is Undefined<br/>" } if (typeof (Say_Hello) == "undefined") { rStr += "Say_Hello() is Undefined<br/>" } result.innerHTML = rStr; }



Exposing Embedded Functions

function function_012(result_name) { let result = document.getElementById(result_name); let rStr = ""; (function () { var ieVar1 = "Hola"; var ieVar2 = "Aloha"; var Say_Hello = function () { return ieVar1 + " and " + ieVar2; }; window.Say_Hello = Say_Hello; })(); if (typeof (ieVar1) == "undefined") { rStr += "ieVar1 is Undefined<br/>" } else { rStr += "<br/>ieVar1: " + ieVar1; } if (typeof (ieVar2) == "undefined") { rStr += "ieVar2 is Undefined<br/>" } else { rStr += "<br/>ieVar2: " + ieVar2; } if (typeof (Say_Hello) == "undefined") { rStr += "Say_Hello() is Undefined<br/>" } else { rStr += "<br/>"+ Say_Hello(); } result.innerHTML = rStr; }



Passing Items Into Closure

One of the cool things about this, is that you can name the exposed method or variable whatever you like. This can be helpful in avoiding name conflicts.
You can pass items into the closure by including them in the immediate invoked function expression and in the function parameters.

( ◄──────── GROUPING OPERATOR ┌────────── FUNCTION PARAMETER ▼ function ( parameterName ) { // CODE HERE } ) ◄──────── GROUPING OPERATOR ( passedInItem ); ◄──────── IMMEDIATELY INVOKED FUNCTION EXPRESSION ▲ │ └────────────────────── ITEM PASSED IN WILL BE FUNCTION PARAMETER function function_013(result_name) { let result = document.getElementById(result_name); let rStr = ""; var outsideVal = 10; (function (oVal) { var ieVar1 = "Hola"; var ieVar2 = "Aloha"; var Say_Hello = function () { oVal *= 2; let rtnString = ieVar1 + " and " + ieVar2 + " times " + oVal; oVal = 5; return rtnString; }; window.Say_Hello = Say_Hello; })(outsideVal); rStr += "<br/>" + Say_Hello(); rStr += "<br/>outsideVal: " + outsideVal; result.innerHTML = rStr; }



document.addEventListener('DOMContentLoaded', () => { });

The Module Pattern

const makeWithdraw = (balance) => ((copyBalance) => { let balance = copyBalance; // THIS VARIABLE IS PRIVATE const doBadThings = () => { console.log("I will do bad things with your money"); }; doBadThings(); return { withdraw(amount) { if (balance >= amount) { balance -= amount; return balance; } return "Insufficient money"; }, }; })(balance); const firstAccount = makeWithdraw(100); // "I will do bad things with your money" console.log(firstAccount.balance); // undefined console.log(firstAccount.withdraw(20)); // 80 console.log(firstAccount.withdraw(30)); // 50 console.log(firstAccount.doBadThings); // undefined; this method is private const secondAccount = makeWithdraw(20); // "I will do bad things with your money" console.log(secondAccount.withdraw(30)); // "Insufficient money" console.log(secondAccount.withdraw(20)); // 0

Rest Parameters

The rest parameter syntax allows a function to accept an indefinite number of arguments as an array, providing a way to represent variadic functions in JavaScript. In mathematics and in computer programming, a variadic function is a function of indefinite arity, i.e., one which accepts a variable number of arguments.

A function definition's last parameter can be prefixed with ... (three period characters), which will cause all remaining (user supplied) parameters to be placed within an Array object.

function f(a, b, ...theArgs) { // ... }

There are some additional syntax restrictions:

Rest Parameters vs The Arguments Object

arguments is an array-like object accessible inside functions that contains the values of the arguments passed to that function.

function func1(a, b, c) { console.log(arguments[0]); // Expected output: 1 console.log(arguments[1]); // Expected output: 2 console.log(arguments[2]); // Expected output: 3 } func1(1, 2, 3);

There are four main differences between rest parameters and the arguments object:

  1. The arguments object is not a real array, while rest parameters are Array instances, meaning methods like sort(), map(), forEach() or pop() can be applied on it directly.
  2. The arguments object has the additional (deprecated) callee property.
  3. In a non-strict function with simple parameters, the arguments object syncs its indices with the values of parameters. The rest parameter array never updates its value when the named parameters are re-assigned.
  4. The rest parameter bundles all the extra parameters into a single array, but does not contain any named argument defined before the ...restParam. The arguments object contains all of the parameters, including the parameters in the ...restParam array, bundled into one array-like object.
function run_rest(result_name) { let result = document.getElementById(result_name); let rStr = ""; rStr = rest_func("Dirk", "Harriman", "Software Engineer", "Musician", "Chef", "Surfer"); result.innerHTML = rStr; } function rest_func(item1, item2, ...itemsN) { let rStr = ""; rStr += "item1: " + item1 + "<br/>"; rStr += "item2: " + item2 + "<br/>"; for (let i = 0; i < itemsN.length; i++) { rStr += "itemsN[" + i + "] = " + itemsN[i] + "<br/>"; } return rStr; }



Arrow Functions

An arrow function expression is a compact alternative to a traditional function expression, with some semantic differences and deliberate limitations in usage:

() => expression param => expression (param) => expression (param1, paramN) => expression () => { statements } param => { statements } (param1, paramN) => { statements }
function function_map_lengths(result_name) { let result = document.getElementById(result_name); let rStr = ""; let materials = ["Hydrogen", "Helium", "Lithium", "Beryllium"]; let material_lengths = []; material_lengths = materials.map((material) => material.length); if (material_lengths.length > 0) { for (let i = 0; i < material_lengths.length; i++) { rStr += "material_lengths[" + i + "] = " + material_lengths[i] + "<br/>"; } } else { rStr = "Empty"; } result.innerHTML = rStr; }



The map() method of Array instances creates a new array populated with the results of calling a provided function on every element in the calling array. The map() method is an iterative method. It calls a provided callbackFn function once for each element in an array and constructs a new array from the results.

map(callbackFn) map(callbackFn, thisArg)

In this case the callback function is the arrow function.


Predefined Functions

JavaScript has several top-level, built-in functions:

eval() The eval() method evaluates JavaScript code represented as a string.
isFinite() The global isFinite() function determines whether the passed value is a finite number. If needed, the parameter is first converted to a number.
isNaN() The isNaN() function determines whether a value is NaN or not. Note: coercion inside the isNaN function has interesting rules; you may alternatively want to use Number.isNaN() to determine if the value is Not-A-Number.
parseFloat() The parseFloat() function parses a string argument and returns a floating point number.
parseInt() The parseInt() function parses a string argument and returns an integer of the specified radix (the base in mathematical numeral systems).
decodeURI() The decodeURI() function decodes a Uniform Resource Identifier (URI) previously created by encodeURI or by a similar routine.
decodeURIComponent() The decodeURIComponent() method decodes a Uniform Resource Identifier (URI) component previously created by encodeURIComponent or by a similar routine.
encodeURI() The encodeURI() method encodes a Uniform Resource Identifier (URI) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two "surrogate" characters).
encodeURIComponent() The encodeURIComponent() method encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two "surrogate" characters).
escape() The deprecated escape() method computes a new string in which certain characters have been replaced by a hexadecimal escape sequence. Use encodeURI or encodeURIComponent instead.
unescape() The deprecated unescape() method computes a new string in which hexadecimal escape sequences are replaced with the character that it represents. The escape sequences might be introduced by a function like escape. Because unescape() is deprecated, use decodeURI() or decodeURIComponent instead.