Advanced JavaScript Concepts Simplified

Table of content

  • Introduction to Advanced JavaScript and some of its concepts

  • Nested function Scope

  • Function closure

  • Function currying

  • JavaScript this keyword

    • Implicit binding

    • Explicit binding

    • New binding

    • Global binding

  • JavaScript classes

  • Iteration, Iterables and Generators function

Introduction to Advanced JavaScript some of its concept

JavaScript is a versatile programming language that is used for web development. According to the University of California Berkeley, JavaScript is the most common programming language in the world. All modern browsers utilize JavaScrip. It also has an easy-to-understand syntax. Although some of the Javascript syntaxes are easy to understand, there are some advanced concepts which are challenging to understand. This article gives a detailed explanation of some of the most confusing JavaScript concepts. Whether you are new to JavaScript or an experienced developer, this article can help you gain a quicker understanding of some of the most challenging concepts of JavaScript.

Some of the concepts we'll be diving into in this article are Function closure, Function Currying, JavaScript this keyword, classes in JavaScript, Iteration, Iterables and lastly Generators.

Nested function scope

JavaScript variables can belong to a local or global scope. In JavaScript, a function can be created(Nested) inside another function. This is called a Nested function. Nested functions have access to the function above them which is the parent function. This implies that the nested function can access the variable in the function above them and implement those variables in its function. This is called a Nested scope

Below are examples of global and local scope

let a = 0;
function sum(){
return a + a
}
sum()

From the example above, "a" is a global variable. This is because "a" is defined outside the function. The variable "a" which is a global variable is accessed inside the function making it a global scope

function sum(){
    let a = 0;
    return a + a
}
sum()

From the above example, "a" is a local variable. This is because "a" is declared inside the function. The variable "a" which is a local variable is accessed inside the function making it a local scope.

In JavaScript, all functions have access to the scope "above" them. Nested functions have access to variables declared in their scope as well as variables declared in the outer scope. In this example, the inner function has access to the outer function.

let a = 5;
function outer () {
    let b = 6;
    function inner (){
        let c = 8;
        console.log(a, b, c);
    }
    inner()
}
outter()//this returns 5, 6 and 8 to the console

We can see from the above example that the inner function can access the outer function and also the global variable "a". This is called the nested scope.

Function Closure

In JavaScript, a function closure refers to the ability of a function to remember and access variables from its parent scope, even after the parent function has finished executing.

When we return a function from another function, we are effectively returning a combination of the function definition along with the function's scope. This would let the function definition have an associated persistent memory which could hold onto live data between execution. This combination of the function and its scope chain is what is referred to as closure in JavaScript.

An example of JavaScript closure is seen below.

function outter(){
    let closure = 0;
    function inner (){
        closure++
        console.log(closure);
    }
    return inner; //closure created
}
const fn = outter()
fn();//1
fn();//2
fn();//3

From the example above, we can see that a closure is created when a function is returned from another function. In closure, an inner function has access to variables in the outer function scope even after the function has finished executing. This results in a continuous increment in the specified value because the inner function has a persistent memory which holds onto live data even after execution.

Function Currying

Currying is a process in programming in which we transform a function with multiple arguments into a sequence of nested functions that take one argument at a time.

The resulting sequence of these functions can then be called one after another passing in one argument at a time. Each function in the sequence returns a new function that expects the next argument until all the arguments have been provided and the final result is produced.

Currying is used to compose reusable code. It can be used to create a sequence of functions that take one argument each which can be called one after another to output a result.

An example can be seen below

function sum (a,b,c){
    return (a+b+c)
}
function curry (fn){
    return function(a){
        return function (b){
            return function (c){
                return fn(a,b,c)
            }
        }
    }
}
const currySum = curry(sum);
const add2 = currySum(2);
const add3 = add2(3)
const add4 = add3(4)
console.log(add4); // displays 9 in the console

