
Functions[learn more]
Functions are one of the most fundamental building blocks of any JavaScript program. In this article, we are gonna study some quite advanced topics of functions.
Default Parameters[learn more]
Sometimes it's useful to have functions where some parameters are set by default. This way we don't have to pass them in manually in case we don't want to change the default.
for example,
const createBooking = (flightNo, numPassengers = 1, price = 12.5) => {
const booking = {
flightNo,
numPassengers,
price
}
console.log(booking);
}
createBooking('LH123',2,12);//{ flightNo: 'LH123', numPassengers: 2, price: 12 }
createBooking('LH123');//{ flightNo: 'LH123', numPassengers: 1, price: 12.5 }
The default value is that they can contain any expression for example,
const createBooking = (flightNo, numPassengers = 1, price = 12.5 * 1.2) => {
// write your code here
}
The default value can contain the values of other parameters for example,
const createBooking = (flightNo, numPassengers = 1, price = 12.5 * numPassengers) => {
// write your code here
}
We can not skip arguments when called a function for example, if you look at the above example, when we call the createBooking function, let's say wanted to leave the numPassengers as it has the default value, but then specify the price(let's say 100) as the second parameters. We can not do this because, if we do that then the number of passengers become 100 because the second argument of the function will always be mapped to the second parameter of the function. So, if we wanted to leave numPassengers then we simply set the second argument to undefined meaning that, when we don't pass an argument into that parameter, then it will take the value of undefined so this is how we basically skip a default parameter. for example,
const createBooking = (flightNo, numPassengers = 3, price = 12.5) => {
const booking = {
flightNo,
numPassengers,
price
}
console.log(booking);
}
createBooking('LH123',undefined,12);//{ flightNo: 'LH123', numPassengers: 3, price: 12 }
Passing Arguments(Value vs Reference)
When we pass arguments to functions, we can pass actually primitive and objects, we also call them as primitive types and reference types. It is super important that, we actually understand how primitives and objects work in the context of functions.
for example,
const flightNo = 'LH234';
const john = {
name: "John",
passport:"234332344"
}
const checkIn = (flightNumber, passenger) => {
flightNumber = "LH999";
passenger.name = "Mr. John";
if(passenger.passport == "234332344"){
console.log("Check in");
}else{
console.log("wrong passport");
}
}
checkIn(flightNo,passenger);
console.log(flightNo);//LH234
console.log(john);//{ name: 'Mr. John', passport: '234332344' }
if we look at the above example, we have a flightNo and a john object. When call the checkIn() function to check-in the passenger, let's say flight number got changed and passenger name got changed. After we called the checkIn() function, what do you think is gonna happen to these two variables here?. Well, we still get the same value for flightNo even after we change the flight number during the execution of checkIn() function. However, if we look at the john object, it's name got changed from "John" to "Mr. John". So, flightNo is a primitive type(String) and as we passed that value into the function, then the flightNumber is basically a copy of that original value and not simply the original value of the flightNo variable. So, flightNumber is a completely different variable and therefore, as we changed it inside checkIn function, it did not get reflected in the outside flightNo variable.
Now, what about the john object we passed into checkIn function and in that function it is called passenger and then we changed that passenger object inside the function, the john object was also affected by the change. So, why did that happen?. Well, answer is that when we pass a reference type to a function, what it copied is really just a reference to that object in the memory heap. So, passenger equals john meaning that, when we try to copy an object, we are really only copying the reference to that object in the memory heap but they both point to the same object in memory. manipulating the passenger object, it is exactly the same as manipulating the john object because both are the same object in the memory heap.
In Summary, passing primitive type to a function is really just the same as creating a copy of that value so that, changing the copy will not affect the original value. On the other hand, when we pass an object to a function, it is really just like copying an object and so whatever we change in a copy will also happen in the original.
First-class and Higher-order functions
JavaScript is a language that has first class functions means that functions are so-called first-class citizens. This means that functions are simply treated as values. why does JavaScript work this way? Well, it's simply because functions are really just another type of objects in JavaScript and since objects are values, functions are values too.
Since functions are values,
- we can store them in variables or object properties. for example,
const add = (a,b) => a + b;
const counter = {
value:0,
increment: function(){
this.value++;
}
}
- We can return functions from functions.
const add = (x) => {
return function(y) {
let z = x + y
console.log(z);
}
}
let finalAdd = add(3);
finalAdd(4);//7
// //Or
add(3)(4);//7
//In ES6
const outerFunction1 = (x) => (y) => console.log(x + y);
outerFunction1(3)(4);//7
From above example, add() function takes an argument x and returns a new function. This returned function takes an argument y, and return x + y
- Pass functions as arguments to other functions
const calculate = (x, PINumber) => {
console.log(x * PINumber());
}
const PI = (y) => {
return 3.14
}
calculate(3, PI);//9.42
- Call methods on functions
const add = (a,b) => a + b;
add.bind(someObject)//we will learn later
So the fact that, JavaScript has first-class functions which make it possible for us to use and write higher order functions. So a higher order function is either a function that receives another functions as an argument or a function that returns a new function.
So let's checkout both type a bit more,
Firstly, for functions that receive another function.
const calculate = (x, PINumber) => {
console.log(x * PINumber());
}
const PI = (y) => {
return 3.14
}
calculate(3, PI);//9.42
We have the same example as before. The calculate() function is the higher order function, because it receives another function as an input. In this example, the PI function and we usually say that, the function that is passed in as a argument of another function is a callback function, that is because the callback function will be called later by the higher order function. In this example, calculate function will call the PI() callback function later.
Secondly, functions that return another function.
const counter = () => {
let counter = 0;
return () => {
counter++;
return counter;
}
}
console.log(counter()());//1
In this example, counter function is the higher order function because it clearly returns a new function.
Functions accepting callback functions
let's create a function that accept other functions as an input.
const word = (str) => {
return str.replaceAll(" ","").toLowerCase()
}
const upperFirstWord = (str) => {
const [first,...others] = str.split(" ");
return [first.toUpperCase(), ...others].join(" ");
}
//this is higher order function
const transformer = (str, fn) => {
return fn(str);
}
console.log(transformer("JavaScript is fun",upperFirstWord));//JAVASCRIPT is fun
console.log(transformer("JavaScript is fun",word));//javascriptisfun
In this example, we are calling the transformer(Higher order) function, we are passing the callback function into it. Functions word() and upperFirstWord() are the callback functions and that's because we do not call them ourselves, instead we call these callback functions later by the transformer() function.
Functions returning functions
let's create a function that returns a new functions.
const greet = (greeting) => {
return (name) => {
console.log(`${greeting} ${name}`);
}
}
//Way 1
const greeterHey = greet("Hey!")
greeterHey("John");//Hey! John
greeterHey("Abraham");//Hey! Abraham
//Way 2
greet("Hey!")("John");//Hey! John
//rewrite the greet function using Es6
const greetEs6 = greeting => name => console.log(`${greeting} ${name}`);
The CALL and APPLY methods
Before talk about these methods, we are going to see about this keyword and learn how we can set the this keyword manually for objects, because it is important to understand the this keyword before seeing how to use these methods.
let's see the following example,
const airlineOne = {
name:"Luftthana",
code: "LH",
booking:[],
book(flightNumber,passengerName){
console.log(`${passengerName} booked a seat on ${this.name} flight ${this.code}${flightNumber}`);
this.booking.push({
passengerName,
flight: `${this.code}${flightNumber}`
})
}
}
In this example, it is important to understand that the this keyword here points to the airline object itself, because the object on which the book() method was called. But now, let's say that after some years, the airline created a new airline with similar object for example,
const airlineTwo = {
name:"Eurowings",
code: "EW",
booking:[]
}
With new airline, of course, we are able to take booking for a airlineTwo flight same as we did for airlineOne flight. To do that, simply copying the book method from airlineOne object and paste in airlinetwo object is a bad practice. So instead, we will just take the book method from airlineOne and store it in an external function variable and then we can reuse that function for all of the different airlines. let's see the following example,
const book = airlineOne.book;
book(23, "Dean John"); //it will throw an error
In this example, we assign the airlineOne.book function to book variable. When we try this book function it will throw an error because, this book function is now just a regular function call and in a regular function call, the this keyword points to undefined because it's a copy of airlineOne.book which means, it is now a sperate function.
How do we actually fix this problem?. In other word, how do we tell JavaScript that we want to create a booking on the airlineTwo flight? Well, basically, we need to tell JavaScript explicitly what the this keyword should be like. If we want to book a airlineOne flight, the this keyword should point to airlineOne and If we want to book a airlineTwo flight, the this keyword should point to airlineTwo. There are three function methods to do that and they are call, apply and bind.
Call() Method
In the call method, the first argument is exactly what we want this keyword to point to, for example.,
//DOES NOT WORK
// book(23, "Dean John"); //it will throw an error
book.call(airlineTwo,239, "John")//John booked a seat on Eurowings flight EW239
console.log(airlineTwo)
Output
{
name: 'Eurowings',
code: 'EW',
booking: [ { passengerName: 'John', flight: 'EW239' } ]
}
So, this time we called the call() method on the book function with the this keyword set to airlineTwo. So, this allows us to manually and explicitly set the this keyword of any function that we want to call. Then, all the arguments after the first one are simply the arguments of the original function.
Apply() method
The apply method does basically exactly the same as call() method. The only difference is that apply() does not receive a list of arguments after the this keyword but instead, it's gonna take an array of arguments and so it will then take the elements from that array and pass it into the function. Let's see the following example,
const book = airlineOne.book;
book.apply(airlineTwo,[239, "John"])//John booked a seat on Eurowings flight EW239
book.apply(airlineOne,[219, "John"])//John booked a seat on Luftthana flight LH219
The BIND method
Just like the call() method, bind() method also allows us to manually set this keyword for any function call but the difference is that bind does not immediately call the function, instead it returns a new function where this keyword is bound. So, it's set to whatever value we pass into bind.
Let's continue with the same example we used for call() and apply() method. let's say we need to use the book function for airlineTwo all the time.
const book = airlineOne.book;
const eurowings = book.bind(airlineTwo);
eurowings(239,"John");//John booked a seat on Eurowings flight EW239
eurowings(219,"Mini");//Mini booked a seat on Eurowings flight EW219
As you can see, we can use the bind() method to create a new function with the this keyword which will set to airlineTwo. So again, this will not call the book function, instead it will return a new function called eurowings where this keyword will always be set to airlineTwo. Also, eurowings function looks like the normal book function call, this is because this function already has the this keyword set in basically.
NOTE: Unlike the call() or apply() methods, we just need to bind the this keyword only once and use it whenever we need. Because every time we use call() or apply() methods, we need to set this keyword by passing the object as the first argument.
In the call() or apply() methods, we can pass multiple arguments besides this keyword. So, in the bind method, we can actually do the same then all of these arguments will also be basically set in bind method. For example, we could use bind to create a function for one specific airline and a specific flight number,
const book = airlineOne.book;
const bookEU232 = book.bind(airlineTwo,232);
bookEU232("John");//John booked a seat on Eurowings flight EW232
bookEU232("Mini");//Mini booked a seat on Eurowings flight EW232
As you can see, in bookEU232 function, since the first argument(flight number) was already set, we just need to pass only remaining name argument. So basically specifying parts of the arguments beforehand, is actually called partial application, meaning that, a part of the arguments of the original function are already applied or set.
Another Example,
Let's create a general function which adds a tax to some value, like shown below
const addTax = (rate, value) => value + value * rate;
console.log(addTax(0.1, 100));//110
Let's say, that there is a tax that we use all the time and let's create a function for that. for example, in United |Kingdom, the VAT which is value added tax is 20% and now we can use the bind function on addTax function to pre set the rate always, then we have function which only calculate the VAT for whatever we pass into it. like shown below
const addVAT = addTax.bind(null,.20);
console.log(addVAT(150));//180
console.log(addVAT(550));//660
console.log(addVAT(1500));//1800
When we use bind method, first argument of bind is this keyword, but in this case, we don't care about the this keyword at all. it's not even here in the addTax function. So, we just say null. It could be any other value because nothing will happen with it, but it's kind of a standard to just use null and now we can pre set the rate to 20%
Now, we might think that what we did here could easily have been done with default parameters which we learned earlier, but this is actually different, because addVAT function is creating a brand new function based on the addTax function.
We could also do the above example using function returning another function,
const addTaxRate = (rate) => {
return (value) => {
return value + value * rate;
}
}
const addVAT = addTaxRate(.20);
console.log(addVAT(150));//180
console.log(addVAT(550));//660
console.log(addVAT(1500));//1800
Immediately Invoked function Expressions(IIFE)
To create the IIFE, we first create the function declaration and then once the function is defined, we wrap parentheses around it. This creates the function expression and add a second pair of parentheses at the end to immediately invoke it. for example,
(function(){
console.log('it is running');
})()
Output:
it is running
How does it work?
Let's consider following code,
function(){
console.log('it is running');
}
We simply write the function expression itself without assigning it to any variable. Now if we try to run this we will get an error says "function statements require a function name" that's because JavaScript expects a function statement. Because we simply started this line of code with the function keyword. However, we can still trick JavaScript into thinking that, this is just an expression and we do that by simply wrapping into parentheses. We transformed the statement that we had before into an expression and if we run this we will not get error but also function did not execute yet because we never call it. We know that this is the function and can immediately call it by adding a pair of parentheses. This pattern is called the Immediately Invoked Function Expression
This will for work same for an arrow function. let's modify the above example,
(() => console.log('it is running'))()//it is running
Why this pattern actually invented in JavaScript?
We already know that function functions create scopes and what's important here is that, a scope from outside does not have access to variable from an inner scope. for example,
(() => {
console.log('it is running');
const isPrivate = 23;
})()
console.log(isPrivate);//ERROR: ReferenceError: isPrivate is not defined
variable isPrivate is defined inside the function and if try to access it outside of the function JavaScript throw an error because of the scope chain. However, Inner scope would have access to anything defined outside called global scope but the global scope does not have access to anything that is inside inner scope. Therefore, we say that all data defined inside a function(inner scope) is private, we can also say that data is encapsulated. for example, isPrivate variable from above example is encapsulated inside of this function scope.
Data encapsulation and data privacy are extremely important concepts in programming. So we often need to protect out variables from being accidentally overwritten by some other parts of the program or even external scripts. It is important to hide variables and that scopes are a good tools for doing this and this is the reason why IIFE were invented.
Now let's see how we can create scope in ES6
Variables declared with let or const, create their own scope inside a block. for example,
{
const isPrivate = 23
}
console.log(isPrivate);//ERROR : ReferenceError: isPrivate is not defined
So, when we create a block using curly brackets and declare isPrivate variable, then the outside still not have access to isPrivate. This is the reason why in modern JavaScript, IIFE are not that used anymore because if we want to create a new scope for data privacy, All we need to do is to just create a block using curly brackets. So there's no need to creating a function to create a new scope.
NOTE: IIFE is needed if we create a variable using var, because if we create a block using curly brackets and define a variable using var, it completely ignore the block and accessible outside the scope.
Closures
Closure is not a feature that we explicitly use as we don't create closures manually, like we create a new array or a new function, so, a closure simply happens automatically in certain situation.
What is closure?
A closure allows us to access to an outer function's scope from an inner function. Let's start with an example to understand the concept,
const secureBooking = () => {
let passengerCount = 0;
return () => {
passengerCount++;
console.log(`${passengerCount} passengers`);
}
}
//way 01
const booker = secureBooking();
booker();//1 passengers
booker();//2 passengers
booker();//3 passengers
In the example, we created a function called secureBooking and this will create the closure. Inside the function, we created a variable passengerCount and set to 0 initially. We will be able to manipulate this variable inside this function. However, this variable can not be manipulated and accessed from the outside. Now, secureBooking function will now return a new function, which will update the passengerCount variable, which is actually defined in the parent function. Inside the child or returned function, increase the passengerCount and log the variable. As you can see in the example, we will be able to successfully manipulate the variable inside the child function that defined in the parent function.
So, we can say that a closure makes a function to remember all the variables that existed at the function's birthplace, so we can imagine that, the secureBooking as being the birthplace of the child/ returned function, which is actually booker function. So, this function remembers everything at its birthplace, by the time it was created. When the booker function is executed, it attempts to increase the passengerCount variable. However, this variable is not in the current scope so, JavaScript will immediately look into the closure and see if it can find the variable there for example, if there was a passengerCount variable set to 0, it would still first use the one in the closure. So, the closure basically has the priority over the scope chain. After running the function, the passengerCount variable becomes one. When we called the booker function again, a closure is still there and attached to the function and the value of passengerCount is 1 and increasing the passengerCount to 2.
Closure Summary
- A closure is the closed-over variable environment of the execution context in which a function was created, even after that execution context is gone.
- A closure gives a function access to all the variables of its parent function, even after that parent function has returned. The function keeps a reference to its outer scope, which preserves the scope chain throughout time.
- A closure makes sure that a function does not loose connection to variables that existed at the function's birth place. It remembers the variables, even after the birthplace is gone. It's like a person who does not lose the connection to their hometown. the person is the function and the hometown is the function's parents scope and the function then does not lose the connection to the variables stored in this parent's scope.
- 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.
Let's finish the closure with an example,
let f;
const g = () => {
const a = 23;
f = () => {
console.log(a * 2)
}
}
g();
f();//46
const h = () => {
const b = 777;
f = () => {
console.log(b * 2)
}
}
h();
f();//1554
EXERCISES
I have attached the github link with examples with answers. I hope it will help you to understand the concept further.
Next Article: JavaScript: Working with Arrays
302 views