Files with JavaScript code that performs a specific task are called modules. It is easy to add, remove, and update functionalities without affecting your entire code because modules are self-contained and separated from other parts of code.
If these modules are in separate JavaScript files you should use them inside the original JavaScript code.
In this chapter we will cover what the require() function does, how it can be used and some differences between the require and import functions.
How to Run ES and CommonJS Modules
JavaScript module execution in the browser is dependent on import and export statements. The ES modules are loaded and exported by these statements, respectively. Most of online browsers support it by default and it is the accepted and official method for reusing modules in JavaScript.
By default, Node.js supports the CommonJS module format which uses module.exports to export modules and require() to import them.
JavaScript require() Function
The require() method, a built-in CommonJS module function that Node.js supports, is how you add modules to your project. The reason for this is that by default, Node.js treats JavaScript code as CommonJS modules.
The require() function accepts a string parameter that specifies the module’s path. This could be −
Core module: Node.js has various built-in modules. When the require() function receives a string parameter that does not begin with “./”, “../”, or “/”, it assumes it is a core module or a module installed in the node_modules folder.
Local Module: If the argument begins with “./” or “../”, Node.js treats it as a relative or absolute path and is trying to load the module from a file in that location.
Installed module: Node.js modules can also be installed via npm (Node Package Manager). In this case, Node.js searches for the module in the node_modules folder.
Usage of require() Function
The require() method is easy to use and understand because all you have to do is assign it to a variable. This function will accept the location name as an input. The following is the general syntax −
const locName =require(locationName);
Let us suppose you have a CommonJS module that exports the function *get_full_name*, as seen below −
//utils.jsconstget_full_name=(first_name, last_name)=>{returnMy full name is ${first_name} ${last_name};};
module.exports = get_full_name;
This module can then be used or included in your JavaScript file using the require() function −
The module is placed within a local file in the above code, so the local address is referred by the file name. But if you want to add an external module from the web you can use the web-based location −
The require() and import() commands both provide code from external files into your JavaScript program, but they work differently. Here are the two main differences −
You can use the require() method throughout your code. You can even use it conditionally which means it will only run under certain conditions.
The import statement is always executed at the start of the file and cannot be used conditionally.
If you want to use require(), the file you are importing must have the .js extension.
If you are using an import, the file’s extension should be .mjs.
The require() function is more flexible and can be used throughout your code, but import is more limiting, always starting at the beginning and frequently needing different file extensions.
In web development responsiveness refers to how a system responds to changes in data. This concept is important for creating modern, dynamic web applications in which user actions need speedy responses like updating a page without reloading it or dynamically changing what the user sees when data is updated.
This chapter covers some powerful JavaScript patterns that can help you build reactive systems. These patterns are commonly used in frameworks like React and Vue but understanding them in simple JavaScript allows you to understand the fundamental concepts.
Why use Reactivity?
Web apps today are highly interactive. When users click buttons, navigate pages or enter data so the application must reply quickly and change what shows on the screen. Addressing such updates manually can be difficult, but reactivity makes it easier by automatically responding to changes in data.
Key Reactivity Patterns in JavaScript
Now we will explore some key reactivity patterns in JavaScript in this section. These patterns allow us to react rapidly to changes in data and how the page responds to user actions. They are used in many popular libraries and frameworks but we will show how to use them in simple JavaScript.
Pub/Sub: Simplified Data Flow Management
The Pub/Sub (Publisher/Subscriber) pattern is a common approach to manage data flows. It differentiates between code that changes data (publisher) and code that responds to those updates (subscriber). This makes it easy to manage the various parts of an app separately.
Example
Following is the simple demonstration of this Reactivity Pattern −
This method is used in systems like Redux in which components notice changes and update accordingly.
Output
This will generate the below result −
News: New update available!
Custom Events: Browser-Built Pub/Sub for Reactivity
The browser provides an API for triggering and subscribing to custom events using the CustomEvent class and dispatchEvent method. The latter allows us to not only create an event, but also attach any needed data to it.
Example
Below is a simple example of this reactivity pattern −
This technique is appropriate for simple event triggering without the need of libraries.
Output
This will give the following outcome −
Event: Event data
Custom Event Targets
If you do not wish to dispatch events globally on the window object, you can create your own event target.
By modifying the native EventTarget class you can transmit events to a new instance. This makes sure your events are only triggered for the new class which prevents global propagation. Also you can connect handlers directly to this instance.
Example
The following code shows a simple use of this reactivity pattern −
Observer Pattern: Flexible Updates for Decoupled Code
The Observer pattern is very similar to PubSub. You can subscribe to the Subject, which informs its subscribers (Observers) of changes and allows them to reply accordingly. This method is important for designing a disconnected and flexible architecture.
Example
Here’s a basic demonstration of how this reactivity pattern works −
console.log(${this.n} got: ${d});}}const s =newSub();const o1 =newObs('Obs 1');
s.sub(o1);
s.notify('New info!');
Output
This will create the below outcome −
Obs 1 got: New info!
Reactive Properties with Proxy
The Proxy object lets you intercept property access actions (get and set) within objects. This enables you to create reactivity and run code whenever a property’s value is retrieved or updated.
Example
This is a simple example showing the reactivity pattern in action −
let d ={ n:'Akash', a:25};let rData =newProxy(d,{get(t, p){
console.log(Read: "${p}" = ${t[p]});return t[p];},set(t, p, v){
console.log(Changed: "${p}" from ${t[p]} to ${v});
t[p]= v;returntrue;}});
rData.n ='Vikas';
Output
This will lead to the following outcome −
Changed: "n" from Akash to Vikas
Individual Property Reactivity
If you don’t need to keep track of all an object’s fields you can use Object.defineProperty to select one or Object.defineProperties to group them together.
Example
The code below provides a clear demonstration of this reactivity pattern −
let u ={ _n:'Akash'};
Object.defineProperty(u,'n',{get(){returnthis._n;},set(v){
console.log(Name changed from ${this._n} to ${v});this._n = v;}});
u.n ='Vikas';
Output
This will produce the following result −
Name changed from Akash to Vikas
Reactive HTML Attributes with MutationObserver
MutationObserver is a JavaScript API that monitors changes to the DOM (Document Object Model) like attribute changes, element additions or deletions etc. It is particularly applicable for running code in response to DOM changes without having to actively monitor them.
Example
Here is a simple implementation of this particular reactivity pattern −
const el = document.getElementById('obs-el');const obs =newMutationObserver((muts)=>{
muts.forEach(m=>{if(m.attributeName ==='class'){
console.log('Class changed!');}});});
obs.observe(el,{ attributes:true});
Output
This will generate the below result −
Class changed!
Reactive Scrolling with IntersectionObserver
The IntersectionObserver API allows you to asynchronously monitor changes in the intersection of a target element with an ancestor element or the viewport of a top-level document. This means you can execute code anytime an element enters or quits the viewport or another specific element.
This is particularly useful for lazy image loading, infinite scrolling, scroll position-based animations, and other features.
Example
Below is an example of this reactivity pattern −
// Function to be called when the observed element is intersectingfunctiononIntersect(entries, obs){
entries.forEach(e=>{
e.target.style.backgroundColor = e.isIntersecting ?'lightgreen':'grey';});}// Create an IntersectionObserver with the callbackconst obs =newIntersectionObserver(onIntersect);// Start observing an elementconst el = document.getElementById('spec-el');
obs.observe(el);
Output
Below is the output when the element enters the viewport −
Element is now in view.
This will be the output when the element exits the viewport −
The classical inheritance phenomenon involves making a new class that actually extends or reuses the attributes, functions, or methods of another class that are used by different programming languages (like C, C++, Java, and so on). JavaScript uses Prototype Inheritance rather than classical inheritance.
Prototype Inheritance happens when one object uses the prototype linkage to access another object’s attributes or methods. All JavaScript objects derive properties and methods from their prototypes. Prototypes are hidden objects that inherit properties and methods from their parent classes.
Syntax
The prototype inheritance syntax contains the __proto__ property, which allows access to the child prototype. The syntax for prototype inheritance is as follows −
child.__proto__ = parent;
Example of Prototypal Inheritance
Assume you have a basic car (a “prototype”) with basic parts like wheels and an engine. Now you want to create a certain vehicle, like a sports car, without beginning from scratch. Instead of creating a new vehicle, you use the existing one as a foundation (prototype). The sports vehicle inherits the basic car’s features (wheels and engine) but it can also be upgraded with optional features like more horsepower or a spoiler.
It works the same way in JavaScript. An object can inherit characteristics and methods from another object (its prototype); still the inherited object can also be extended.
Code for the Example
In this case, sportsCar inherits the car’s characteristics and methods while also including a new feature called speed.
// Basic car object (prototype)let car ={
wheels:4,drive:function(){
console.log("The car is driving");}};// SportsCar objectlet sportsCar ={
speed:200,};// SportsCar object inherits from car object
sportsCar.__proto__ = car;
console.log(sportsCar);// Calling method from car object using sportsCar
sportsCar.drive();// Inherited from car
console.log(sportsCar.wheels);// New property in sportsCar
console.log(sportsCar.speed);
Output
Here is the outcome of the above code −
The car is driving
4
200
We can now use any method and property from the ‘car’ object to the ‘sportsCar’ object. Later in the chapter, we will talk about how an object’s characteristics and methods are inherited.
Properties of Prototypal Inheritance
Here are some of the properties of Prototypal inheritance is listed −
Properties are the characteristics that identify an object, such as its status or features.
In the case of prototypical inheritance, the prototype’s properties are shared by all instances that inherit from it.
When a property is accessed on an object, JavaScript searches for it first in the object and then in its prototype chain.
Properties can include data values, arrays, objects or other JavaScript data types.
Properties may be data values, arrays, objects, or any other JavaScript data type.
Methods of Prototypal Inheritance
Methods are functions that are bound to an object and can perform operations or computations on its data. Prototypical inheritance allows all instances that inherit from a prototype to share the methods provided in it. Methods can be invoked on an object, giving access to its attributes and other methods. It can execute a variety of tasks, such as modifying data, making calculations, and communicating with other objects or the environment.
Now let us discuss some methods of prototypal inheritance in the below section −
Object.create
Object.create is a JavaScript method that generates a new object from the prototype and its properties. It enables you to create an object that inherits from a prototype without requiring a constructor method. This function is commonly used to initialize the prototype chain for objects, so it allows prototype-based inheritance.
The syntax for Object.create is provided below −
Object.create(proto,[propertiesObject]);
Here,
– proto is the prototype object that the newly formed object will inherit from.
– propertiesObject (optional) is an object that defines additional properties for the newly created object. These properties are added to the newly created object and replace properties with the same name from the prototype chain.
Example
Here is an example for showing how to use Object.create method −
// Create a prototype objectconst animalPrototype ={describe:function(){
console.log(This is a ${this.species}, and it is ${this.age} years old.);}};// Create a new object that inherits from the animalPrototypeconst tiger = Object.create(animalPrototype);
tiger.species ='Tiger';
tiger.age =5;// Call the describe method on the tiger object
tiger.describe();
Output
This will generate the below result −
This is a Tiger, and it is 5 years old.
Object.prototype.constructor
The property Object.prototype.constructor denotes the function that produced the object instance. When an object is created with a constructor function or class, the constructor attribute is immediately set to the prototype.
The constructor property is widely used to validate an object’s type and create new instances of the same type via JavaScript’s prototype-based inheritance.
Example
Here is an example of how to use Object.prototype.constructor −
// Constructor function for creating Animal objectsfunctionAnimal(species, age){this.species = species;this.age = age;}// Creating an instance of the Animal objectconst animal =newAnimal('Tiger',5);// Accessing the constructor property of the prototype
console.log(animal.constructor);
In JavaScript the hasOwnProperty function is used to determine whether an object has a given property rather than inheriting it from its prototype chain. It returns a boolean value indicating whether the object contains the given property.
The hasOwnProperty function is widely used in JavaScript to differentiate between an object’s own properties and those inherited from its prototype chain to make sure the property being checked exists directly on the object.
Syntax
The syntax for the hasOwnProperty is given below −
object.hasOwnProperty(propertyName)
Here,
object is an object to which the hasOwnProperty method is applied.
PropertyName is the name of the property to look for in the object.
Example
Below is an example of using the hasOwnProperty method −
The prototype chain is used to represent multiple inheritances at various hierarchy levels. We can connect one prototype to another using the steps mentioned below.
Example
Here’s an example of the prototype chain in JavaScript −
Prototype inheritance inherits both the object’s properties and its methods. We can create a function in the parent class and call it from the child class. We can also include getter and setter methods in the parent class for usage by the child class.
Example
Here is the example for demonstrating how you can inherit methods −
let userBase ={// Parent object
canRead:true,
profession:"",showReadPermission:function(){
console.log(this.canRead);},// Setter method to set the profession of usersetinfo(value){this.profession = value;},// Getter method to get profession detailgetinfo(){return${this.profession};},};let writer ={// Child object
Here is the list of drawbacks you should consider while working with Prototypal inheritance −
It limits flexibility because a single __proto__ property can only inherit from one class.
Multiple inheritances can only happen at different levels. To inherit a second class we have to use the __proto__.__proto__ property, extending the hierarchy and making tracking more difficult.
Prototype relationships can only be created using objects.
Accessing elements with the same name as an element in the base class is difficult, that is, properties with the same name may exist in different classes, but they are difficult to access.
The same prototypes cannot be inherited since they create a loop.
The Lisp programming language family is built around S-expressions. In this article, you will learn about the steps of making a simple S-expression parser. This can form the basis for the Lisp parser.
Lisp is the easiest language to implement and creating a parser is the first step. We can use a parser generator for this but it is easier to write the parser ourselves. We will use JavaScript.
What are S-expressions?
To define nested list data structures, we use s-expressions which are commonly used in Lisp and other functional programming languages. One s-expression can be either one atom or a sequence of s-expressions.
If you do not know the Lisp language, S-expressions look like this −
(+ (second (list "xxx" 10)) 20)
This is a data format in which everything is made up of atoms or lists surrounded by parenthesis (atoms from other lists are separated by spaces).
Like JSON, S-expressions can have a variety of data types. Numbers, strings, and symbols (without quotations) can represent variable names in several languages.
In addition, you can use a specific dot operator to form a pair like the below.
(1 . b)
A list can be represented as doted pairs (which means that it is a linked list data structure).
This is a list −
(1 2 3 4)
It can be written as −
(1 . (2 . (3 . (4 . Nil))))
The special symbol “nil” represents the conclusion of an empty list. This format allows you to generate any binary tree. However, we will not use this doted notation in our parser to avoid complicating things.
What are the Uses of S-expressions?
S-expressions are used for creating Lisp code, which can also be used to communicate data.
They are also present in the textual version of WebAssembly. Probably because the parser is easy and you do not have to create your own format. Instead of JSON, use them to communicate between the server and the browser.
Step-by-step S-expression Parser in JavaScript
Here are the steps you need to follow for s-expression parser −
Tokenize the Input: First, divide the input string into tokens, which can be parenthesis (,) or symbols.
Recursive parsing: Tokens are processed recursively to create the structure. When it finds an opening parenthesis, it generates a new list. A closing parenthesis indicates the end of the current list.
Base Cases: Symbols (like integers or words) are returned as values but lists are created using expressions within parentheses.
Example
The following code converts the input string to readable tokens (symbols, integers, and parentheses). The parse() method loops over each token continuously. When it detects a (, it creates a new list. When it finds a ), it finishes the list. Numbers are parsed as JavaScript numbers; everything else is interpreted as a symbol (string).
// Function to tokenize the input string into S-expression tokensfunctiontokenize(input){return input
// Add spaces around '(' .replace(/\(/g,' ( ')// Add spaces around ')'.replace(/\)/g,' ) ').trim()// Split by whitespace.split(/\s+/);}// Recursive function to parse tokens into an S-expressionfunctionparse(tokens){if(tokens.length ===0){thrownewError("Unexpected end of input");}// Get the next tokenlet token = tokens.shift();// Start a new listif(token ==='('){let list =[];// Process until we reach a closing parenthesiswhile(tokens[0]!==')'){// Recursively parse the inner expressions
list.push(parse(tokens));}
tokens.shift();// Remove the closing ')'return list;}elseif(token ===')'){thrownewError("Unexpected ')'");}else{// Return an atom (symbol or number) returnatom(token);}}// Function to identify if a token is a number or symbolfunctionatom(token){let number =Number(token);if(!isNaN(number)){// If it's a number, return it return number;}else{// Otherwise, return it as a symbol return token;}}// Usagelet input ="(+ 1 (* 2 3))";// Tokenize the inputlet tokens =tokenize(input);// Parse the tokens into an AST (Abstract Syntax Tree)let ast =parse(tokens);
console.log(ast);
Output
If you run the above code with the input, the output will be −
In JavaScript, a package manager is a tool that makes simple to install, manage, and update libraries, or packages, in your project. You can save time and add functionality without having to start from scratch by using these packages, which are reusable code pieces provided by other developers.
Need of a Package Manager
Suppose there were no package managers. The following duties were likely to be completed by hand in that condition −
Find all of the packages that are suitable for your project.
Verify that the packages do not include any known vulnerabilities.
Pick up the packages.
Place them where they belong.
Keep an eye out for any developments on each of your packages.
Update all packages if a new release takes place.
Get rid of any packages that are unnecessary.
It takes a lot of effort and time to manually arrange tens or even hundreds of things. The two most common package managers in JavaScript are as follows −
Node Package Manager (npm)
Node Package Manager, or NPM, is a platform that makes it easier for developers to use and share JavaScript code. It is used with Node.js, a JavaScript runtime that makes JavaScript work not only on a browser but also on a server.
This is what NPM does −
NPM makes it simple to add, modify, and remove code parts known as packages. These packages work similarly to the building blocks that programmers use to add specific features to their applications, including managing user input or connecting to a database.
Using commands in the terminal, developers may quickly set up or modify code with NPM (for example, npm install to add a package).
NPM offers millions of packages that people from all around the world contribute to, so if you need code for a common task, someone has undoubtedly already created a package for it.
Many packages are interdependent. NPM keeps an eye on those links and ensures that you have all the resources needed for the code to run.
Commands
Here are some of the commands that you can use to take the benefits of NPM −
npm install “package-name”: Using this commands you can install a package.
npm uninstall “package-name”: By this command you are able to remove a package.
npm update “package-name”: Using this command you can update a package.
npm init: By this command you can set up a new project and create a package.json file.
The package.json file
Every JavaScript project, whether Node.js or a browser application, can be scoped as a npm package that includes its own package information and package.json file is used to describe the project.
When npm init is used to start a JavaScript/Node.js project, package.json is created with the following basic metadata provided by developers −
name: the name of your JavaScript library/project
version: It shows the version of your project. This field is often ignored in application development because there looks to be no need for versioning open-source libraries. But it can be used as a source for the deployment’s version.
description: The project’s description.
license: The project’s license
Yarn
Yarn, like NPM, is a platform that allows developers to manage code packages. Facebook created it to address certain problems that developers experienced with NPM, like performance and stability.
The installation method is divided into three steps −
Resolution: Yarn begins resolving dependencies by sending queries to the registry and iteratively looking for each dependency.
Fetching: Next, Yarn checks a global cache directory to see if the required package has been downloaded. If it has not already, Yarn gets the package’s tarball and saves it in the global cache so it can work offline and avoid downloading dependencies several times. Tarballs containing dependencies can also be placed in source control for full offline installations.
Linking: Finally, Yarn connects everything together by copying all of the required files from the global cache to the local node_modules directory.
Commands
Here are some of the commands you can use for using yarn −
yarn add “package-name”: You can use this command for installing a package.
yarn remove “package-name”: You can use this command for removing a package.
yarn upgrade “package-name”: Use this command to update a package.
yarn init: You can set up a new project and create a package.json file.
Yarn uses a lock file (yarn.lock) to verify that all developers on your team are using the same version of packages. This helps to avoid bugs caused by version variations.
Why Use a Package Manager?
Both npm and Yarn are widely used, and the decision between the two is frequently based on personal or project preferences. Here are the reasons why you should use package manager −
Save time: It saves time by easily installing and utilizing existing libraries.
Dependency Management: Automatically manage libraries that your chosen libraries require.
Easy Updates: When upgrades or security updates are available, packages can be updated rapidly.
If you are a web developer you have seen the terms mutable and immutable objects in JavaScript. But what these words mean, and why do they matter? This section will help you understand.
What mutable and immutable objects are
The main differences between them
How to make and change them
The benefits and drawbacks of using each type in your code
This knowledge is very helpful for writing cleaner, safer, and more reliable JavaScript code.
Mutable Objects
Mutable objects in JavaScript are ones that can be changed after they are created, including arrays, objects, functions, and dates. To add, removeor modify values in these objects, use bracket notation (object[“property”]) or dot notation (object.property).
But, changing these objects can sometimes lead to problems: it can unintentionally affect other parts of your code, make debugging more difficult, and use more memory. Because of this, even if you can directly change mutable objects, it is usually better to avoid doing so to keep your code simpler and more reliable.
A mutable data type allows you to change it. Mutability lets you change existing values without creating new ones.
For each object a pointer is added to the stack, pointing to the object in the heap. Consider the following code −
Changing the age of student2 affects the age of the student object. You know it is because they both point to the same thing.
Clone Object Properties
To clone the properties of an object, use the Object.assign() function with the spread operator. These allow you to update the attributes of the cloned object while keeping the original object’s properties unchanged.
How Object.assign() Function Works
The object.assign method replicates properties from one object (the source) to another (the target), returning the updated target object.
Here is the syntax −
Object.assign(target, source)
The method takes two arguments: target and source. The target is the object that receives the new attributes, whereas the source is where the properties originate. The target may be an empty object {}.
When the source and target have the same key, the source object overwrites the value of the key on the target.
The properties of the student object were copied into an empty target.
student2 now owns its own properties. You can show this by modifying the values of any of its characteristics. Making this adjustment will have no effect on the student object’s property values.
The value of student2.age, which was modified to 25, has no bearing on the value of student.age because they both have separate attributes.
Immutable Objects
Immutable objects are those that cannot be modified after it has been created. Strings, numbers, booleans, nulls, and undefined are all immutable in JavaScript. You can not alter their values or characteristics directly.
Rather, you have to create a new object with the changes that are required. For example, you can combine two strings or add two integers, but this will result in a new string or number that does not change the original ones.
Immutable objects offer several advantages over mutable ones. They can reduce side effects, make your code easier to understand and maintain, and boost performance.
Creating Immutable Objects
There are several methods to build immutable objects in JavaScript. One option is to use the Object.freeze() method which blocks changes to an existing object. For example- if you freeze an array or object and then attempt to edit it you will receive an error or have no effect.
Another option is to use the const keyword which creates a constant variable that cannot be reassigned. For example- if you declare a string or an integer as const and then try to reassign it, you will receive an error. But const just makes the variable immutable not the object it refers to.
Let us consider an example −
const num =46;const newNum = num;
By looking at the code above, num has been reassigned to newNum. Both num and newNum now have the same value: 46. Changing the value on newNum does not affect the value on num.
let student1 ="Aarti";let student2 = student1;
The code above creates a variable called student1 and assigns it to student2.
Changing student1 to Saurabh does not affect student2’s initial value. This shows that in primitive data types, actual values are duplicated, so each has its own. Student1 and student2 have distinct stack memory addresses.
The stack follows the Last-In, First-Out principle. The first thing to enter the stack is the last to leave, and vice versa. Accessing objects saved in the stack is simple.
Modifying Immutable Objects
Immutable objects cannot be changed directly, so you have to use other techniques to edit them. One option is to use the spread operator (…), which duplicates the properties or elements of an object or array into a new one. For example, you can use the spread operator to generate a new array with a new item or a new object with a new attribute without changing the originals.
Another option is to use the Object.assign() method, which transfers the properties of one or more source objects to a target object. For example, you can use Object.assign() to create a new object that inherits the properties of two existing objects without modifying them.
Using the Spread Operator
For arrays: Assume you have an array named fruits −
const fruits =["apple","banana","orange"];
For creating a new array using an additional item without modifying or altering fruits −
const newFruits =[...fruits,"grape"];
console.log(newFruits);// Original array is unchanged
console.log(fruits);
For Objects: Object.assign() creates a new object by combining the properties from multiple objects. Assume you have two objects: person and additionalInfo.
const person ={ name:"Maya", age:30};const additionalInfo ={ country:"India", hobby:"reading"};
For creating a new object using the properties from both person and additionalInfo −
const newPerson = Object.assign({}, person, additionalInfo);
console.log(newPerson);// Original object is unchanged
console.log(person);
Immutability can have several advantages for your web development projects −
One of them is to avoid side effects which are changes in your program’s state or behavior that you did not want or determine.
For example- if you send a mutable object as an argument to a function, the function may change the object, affecting other sections of your code that depend on it.
But if you supply an immutable object the function cannot change it, allowing you to avoid this problem. Another benefit is improved performance, particularly in frameworks that use a virtual DOM, like React.
A virtual DOM is a replica of the real DOM that is updated in memory and then compared to the actual DOM to apply the changes.
If you use immutable objects the comparison can be faster and more efficient because you just need to check the reference rather than the value.
Drawbacks of Immutability
Immutability can have various disadvantages, which you should be aware of −
One of them is to increase memory consumption as you must build new objects every time you wish to change them.
This can also have an impact on trash collection which is the process of cleaning up memory by removing unneeded items.
For example- if you generate a large number of temporary objects in a loop, you risk overloading memory and slowing trash collection.
Another disadvantage is that creating and modifying immutable objects requires the use of additional methods or operators, making your code more verbose and difficult to read.
To get the same outcome you may need to write more lines of code or utilize layered spread operators.
Mutability vs Immutability
Here is the basic difference between mutability vs immutability in the tabular form −
Aspect
Mutability
Immutability
Meaning
You can change the value after creation.
Once created, the value cannot be changed.
Example
Arrays or objects (e.g., let numbers = [1, 2, 3];’)
Strings or numbers (e.g., ‘const name = “Amit”;’)
Can you change it?
Yes, you can change or modify it.
No, you cannot change it directly.
How to change?
You modify the original value.
You create a new value instead of changing the original.
JavaScript enables dynamic and interactive features on websites and web apps. It is a key component of modern web development. But as JavaScript files grow larger and more complex, they can significantly affect page load times and overall speed.
To solve this issue, developers use techniques like code minification to make JavaScript code simpler without compromising functionality.
What is the “Code Minification”?
Minimization is another name for minification. Code minification is the process of optimizing code to minimize bandwidth use, save space, and speed up page loads.
All of the fundamental programming languages, like HTML, CSS, and JavaScript provide code minification. The procedure is not immediate, though. Making code more concise without sacrificing functionality requires some effort.
JavaScript code must be parsed, compressed and the output obtained in order to minify it. It should be nearly impossible to read with the naked eye once it has been minified. Everything that once made the code readable has been eliminated, including extraneous white spaces, comments, and newline characters.
It may be necessary to make other code changes, including rewriting local variables, using implicit conditionals, removing block delimiters or inlining methods.
Need for Minimizing the Code
Here are the showing the need for minimizing the code in JavaScript −
Faster Load Times: Minified code reduces file size which leads to faster download and execution times, ultimately improving the user experience.
Bandwidth optimization: Smaller file sizes limit the amount of data exchanged between the server and the client resulting in lower bandwidth and hosting costs.
Improved SEO: Search engines like faster-loading pages, which can result in higher search engine rankings and more organic traffic to the website.
Enhanced User Experience: Reduced load times boost user engagement and satisfaction while decreasing bounce rates and increasing conversions.
Un-minified vs Minified JavaScript Code
Let’s look at some sample code below. The first block contains common, un-minified JavaScript −
// Program to check if a word is a palindromefunctionisPalindrome(word){// find the length of the wordconst length = word.length;// loop through half of the wordfor(let i =0; i < length /2; i++){// check if first and last characters are the sameif(word[i]!== word[length -1- i]){return'This is not a palindrome';}}return'This is a palindrome';}// take inputconst inputWord ="hello";// call the functionconst result =isPalindrome(inputWord);
console.log(result);
Output
This will generate the below result −
This is not a palindrome
Now we will see how the same code will look once it has been minified. See the minified version below −
functionisPalindrome(w){const l=w.length;for(let i=0;i<l/2;i++)if(w[i]!==w[l-1-i])return"This is not a palindrome";return"This is a palindrome"}const inputWord="hello",result=isPalindrome(inputWord);console.log(result);
As you can see, the second piece of code is significantly smaller and more concise. That means it will load and render faster, reducing page load time and boosting content.
We have reduced 529 bytes to 324 bytes, gained 205 bytes of free space, and cut page load time by over 40%.
How to minify JavaScript Code
You can minify JavaScript code in a variety of ways. Each of these methods takes a different strategy from the others.
It is nearly impossible to manually minify all of the code in large JavaScript files. Manually minifying JavaScript files is only feasible for small files owing to the time involved.
To begin the manual JavaScript minification process, open your JavaScript file in your choice text editor and manually delete each space one at a time. It will take a few minutes to clean up all of the gaps and comments in the JavaScript file. Some of these text editors may even support regular expressions, probably speeding up the process significantly.
The other option is to install minification software on your computer and run it from the command line. You would need to choose the file you want to minify and provide it in the command line switch with the destination file. The remaining tasks would be handled by the minifying tool.
Code Minification Vs Compression
While both code minification and compression work to minimize file size, they vary in their approach and purpose −
Code Minification: Code minification includes removing unnecessary characters from source code in order to improve readability and load times while maintaining functionality.
Compression: Techniques for reducing file size by encoding data more efficiently, which often result in irreversible changes to the file structure. Gzip, Brotli, and deflate are common compression techniques used in network transmission to reduce bandwidth usage.
As our systems grow and start doing more complex calculations, the need for speed grows and process optimization becomes necessary. Ignoring this issue results in applications that use a lot of system resources and operate slowly.
In this chapter, we will discuss memoization, a technique that can significantly reduce processing time when used properly.
What is Memoization?
Memorization is a technique for speeding up applications by storing the results of expensive function calls and providing them when the same inputs are used again.
Let’s try to understand this by dividing the term into smaller parts −
Expensive Function Calls: In computer applications, memory and time are the two primary resources. So a function call that uses a lot of these two resources because it is performing a lot of calculations is considered expensive.
Cache: A cache is only a short-term data storage system that holds information to allow faster processing of future requests for that information.
Benefits of Memoization
After receiving input, a function does the necessary computation, caches the result, and then returns the value. If the same input is received again in the future, the process will not need to be repeated. It would just return the response that was saved in memory. As a result, a code’s execution time will be greatly reduced.
When to Use Memoization?
Here are some of the points mentioned while you should use memoization −
When a function calls itself. For example, consider the recursive functions.
When the function is pure (returns the same value every time it is invoked). If the value changes with each function call, there is no reason for holding it. As a result, we cannot use memoization in JavaScript when the function is impure.
When the function has a high temporal complexity. In this case, keeping the results in a cache improves efficiency by reducing time complexity by avoiding re-computation of functions.
Memoization in JavaScript
JavaScript memorization is an optimization technique used to minimize the application’s runtime, complexity, and proper use of time and memory. The procedure involves reducing the number of expensive function calls (a function that recursively calls itself and has some overlapping issues) by using an additional space (cache).
We store the values that were computed in those previous subproblems using memoization. The saved value is then used once again if the identical subproblem comes up, which lowers the time complexity by reducing the need to perform the same calculation repeatedly.
How Does Memoization Work?
JavaScript Memoization purely based on two below concepts −
Closure
High-order function
Closures
The Closure is made up of a function enclosed by references to the state. A closure provides access to an outside function’s scope from an inside function. The closure is formed in JavaScript when a function is created.
let greeting ="Welcome";functionwelcomeMessage(){let user ="Rahul";
console.log(${greeting} to the program, ${user}!);}welcomeMessage();
Output
This will generate the below result −
Welcome to the program, Rahul!
In the above JavaScript code −
The variable greeting is a global variable. It can be accessible from anywhere, including the welcomeMessage() function.
The variable user is a local variable that can only be used within the welcomeMessage() function.
Lexical scoping allows for nested scopes, with the inner function having access to the variables specified in the outer scope. Hence, in the code below, the inner function welcome() gets access to the variable user.
functionwelcomeMessage(){let user ="Rahul";functionwelcome(){
If we run this code, we will get the same results as before. But it is important to note that a local variable is often only present during the function's execution.
This means that after welcomeMessage() is executed, the user variable is no longer available. In this case, when we call gree(), the reference to welcome() remains, as does the user variable. A closure is a function that keeps the outside scope in the inside scope.
Greetings Rahul!
Higher-Order Functions
Higher-order functions act on other functions by passing them as arguments or returning them. In the code above, welcomeMessage() is an example of a higher-order function.
Now, using the well-known Fibonacci sequence, we will look at how memoization uses these concepts.
Fibonacci sequence: The Fibonacci sequence is a set of numbers that begin and end with one, with the rule that each number (known as a Fibonacci number) is equal to the sum of the two numbers preceding it.
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...
So a simple recursive function for this problem will be as follows −
If we plot the recursion tree for the above function at n=4, it will look like this. As you can see, there are too many unnecessary computations. Let's try to fix this via memoization.
functionmemoizedFibSeq(num, memo){// Initialize memo array if not provided
memo = memo ||[1,1];// if num is less than or equal to 1, return the result directlyif(num <=1)return memo[num];// If result is already computed, return it from memoif(memo[num])return memo[num];// Calculate and store the result in memo
memo[num]=memoizedFibSeq(num -1, memo)+memoizedFibSeq(num -2, memo);return memo[num];}// Calculate the 10th Fibonacci number
console.log(memoizedFibSeq(10));
Output
Here is the outcome of the above code −
89
We modify the function in the code example above to accept an optional input named memo. We use the cache object as a temporary memory to store Fibonacci numbers and their corresponding indices as keys, which can then be retrieved later in the execution.
This chapter will show you how to use JavaScript’s localStorage to save data for several browser sessions. We will demonstrate how to use this method using the Window.localStorage property, as well as go over the principles of web storage in JavaScript.
What is localStorage in JavaScript?
The localStorage feature allows JavaScript apps and websites to save key-value pairs in a web browser with no expiration date. This means that the stored data remains even if the user closes the browser or restarts the computer.
LocalStorage is a window object property, so it can interact with and manipulate the browser window. It can also work when combined with other window settings and methods.
Syntax
The below Syntax returns a storage object and it can be used to get the current origin’s local storage space.
myStorage = window.localStorage;
What is Window.localStorage?
The localStorage method is accessible via the Window.localStorage property. Window.localStorage is a JavaScript Window interface component that represents a window with a DOM content within.
The Window interface has a wide number of functions, constructors, objects and namespaces. Window.localStorage is a read-only property that returns a reference to a local storage object which is intended to store data that can only be accessed by the origin that created it.
When to use localStorage
In simple terms, localStorage securely stores and retrieves data. While localStorage can save small amounts of data, it is not suitable for large datasets. Because localStorage is accessible to anybody who uses the device, you should avoid keeping critical information there. You can use it to save user preferences like language or theme. If you use it regularly it can also serve as a data cache. LocalStorage can save form data so that it is not lost when the user closes the browser.
If your application requires you to log in, you can use localStorage to store your session data. You can stay logged in even after closing and reopening the browser.
How does localStorage work?
You have heard it before: localStorage stores data. And, if you’re saving data, you might need to access it later. In this section, we will look at exactly how localStorage works. Here’s an overview of how it works −
setItem() It adds a key and value to localStorage.
getItem() It retrieves/gets things from local storage.
removeItem(): It removes an item from localStorage.
clear(): To erase all data from localStorage, use clear().
key(): To obtain a localStorage’s key, it passes a number.
Usage of setItem() Method
The setItem() method lets you store values in localStorage. It accepts two parameters: a key and a value. The key can be used later to get the value linked to it. Here is how it should look −
localStorage.setItem('name','Ankit Sharma');
In the code above, the key is name, and the value is Ankit Sharma. As we said before, localStorage can only store strings. To store arrays or objects in localStorage you have to first convert them to strings.
To do this, we will use the JSON.stringify() method before providing to setItem(), as follows −
The getItem() method gives you access to the data saved in the browser’s localStorage object. This method takes a single parameter, the key, and returns the value as a string −
localStorage.getItem('name');
This returns a string with the value “Ankit Sharma”. If the key given is not found in localStorage, it will return null. For arrays, we use the JSON.parse() function, which converts a JSON string in a JavaScript object −
JSON.parse(localStorage.getItem('user'));
Using the array we constructed previously, here’s how to access it from local storage −
This method returns the array [“Ankit”, 25]. You can look into the webpage and find it in the console, as follows −
[Log] Name from localStorage: - "Ankit Sharma" (example.html, line 22)
[Log] User array from localStorage: - ["Ankit", 25] (2) (example.html, line 26)
This output is from Safari so it will look a little different on other browsers. Let us compare it with a different array which is not stored with localStorage −
So now we have two arrays on the console you can see in the below output −
[Log] Name from localStorage: - "Ankit Sharma" (example.html, line 22)
[Log] User array from localStorage: - ["Ankit", 25] (2) (example.html, line 26)
[Log] ["Anjali", 27] (2) (example.html, line 29)
Normally, if you comment them out in the code editor they should disappear from the terminal. But anything saved via localStorage stays. Here is one example.
Usage of removeItem() Method
To delete an item from localStorage you can use the removeItem() function. When a key name is given to the removeItem() method so the current key is deleted from storage. If no object is linked with the given key this method will do nothing. Here’s the code.
.localStorage.removeItem('name');
Usage of clear() Method
To delete all things from localStorage you can use the clear() function. When this method is used so it will clear all records in the domain’s storage. It does not accept any parameters. Use the following code −
localStorage.clear();
Usage of key() Method
The key() function is useful for looping over keys while giving a number or index to localStorage to get the key’s name. The index option gives the key’s zero-based index for which you want to retrieve the name. Below is how it looks −
localStorage.key(index);
Advantages of LocalStorage
Here are some advantages of using localStorage −
The first advantage of localStorage is that saved data never expires.
You can still access the data offline since localStorage stores data that is available even without an internet connection.
LocalStorage is more secure than cookies and provides greater control over your data.
LocalStorage provides more storage capacity than cookies.
Cookies can only store four kilobytes of data, whereas local storage can contain up to five megabytes.
Disadvantages of LocalStorage
Here are some disadvantages of localStorage −
LocalStorage is synchronous which refers to each operation occurs one after the other.
This has little impact on smaller datasets but as your data grows it can become a significant concern.
Local storage is more secure than cookies, but it should not be used to store sensitive information.
Anyone with access to the user’s device can view the data stored in localStorage.
Also localStorage can only save strings; if you want to store other data types, you must first convert them to strings.
Finally, storing too much data in localStorage may cause your application to slow down.
Lexical scope refers to an expression’s definition area. In other words, an item’s lexical scope is the context in which it was generated.
Lexical scope is sometimes known as static scope. The lexical scope of an item is not always determined by where it was invoked (or called). The lexical scope of an object serves as its definition space.
So you may know that variables and methods have different levels of scope −
Global Scope: Variables defined outside of any function or block yet accessible throughout the program.
Local Scope: Variables defined within a function or block that can only be accessed within that function or block are considered local scope.
Nested Scope: Inner functions can access variables in their parent functions.
Block Scope: Variables defined with let and const are only valid in the block where they are declared, such as loops or conditionals.
Now let us see some examples of lexical scope in JavaScript in the below section.
Example 1
Let us see the example code below −
// Define a variable in the global scopeconst myName ="Shwetaa";// Call myName variable from a functionfunctiongetName(){return myName;}
In the above sample code, we defined the myName variable in the global scope and used it in the getName() function.
So here question arises, Which of the two spaces covers myName’s lexical scope? Is it the global or local scope of the getName() function?
So you have to remember that lexical scope refers to definition space, not invocation space. As a result of our definition of myName in the global environment, its lexical scope is global.
Example 2
Let us see another example of lexical scope in JavaScript −
So here question arises, Where is myName’s lexical scope?
And the answer is, Notice how we defined and invoked myName within getName(). As a result, myName’s lexical scope is the local environment of getName(), which is myName’s definition space.
How Does Lexical Scope Work?
The code that can access a JavaScript expression is determined by the environment in which it was defined.
In other words, only code in an item’s lexical scope has access to it. For example, take the following code −
// Define a functionfunctionmyLastName(){const lastName ="Sharma";return lastName;}// Define another functionfunctionshowFullName(){const fullName ="Swati "+ lastName;return fullName;}// Invoke showFullName():
console.log(showFullName());
Output
The calling above will return −
Uncaught ReferenceError: lastName is not defined
Notice that the previous snippet’s call to showFullName() resulted in an Uncaught ReferenceError. The error was returned because the item can only be accessed by code within its lexical scope.
As a result, the showFullName() function and its internal code cannot access the lastName variable because it was defined in a different scope. In other words, lastName’s lexical scope differs from that of showFullName().
LastName’s definition space is showLastName(), whereas showFullName()’s lexical scope is the global environment.
Because showFullName() and showLastName() are in the same lexical scope, they both returned “Swati Sharma” in the example above.
In other words, because the two procedures are written in the same global scope, showFullName() might call showLastName().
Summary
Understanding lexical scope in JavaScript is important for creating clean, maintainable code. By properly scoping variables and functionsyou canminimize naming conflicts, improve code readability, and prevent undesirable effects. Understanding lexical scope leads to more ordered and efficient programs.