DEV Community

Cover image for JavaScript Essentials: Demystifying this, Currying
Jayant
Jayant

Posted on • Edited on

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.  
Enter fullscreen mode Exit fullscreen mode

Arrow functions doesn't have their own this. They inherit it from their outerscope.

Scope - means the region of the code where the variable or function are accessible.

  • Arrow functions do not bind their own this.

  • Instead, they lexically inherit this from their defining scope.

  • Regular function this depends on how they are called and Arrow function this depends on where they are defined.

  • 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

Enter fullscreen mode Exit fullscreen mode
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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
jay818 profile image
Jayant

Let me know your opinion on this