Function Advance
Function Declaration VS Expression
2 ways to declare function.
//function Declaration
function walk() {
console.log('walk');
}
//Another way to define function(i.e function expression)
//Function Expression
let run = function () {
console.log('running');
};// semi colon is need here
//above function don't have name.
// so it is called anonymous function expression
// we call anonymous function using run reference like
run();
//we can decalre another variable called move abd set it like
let move = run
// and call move, both move and run are referencing same anonymous function
move()
console.log(move.name);// run
//we can give it name and call it as name function expression
let dog = function run1() {
console.log('run');
}
dog()// we can call it using it's reference only not name
console.log(dog.name)// But we get name of function as run1
Key difference between function declaration and function expression is how we can call these function.
And function defined using function declaration are hoisted but function expression are not hoisted means
- we can call function before we declaring it, using function declaration but can't using function expression.
Arguments
// 03 - Arguments
/*
JavaScript is dynamic language
He have the same concept in the arguments of a function
*/
function sum(a, b) {
return a + b;
}
console.log(sum(1, 2)); // This will return 3 as expected
console.log(sum(1)); // This will return NaN (Not a Number). Because the function is returning 1 + undefined
console.log(sum()); // This will return NaN (Not a Number). Because the function is returning undefined + undefined
console.log(sum(1, 2, 3, 4, 5)); // This will return 3. Only the first two arguments are used, the rest is being ignored.
// The point is we do not get an error.
// If want to have the flexibility to pass as many arguments as we want.
// Every function in JavaScript as a special object called arguments
function sum2() {
let total = 0;
for (let num of arguments) {
total += num;
}
return total;
}
console.log(sum2(1, 2, 3, 4, 5));
Rest Operator
// 04- The Rest Operator
// If we want a function with varying number of parameters we can use the rest operator. It should not be mistaken by the spread operator
// Syntax
function f(a, b, ...theArgs) {
// ...
}
/*
Description
A function's last parameter can be prefixed with ... which will cause all remaining (user supplied) arguments to be placed within a "standard" JavaScript array.
Only the last parameter can be a "rest parameter".
*/
function sum(...args) {
return args.reduce((a, b) => a + b); // Here we use the reduce method to sum all the values in the array args
}
console.log(sum(1, 2, 3, 4, 5));
// For example we could use this function to calculate the total of a shopping cart. The rest operator has to be the last parameter.
function sumShoppingCart(discount, ...prices) {
let total = prices.reduce((a, b) => a + b);
return total * (1 - discount);
}
console.log(sumShoppingCart(0.1, 50, 30));
Getter and Setter
// 06- Getters and Setters
/*
Getter and setters allows to use method of an object like a properties
example
*/
const person = {
firstName: 'Ram',
lastName: 'Hari',
get fullName() { // get keyword is used to set getter
return `${this.firstName} ${this.lastName}`
},
set fullName(name) { // set keyword is used to set setter
const nameParts = name.split(' '); // how to set value in setter, logic have to be witter inside setter
this.firstName = nameParts[0];
this.lastName = nameParts[1];
}
}
console.log(person.fullName); // Here we print Ram Hari using the getter just like properties of object
person.fullName = 'bishal bomjan' // Here with the setter we modify the full name to Bishal Bomjan just like properties
console.log(person.fullName) // Now with the getter the result is Bishal Bomjan
console.log(person)
Default Parameter
// 05- Default Parameters
/*
Syntax
function [name]([param1[ = defaultValue1 ][, ..., paramN[ = defaultValueN ]]]) {
statements
}
Description
In JavaScript, function parameters default to undefined. However, it's often useful to set a different default value. This is where default parameters can help.
In the past, the general strategy for setting defaults was to test parameter values in the function body and assign a value if they are undefined.
In the following example, if no value is provided for b when multiply is called, b's value would be undefined when evaluating a * b and multiply would return NaN.
*/
function interests(principal, rate, years) {
return ((principal * rate) / 100) * years;
}
console.log(interests(10000, 3.5, 5));
// If we want to have default value for interest rate and years
function interests1(principal, rate = 3.5, years = 5) {
rate = rate || 3.5 // We could use the or operator
year = year || 5 // It is old style of programming
return ((principal * rate) / 100) * years;
}
console.log(interests1(10000));
// From ES6 there is a new way to set the defaults values
function interestsDefault(principal, rate = 3.5, years = 5) {
return ((principal * rate) / 100) * years;
}
console.log(interestsDefault(10000, 3.5, 5));
// Once we give a paramter a default value, all the following parameters should also have a default value.
Exception Handling(Try and Cache)
// 07- Try and Catch
/*
Try block containe code that should be check for exception and catch block return exception
if exception is found
finally block will always run if exception are found or not
In finally we do task like closing file, database entries general things that we have to do.
Syntax
try {
try_statements
}
catch (exception_var) {
catch_statements
}
finally {
finally_statements
}
Description
The try statement consists of a try-block, which contains one or more statements.
{} must be always used, even for single statements. At least one catch-block, or a finally-block, must be present.
This gives us three forms for the try statement:
try...catch
try...finally
try...catch...finally
A catch-block contains statements that specify what to do if an exception is thrown in the try-block.
If any statement within the try-block (or in a function called from within the try-block) throws an exception,
control is immediately shifted to the catch-block.
If no exception is thrown in the try-block, the catch-block is skipped.
The finally-block will always execute after the try-block and catch-block(s) have finished executing.
It always executes, regardless of whether an exception was thrown or caught.
You can nest one or more try statements. If an inner try statement does not have a catch-block,
the enclosing try statement's catch-block is used instead.
You can also use the try statement to handle JavaScript exceptions.
*/
const person = {
firstName: "Miguel",
lastName: "Pimenta",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
if (typeof name !== "string") // retrun (simple approach)
throw new Error("Name is not a string"); // Here we throw an exception if the type of the user entry is not a string.
const nameParts = name.split(" ");
if (nameParts.length !== 2) throw new Error("Enter a first and last name");
if (nameParts.length) this.firstName = nameParts[0];
this.lastName = nameParts[1];
},
};
try {
person.fullName = null;
} catch (error) {
console.error(error);
} // finally {
// console.log('Restart')
// }
try {
person.fullName = "";
} catch (error) {
console.error(error);
} // finally {
// console.log('Restart')
// }
Local vs Global Scope
// 08- Local vs Global Scope
/*
Scope determines the accessibility (visibility) of variables.
JavaScript Scope
In JavaScript there are two types of scope:
Local scope
Global scope
JavaScript has code block scope: Each code block creates a new scope.
Scope determines the accessibility (visibility) of these variables.
Variables defined inside a code block are not accessible (visible) from outside the code block.
*/
const color = "red"; // This variable has global scope. We can acesses it anywhere
function start() {
const message = "hi"; // This variable is only accessible inside of the function. The scope of this constant is limited to the block in which it is define.
const color = "blue"; // If we define a new variable "color" with local scope, it will override the variable with global scope. Local scope take precedence over global scope.
console.log(color);
if (true) {
const another = "bye"; // This const it is only visible in the if block
}
// console.log(another); // If we try to log the "another" outside of the if block, we will have a Reference Error.
for (let i = 0; i < 5; i++) {
console.log(i); // The variable i is only accessible in the for loop code block.
}
}
function stop() {
const message = "bye"; // Although we have here another variable named "message", this is perfectly fine. This variable is in the local scope of the "stop()" function.
}
// In general we should avoid defining global variables, its considered a bad practice.
// This applies to the let and const key words.
This keyword
What is ‘this’ keyword in JS?
this reference that object that is executing the current function.
if function is part of object (i.e. method). Then this reference that object itself.
If function is regular function, not part of an object, this reference global object. window object in case of browser and global in node.
const video = {
title: 'a',
play() {
console.log(this);//{ title: 'a', play: [Function: play] }
}
}
video.play()
// here this reference object itself
// another example
video.stop = function () {
console.log(this) //{ title: 'a', play: [Function: play], stop: [Function (anonymous)] }
}
video.stop()
Look at 2nd example
//another example
const music = {
title: 'a',
tags: ['a', 'b', 'c'],
showTags() {
this.tags.forEach(function (tag) {
console.log(this.title, tag) // we get undefined for this.title
console.log(this) // this is refencing window object
})
}
}
music.showTags()
// Shouldn't this reference the video object?
// No, because inside callback function, it is just regular function.
// It is not method in video object, only method is showTags
// we have few solution to sovle this particular case
// forEach method has 2 parameters
// 1st is callback funtion and
// 2nd is thisArgs, thisArgs we pass object and this will reference that object.
const music1 = {
title: 'a',
tags: ['a', 'b', 'c'],
showTags() {
this.tags.forEach(function (tag) {
console.log(this.title, tag)
console.log(this)
}, { firstName: 'Bishal', title: 'code' }) // now this will refrence this object
}
}
music1.showTags()
//output
// code a
// { firstName: 'Bishal', title: 'code' }
// code b
// { firstName: 'Bishal', title: 'code' }
// code c
// { firstName: 'Bishal', title: 'code' }
// we don't want to refrence new object { firstName: 'Bishal', title: 'code' }.
// we want music object so
const music2 = {
title: 'a',
tags: ['a', 'b', 'c'],
showTags() {
this.tags.forEach(function (tag) {
console.log(this.title, tag)
console.log(this)
}, this)//upto this point 'this' is refrencing showTags method
}
}
music2.showTags()
//we get expected output
// a a
// { title: 'a', tags: [ 'a', 'b', 'c' ], showTags: [Function: showTags] }
// a b
// { title: 'a', tags: [ 'a', 'b', 'c' ], showTags: [Function: showTags] }
// a c
// { title: 'a', tags: [ 'a', 'b', 'c' ], showTags: [Function: showTags] }
//But not all methods in JS give ability to pass 'thisArgs'
//we have different solution for that.
Changing This
let see few way to change value of this.
// solution 1
const music = {
title: 'a',
tags: ['a', 'b', 'c'],
showTags() {
const self = this // upto this point 'this' reference music object so we assign it to self
this.tags.forEach(function (tag) {
console.log(self.title, tag)
})
}
}
music.showTags()
// This is not prefered approach but we see this format in many JS application.
//Solution 2
function playVideo() {
console.log(this)
}
//Function are technically object.
//so it have properties and methods
// we have 3 method apply, bind and call with this we can change the value of 'this'
//look at call method
playVideo.call({ name: 'bishal' })
//It is just like calling function but 1st parameter is 'thisArgs'
// which is object that refrence by this and other pramerter are normal funtion parameter
// now playVideo.call({ name: 'bishal' }) will refrence this object { name: 'bishal' }
//apply method
playVideo.apply({ name: 'bishal bomjan' })
//similar to call method
//only difference is if we have multiple argrument for above function
// in call method we pass it like
playVideo.call({ name: 'bishal' }, 122, 4, 3, 3) // normal argument pass
//in apply method we passt it like
playVideo.apply({ name: 'bishal bomjan' }, [122, 4, 3, 3]) // pass it as array
//bind method
//bind method doesn't call function, it return new function and sets 'this' to point given object
const fn = playVideo.bind({ name: 'hari' })
fn() //{ name: 'hari' }
//we can also call it directly without assigining
playVideo.bind({ name: 'hari' })() //{ name: 'hari' } same output
//here we can call bind method no need of self declartion
const music1 = {
title: 'a',
tags: ['a', 'b', 'c'],
showTags() {
//const self = this
this.tags.forEach(function (tag) {
console.log(this.title, tag)
}.bind(this)) // 'this' is pointing music1 object
}
}
music1.showTags()
//Newer and better solution stating from ES6 arrow function
const music2 = {
title: 'a',
tags: ['a', 'b', 'c'],
showTags() {
this.tags.forEach((tag) => {
console.log(this.title, tag)
})
}
}
music2.showTags()
// 'this' in arrow function inherit 'this' from containing function
Comments
Post a Comment