JavaScript Essentials: Demystifying this, Currying
Here are the thing we are goona talk about in depth In Depth about this Currying Lexical Environment 1. This It is a special keyoword that refers to the object that is executing the current function. // this in different context 1. Global Scope(non-strict) - window(JS) | global(Node) 2. Global Scope(strict) - undefined 3. Regular Function Scope(non-strict) - window(JS) | global(Node) 4. Regular Function Scope(strict) - undefined 5. Object Method - object 6. Event Handler - element 7. Constructor Function - object 8. Arrow Function - inherited from outer scope. Arrow functions doesn't have their own this. They inherit it from their outerscope. Arrow functions do not bind their own this. Instead, they lexically inherit this from their defining scope. Arrow fn can't be used where we need dynamic value of this. For Ex : In constructor function const Person = (name)=>{ this.name = name; } const Jayant = new Person("jayant"); // TypeError: Person is not a constructor const Person2 = function(name){ this.name = name; } const Jayant2 = new Person2("jayant"); // works fine Example - // Example 1: // Object literals do NOT create a lexical scope. const obj = { name: 'jayant', getName: function() { console.log(this.name); }, getNameArrow: () => { console.log(this.name); } } obj.getName(); // jayant; // Arrow function inherit this from the outer scope, we have defined obj in global scope. so this points to window. obj.getNameArrow(); // undefined; // Example 2: const obj = { name: 'jayant', getName: function() { return ()=>{ console.log(this.name); } }, getNameArrow: () => { return function(){ console.log(this.name); } } getNameArrow2:()=>{ return ()=>{ console.log(this.name); } } } // Arrow function inherit this from the outer scope, in outer scope we have getName function. so this points to obj. obj.getName()(); // jayant // getNameArrow is an arrow function they inherit this from the scope where it is defined, so this points to window. // Then we have returned a function which is not an arrow function. //**So when a regular function is called without an object context, like below it will point to window.*** obj.getNameArrow()(); // undefined //const fn = obj.getNameArrow(); //fn(); obj.getNameArrow2()(); // undefined Function Type This determined by Regular Function How the function is called Arrow Function Where the function is defined In case of Arrow Function, it will be fixed from start that this to point to, whereas in case of regular function it can change on runtime. Arrow functions are great for preserving this when you don't want it to change — especially in callbacks, timers, or array methods. Examples // Callbacks const obj = { name: 'jayant', skills :["JS","TS","k8"], printSkills(){ // using regular function - so this will not inherited so thiswill refer to the window and this.name results in undefined this.skills.forEach(function(skill){ console.log(`${this.name} knows ${skill}`); }) }, printSkillsArrow(){ // using arrow function this.skills.forEach((skill)=>{ console.log(`${this.name} knows ${skill}`); }) } } obj.printSkills(); // undefined knows JS, undefined knows TS, undefined knows k8 obj.printSkillsArrow(); // jayant knows JS, jayant knows TS, jayant knows k8 // setTimeout function timely(){ this.seconds = 0; // as we are using regular function so this will not inherited from outer scope so this will point to window. setTimeout(function(){ console.log(this.seconds); },1000) } // new keyword will create a new object and this will point to that object. // { // seconds:0 // } // But the function inside setTimeout points to the window object and seconds is not defined in window object. new timely(); // But If I do this then it is ok function timely2(){ // Now this points to the window object. this.seconds = 0; setTimeout(()=>{ console.log(this.seconds); },1000) } timely2(); // Other way to make it work // 1) Using Arrow function function timely3(){ this.seconds = 0; setTimeout(()=>{ console.log(this.seconds); },1000) } new timely3(); // 2) using bind function timely4(){ this.seconds = 0; setTimeout(function(){ console.log(this.seconds); }.bind(this),1000) } new timely4(); // 3) Save this into a variable function timely5(){ this.seconds = 0; const self = this; setTimeout(function(){ console.log(self.seconds); },1000) } new timely5(); 2. Currying It is a technique in which a function with multiple argumen

