This is related to CoderDost Course on Advance JavaScript
We have 3 types of variable in JavaScript var, let and const
☠️☠️ var is the old one, and should not be used now in any case. As it has many issues with creating scopes
Also there are 4 kinds of scope in Javascript - Block Scope, Global Scope, Function Scope, Module Scope
The scope is the current context of execution in which values and expressions are "visible" MDN
Global Scope : Any variable/expression which is written outside - i.e. not inside any functions, blocks etc. This is shared across files.
{ // block scope
let x = 0;
let y = 0;
console.log(x); // 0
let x = 1; // Error
}
{
let x = 1;
let y = 1;
x = 2;
console.log(x);// 2
}
console.log(x); // Error in Global Scope
Temporal Dead Zone(TDZ) : the area in which a variable is not accessible. Temporal because it depends on time of excution not position
{
// TDZ starts
const say = () => console.log(msg); // hi
let msg = 'hi';
say();
}
{
const x; //Error
const y=0;
}
{
const x=1;
x=2 // Error
}
console.log(x); // Error
let x = 0 // shadowed variable
{
let x = 1;
console.log(x)
}
}
var x = 1;
var x = 2; // valid
console.log(y) // valid
var y = 3
z=4
console.log(z) // valid
var z;
NOTE : You should NOT use var now ❌
for(let i=0;i<5;i++){
setTimeout(
()=>console.log(i),
1000)
} // prints 0,1,2,3,4
for(var i=0;i<5;i++){
setTimeout(
()=>console.log(i),
1000)
} // prints 5,5,5,5,5
In modern javascript, a file can be considered as module, where we use export and import syntax to use variable across files. We
<script src="index.js" type="module"></script>
export { someVar, someFunc}
import { someVar} from './app.js'
function sayHi(){
console.log(this) // this will refer to window
}
// Strict mode can change this behaviour;
`use strict`
function sayHi(){
console.log(window) // this is a better way of code
}
function sayHi(name){
return name;
}
sayHi() // this call will create a function scope
sayHi() // this call will create another function scope
let name = 'john'
console.log(name)
--- title: Lexical Enviroment (Global variable) --- classDiagram null <|-- LexicalEnviroment : [outer] LexicalEnviroment <-- name class LexicalEnviroment{ name: 'john' } class name{ }
let name = 'john';
function sayHi(){
let greet = "hi"
console.log(greet)
}
sayHi()
console.log(name, sayHi)
--- title: Lexical Enviroment (functions) --- classDiagram null <|-- LexicalEnviroment1 : [outer] LexicalEnviroment1 <|-- LexicalEnviroment2 : [outer] LexicalEnviroment1 <-- name LexicalEnviroment1 <-- sayHi LexicalEnviroment2 <-- greet class LexicalEnviroment1{ name: 'john', sayHi: function } class LexicalEnviroment2{ greet: 'hi' }
let name = 'john';
function sayHi(){
let greet = "hi"
console.log(name)
}
sayHi()
--- title: Lexical Enviroment (functions) --- classDiagram null <|-- LexicalEnviroment1 : [outer] LexicalEnviroment1 <|-- LexicalEnviroment2 : [outer] LexicalEnviroment2 <-- name class LexicalEnviroment1{ name: 'john', sayHi: function } class LexicalEnviroment2{ greet: 'hi' }
The movement of variable declaration to top of scope - before execution
let name = 'john';
sayHi() // valid
function sayHi(){
let greet = "hi"
console.log(name)
}
sayHello() // error
let sayHello = function(){
console.log(name)
}
Temporal Dead Zone(TDZ) :
let x = 1;
{
console.log(x) // Reference error
let x = 2;
}
function createUser(name){
let greeting = 'Hi '
function greet(){
return greeting + name + ' is Created';
}
return greet()
}
createUser('john') // Hi john is created;
greet
function itself.function createUser(name){
let greeting = 'Hi '
function greet(){
return greeting + name + ' is Created';
}
return greet // returned just definition of function
}
let welcomeJohn = createUser('john')
welcomeJohn() // // Hi john is created;
function initCounter() {
let count = 0;
return function () {
count++;
};
}
let counter = initCounter();
counter() // 0
counter() // 1
let counter1 = initCounter();
counter1() // 0
counter1() // 1
NOTE : so whenever you have a function which wants to preserve a value over many calls - it's a time for closure.
function init() {
let name = 'john';
function greet() {
console.log(name)
}
return greet;
}
let sayHi = init();
sayHi();
--- title: Lexical Enviroment (functions) --- classDiagram null <|-- LexicalEnviroment1 : [outer] LexicalEnviroment1 <|-- LexicalEnviroment2 : [outer] LexicalEnviroment2 <|-- LexicalEnviroment3 : [outer] LexicalEnviroment1 --> init LexicalEnviroment3 --> name LexicalEnviroment1 --> sayHi class LexicalEnviroment1{ sayHi: ---- init: function } class LexicalEnviroment2{ name: 'john' greet: function } class LexicalEnviroment3{ --empty-- }
function initCounter(id) {
let count = 0;
return function () {
count++;
document.getElementById(id).innerText = count;
};
}
let count = 10;
let counter1 = initCounter('btnCount1');
let counter2 = initCounter('btnCount2');
// here `btn1` and `btn2` are id of HTML buttons.
<button onclick="counter1()">1</button>
<p id="btnCount1"></p>
<button onclick="counter2()">2</button>
<p id="btnCount2"></p>
function initAddString(inputId, outputId) {
let str = '';
return function () {
str += ' ' + document.getElementById(inputId).value;
document.getElementById(inputId).value = '';
document.getElementById(outputId).innerText = str;
};
}
let strAdder1 = initAddString('text1', 'text-output1');
let strAdder2 = initAddString('text2', 'text-output2');
<input type="text" id="text1">
<button onclick="strAdder1()">Add String</button>
<p id="text-output1"></p>
<input type="text" id="text2">
<button onclick="strAdder2()">Add String</button>
<p id="text-output2"></p>
// Immediately invoked function expressions
(function(){
var x = 1; // this var is now protected
})()
(function(a){
var x = a; // this var is now protected
})(2)
function sum(a){
return function(b){
return function(c){
console.log(a,b,c)
return a+b+c
}
}
}
let add = a => b => c => a+b+c
let log = time => type => msg => `At ${time.toLocaleString()}: severity ${type} => ${msg}`
log(new Date())('error')('power not sufficient')
let logNow = log(new Date())
logNow('warning')('temp high')
let logErrorNow = log(new Date())('error')
logErrorNow('unknown error')
function op(operation) {
return function (a) {
return function (b) {
return operation === 'add' ? a + b : a - b;
};
};
}
const add3 = op('add')(3);
const sub3 = op('sub')(3);
const add = op('add');
add3(6);
sub3(6);
add(1)(2);
let person = {name:'john'}
let human = person;
--- title: Reference are point to same value --- flowchart LR person --> Object human--> Object
let person = {name:'john'} // Object1
person = {name:'wick'}; // Object2
--- title: Reference can be changed for a variable (Garbage collection of Object1) --- flowchart LR person -.- Object1 person --> Object2
const person = {name:'john'} // Object1
person = {name:'wick'}; // ERROR
let person = {
name: 'John',
address: { city: 'delhi', state: 'delhi' },
};
--- title: Object properties can point to other objects --- flowchart LR person --> Object Object_address --> addressObject
let addressObject = { city: 'delhi', state: 'delhi' }
let person = {
name: 'John',
address: addressObject
};
Many methods can be used to copy object without old reference
let person = {name:'john'}
let newPerson = Object.assign({}, person)
let person = {name:'john'}
let newPerson = {...person}
But problem which these is they just create a copy of properties of that object , but not creating a copy of their references also.
let addressObject = { city: 'delhi', state: 'delhi' }
let person = {
name: 'John',
address: addressObject
};
let newPerson = Object.assign({}, person)
person === newPerson; // false
person.address === newPerson.address // true
This is a hard problem to solve in past as there can be multiple level of nested objects and there can be references to functions etc also. few methods which are there:
let addressObject = { city: 'delhi', state: 'delhi' }
let person = {
name: 'John',
address: addressObject
};
let str = JSON.stringify(person)
let jsonObject = JSON.parse(str);
let addressObject = { city: 'delhi', state: 'delhi' }
let person = {
name: 'John',
address: addressObject,
};
person.me = person
let newPerson = structuredClone(person);
let person = {
name:'john',
sayHi: function(){
return "hi";
}
}
person.sayHi() // hi
let person = {
name:'john',
sayHi: function(){
return "hi "+ this.name;
}
}
person.sayHi() // hi john
let person = {
name:'john',
sayHi: function(){
return "hi "+ this.name;
}
}
person.sayHi() // hi john
function sayHi(){
return "hi "+ this.name;
}
sayHi() // Error
// here this will "undefined" in Strict mode
let obj1 = {name: 'john'}
let obj2 = {name: 'wick'}
// you can add functional property
obj1.say = sayHi;
obj2.say = sayHi;
obj1.say() // hi john
obj2.say() // hi wick
let person = {
name:'john',
sayHi: ()=> {
return "hi "+ this.name;
}
}
person.sayHi() // Error
let person = {
0:'john',
sayHi: ()=> {
return "hi "+ this.name;
}
}
person["0"] // this number will convert to string
const id = Symbol("id"); // "id" is descriptor
let person = {
name:'john',
[id]:1
}
person[id] // 1
// note that we have put square [] on property so that it is not confused with "id" string.
Symbol are always unique - so there is no chance of collision. Even with same "descriptor" they will be uniquely initialized.
You can get Symbol for some descriptor or key using some methods
// get symbol by name
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
for..in loop ignore Symbols. Also methods like Object.keys() ignore these properties.
function sayHi(greet){
return greet
}
sayHi.name // name of function
sayHi.length // length of arguments
sayHi.count =0; // function can have properties
sayHi.count++;
sayHi.count;
sayHi() // works
function sayHi(greet){
return greet
}
sayHello() // Error
let sayHello = function(){ // functional expression
}
sayHello.name // sayHello
function Person(name){
this.name = name
}
const p = new Person('john') // constructor
let sayHello = function fx(user){ // named functional expression
if(user){
return "hello " + user
} else {
return fx('anonymous')
}
}
// this can help in case where sayHello is re-assiged to something
let sayHi = sayHello
sayHello = null
sayHi()
let modifiedFx = Decorator(preDefinedFx)
function heavy(x) {
console.log(x + ':heavy');
return x + ':heavy';
}
function memoized(fx) {
let map = new Map();
return function (x) { // wrapper
if (map.has(x)) {
return map.get(x);
} else {
let memoValue = fx(x);
map.set(x, memoValue);
return memoValue;
}
};
}
let memoizedHeavy = memoized(heavy)
memoizedHeavy(2);
memoizedHeavy(2); // take from cache
Another Problem
let task = {
name: 'demo',
heavy(x) {
console.log(x + ':heavy:' + this.name);
return x + ':heavy' + this.name;
},
};
function memoized(fx) {
let map = new Map();
return function (x) {
if (map.has(x)) {
return map.get(x);
} else {
let memoValue = fx(x);
map.set(x, memoValue);
return memoValue;
}
};
}
task.memoizedHeavy = memoized(task.heavy)
task.memoizedHeavy(1) // 1:heavyundefined
Solution : use function.call()
person = {
name: 'demo',
age: 12,
location: 'delhi',
};
function checkName(a) {
return !!this.name;
}
checkName() // Error
checkName.call(person)
checkName.call(person, 1) // a = 1
person = {
name: 'demo',
age: 12,
location: 'delhi',
};
function checkName(a) {
return !!this.name;
}
checkName() // Error
checkName.apply(person)
checkName.apply(person, [1]) // a = 1
person = {
name: 'demo',
age: 12,
location: 'delhi',
};
function checkName(a) {
return !!this.name;
}
checkName() // Error
let boundCheckName = checkName.bind(person)
boundCheckName();
Solution
let task = {
name: 'demo',
heavy(x) {
console.log(x + ':heavy:' + this.name);
return x + ':heavy' + this.name;
},
};
function memoized(fx) {
let map = new Map();
return function (x) {
if (map.has(x)) {
return map.get(x);
} else {
let memoValue = fx.call(this,x);
map.set(x, memoValue);
return memoValue;
}
};
}
task.memoizedHeavy = memoized(task.heavy)
task.memoizedHeavy(1) // 1:heavydemo
let count = 1;
function showCount() {
count++;
console.log({ count });
}
function debounce(fx, time) {
let id = null;
return function (x) {
if (id) {
clearTimeout(id);
}
console.log({ id });
id = setTimeout(() => {
fx(x);
id = null;
}, time);
};
}
let showCountD = debounce(showCount, 2000);
setTimeout(showCountD, 1000);
setTimeout(showCountD, 1500);
setTimeout(showCountD, 2000);
setTimeout(showCountD, 2500);
setTimeout(showCountD, 5000);
const el = document.getElementById('text1');
const logo = document.getElementById('text-output1');
el.addEventListener(
'keyup',
debounce(function (e) {
logo.innerText = e.target.value;
}, 1000)
);
<input type="text" id="text1">
<p id="text-output1"></p>
let count = 1;
function showCount() {
count++;
console.log({ count });
}
function throttle(fx, time) {
let id = null;
let arg = [];
return function (x) {
arg[0] = x;
if (!id) {
id = setTimeout(() => {
fx(arg[0]);
id = null;
}, time);
}
console.log({ id });
};
}
let showCountT = throttle(showCount, 2000);
setTimeout(showCountT, 1000);
setTimeout(showCountT, 1500);
setTimeout(showCountT, 2000);
setTimeout(showCountT, 2500);
setTimeout(showCountT, 5000);
function throttle(fx, time) {
let id = null;
let arg = [];
return function (x) {
arg[0] = x;
if (!id) {
id = setTimeout(() => {
fx(arg[0]);
id = null;
}, time);
}
};
}
function sayHi(){console.log('hi')}
document.addEventListener('scroll',throttle(sayHi,1000))
Iterables are objects in which we can make array like iteration (Example using for..of loop of spread operators)
To make any object iterable we have these conditions
iterable[Symbol.iterator]() => Iterator
Iterators are objects which have :
{value:-some-value-, done:-boolean-}
e.g. *{value: 1, done: false}Now, making an Iterable is like this:
let iterator = {
i: 0,
next: function () {
return { value: this.i, done: this.i++ > 5 };
},
};
let iterable = {
name: 'john',
age: 34,
[Symbol.iterator]() {
return iterator;
},
};
let range = {
start: 0,
end: 5,
[Symbol.iterator]() {
let that = this; // this line is very important
let i = this.start;
return { // iterator object
next: function () {
return { value: i, done: i++ > that.end };
}
};
},
};
let num = [1, 2, 3];
let iterator = num[Symbol.iterator]();
iterator.next();
iterator.next();
iterator.next();
iterator.next();
break
etc.An object can be
Example :
// iterable + array-like
let arr = [1,2,3]
// only iterable
let range = {
start: 0,
end: 5,
[Symbol.iterator]() {
let that = this; // this line is very important
let i = this.start;
return {
next: function () {
return { value: i, done: i++ > that.end };
},
};
},
};
// only array-like
let array = {
0: 1,
1: 5,
length:2
};
// none
let obj = {
name:'john'
}
let arrayLike = {
0: 0,
1: 5,
length: 2
};
let arr = Array.from(arrayLike);
// also used for general things
let set = new Set()
set.add(1);
set.add(2);
let arr2 = Array.from(set) // [1,2]
let map = new Map();
let person = {name:'john'}
let personAccount = {balance: 5000}
map.set('1', 'str1'); // string key
map.set(1, 'num1'); // numeric key
map.set(true, 'bool1');
map.set (person, personAccount)
map.get(1) // 'num1'
map.get('1') // 'str1'
map.get(person) // { balance : 5000 }
map.size // 4
map.keys() // iterable of keys
map.values() // iterable of values
map.entries() // iterable of key-value pair
map.has(1) // key exists
let obj = {a:1,b:2,c:3};
let map = new Map(Object.entries(obj));
let map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
let obj = (Object.fromEntries(map.entries())); // {a:1,b:2,c:3}
let set = new Set();
let obj1 = { name: "John" };
let obj2 = { name: "Jack" };
let obj3 = { name: "Peter" };
set.add(obj1);
set.add(obj2);
set.add(obj3);
set.add(obj2);
set.add(obj3);
// set keeps only unique values
set.size; // 3
set.keys() // iterable of keys
set.values() // iterable of values (Same as keys)
set.entries()
let weakMap = new WeakMap()
let person = {name:'john'}
weakMap.set(person, {....});
person = null // in future we decide to remove this key
// so weakMap will remove it from memory space automatically
function* generatorFunction(){
yield 1;
yield 2;
yield 3
}
let generator = generatorFunction();
generator.next() // {value:1, done:false}
generator.next() // {value:2, done:false}
generator.next() // {value:3, done:false}
generator.next() // {done:true}
function* generator() {
let i = 0;
while (true) {
yield i;
i++;
}
}
const gen = generator();
function createID(it) {
return it.next().value;
}
createID(gen);
createID(gen);
createID(gen);
createID(gen);
createID(gen);
function* generatorFunction(){
yield 1;
yield 2;
yield 3
}
let generator = generatorFunction();
let nums = [...generator] // [1,2,3]
NOTE: Don't put a Spread operator or for..of loop on inifinite iterable
let range = {
start: 0,
end: 5,
*[Symbol.iterator]() { // * makes it generator function
for(let value = this.start; value <= this.end; value++) {
yield value;
}
}
};
for(let r of range){
console.log(r)
}
Better version - with function
function range(start,end){
return {
*[Symbol.iterator]() {
for(let value = start; value <= end; value++) {
yield value;
}
}
}
};
for(let r of range(1,5)){
console.log(r)
}
let values = [...range(1,5)]
Better - Better version - with function
function* range(start,end){
for(let value = start; value <= end; value++) {
yield value;
}
};
let generator = range(1,5)
console.log([...generator]) // [1,2,3,4,5]
function* generatorFunction(){
yield 1;
yield 2;
return 3
}
let generator = generatorFunction();
generator.next() // {value:1, done:false}
generator.next() // {value:2, done:false}
generator.next() // {value:3, done:true} **
**Composed Generator using - yield*
function* range(start,end){
for(let value = start; value <= end; value++) {
yield value;
}
};
function* multiRange(){
yield* range(0,5),
yield* range(100,105)
yield* range(200,205)
}
let generator = multiRange();
console.log([...generator]) //[ 0, 1, 2, 3, 4, 5, 100, 101, 102, 103, 104, 105, 200, 201, 202, 203, 204, 205 ]
function* generatorFunction(){
let result = yield 1;
console.log(result)
let result2 = yield 2;
console.log(result2)
let result3 = yield 3
console.log(result3)
}
let generator = generatorFunction();
let r1 = generator.next()
let r2 = generator.next(r1.value)
let r3 = generator.next(r2.value)
generator.next(r3.value)
without generators
let range = {
start: 0,
end: 5,
[Symbol.asyncIterator]() {
let that = this; // this line is very important
let i = this.start;
return {
next: async function () {
await new Promise((resolve) => setTimeout(resolve, 1000));
return { value: i, done: i++ > that.end };
},
};
},
};
(async function () {
for await (let f of range) {
console.log(f);
}
})();
with generators
let range = {
start: 0,
end: 5,
async *[Symbol.asyncIterator]() {
for(let i = this.start; i <= this.end; i++) {
await new Promise((resolve) => setTimeout(resolve, 1000));
yield i
};
},
};
(async function () {
for await (let f of range) {
console.log(f);
}
})();
async function* getDataAsync(page) {
let response = await fetch(
'https://projects.propublica.org/nonprofits/api/v2/search.json?q=x&page='+page
);
let result = await response.json();
for(let org of result.organizations){
yield org.name;
}
}
async function* getData() {
let response = await fetch(
'https://projects.propublica.org/nonprofits/api/v2/search.json?q=x'
);
let result = await response.json();
for (let i = 0; i <= result.num_pages; i++) {
yield* await getDataAsync(i);
}
}
(async function () {
let orgs = []
for await (let f of getData()) {
orgs.push(f);
}
console.log(orgs); // List of all organization in API
})();
[[Prototype]]
--- title: Prototype Inheritance --- classDiagram prototypeObject <|-- object : [[prototype]]
--- title: Prototype example --- classDiagram note "animal is prototype of dog" animal <|-- dog : [[prototype]] note "dog is \nprototypically inherited \nfrom animal" animal : eats class dog{ barks }
let animal = { eats: true };
let dog = { barks: true };
dog.__proto__ = animal;
dog.barks // true
dog.eats // true
--- title: Prototype chaining --- classDiagram animal <|-- dog : [[prototype]] animal : eats animal : walks() class dog{ barks }
let animal = {
eats: true,
walks: function () {
return 'walks';
},
};
let dog = { barks: true };
dog.__proto__ = animal;
dog.walks() // walks
--- title: Prototype chain can be longer and longer --- classDiagram animal <|-- dog : [[prototype]] dog <|-- myDog : [[prototype]] animal : eats animal : walks() class dog{ barks } class myDog{ name }
let animal = {
eats: true,
walks: function () {
return 'walks';
},
};
let dog = { barks: true };
let myDog = { name: 'sifu' };
dog.__proto__ = animal;
myDog.__proto__ = dog;
myDog.name // sifu
myDog.barks // true
myDog.walks() // walks
--- title: Prototype end at "null" --- classDiagram null <|-- Object_prototype Object_prototype <|-- animal animal <|-- dog : [[prototype]] dog <|-- myDog : [[prototype]] animal : eats animal : walks() class dog{ barks } class myDog{ name }
__proto__
is a getter/setter for [[Prototype]]
getter/setter
properties.__proto__
is not used now , and recommended way is to use Object.getPrototypeOf() and Object.setPrototypeOflet animal = {
eats: true,
walks: function () {
return 'walks';
},
};
let dog = { barks: true };
let myDog = { name: 'sifu' };
dog.__proto__ = animal;
myDog.__proto__ = dog;
myDog.walks = function(){
return 'walks slowly'; // this will not affect prototype
}
myDog.walks() // walks slowly
dog.walks() // walks
avoid
looping on inherited ones use Object.hasOwn
or Object.prototype.hasOwnProperty
inherited
properties.// simple object initialization
let usr = {
name : 'john'
}
// now using a constructor function
function User(name){
this.name = name
}
let user = new User('john');
console.log(user)
// User{ name : 'john'}
console.log(usr)
// {name : 'john'}
function User(name){
this.name = name
}
let user = new User('john');
console.log(User.prototype) // prototype object
// User.prototype.constructor = User
// this above assignment is done by the constructor call itself
User.prototype.constructor === User // true
// user.__proto__ = User.prototype
// this above assignment is done by the constructor call itself
user.__proto__ === User.prototype // true
--- title: .prototype property --- classDiagram class User{ prototype } User_prototype <|-- user : [[prototype]] User_prototype : constructor class user{ name }
function User(name){
this.name = name
}
User.prototype.sayHi = function () {
return this.name;
};
let user = new User('john');
let user1 = new User('wick');
user.sayHi()
// 'john'
user1.sayHi();
// 'wick'
prototypes
. you can have inherited methods.--- title: .prototype property --- classDiagram class User{ prototype } User_prototype <|-- user : [[prototype]] User_prototype : constructor User_prototype : sayHi class user{ name }
function User(name){
this.name = name
}
User.prototype.reverseName = function () {
return this.name.split('').reverse().join('');
};
let user = new User('john');
let user1 = new User('wick');
user.reverseName()
// 'nhoj'
user1.reverseName();
// 'kicw'
remember prototype based methods are directly available on their created object instances.
you can also change the prototype completely, not recommended though
let animal = {
eats: true,
walks: function () {
return 'walks';
},
};
function Dog(){
this.barks = true
}
Dog.prototype = animal;
let dog = new Dog();
dog.walks()
// walks
dog.__proto__ === animal; // true
Dog.prototype === dog.__proto__ // true
--- title: .prototype property --- classDiagram class Dog{ prototype } animal <|-- dog : [[prototype]] animal : eats animal : walks() class dog{ barks }
let obj = {}
let obj1 = new Object();
Object.prototype === obj1.__proto__ // true
Object.prototype === obj.__proto__ // true
let arr = []
let arr1 = new Array();
Array.prototype === arr1.__proto__ // true
Array.prototype === arr.__proto__ // true
function Fx(){
}
Function.prototype === Fx.__proto__ // true
let d = new Date();
d.getTime(); // getTime is given by Date.prototype
Primitive types also get wrapped into a Object when used as an Object
"hello".toString()
10.1111.toFixed(2)
if(!Array.prototype.contains){
Array.prototype.contains = function(searchElement) {
return this.indexOf(searchElement)>=0 ? true : false
}
}
// similar to includes()
NOTE : Shims
are piece of code to correct some existing behaviour, while Polyfills
are new API/ behaviours.
Some properties and methods are directly created on these Native constructors.
These are not available on instances, and only available on Native contructors
Classes are easier way to implement inheritance in JavaScript.
It's a syntactic sugar to Protypical Inheritance BUT more functionalities than it.
ProtoType Version
function User(name){
this.name = name
}
User.prototype.sayHi = function () {
return this.name;
};
let user = new User('john');
user.sayHi() // john
Class Version
class User {
constructor(name) {
this.name = name;
}
sayHi() {
return this.name;
}
}
let user = new User('john');
user.sayHi() // john
constructor
method is added when called with new
;class User {
constructor(name) {
this.name = name;
}
sayHi() {
return this.name;
}
}
User.prototype.sayHello = function(){
return "hello "+this.name;
}
let user = new User('john');
user.sayHello() // hello john
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName(){
return this.firstName + ' ' + this.lastName;
}
set fullName(_fullName){
this.firstName = _fullName.split(' ')[0];
this.lastName = _fullName.split(' ')[1];
}
}
let user = new User('john', 'wick');
user.fullName // john wick
user.fullName = "john cena"
user.firstName // john
user.lastName // cena
[ ]
let variableName = "hello"
class User {
constructor(name) {
this.name = name;
}
[variableName]() {
return this.name;
}
}
let user = new User('john');
user.hello() // john```
class Button {
constructor(value) {
this.value = value;
}
click() {
return this.value;
}
}
let button = new Button("play");
button.click() // play
setTimeout(button.click, 1000);
// this has issue - this has changed here
this
.2 Solution exists :
setTimeout(()=>button.click(), 1000);
constructor
object.setTimeout(button.click.bind(button), 1000);
Also you can add this arrow style function in class definition - which will act as class field
class Button {
constructor(value) {
this.value = value;
}
click = () => { // this is a class field
return this.value;
}
}
let button = new Button("play");
button.click() // play
setTimeout(button.click, 1000);
We can inherit Parent Class properties and metods in a Child Class. using extends keyword
Here we have Shape as Parent and Rectangle as Child :
class Shape {
constructor(name) {
this.name = name;
}
displayShape() {
return 'Shape ' + this.name;
}
}
class Rectangle extends Shape {
}
let rect1 = new Rectangle('rect1');
rect1.displayShape(); // Shape rect1
// constructor of Child is implicitly created and it calls constructor of Parent
// constructor(...args){
// super(..args)
// }
--- title: .prototype property --- classDiagram class Shape{ prototype } Shape_prototype <|-- Rectangle_prototype : [[prototype]] Shape_prototype : constructor Shape_prototype : name Shape_prototype : displayName Rectangle_prototype <|-- rect1 : [[prototype]] Rectangle_prototype : constructor class Rectangle_prototype{ } class Rectangle{ prototype }
class Shape {
constructor(name) {
this.name = name;
}
displayShape() {
return 'Shape ' + this.name;
}
}
class Rectangle extends Shape {
constructor(name, width, height) {
super(name);
this.width = width;
this.height = height;
this.area = width * height;
}
}
let rect1 = new Rectangle('rect1', 10, 11);
rect1.displayShape();
rect1.area;
class Shape {
constructor(name,area) {
this.name = name;
this.area = area;
}
static areEqual(shape1, shape2){
return shape1.name === shape2.name && shape1.area === shape2.area
}
}
let s1 = new Shape('rectangle',100)
let s2 = new Shape('rectangle',100)
Shape.areEqual(s1,s2) // true
class User {
type = "admin"
constructor(name) {
this.name = name;
}
}
let user = new User('john')
user.type = "normal"
type
and name
both are accessible - so they are called publicclass User {
_type = "admin"
constructor(name) {
this.name = name;
}
get type(){
return this._type
}
set type(type){
this._type = type;
}
}
let user = new User('john')
user.type = "normal"
But what is benefit ?
class User {
_type = "admin"
constructor(name) {
this.name = name;
}
get type(){
return this._type
}
set type(type){
if(type==('normal' || 'admin')){
this._type = type;
} else {
throw Error('admin / normal ?')
}
}
}
let user = new User('john')
user.type = "normal"
class User {
#type = "admin"
constructor(name) {
this.name = name;
}
get type(){
return this.#type
}
set type(type){
if(type==('normal' || 'admin')){
this.#type = type;
} else {
throw Error('admin / normal ?')
}
}
}
let user = new User('john')
user.type = "normal"
user.#type // Error
class Shape {
constructor(name) {
this.name = name;
}
displayShape() {
return 'Shape ' + this.name;
}
}
class Rectangle extends Shape {
constructor(name, width, height) {
super(name);
this.width = width;
this.height = height;
this.area = width * height;
}
}
let rect1 = new Rectangle('rect1', 10, 11);
rect1 instanceof Rectangle // true
rect1 instanceof Shape // true
console.log(1)
setTimeout(console.log,1000,3); // Timer API
console.log(2)
function sum(a, b) {
return a + b
}
let asyncFx =(a,b)=>setTimeout(()=>sum(a,b),1000)
How to get that value back in program ??
function sum(a, b) {
return a + b
}
let asyncFx = (a,b,cb)=>setTimeout(()=>cb(sum(a,b)),1000)
// callback is passed from outside, and called from inside of async function.
asyncFx(3, 1, function (result) {
console.log({result})
})
function sum(a, b) {
if(a>0 && b>0){
return [null,a + b]
} else{
return ['input', null]
}
}
let asyncFx = (a,b,cb)=>setTimeout(()=>cb(...sum(a,b)),1000)
asyncFx(3, 1, function (error,result) {
if(error){
console.log({result})
} else{
console.log({error})
}
})
function sum(a, b) {
if(a>0 && b>0){
return [null,a + b]
} else{
return ['input', null]
}
}
let asyncFx = (a,b,cb)=>setTimeout(()=>cb(...sum(a,b)),1000)
let x = 4;
let y = 5;
asyncFx(3, 1, function (error, result) {
console.log({ result });
asyncFx(x, result, function (error, result) {
console.log({ result });
asyncFx(y, result, function (error, result) {
console.log({ result }); // Callback hell
});
});
});
--- title: Publish Subscriber model --- flowchart LR publisher-->|event|subscriber1 publisher-->|event|subscriber2
Example : Youtube video release
let promise = new Promise((resolve, reject)=>{
// async task is inside this
// if async task is successful
resolve(data);
// else task is having error
reject(error)
})
--- title: Promise has many states --- stateDiagram-v2 state if_state <<choice>> [*] --> Pending Pending --> if_state if_state --> fullfilled: resolve(data) if_state --> rejected: reject(error)
let promise = new Promise((resolve, reject)=>{
// async task is inside this
// if async task is successful
resolve(data);
// else task is having error
reject(error)
})
promise.then(successCallback).catch(errorCallback)
--- title: then-catch subscribers --- flowchart LR promise-->|resolve|then promise-->|reject|catch
function sum(a, b) {
if(a>0 && b>0){
return [null,a + b]
} else{
return ['input', null]
}
}
let asyncFx = (a,b,cb)=>setTimeout(()=>cb(...sum(a,b)),1000)
asyncFx(3, 1, function (error,result) {
if(error){
console.log({result})
} else{
console.log({error})
}
})
function sum(a, b) {
if (a > 0 && b > 0) {
return [null, a + b];
} else {
return ['input not correct', null];
}
}
let asyncFx = (a, b) =>
new Promise((resolve, reject) => {
setTimeout(() => {
let output = sum(a, b);
if (output[0]) {
reject(output[0]);
} else {
resolve(output[1]);
}
}, 1000);
});
asyncFx(-2,4)
.then(data=>console.log(data))
.catch(err=>console.log(err))
asyncFx(1, 4)
.then((data) => {
console.log(data);
return asyncFx(1, 4);
})
.then((data) => {
console.log(data);
return asyncFx(3, 6);
})
.then((data) => {
console.log(data);
})
.catch((err) => console.log(err));
Note : catch is only one it catches for all above then. Also note that catch
works for reject
and also any error
throw by code.
asyncFx(1, 4)
.then((data) => {
console.log(data);
return asyncFx(1, 4);
})
.then((data) => {
console.log(data);
return asyncFx(3, 6);
})
.then((data) => {
console.log(data);
})
.catch((err) => console.log(err));
finally(()=>{
doSomething() // after everything is completed
})
Parallel execution of async functions - only work when all promises are fullfiled
Promise.all([
asyncFx(1,2),
asyncFx(2,3),
asyncFx(5,6)
]).then(results=>{
console.log(results) // array of resolved value, same order
})
Parallel execution of async functions - only work when all promises are fullfiled or rejected
Promise.allSettled([
asyncFx(1,2),
asyncFx(2,3),
asyncFx(5,6)
]).then(results=>{
console.log(results) // array of resolved/reject objects, same order
})
Parallel execution of async functions - works when any one of promises are fullfiled or rejected
Promise.race([
asyncFx(1,2),
asyncFx(2,3),
asyncFx(5,6)
]).then(results=>{
console.log(results) // value of first settled (resolved/rejected) promise
})
Parallel execution of async functions - works when any one of promises are fullfiled
Promise.race([
asyncFx(1,2),
asyncFx(2,3),
asyncFx(5,6)
]).then(results=>{
console.log(results) // value of first fullfilled promise
})
created already promise which gets rejected just after creation
let promise = Promise.reject('error')
created already promise which gets resolved just after creation
let promise = Promise.resolve(123)
async function sayHi(){
return "hi"
}
sayHi().then(result=>console.log(result)) // hi
"hi"
is wrapped inside using Promise.resolve
function sum(a, b) {
if (a > 0 && b > 0) {
return [null, a + b];
} else {
return ['input not correct', null];
}
}
let asyncFx = (a, b) =>
new Promise((resolve, reject) => {
setTimeout(() => {
let output = sum(a, b);
if (output[0]) {
reject(output[0]);
} else {
resolve(output[1]);
}
}, 1000);
});
async function init() {
let result = await asyncFx(4, 5);
console.log({ result });
}
init();
async function init() {
try {
let result = await asyncFx(4, 5);
console.log({ result });
} catch (err) {
console.log(error);
}
}
init();
async function init() {
let results = await Promise.all([
asyncFx(1, 2),
asyncFx(2, 3),
asyncFx(5, 6),
]);
console.log(results)
}
Move to Async Generators ==
own
or inherited
enumerable
or non-enumerable
String
or Symbol
true/false
true/false
true/false
object1 = {property1:42}
Object.defineProperties(object1, {
property1: {
value: 42,
writable: true,
enumerable: true,
configurable: true
},
property2: {}
});
It shows up many slient errors in JavaScript.
'use strict' // file level strict mode
function myStrictFunction() {
// Function-level strict mode syntax
"use strict";
}
variable = 10
let obj = {a:1,a:2}
new
Object() and Object() are sameconstructor
set to the reference of creator function. Not enumerable
const o1 = {};
o1.constructor === Object; // true
const o2 = new Object();
o2.constructor === Object; // true
const a1 = [];
a1.constructor === Array; // true
const a2 = new Array();
a2.constructor === Array; // true
const n = 3;
n.constructor === Number; // true
Object.prototype
deprecated
Object.getPrototypeOf
and Object.setPrototypeOf
Array.prototype
if applied on array object.enumerable
and own
properties ONLYString
and Symbol
both type of properties are copied.Shallow
Copyconst target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// Expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget === target);
// Expected output: true
prototype
class
syntaxo = {};
// Is equivalent to:
o = Object.create(Object.prototype);
o = Object.create(Object.prototype, {
// foo is a regular data property
foo: {
writable: true,
configurable: true,
value: "hello",
enumerable: true,
},
// bar is an accessor property
bar: {
configurable: false,
get() {
return 10;
},
set(value) {
console.log("Setting `o.bar` to", value);
},
},
});
o = Object.create(null);
// Is equivalent to:
o = { __proto__: null };
function Constructor() {}
o = new Constructor();
// Is equivalent to:
o = Object.create(Constructor.prototype);
const object1 = {};
Object.defineProperties(object1, {
property1: {
value: 42,
writable: true,
enumerable: true,
configurable: true
},
property2: {}
});
console.log(object1.property1);
// Expected output: 42
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
object1.property1 = 77;
// Throws an error in strict mode
console.log(object1.property1);
// Expected output: 42
// Map to Object
const map = new Map([
["foo", "bar"],
["baz", 42],
]);
const obj = Object.fromEntries(map);
console.log(obj); // { foo: "bar", baz: 42 }
// Transform object
const object1 = { a: 1, b: 2, c: 3 };
const object2 = Object.fromEntries(
Object.entries(object1).map(([key, val]) => [key, val * 2]),
);
console.log(object2);
// { a: 2, b: 4, c: 6
const object1 = {
property1: 42
};
const descriptor1 = Object.getOwnPropertyDescriptor(object1, 'property1');
console.log(descriptor1.configurable);
// Expected output: true
console.log(descriptor1.value);
// Expected output: 42
// Only getting enumerable properties - trick
const target = myObject;
const enumAndNonenum = Object.getOwnPropertyNames(target);
const enumOnly = new Set(Object.keys(target));
const nonenumOnly = enumAndNonenum.filter((key) => !enumOnly.has(key));
console.log(nonenumOnly);
const proto = {};
const obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true
own
property. Object.hasOwnProperty() is older version of same.const object1 = {
prop: 'exists'
};
console.log(Object.hasOwn(object1, 'prop'));
// Expected output: true
console.log(Object.hasOwn(object1, 'toString'));
// Expected output: false
console.log(Object.hasOwn(object1, 'undeclaredPropertyValue'));
// Expected output: false
===
but it also differentiate +0 and -0 and NaNObject.isExtensible() : true if you can add more properties to an object.
Object.prototype.isPrototypeOf() : check if object exists in another object's proto chain
function Foo() {}
function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
const bar = new Bar();
console.log(Foo.prototype.isPrototypeOf(bar));
// Expected output: true
console.log(Bar.prototype.isPrototypeOf(bar));
// Expected output: true
Object.keys() : array of keys (own, enumberable, string type)
Object.preventExtensions() : prevents adding of new properties, also prevents re-assignment of prototype value.
Object.prototype.propertyIsEnumerable() : check if enumerable
own property.
Object.seal() : seals objects for further addition of new properties, and also make configurable: false
for all properties. but allow old property value modifications.
Object.setPrototypeOf() :
const obj = {};
const parent = { foo: 'bar' };
console.log(obj.foo);
// Expected output: undefined
Object.setPrototypeOf(obj, parent);
console.log(obj.foo);
// Expected output: "bar"
const date1 = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
console.log(date1.toLocaleString('ar-EG'));
// Expected output: "٢٠/١٢/٢٠١٢ ٤:٠٠:٠٠ ص"
const number1 = 123456.789;
console.log(number1.toLocaleString('de-DE'));
// Expected output: "123.456,789"