JavaScript Functions

JavaScript functions are used to perform operations. We can call JavaScript function many times to reuse the code.

Advantage of JavaScript functions
There are mainly two advantages of JavaScript functions.
  1. Code reusability: We can call a function several times so it save coding.
  2. Less coding: It makes our program compact. We don’t need to write many lines of code each time to perform a common task.

Default Parameter

Default function parameters allow named parameters to be initialized with default values if no value or undefined is passed.

// ================= //
// Default Parameter //
// ================= //
const bookings = [];

const createBooking = function (flightNum, numPassengers = 1, price = 199) {
  /*
  // ES5 | Setting Default Values Using Short Circuiting
  // 👉🏻 OR(||) operator for set Default Value & Avoid Falsey Value
  numPassengers = numPassengers || 1;
  price = price || 199;
  */
  const booking = {

    /* OLD Method
    flightNum : flightNum,
    numPassengers: numPassengers,
    price: price,
    */

    // NEW Method | Enhanched Object Literal
    flightNum,
    numPassengers,
    price,
  };
  console.log(booking);
  bookings.push(booking);
};
createBooking('B707');
// returns:
// BEFORE: flightNum: 'B707', numPassengers: Undefined, price: Undefined
// AFTER: FlightNum: 'B707', numpassengers: 1, price: 199

createBooking('LH123, 2, 800');
// returns: FlightNum: 'LH123', numpassengers: 2, price: 800

// Parameter Expression
const createBooking = function (flightNum, numPassengers = 1, price = 199 * numPassengers) {} // Expression parameter must be defined before
// We cannot skip arguement when using Parameter Expression 
// So use undefined
createBooking('Ex123', undefined, 1000)


How Passing Arguments Works: Value vs. Reference

Passed by Reference:
  • Object
  • Array
Passed by Value:
  • Everything Else
👉🏻 Passing by Value copy data but Passing by Reference doesn't.
👉🏻 When you passed by Reference you are still passed by value but that value is a ref of that Data.
// ================================================ //
// How Passing Arguments Works: Value vs. Reference //
// ================================================ //

const person = {
  name: 'Rafe',
  age: 27,
  occupation: 'Developer',
  country: 'Bangladesh',
};
function previewObj(x) {
  x.name = 'Nur Md. Rafe';
  console.log(x);
}
previewObj(person); // {name: 'Nur Md. Rafe', age: 27, occupation: 'Developer'}
console.log(person); // {name: 'Nur Md. Rafe', age: 27, occupation: 'Developer'}
// If We change one of then BOTH will be changed and same

// Array ==>
const arr = [10, 20, 30, 40];

function previewArr(x) {
  arr.push('50');
  console.log(x);
}
previewArr(arr); // [10, 20, 30, 40, '50']
console.log(arr); // [10, 20, 30, 40, '50']

// Number ==>
const num = 12

function previewValue(x){
  x = 30
  console.log(x);
}
previewValue(num); // 30
console.log(num); // 12


Callback Function

// ================= //
// Callback Function //
// ================= //

const oneWord = function (str) {
  return str.toLowerCase().replace(/ /g, ''); // remove all spaces
};

const upperFirstWords = function (str) {
  const [first, ...others] = str.split(' ');
  return [first.toUpperCase(), ...others].join(' ');
};

// Higher-Order Function
const transformer = function (str, fn) {
  console.log(`Original String: ${str}`);
  console.log(`Transformed String: ${fn(str)}`);
  console.log(`Transformed by: ${fn.name}`);
};

transformer('JavaScript is the best!', upperFirstWords);
// returns:
// Original String: JavaScript is the best!
// Transformed String: JAVASCRIPT is the best!
// Transformed by: upperFirstWords
transformer('JavaScript is the best!', oneWord);
// returns:
// Original String: JavaScript is the best!
// Transformed String: javascriptisthebest!
// Transformed by: oneWord

// JS uses callbacks all the time
const high5 = function () {
  console.log('👋🏻');
};
document.body.addEventListener('click', high5);
// addEventListener ==> Higher-Order Function
// high5 ==> Callback Function
['Nur', 'Mohamod', 'Rafi'].forEach(high5); // [i] times function execution


Abstraction

it is a process of hiding implementation details and only showing the functionality to the user. 


