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);
}
// 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.
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.
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:
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.
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:
It creates an extra scope of variables, which helps to confine variables to the place where
they are useful.
It is now an expression instead of a sequence of statements. This allows you to write complex
computation logic when initializing variables.
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;
}
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:
A function definition can only have one rest parameter.
The rest parameter must be the last parameter in the function definition.
Trailing commas are not allowed after the rest parameter.
The rest parameter cannot have a default value.
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.
There are four main differences between rest parameters and the arguments object:
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.
The arguments object has the additional (deprecated) callee property.
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.
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:
Arrow functions don't have their own bindings to this, arguments, or super, and should not
be used as methods.
Arrow functions cannot be used as constructors. Calling them with new throws a TypeError.
They also don't have access to the new.target keyword.
Arrow functions cannot use yield within their body and cannot be created as generator functions.
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.