JavaScript

Last Updated: 1/8/2023

Functions in Detail

Function Expressions

  • A function can also be defined using an expression.
  • Function expression ends with a semicolon because it is a part of an executable statement.
  • Functions stored in variables do not need function names. They are always invoked using the variable name.
// Function Expression - Named
let runNamed = function run() {
	console.log("run");
};

// Function Expression - Anonymous
let runAnonymous = function() {
	console.log("run");
};

runNamed();
runAnonymous();

Hoisting

  • The process whereby the interpreter moves the declaration of functions, variables or classes to the top of their scope, prior to execution of the code.
walk();
function walk() {
    console.log("walk");
}

Arguments

  • Functions have a built-in object called the arguments object.
  • The argument object contains an array of the arguments used when the function was called.

Function can be called with or without arguments. If the argument is not supplied, the parameter value will be undefined.

function sum(x, y) {
    return x + y;
}

sum();
sum(1);
sum(1, 2);
sum(1, 2, 3, 4)

Function with varying number of parameters

function getSum() {
	let sum = 0;
	for(let value of arguments) {
	    sum += value;
	}
	return sum;
}
console.log(getSum(1, 2, 3, 4, 5));

Rest Operator

  • Allows a function to accept an indefinite number of arguments as an array
  • A function definition can only have one rest parameter, and the rest parameter must be the last parameter in the function definition.
  • Rest parameter bundles all the extra parameters into a single array
function getSum(...args)
{
 return args.reduce((a, b) => a + b);
}
console.log(getSum(1, 2, 3, 4, 5, 6))

Default Parameters

  • Allow named parameters to be initialized with default values if no value or undefined is passed.
  • Whenever you want to give the function parameter a default value, make sure that parameter is the last parameter in the list, or give all the parameters after that a default value.

Old Way

function calculateInterest(principal, rate, years) {
	rate = rate || 10;
	years = typeof years !== "undefined" ? years : 1;
	return principal * (rate / 100 ) * years;
}
function calculateInterest(principal, rate = 10, years = 1) {
	return principal * (rate / 100 ) * years;
}

console.log(calculateInterest(10000, 5, 2));
console.log(calculateInterest(10000));

Getters and Setters

const person = {
    firstName: "Ganesh",
    lastName: "Kumar",
    get fullName() {
        return `using prop - ${this.firstName} ${this.lastName}`;
    },
    set fullName(value) {
        const parts = value.split(" ");
        this.firstName = parts[0];
        this.lastName = parts[1];
    }
}
// getter
console.log(person.fullName);

// setter
person.fullName = "Siva Kumar";
console.log(person.fullName);

Error Handling

  • When executing JavaScript code, different errors can occur. Errors can be coding errors made by the programmer, errors due to wrong input, and other unforeseeable things.

Try Catch Finally

  • try statement defines a code block to run.
  • catch statement defines a code block to handle any error.
  • finally statement defines a code block to run regardless of the result.
function getWords(text) {
    return text.split(" ");
}

try {
   console.log(getWords());
}
catch(error) {
    console.log(error);
}

Throw Custom Error

  • Sometimes you want to report an error in your application. To do this you need to throw an exception.
  • Use the throw keyword and then create a new error object. This is how you throw an exception
  • Difference between error and exception,
  • new Error("invalid data") is just a plain object.
  • throw new Error("invalid data") the moment you throw the error it is referred as an exception. So this is an exceptional situation that should not have happened.
  • When you throw an exception, the lines after the throw statement are not executed. Will jump out of this method and the control will move to the catch block.
  • You can throw string, number, boolean, object
function getWords(text) {
    if(typeof text !== "string")
        throw new Error("Invalid data");

    return text.split(" ");
}
throw 100; // throw number
throw "too low"

Notes

  • You should do error handling at the beginning of a function or a method, this is called defensive programming. So you want to make sure that the values coming in are valid, they're in the right shape, so we can execute our logic.

this

  • this references the object that is executing the current function
  • If the function is part of an object, you call that function a method, this references that object itself
  • If that function is a regular function, which means it's not part of an object, this means the global object, which is the window object in browsers and global in Node
  • When you use the new operator it creates a new empty object, and sets this in the constructor function to point to the empty object.

Changing this

  • You can define a constant, called self, and setting it to this or that.
  • Function is technically, an object, so it has properties and methods that you can access using a dot notation. You have 3 methods apply, bind and call, and with these you can change the value of this
  • The difference between call and apply is only about passing arguments. With the apply method we have to pass arguments as an array
  • bind method does not call the function instead it returns a new function, and sets this to point to the object permanently. So bind returns the new function you can store the result in a constant and call the function just like a regular function.
  • Arrow functions inherit this from the containing function. In other words, this is not rebound to a new object.

Problem

const video = {
    title: "Avatar",
    tags: ["fiction", "graphics"],
    play() {
        console.log("video", this);
    },
    display: function() {
        this.tags.forEach(function(element){
            console.log(this.title, element);
        });
    }
}

Solution 1 - thisArg

const video = {
    title: "Avatar",
    tags: ["fiction", "graphics"],
    play() {
        console.log("video", this);
    },
    display: function() {
        const self = this;
        this.tags.forEach(function(element){
            console.log(this.title, element);
        }, this);
    }
}
video.play();
video.display();

Solution 2 - Self

const video = {
   title: "Avatar",
   tags: ["fiction", "graphics"],
   play() {
       console.log("video", this);
   },
   display: function() {
       const self = this;
       this.tags.forEach(function(element){
           console.log(self.title, element);
       });
   }
}
video.play();
video.display();

Solution 3 - bind

const video = {
    title: "Avatar",
    tags: ["fiction", "graphics"],
    play() {
        console.log("video", this);
    },
    display: function() {
        const self = this;
        this.tags.forEach(function(element){
            console.log(this.title, element);
        }.bind(this));
    }
}
video.play();
video.display();

Solution 4 - arrow

const video = {
    title: "Avatar",
    tags: ["fiction", "graphics"],
    play() {
        console.log("video", this);
    },
    display: function() {
        const self = this;
        this.tags.forEach((element) => {
            console.log(this.title, element);
        });
    }
}
video.play();
video.display();