Functions

Throughout the other sections we have seen functions being created and used, now its time to dive deeper, I will be looking at function expressions, declarations and arrow functions, looking at default parameters and spread operators and finally private variables using closures.

One point to remember is that functions are objects in Javascript, which means they have properties and methods just like other functions. Function names are really just a pointer to an object, there are a number of ways to create a function but the best two are below

Create basic function example (traditional)
let sum = function(num1, num2) {
    return num1 + num2;
};
Using arrow function example
let sum = (num1, num2) => {                // notice the arrow function, multiple arguments need brackets
    return num1 + num2;
};

// For single arguments you don't need to use the brackets
let display = d => { console.log(d); }

// However if you pass zero arguments you need empty brackets
let displayHello = () => { console.log("Hello World!"); }

Note: you can sometimes don't have use the body curly braces but things do start to get confusing.

Because functions are objects you can create multiple variables all pointing to the same function

Function name
let func1 = function() { console.log("Hello World!");}

let func2 = func1;

func2();                                // "Hello World!"
console.log(func2.name);                // func1

Function Arguments

Because functions are objects you can pointer any number of variables to the same function, also you can pass any number of arguments to a function it does not matter what you have declared. You cannot overload function in Javascript

Function name
let func1 = function() { console.log("Hello World!"); }

let func2 = func1;

func2();                                // "Hello World!"
console.log(func2.name);                // func1
Function arguments
let func3 = (name, age) => {
    console.log(name + " " + age);
    
}

func3("Paul", 21);
func3();
func3("Paul", 21, "Valle", 1999);

-----------------------------------------------------------------------
function argumentTest () {
    console.log(arguments.length);                          // 2
    console.log(arguments[0] +  " " + arguments[1]);        // "Paul Valle"
}

argumentTest("Paul", "Valle");

Note: you cannot use arguments with the arrow function

You can apply default values to parameters in ES6

Default parameters
// ES5
function makeKing(name) {
    name = (typeof name !== 'undefined') ? name : 'Henry';
    return `King ${name} VIII`;
}

// ES6
function makeKing(name = 'Henry') {
    return `King ${name} VIII`;
}

let makeKing = (name = 'Henry') => `King ${name}`;             // using the arrow function

Spread and Rest Arguments

Spread opeartor allows you to manage groups of arguments, the rest opeartor allows us to represent an indefinite number of arguments as an array

Spread operator example
function getSum() {
    let sum = 0;
    for (let i = 0; i < arguments.length; ++i) {
        sum += arguments[i];
    }
    return sum;
}

let values = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(getSum.apply(null, values));                // use apply to flatten the array, results in 45

// spread operator examples
console.log(getSum(...values));                         // 45
console.log(getSum(...values, 5));                      // 50 = the array elements (45) + 5
console.log(getSum(-1, ...values, 10));                 // 59 = -1 + array elements (45) + 10
console.log(getSum(...values, ...[10, 11, 12]));        // 78
Rest operator
function getSum(...values) {                        // rest parameter
    let sum = 0;
    for (let i = 0; i < arguments.length; ++i) {
        sum += arguments[i];
    }
    return sum;
}

console.log(getSum(1,2,3));                   // 6
console.log(getSum(1,2,3,4,5));               // 15
console.log(getSum(1,2,3,4,5,6,7,8,9));       // 45

-----------------------------------------------------------------------------------
// Same as above but more readable
function getSum(...values) {                        // rest parameter
    let sum = 0;
    for (let arg of values) sum += arg;
    return sum;
}

console.log(getSum(1,2,3));                   // 6
console.log(getSum(1,2,3,4,5));               // 15
console.log(getSum(1,2,3,4,5,6,7,8,9));       // 45

Passing Functions

You can even pass a function as a argument to functions

Passing a function as a argument
function getSum(...values) {                                // rest parameter
    let sum = 0;
    for (let arg of values) sum += arg;
    return sum;
}

function funcTest(func, ...values) {
    return func(...values);
}

console.log(funcTest(getSum,1,2,3));                   // 6
console.log(funcTest(getSum,1,2,3,4,5));               // 15
console.log(funcTest(getSum,1,2,3,4,5,6,7,8,9));       // 45

Function Internals

Functions have a number of special objects, callee, this, caller and a number of properties (length and prototype) and methods (toString(), valueof(), apply and call())

arguments.callee
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1);                 // will call the correct function even if renamed
    }
}
this
// when run inside a web page this is the window
window.color = 'red';

function sayColor() {
     console.log(this.color);
}

sayColor();
caller
function outer() {
    inner();
}

function inner() {
    // contains a reference to the function that called this function or null if the function was called from the global
    console.log(arguments.callee.caller);             
}

outer();
length
// length = number of arguments
function sum(num1, num2) {
    return num1 + num2;
}

console.log(sum.length);                         // 2 arguments
apply
// apply() method accepts two arguments: the value of this inside the function and an array of arguments. 
// This second argument may be an instance of Array, but it can also be the arguments object.

function sum(num1, num2) {
    return num1 + num2;
}

function callSum1(num1, num2) {
    return sum.apply(this, arguments);                     // passing in arguments object
}

function callSum2(num1, num2) {
    return sum.apply(this, [num1, num2]);                  // passing in array
}

console.log(callSum1(10, 10));                             // 20
console.log(callSum2(10, 10));                             // 20
call
// call() is same as apply() but arguments are passed to it differently.
// The first argument is the this value, but the remaining arguments are passed directly into the function.

function sum(num1, num2) {
    return num1 + num2;
}

function callSum(num1, num2) {
    return sum.call(this, num1, num2);
}

console.log(callSum(10, 10)); // 20

------------------------------------------------------------------------------
color = 'red';

let o = {
    color: 'blue'
};

function sayColor() {
    console.log(this.color);
}

sayColor();                     // red
sayColor.call(o);               // blue (the object passed)

Closures

Closures are functions that have access to variables from another functions scope. The closure has three scope chains:

Closure example
let a = 1;

function outer() {
    let b = 2;

    function inner() {
        let c = 3;
        let d  = a + b +c
        return d;               // has access to global, outer and inner variables
    }
    return inner();
}

console.log(outer());

Note: When a function is not defined using the arrow syntax, the this object is bound at runtime based on the context in which a 
function is executed, when used inside global functions, this is equal to window in nonstrict mode and undefined in strict mode, 
whereas this is equal to the object when called as an object method.Anonymous functions are not bound to an object in this context, meaning the this 
object points to window unless executing in strict mode (where this is undefined)

Private and Static Variables

Javascript does not really have private variables but any variable inside a function is not accessible

Private access example
function count() {
    let count = 0;                              // private access

    this.getCount = function() {
        return count;
    }

    this.increaseCount = function() {
        count++;
    }

    this.decreaseCount = function() {
        count--;
    }
}

let counter = new count();

console.log(counter.getCount());                // 0

counter.increaseCount();
counter.increaseCount();
console.log(counter.getCount());                // 2

counter.decreaseCount();
console.log(counter.getCount());                // 1

console.log(counter.count);                     // undefined no access
Static variable example
function foo() {

    if( typeof foo.counter == 'undefined' ) {
        foo.counter = 0;
    }
    foo.counter++;
    console.log(foo.counter);
}

foo();
foo();
foo();