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.