Functions Returning Functions

// ============================= //
// Functions Returning Functions //
// ============================= //

const greet = function (greeting) {
  return function (name) {
    console.log(`${greeting} ${name}`); // greet('Hello')('Jonas')
  };
};
greet('Hello')('Jonas');

const greeterHey = greet('Hey');
greeterHey('Rafe'); // Hey Rafe


This - Call(), Apply() and Bind()

👉🏻 call('obj', 'x', 'y', ...'z') instant call
👉🏻 apply('obj', ['x', 'y', ...'z']) instant call
👉🏻 bind('obj', 'x', 'y', ...'z') store variable then call


#Use Case

👉🏻 bind() with addEventListener (if use this.function)
👉🏻 bind() for Partial Application that means a part of arguments of the original function that already applied or pre-set
// ========================== //
// call(), apply() and bind() //
// ========================== //

// Call() Method ==>

const airbnb = {
  airline: 'airBnb',
  airCode: 'Bnb',
  bookings: [],
  book(flightNum, name) {
    console.log(
      `${name} booked a seat on ${this.airline} flight ${this.airCode}`
    );
    this.bookings.push({ flight: `${this.airCode} ${flightNum}`, name });
  },
};

airbnb.book('777', 'Nur Rafi'); // Nur Rafi booked a seat on airBnb flight Bnb
console.log(airbnb.bookings);

const eurowings = {
  airline: 'Eurowings',
  airCode: 'EW',
  bookings: [],
};

const book = airbnb.book; // Save function method

book.call(eurowings, 'call707', 'callName');
book.call(airbnb, 'Nan', 'noName');

const swiss = {
  airline: 'Swiss Air Lines',
  airCode: 'LX',
  bookings: [],
};

book.call(swiss, 583, 'Mary Cooper');

// Apply() Method ==>

book.apply(swiss, [583, 'Mary Cooper']);

const flightData = [555, 'George Cooper'];
book.apply(swiss, flightData);

book.call(swiss, ...flightData); // Modern Method

// Bind() Method ==>

const bookEW = book.bind(eurowings);
const bookBNB = book.bind(airbnb);
const bookSwiss = book.bind(swiss);

bookEW(23, 'Steven Williams');

// Pre-set part of original functions Arguments
const bookEW23 = book.bind(eurowings, 23);

bookEW23('Jonas Schmedtmann');
bookEW23('Martha Cooper');
bookEW23('Nur Rafi');

// With Event Listeners for REuseable function.this

airbnb.planes = 300;
airbnb.buyPlane = function () {
  this.planes++;
};
document
  .querySelector('.buy')
  .addEventListener('click', airbnb.buyPlane.bind(airbnb));

// Partial Application | Pre-set Arguments

const addTax = (rate, value) => value + value * rate;
console.log(addTax(0.1, 200));

const addVAT = addTax.bind(null, 0.23); // pre-set arguments
console.log(addVAT(100));

Coding Challenge #1

Let's build a simple poll app!
A poll has a question, an array of options from which people can choose, and an
array with the number of replies for each option. This data is stored in the starter
'poll' object below.

Your tasks:
1. Create a method called 'registerNewAnswer' on the 'poll' object. The method does 2 things:

1.1. Display a prompt window for the user to input the number of the selected option. The prompt should look like this:
What is your favorite programming language?
0: JavaScript
1: Python
2: Rust
3: C++
(Write option number)