Here are the thing we are goona talk about in depth
- In Depth about this
- Currying
- Lexical Environment
1. This
It is a special keyoword that refers to the object that is executing the current function.
// this in different context
1. Global Scope(non-strict) - window(JS) | global(Node)
2. Global Scope(strict) - undefined
3. Regular Function Scope(non-strict) - window(JS) | global(Node)
4. Regular Function Scope(strict) - undefined
5. Object Method - object
6. Event Handler - element
7. Constructor Function - object
8. Arrow Function - inherited from outer scope.
Arrow functions doesn't have their own this. They inherit it from their outerscope.
Arrow functions do not bind their own this.
Instead, they lexically inherit this from their defining scope.
-
Arrow fn can't be used where we need dynamic value of this.
- For Ex : In constructor function
const Person = (name)=>{ this.name = name; } const Jayant = new Person("jayant"); // TypeError: Person is not a constructor const Person2 = function(name){ this.name = name; } const Jayant2 = new Person2("jayant"); // works fine
Example -
// Example 1:
// Object literals do NOT create a lexical scope.
const obj = {
name: 'jayant',
getName: function() {
console.log(this.name);
},
getNameArrow: () => {
console.log(this.name);
}
}
obj.getName(); // jayant;
// Arrow function inherit this from the outer scope, we have defined obj in global scope. so this points to window.
obj.getNameArrow(); // undefined;
// Example 2:
const obj = {
name: 'jayant',
getName: function() {
return ()=>{
console.log(this.name);
}
},
getNameArrow: () => {
return function(){
console.log(this.name);
}
}
getNameArrow2:()=>{
return ()=>{
console.log(this.name);
}
}
}
// Arrow function inherit this from the outer scope, in outer scope we have getName function. so this points to obj.
obj.getName()(); // jayant
// getNameArrow is an arrow function they inherit this from the scope where it is defined, so this points to window.
// Then we have returned a function which is not an arrow function.
//**So when a regular function is called without an object context, like below it will point to window.***
obj.getNameArrow()(); // undefined
//const fn = obj.getNameArrow();
//fn();
obj.getNameArrow2()(); // undefined
Function Type | This determined by |
---|---|
Regular Function | How the function is called |
Arrow Function | Where the function is defined |
In case of Arrow Function, it will be fixed from start that this to point to, whereas in case of regular function it can change on runtime.
Arrow functions are great for preserving this
when you don't want it to change — especially in callbacks, timers, or array methods
.
Examples
// Callbacks
const obj = {
name: 'jayant',
skills :["JS","TS","k8"],
printSkills(){
// using regular function - so this will not inherited so thiswill refer to the window and this.name results in undefined
this.skills.forEach(function(skill){
console.log(`${this.name} knows ${skill}`);
})
},
printSkillsArrow(){
// using arrow function
this.skills.forEach((skill)=>{
console.log(`${this.name} knows ${skill}`);
})
}
}
obj.printSkills(); // undefined knows JS, undefined knows TS, undefined knows k8
obj.printSkillsArrow(); // jayant knows JS, jayant knows TS, jayant knows k8
// setTimeout
function timely(){
this.seconds = 0;
// as we are using regular function so this will not inherited from outer scope so this will point to window.
setTimeout(function(){
console.log(this.seconds);
},1000)
}
// new keyword will create a new object and this will point to that object.
// {
// seconds:0
// }
// But the function inside setTimeout points to the window object and seconds is not defined in window object.
new timely();
// But If I do this then it is ok
function timely2(){
// Now this points to the window object.
this.seconds = 0;
setTimeout(()=>{
console.log(this.seconds);
},1000)
}
timely2();
// Other way to make it work
// 1) Using Arrow function
function timely3(){
this.seconds = 0;
setTimeout(()=>{
console.log(this.seconds);
},1000)
}
new timely3();
// 2) using bind
function timely4(){
this.seconds = 0;
setTimeout(function(){
console.log(this.seconds);
}.bind(this),1000)
}
new timely4();
// 3) Save this into a variable
function timely5(){
this.seconds = 0;
const self = this;
setTimeout(function(){
console.log(self.seconds);
},1000)
}
new timely5();
2. Currying
It is a technique in which a function with multiple arguments is broken into multiple functions that take one argument at a time.
const curriedMultiply1 = function(a){
return function(b){
return function(c){
return a*b*c;
}
}
}
const curriedMultiply2 = (a)=>(b)=>(c)=>a*b*c;
const multiply = curriedMultiply1(1);
const multiply1 = multiply(2)(3);
console.log(multiply1); // 6
const multiply2 = curriedMultiply2(1)(2)(3);
console.log(multiply2); // 6
UseCase
-
Code Reuseability
const Map = function(fn,arr){ return arr.map(fn); } const curriedMap = curry(Map); const doubleAllBy2 = curriedMap(x=>x*2); const newArr = doubleAllBy2([1,2,3,4,5]); // [2,4,6,8,10]
-
Delayed Execution
// we can also fetch data in other api call. const fetchData = function(url,method,data); const curriedFetch = curry(fetchData); const fetchUser = curriedFetch("https://jsonplaceholder.typicode.com/users","GET"); const fetchUserById = fetchUser(1); const fetchUserById = fetchUser(2);
Let's Implement a Curry Function
// INPUT - Function
// OUTPUT - Function
// It should return a function that keeps on taking the arguments until the length of the arguments is equal or greater than to the length of the function.
// CHALLENGES
// 1) How to get the length of the function. - fn.length(Gives length)
// 2) We need to preseve this, as we are calling the function multiple times. - Using arrow function.
// 3) discards empty values.
function curry(fn){
// We have used regular function, so that it can have dynamic this.
return function curried(...args){
if(args.length >= fn.length){
// run the fn
return fn.apply(this,args);
}
// we are using arrow function, so that it can inherit this from the outer scope.
// we want to preserve this.
return (...nextArgs)=>{
return curried.apply(this,[...args,...nextArgs]);
}
}
}
// How will it be discard empty values.
const multiple = (a,b,c)=>a*b*c;
const curriedMultiply = curry(multiple);
// as nothing is passed ...nextArgs will return nothing.
const multiply = curriedMultiply(1)()()(2)()(3) // 6
One More Curry Usecase.
// Logger
function logger(type,message){
console.log(`[${type}] ${message}`);
}
const curriedLogger = curry(logger);
const infoLogger = curriedLogger("INFO");
const errorLogger = curriedLogger("ERROR");
infoLogger("This is an info message"); // [INFO] This is an info message
errorLogger("This is an error message"); // [ERROR] This is an error message
3. Lexical Environment
It is the environment in which code is written and executed.
A lexical environment have
- Environment Record - It is a place where the variables and function declarations are stored.
- Reference to the outer lexical environment - It is a reference to the lexical environment in which the current lexical environment is defined.