# Closures
Closures are functions that have access to their lexical environment.
Read more about closures in this
tutorial
const CounterFactory = function() {
let counter = 0;
return {
getCount : () => counter,
increment : () => {counter++;},
decrement : () => {counter--;},
}
}
const counter = CounterFactory();
console.log(counter);
counter
variable. Thus, whenever we call
increment
or decrement
the
counter
variable's value is changed as it is referenced
by position.
Below is a demo of the same.
The below demo could also be asked as an interview question as Implement a counter with increment & decrement functionality. Avoid using Global variables.
Count:
# Callback functions
Functions passed as arguments to other functions are called callback functions
function x(y){
console.log("X");
y();
}
function y(){
console.log("Y");
}
x(y);
x(y)
would log X
and
Y
to the console.
Here,
y
is a callback function that is passed to
x
as an argument.
Clicking on the button below would call x(y). Check the console to see the log.
# Callback Queue & Main thread blocking
Callback functions can only be executed by the Javascript runtime engine when they are pushed in the call stack.
But they cannot be directly moved to the call stack. Instead, they are pushed to the callback queue after the
timeout expiers(in case setTimeout
). A program called the event loop continously keeps track of the
callback queue and if there are any callback functions in the callback queue. It pushes the first one into the
call stack only if the call stack is empty.
If the callstack is not empty, the event loop would wait for the callstack to be empty and only then push the callback function into the call stack.
But there's another type of queue called the microtask queue which has higher priority than callback queue.
All functions coming through promises and mutation observer are pushed into the callback queue.
This means functions into the microtask queue will always get higher priority even if there's function present in the callback queue.
function blocking() {
console.log("Start");
setTimeout(() => {
console.log("I was supposed to be called after 1s.")
console.log("setTimeout Callback func");
}, 1000);
console.log("End");
//Blocking the Main Thread
console.log("Started Blocking the Main Thread");
let startDt = Date.now();
let endDt = Date.now();
// till the endDt is 10s from the start time
// the loop keeps running on the main thread i.e. on the callstack
while (endDt < startDt + 10000) {
endDt = Date.now();
}
console.log("Successfully blocked the main thread for 10 seconds");
console.log("Ended Blocking the Main Thread");
}
After calling the function, a timeout is registered to be executed after 1 second.
But we are runing a
while
loop on the main thread for 10s. Thus,
after 1 second the callback function is pushed to the callback queue. But the
event loop is unable to push the callback function to the call stack and the
callback function is executed after 10 seconds once the call stack is empty.
# First Class Functions
First class objects are called first class citizens.
These objects have the following properties:
> Be stored in a variable;
> Be passed as arguments to functions;
> Be returned by functions;
> Be stored in some data structure; and,
> Hold their own properties and methods.
Functions have all these properties and hence are called First class functions.
Below are demos of the same.
-
# Function Declaration/Statement
function greet(user) { //function declaration
window.alert("Welcome! " + user);
}
These function can also be store in variable.const greetVar = greet; // using functions as variables
Note that the function's reference is store in thegreetVar
variable -
# Function Expression
const greetFuncVar = function (user) {
window.alert("Welcome! " + user);
}Function Statement/Declaration vs ExpressionFunction statement are hoisted where as function expression are not.
Hence, function statements can be called before they are initialised but function expressions cannot.
// Function Declaration/Statement:
func(); // this is allowed
function func() {
console.log("Function statement called");
}
// Function Expression:
// func2(); // this is not and will throw a Error.
//cannot call func2 before initialisation
const /* ||let||var */ func2 = function () {
console.log("Function expression called");
}; -
# Anonymous Functions
function (){ // unamed Anonymous function
console.log("Anonymous funtion");
}
Note, The above code as is will give a SyntaxError as Function statements require a function name
Anonymous can be named or unamed.
Below is an example of Named Anonymous function.
//Named anonymous function. xyz is created as a local variable
//and the is accessible inside the xyz function scope
const abc = function xyz() {
console.log("Named anonymous function");
// these have access to their function
console.log(xyz);
};
abc(); // this is allowed
//outer function doesnot has access to this function.
// xyz(); // hence this is not allowed -
# Higher order function
Higher order functions are functions that accept anothe function as argument and perform additional operations on it.
Here,timerFactory
is a higher order function that takes a callback function and returns another function with additional
functionality to track the time need to execute the function added to it.
function timerFactory(callbackFunc) { // higher order function
return function (...args) {
const startTime = Date.now();
callbackFunc(...args);
const timeElapsed = (Date.now() - startTime) / 1000;
console.log(`[INFO] function '${callbackFunc.name}' took ${timeElapsed}s`);
};
}
We can now pass an function as a callback andtimerFactory
will return another function with the ability to track timeelapsed to it.
const sayHi = () => window.alert("Hi there!! developer");
const sayHiTimmed = timerFactory(sayHi);
Check console for the time log.
# Arrow Functions
() => window.alert("Hi! from Arrow") // arrow funtion
-
# Arrow vs Normal Functions
Arrow functions donot have their own 'this' binding and bind the 'this' scope to the lexical environment.
This means arrow functions bind 'this' to the scope in which they are defined.
Where as in,
Normal functions the binding of 'this' is derived from the context in which they are called. Hence, arrow functions are not suitable for object methods.
Examples follow to illustrate this.
const obj = {
offset: 10,
getOffsetArrow: (val) => {
// console.log( this, this.offset);
return this.offset + val;
},
getOffsetNormal: function (val) {
// console.log(this, this.offset);
return this.offset + val;
},
};
window.alert("Arrow function result : ", obj.getOffsetArrow(20));
As normal functions get their 'this' binding from the context in which they are called, thus as the 'getOffsetNormal' is called on the 'obj' object, so the 'offset' refers to the offset of the object.
window.alert("Normal function result : ", obj.getOffsetNormal(20));
-
# Call, Apply & Bind are not suitable for Arrow funtions
Call, Apply and Bind in Normal Functions
const objToBind = {
name: "Person Object",
};
window.name = "Window Object";
function logger(x, y) {
console.log(this.name, x, y);
}
logger.call(objToBind, 1, 2);
logger.apply(objToBind, [3, 4]);
const boundLogger = logger.bind(objToBind);
boundLogger(5, 6);
In all the above invokcations
logger
will be called and the 'this' will be bound to the 'objToBind' object's 'this' context
Call, Apply and Bind in Arrow Functions
const objToBind = {
name: "Person Object",
};
window.name = "Window Object";
const arrowLogger = (x, y) => {
window.alert(this.name + x + y);
};
thus the 'name' property is refering to the outer scope's 'name'
variable i.e. the window object in this case. And as we've defined
'name' on the window object earlier, hence that is used here.
arrowLogger.call(objToBind, 1, 2);
arrowLogger.apply(objToBind, [3, 4]);
const boundLoggerArrow = arrowLogger.bind(objToBind);
boundLoggerArrow(5, 6);
-
# Arrow functions and setTimeout
Arrow functions are most useful incase of
Normal functions & setTimeoutsetTimeout
methods when the function is called in a different context.
Following example illustrates this.
const mockService_Broken = {
data: 10,
getData: function () {
setTimeout(function () {
window.alert("Response : " + this.data);
}, 2000);
},
};mockService_Broken.getData();
Normal function bind their 'this' to the context in which they are called.
As timeouts are called in the global/window context, thus as 'data' is not
defined in global/window object, hence we get undefined.
Arrow functions & setTimeout
const mockService_Ok = {
data: 10,
getData: function () {
setTimeout(() => {
console.log("Response : " + this.data);
}, 2000);
},
};mockService_Ok.getData();
Arrow function bind their 'this' to lexical environment in which they
are declared(window/global object if there's not any) irrespective of
the calling context. Thus, even if the setTimeout callback is called
in the window context, the 'this' is bound to the context in which
they are declared. Thus, 'this.data' references the 'data' property
of the 'mockService_Ok' object
# Debouncing & Throttling in JS
This Tutorial blog explains both these topics very clearly.
-
# Debouncing
Debouncing is a technique used to limit the number of callbacks invoked due to an event listener.
In debouncing we set up a timer when an event fires. And only after the timer is over, do we call the callback associated with the event.
If while the timer is runing, the event fires again we cancel the previous timer and the callback that was supposed to be invoked at the end of the timer is canceled and we start a new timer with a new callback altogether.
Following demo illustrates this clearly.
Count without debounce:
Count with debounce:
Let's take a look at the code.
let counter = 0;
let debounceCounter = 0;
let timeoutId;
function debounce() {
counter++;
count.textContent = counter;
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function () {
debounceCounter++;
debounceCount.textContent = debounceCounter;
}, 300);
}
document.getElementById("debounce-input").addEventListener("input", debounce);
The normal count increments on every input. But the debounce count increments only after the use has stopped typing for300ms
.
If user types something before the timer is over then, the previous timeout is canceled(and also the callback associated with it) and a new timer is setup again. -
# Throttling
Throttling is anothe way to limit the number of times a callback is invoked due to an event. But this is different from debouncing.
In throttling we only invoke a callback associated with an event once in a given time frame no matter how many times the particular event is fired.
Following demo illustrates this.Mouse over the grey area to trigger the mouse over event.
Count without throttle:
Count with throttle:
Let's look at the code and understand what's going on.
let nothrottlecounter = 0;
let throttleCounter = 0;
let thottleTimeoutId;
function throttle() {
nothrottlecounter++;
nothrottleCount.textContent = nothrottlecounter;
/**
* If there's a timmer already running then just return and
* don't touch the throttled function.
*/
if (thottleTimeoutId) return;
thottleTimeoutId = setTimeout(function () {
/**
* else when the timer stops, we set the timer to 'undefined'
* so that, the next time the event is fired the throttled
* function gets invoked.
*/
thottleTimeoutId = undefined;
throttleCounter++;
throttleCount.textContent = throttleCounter;
}, 250);
}
document
.getElementById("mouse-hover-zone")
.addEventListener("mousemove", throttle);
every time the event is triggered. But the throttled functions is called only once in250ms
interval.
Even if the event is triggered during this time, the throttled function doesnot get invoked.
# Promises
Promises in javascript represent an eventual completion order failure of an asynchronous operation.
These asynchronous operations are called blocking code like file read/write, DB and API operations
that block the main thread if they are called synchronously.
Promises have 3 states Pending,
Resolved and Rejected
and they can live in either one of these states.
const promise = new Promise(function (resolve, reject) {
if(/*operation Successful*/){
//resolve the promise
resolve(/*pass arguments*/)
}
else{ //operation failed
//reject the promise by specfying the error
reject(new Error("mention cause of error here"))
}
});
promise
variable holds a Promise
object.
And the state of this promise is Pending. Once the operation is complete( maybe
some DB operation or API call) we either resolve the promise if the operation was successful
or reject the promise if the operation failed.
A
Promise
will finally be either Resolved or Rejected.
If a Promise
is resolved then we chain a .then
method and
pass a callback to the
.then
method than will be called after the promise is resolved. In case a Promise
is rejected
then we chain a
.catch
method and pass a error handling callback to the .catch
method.
// creating a promise object
const resolverPromise = new Promise(
function (resolve, reject) {
setTimeout(() => {
resolve("SDE Mayukh");
}, 1000);
});
resolverPromise.then((username) => window.alert("Welcome!! " + username));
rejectorPromise.catch((err) => window.alert("Unable to fetch data. " + err));