1.2. Based on the input number, update the 'answers' array property. For example, if the option is 3, increase the value at position 3 of the array by
1. Make sure to check if the input is a number and if the number makes sense (e.g. answer 52 wouldn't make sense, right?)

2. Call this method whenever the user clicks the "Answer poll" button.

3. Create a method 'displayResults' which displays the poll results. The method takes a string as an input (called 'type'), which can be either 'string'
or 'array'. If type is 'array', simply display the results array as it is, using console.log(). This should be the default option. If type is 'string', display a string like "Poll results are 13, 2, 4, 1".

4. Run the 'displayResults' method at the end of each 'registerNewAnswer' method call.

5. Bonus: Use the 'displayResults' method to display the 2 arrays in the test data. Use both the 'array' and the 'string' option. Do not put the arrays in the poll object! So what should the this keyword look like in this situation?

Test data for bonus:
§ Data 1: [5, 2, 3]
§ Data 2: [1, 5, 3, 9, 6, 1]
Hints: Use many of the tools you learned about in this and the last section �
// ======== //
// Solution //
// ======== //

const poll = {
  question: 'What is your favourite programming language?',
  options: ['0: JavaScript', '1: Python', '2: Rust', '3: C++'],
  answers: new Array(4).fill(0), // This generates [0, 0, 0, 0]

  // 1.......
  registerNewAnswer() {
    // Get the answer
    const answer = Number(
      prompt(
        `${this.question}\n${this.options.join('\n')}\n(Write option number)`
      )
    );
    console.log(answer);
    // 2.......
    // Register answer
    typeof answer === 'number' &&
      answer < this.answers.length &&
      this.answers[answer]++; // last && = Expression
    this.displayResult();
    this.displayResult('string');
  },
  // 3.......
  displayResult(type = 'array') {
    if (type === 'array') {
      console.log(this.answers);
    } else if (type === 'string') {
      // Poll results are 13, 2, 4, 1
      console.log(`Poll results are ${this.answers.join(', ')}`);
    }
  },
};

// console.log() = call after method
// type of x === "number" && others...conditions
// type of y === "string" && others...conditions

// 4.......
document
  .querySelector('.poll')
  .addEventListener('click', poll.registerNewAnswer.bind(poll));

// 5.......
// BONUS //

poll.displayResult.call({answers: [5, 2, 3]}, 'string') // Poll results are 5, 2, 3
poll.displayResult.call({answers: [1, 5, 3, 9, 6, 1]}, 'string') // Poll results are 1, 5, 3, 9, 6, 1


Immediately Invoked Function Expressions (IIFE)

In the past, as there was only var, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called “immediately-invoked function expressions” (abbreviated as IIFE).

That’s not something we should use nowadays, but you can find them in old scripts.

// IIFE
(function() {
  console.log('This wil never run again');
  const isPrivate = 7;
})();

console.log(isPrivate); // Error

(() => console.log('This will ALSO never run again'))
// Ways to create IIFE

(function() {
  alert("Parentheses around the function");
})();

(function() {
  alert("Parentheses around the whole thing");
}());

!function() {
  alert("Bitwise NOT operator starts the expression");
}();

+function() {
  alert("Unary plus starts the expression");
}();
NOTE:
👉🏻 Private Function / Data Privacy
👉🏻 Function Expression because of function wrap into parenthesis()


Closures

An inner function always has access to its outer function's variable/parameter and object, even that parent function has returned inner function can still access its outer function's variable because the function keeps a reference/memory to its outer scope and this is called 'Closures'.

A closure makes sure that a function doesn't lose connection to variables that existed at the function's births place.

A closure is like a backpack that a function carries around wherever it goes. This backpack has all the variables that were present in the environment where the function was created.

👉🏻 We do NOT have to manually create closures, this is a JavaScript feature that happens automatically. We cannot access closure and take variables from it by code because it is an internal property of a function [[Scopes]]: Scopes[3]. A closure is Not a tangible JavaScript object.

The closure has three scope chains listed as follows:

console.dir(booker)
  • Access to its own scope.
  • Access to the variables of the outer function.
  • Access to the global variables.
// ======== //
// Closures //
// ======== //

// inner func can use variable of outer funcion

const calcSum = function() { // OR -- function calcSum(){}
  const x = 10;
  return function () {
    const y = 10;
    console.log(`Sum: ${x + y}`); // inner func can use variable/parameter of outer funcion
  };
}
calcSum()(); // Sum: 20

// inner func can use parameter of outer funcion

function aParentFunc(a) {
  return function (b) {
    console.log(`Sum: ${a + b}`);
  };
}

aParentFunc(6)(4); // Sum: 10

// using cache memory of function

const secureBooking = function(){
  let passengerCount = 0 // Can't be manipulated and access by outside
  return function(){
    passengerCount++;
    console.log(`${passengerCount} passengers`);
  }
}
const booker = secureBooking();

booker(); // 1 passengers
booker(); // 2 passengers
booker(); // 3 passengers

console.dir(booker)
[[Scopes]]: Scopes[3]