From the example above, the function "sum" which has multiple arguments (a, b, c) is transformed into a sequence of nested functions containing each of the arguments passed in the sum function. This function can be passed one after the other to produce the final result returned in the sum function.

JavaScript this Keyword

JavaScript this keyword refers to the current context of a function or a method. It is a special variable that is created when a function is called. This keyword refers to an object. It makes the object reusable by letting you decide the object value. it is determined entirely by how a function is called.

You can choose any of these four ways to use and determine 'this' keyword

  • Implicit binding rule

  • Explicit binding rule

  • New binding rule

  • Default binding

Implicit Binding Rule

In implicit binding, when a function is invoked with the dot(.) notation, the object to the left of the notation is what this keyword is referencing.

An example is seen below

const person = {
    name: "Harry"
    sayMyName: function(){
    console.log(`Hello my name is ${this.name}`)
    } 
}
person.sayMyName();//Hello my name is Harry

From this example, the function "sayMyName" is being invoked below the object and the variable person is at the left side of the dot notation. Following the implicit rule, "this" refers to the object "person" the function method "sayMyName" is called on.

Explicit Binding Rule

In explicit binding, if the function containing this keyword is not a part of the object, we have to explicitly specify the object when the function is called.

The call method can be used to make explicit binding possible. The call method calls the function with the object "person" as an argument.

const person = {
    name: 'sheddy'
 }

function sayMyName () {
    console.log(`My name is ${this.name}`);
}
sayMyName.call(person)//'my name is sheddy' is displayed in the console

In this example, the function "sayMyName" which contains this keyword is outside the object "person". The call method is used to call the function with an object as an argument.

New Binding Rule

In JavaScript, the new keyword is a very powerful tool. It is used to create an object from a constructor function. In the context of this keyword in JavaScript, when a function is called with the "new" keyword, "this" refers to the newly created object.

Here is an example explaining the new binding rule

function person (name) {
    //this ={} new keyword creates an empty object which can be updated using the new keyword used to create new values
    this.name = name
}

const p1 = new person('Sheddy')
console.log(p1.name); // Sheddy is displayed in the console

From the example above, the new keyword is used to create a new value of "name". The new keyword creates an empty object which "this" reference thereby making it reusable. so the function "person" can be replaced with a new parameter every time the new keyword is used.

Default Binding rule

The fallback binding if none of the three rules is matched is the default binding. JavaScript falls back to the global scope if none of the three binding rules is met.

globalthis.name = "sheddy"
function sayMyName () {
    console.log(`My name is ${this.name}`);
}
sayMyName();// this display my name is sheddy to the coonsole

From the example above, this keyword searches for a variable within the function scope and cannot find it. However JavaScript helps us further to search globally if a variable 'name' can be found. When found it is implemented in the function scope and displayed on the console.

The four binding rules have an order of precedence in JavaScript. The new binding rule has the highest precedence. The explicit rule comes second followed by the implicit rule. The default binding rule is only active when the previously mentioned three binding rules can not be found

JavaScript Class

A class is a template in JavaScript used for creating an object that shares the same property and method. Using the class template is a way to easily create reusable code. Classes in JavaScript are created using the "class" keyword. The template must first carry a method named constructor which contains arguments to be used in the method. The constructor function is called when a new object is created based on the class, and it initializes the object's properties.

//Create a class
class person {
        //initialize properties
        constructor(fName, lName){
            this.firstName = fName;
            this.lastName = lName
        }
        //add method
        sayMyName(){
            return this.firstName + ' ' + this.lastName
        }
    }
    //create an instace of the class
    const newName = new person('Declan', 'rice')
    console.log(newName.sayMyName());

From the example above, to create a class, four conditions should be met which are

  • Create the class template

  • Initialize the properties in the constructor method

  • Add a method

  • create an instance of the class

When using classes in JavaScript, you can inherit a property in a class. the properties of the class can be inherited into another class using two keywords, Extend and Super.

