Here are the thing we are goona talk about in depth
- Hoisting
- Temporal Dead Zone
- Diff b/w Function & Function Exp.
- Shallow Copy vs Deep Copy
- Object.assign
- Slice vs Splice
- forEach vs Map
- Global Execution Context
- Polyfilling
- Map Deep Dive
- Type Coercion
1. Hoisting
It is a JS Mechanism in which variable and function declartions are moved to the top of their scope before the code is executed.
- Var is Hoisted and Initialized with undefined.
console.log(a) // undefined
var a = 10;
- Let and Const are Hoisted but not Initialized.
console.log(a) // Reference Error , Temporal Dead Zone.
let a = 10;
- Function Declartion are hoisted,we can access them any where in the code.
console.log(getNum());
function getNum(){
return 2*2;
}
- Function Expression are not fully Hoisted.
console.log(getNum()); // ReferenceError, getNum2 is not defined : Same for let , TDZ
const getNum =()=>{
return 2*2;
}
console.log(getNum4()); // TypeError: getNum4 is not a function
var getNum4 =()=>{
return 2*2;
}
Above only the variable is hoisted not the function.
2. Temporal dead zone
It is a period between start of the scope/block till the variable is defined and initialized.
When it occurs?
- Only Exists for variable that are decalred with let and const.
- It basically prevents them from being accessed before initialization.
- Accessing a variable in TDZ results in a ReferenceError.
- TDZ applies to function expressions assigned to let and const as well.
3. Function Dec Vs Function Expression
Function Dec. : Function defined with function keyword.
- They are Hoisted.
- Accessible in the entire scope.
Function Exp. : when a function is assigned to a variable.
- Not Hoisted
- Available only after declaration
Use function declarations when you need a function to be accessible anywhere in the scope.
Use function expressions for more control over when and where a function is used.
4. Shallow Copy Vs Deep Copy
Shallow Copy : Copies the top level properties only,For Nested Objects reference is passed.
let obj1 = { name: "Jayant", address: { city: "Delhi" } };
let obj2 = {...obj1};
obj2.address.city = "Gurugram";
console.log(obj1);
console.log(obj2);
// Both prints the same output
// {
// "name": "Jayant",
// "address": {
// "city": "Gurugram"
// }
// }
Other ways to make shallow copy
Object.assign()
Spread Operator
-
Slice Operator
[ New array will contain refernce in case of nested object ] Concat Operator
Deep Copy : It Copied all the properties.
let obj1 = { name: "Jayant", address: { city: "Delhi" } };
// Creating a deep copy
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.address.city = "Mumbai"; // β
Changes only obj2
console.log(obj1); // { name: "Jayant", address: { city: "Delhi" } }
console.log(obj2); // { name: "Jayant", address: { city: "Mumbai" } }
Other ways to make shallow copy
Recursive based custom deep copy function
5. Object.assign()
It is an JS method that is used to copy properties from one or more object to other
We can do the same thing using Spread Operator
.
// Syntax : Creates Shallow Copy
Object.assign(<target>,..sources)
// Example
let obj1 = {
name:"Jayant"
}
let obj2 = {
status:"single"
}
// If two objects have the same property, the last object's value will overwrite previous values.
let obj3 = Object.assign({},obj1,obj2);
6. Slice Vs Splice
Slice is used to extract parts of the array
Slice makes shallow copy
Splice is used to add/remove elements from the array
// Slice : arr.slice(start,end);
// end is exclusive, include nhi hoga
const arr = [1,2,3,4,45,5,6,7];
const newArray = arr.slice(1,4);
console.log(newArray); // [2,3,4 ]
console.log(arr.slice(-2)); // [6,7] , get last 2 elemnets
// Splice : arr.splice(start,deleteCount,...items);
const itemsToAdd = [2,3,4,5,6,7];
const newArr = [1,2,3,4,45,5,6,7];
console.log(newArr.splice(1,7,...itemsToAdd)); //[2,3,4,45,5,6,7]
console.log(newArr); // [1,2,3,4,5,6,7];
const newArr2 = [1,2,3,4,45,5,6,7];
console.log(newArr2.splice(-2)); // [6,7] // removed last 2 elements
console.log(newArr2); // [1,2,3,4,45,5]
7. ForEach Vs Map
ForEach and Map doesn't support early return, if you want to return early return you can use some,every,reduce,for of loop.
[1,2,3,4,5,,6,7].forEach((el)=>{
if(!el){
break; // Gives Error, Same for Map
}
console.log(el)
})
8.Execution Context
It is an Environment where code is executed.
It is of 2 Types:
1) Global EC
2) Function EC
Global EC
- It is the first EC that runs when JS file runs.
It contains global variable and functions,Lexical Environmet. It is removed when program stops.
9. Polyfilling
It is an act of writing code that make old browser compaitable of newer features.
Older browsers (like Internet Explorer) do not support Array.includes().
To add this functionality, we can write a polyfill.
Polyfill for Array.includes()
if (!Array.prototype.includes) {
Array.prototype.includes = function (element) {
return this.indexOf(element) !== -1;
};
}
10, Map Deep Dive
Generally we pass only 1 argument to the Callback
But internally, map() provides 4 arguments to the callback function
// array is accessible in case of forEach also
arr.map((value,index,array,this)=>{})
const arr = [1,2,3,4,5,,65];
console.log(arr.map((val)=>{
console.log(arr[0]);
return val;
})); // arr is already available in the scope so what is the need of it?
// the third optional argument can be useful when we don't have access to it.
// For Example in a Function
function processData(arr) {
return arr.map((val, index, array) => {
console.log(array[0]); // Accessing the first element dynamically
return val * 2;
});
}
console.log(processData([10, 20, 30]));
Sparse Array - Array with missing elements
const arr = [1,2,,5,6,,7,8,9] // Sparse Array
// By default map skips this and doesn't call the callback function for them.
// the empty slots remains in the array
console.log(arr.map((val)=>{
if(!val){
return 0;
}
return val*2
})) // [2, 4, 6, 8, 10, empty, 130]
if you want to give a default value instead of the empty value in case of sparse array, you can do this with the help of 3forEach or spread operator.
const arr = [1,2,,5,6,,7,8,9];
// using spread operator converts a sparse array into a dense one with explicit undefined values || use can use forEcah also.
console.log([...arr].map((val,index,arr)=>{
if(!val){
return arr[index-1] || 0;
}
return val*2
})) // [2, 4, 2, 10, 12, 6, 14, 16, 18]
map() Sets the Range of Elements Before Execution : Means Elements that are being processed by the callback are determined before the first callback execution.
- New elements added to the array during execution are ignored.
- Deleted elements before being processed are skipped.
11. Type coercion
Automatic conversion from one data type to another.
It happens in three ways:
Implicit Coercion β JS automatically converts types.
console.log(10+"2"); // [Concatation]
console.log(10-"2"); // [conversion] - To number
console.log(10*"2"); // [conversion] - To number
console.log(10/"2"); // [conversion] - To number
console.log(true+1) // true converted to number gives 1
// false -> 0
console.log(null+undefined) // NaN
// null -> 0
// undefined -> NaN
console.log(null + 10); // 10 (null β 0)
console.log(undefined + 10); // NaN (undefined β NaN)
const obj = { name: "John" };
console.log(String(obj)); // "[object Object]"
console.log(obj + ""); // "[object Object]"
// [] -> ""
console.log([] + 2); // "2" (Array β Empty String β Concatenation)
console.log([] - 2); // -2 (Array β Empty String β 0 - 2 = -2)
console.log([1] + [2]); // "12"
Explicit Coercion β We manually convert types using functions.
Abstract Operations β Internal rules for type conversion in JS.
Top comments (11)
You really need to mention
structuredClone()
as a way to deep copy. It is builtin and requires much less processing than json conversion. It also derefs pointers in a much better way. IMO, it's the best way to deep copy.Pls share your opinion here
I think you need to go beyond map and include filter when sparced elements of an array might include falsy values sucn as null or indefined. You would save ton of time by using
array.filter(Boolean).map(...)
to assert right data.Actually I was expecting Proxy object, hashmaps with tiny pointers, and Observables new patterns In this article. Not bad for the first suggestions.
Seems the author doesn't know the difference between what is hoisting and how it looks like, yes the effect is similar to what's described but that's really not what hoisting is about.. hoisting is about how js is compiled and yes it's a compiled language and hoisting is a result or way of how lexing is being done
JSON is not the way to deep copy. Yes, it covers basic data types, but you lose any class objects or types that are not supported by JSON. I know you are just providing an example, but you should explicitly mention this as it is a bad habit.
IMO deep copying something you don't know the shape of is a bad habit by default. If required, you should create a function that takes in the object you need and makes a deep copy of it.
Our senior devs spend way too much of their time hoisting and polyfilling. I always found their copies to be way too shallow, especially if you consider how much we pay them. Reading this article really stepped up their game.
What a great read! Thank you for your insights! πΌ
should probably mention that
let
andconst
have a different scope (block scope) thanvar
(function scope)If your senior js developer needs this article to improve programming you need to talk to HR to do better interviews.... Some of this stuff are basic and incorrect.
Top notch