The "Extend" keyword is used to call the initial class created while the super keyword is used to get the arguments.


class person {
        constructor(fName, lName){
            this.firstName = fName;
            this.lastName = lName
        }
        sayMyName(){
            return this.firstName + ' ' + this.lastName
        }
    }
//Inheriting using extend and super keyword
class Hero extends person {
        constructor(fName, lName){
            super(fName, lName)
            this.isHero = true
        }
        fightCrime(){
            console.log('fighting crime');
        }
    }
    const batman = new Hero('Bruce', 'baner')
    console.log(batman.sayMyName());

From the example above, a new class was created and the properties of the previous class were passed into the new "Hero" class using the Extend and Super keywords.

Iterables, Iterators and Generators

Our final topic in this article is Iterables, Iterators and generators. Let's consider these examples

const array = ['a', 'b', 'c', 'd', 'e']
for(i=0; i<array.length; i++){
    console.log(array[i]);
}
const arr = ['a','b', 'c','d']
for(const item of arr){
   console.log(item);
}

The two examples above show us how to iterate through an array using different methods.

The first syntax uses a for loop to iterate through the element of the array. This method brings difficulty in accessing the element in the array. Here, to apply functionality in each element, we first need to figure out how to access that element. When using the for loop, Iterating through and accessing the elements in a string is different from Iterating and accessing elements in an array.

The second example uses the 'for of' loop which can iterate through the elements of an array and also Iterate through the elements of a string easily. This allows us to access data from a collection one at a time which allows us to focus on what to do with that data rather than how to access that data.

Iterable and Iterator Protocol

An iterator is an object that provides a way to access the elements of an iterable(array, string etc), one at a time. The object which implements the iterable protocol is called an iterable.

For an object to be iterable, it must complete the following process.

  • Implement a method at the key [symbol.iterator]

  • The method should not accept any argument and should return an object which conforms to the iterator protocol. The iterator protocol decides whether an object is an iterator or not.

  • The object must have a next() method that returns an object with two properties.

    • Value: which gives the current element

    • done: which is a boolean value indicating whether or not any more elements could be iterated upon.

const obj = {
    [Symbol.iterator]: function () {
        let step = 0;
        const iterator = {
            next:function(){
                step++
                if(step === 1){
                    return {value: 'Hello', done:false}
                }else if(step === 2){
                    return {value: 'world', done:false}
                }else{
                    return {value: undefined, done:true}
                }
            }
        }
        return iterator
    }
}
for (const word of obj){
    console.log(word);
}

The example above show the step-by-step process of creating an iterable as listed above. The 'for of' loop can be used to iterate through iterable which gives access to every element in a string or an array.

Generators

Generators are a special class of function that simplifies the task of writing iterators or creating an iterable. A generator function allows for pausing and resuming the execution of the function at specific points and yielding values to the user.

Generators are defined using the "function*" syntax, and they use the 'yield' keyword to pause the execution of the function and return a value. "Yield" is an operator which a generator uses to pause the execution of a function.

Generators are useful for creating iterators that can be used to iterate over collections of data more flexibly and efficiently. Since a generator function always return a generator object, we can assign the generator function to a variable and that variable automatically becomes an iterator that allows the use of the 'for of' loop on it.

An example is seen below

 function* generatorFunction(){
    yield 'Hello',
    yield 'World'
}
const generatorObj = generatorFunction();
for(const object of generatorObj){
    console.log(object);
}

Conclusion

In conclusion, advanced JavaScript concepts can seem confusing at first, but with the right approach, they can be simplified and understood by developers of all levels. By breaking down concepts like function closures, currying, classes, Iterables, and generators into easy-to-understand explanations, developers can gain a deeper understanding of how these concepts work and how they can be used to create more efficient code. With practice and a willingness to learn, developers can master these concepts and take their JavaScript skills to the next level.