第十九章·其他杂项
In this chapter we will be discussing various topics that will come across through while writing code. The topics are listed below:
- API and AJAX
- Building and Deploying JS application
- Callback
- Currying
- Debugging
- ECMA Script
- Global Footprint
- Hoisting
- Linked List
- Polyfills and Transpilers
- Single Thread Nature
- Template Literals
- Testing
Web API and AJAX
In this section, we will discuss API and AJAX, the two important technologies that enable applications to interact with servers and send or retrieve data without the need for a full page reload.
API (Application Programming Interface)
An API (Application Programming Interface) is a set of rules and protocols that allows different software applications to communicate with each other. It defines the methods and data formats that applications can use to request and exchange information.
Key Points
APIs enable developers to access the functionality of other software components, services, or platforms without needing to understand their internal workings.
APIs provide a way for applications to send requests and receive responses, allowing them to interact and share data seamlessly.
Common types of APIs include web APIs that allow web applications to communicate over the internet, library APIs that provide reusable code functions, and operating system APIs that enable interaction with the underlying OS.
APIs are essential for creating integrations, building software on top of existing platforms, and enabling interoperability between different systems.
API documentation, often provided by developers or service providers, explains how to use an API, including available endpoints, request methods, and response formats.
Examples of popular APIs include social media APIs (e.g., Facebook Graph API), payment gateway APIs (e.g., PayPal API), and cloud service APIs (e.g., AWS API).
Benefits of APIs
Interoperability: APIs allow different software systems to work together, promoting compatibility and data exchange.
Efficiency: Developers can leverage existing APIs to save time and effort, focusing on building specific functionality.
Scalability: APIs enable the expansion of services and features by integrating with third-party tools and services.
Innovation: APIs foster innovation by opening up opportunities for developers to create new applications and services.
Security: APIs often include authentication and authorization mechanisms to ensure secure access to data and services.
AJAX (Asynchronous JavaScript and XML)
AJAX, short for Asynchronous JavaScript and XML, is a foundational technology in web development. It enables web applications to make asynchronous requests to a server, retrieve data, and update parts of a web page without requiring a full page reload. While the name suggests XML, AJAX can work with various data formats, with JSON being the most common.
What is AJAX?
At its core, AJAX is a technique that allows web pages to communicate with a server in the background, without disrupting the user's interaction with the page. This asynchronous behavior is achieved using JavaScript and enables the development of more interactive and responsive web applications.
How does AJAX work?
JavaScript: AJAX heavily relies on JavaScript to initiate requests and handle responses asynchronously.
XMLHttpRequest (XHR) Object: While historically the
XMLHttpRequestobject was used, modern web development often employs thefetchAPI for AJAX requests, which provides a more intuitive and flexible approach.Server Communication: When a user triggers an event, such as clicking a button, JavaScript sends an HTTP request to the server. These requests can be GET (for fetching data) or POST (for sending data to the server).
Asynchronous Processing: AJAX requests are asynchronous, meaning that the browser can continue executing other code while waiting for the response. This prevents the user interface from freezing.
Response Handling: Once the server processes the request, it sends a response back to the client. JavaScript then handles this response, typically by updating the Document Object Model (DOM) with the new data.
Rendering: The updated content is rendered on the web page without requiring a full page reload, resulting in a smoother user experience.
Benefits of AJAX
Improved User Experience: AJAX allows web applications to load and display data without the need for full page refreshes, making the user experience more seamless and interactive.
Efficiency: AJAX requests are lightweight and only transfer the necessary data, reducing bandwidth usage and enhancing application performance.
Real-time Updates: AJAX is crucial for building real-time features like chat applications, live notifications, and dynamic content updates.
Dynamic Loading: Content can be loaded on-demand, leading to faster initial page load times and more responsive applications.
Common Use Cases
Some common scenarios where AJAX is used include:
Form Submissions: Submitting forms without reloading the entire page for validation and data submission.
Infinite Scrolling: Loading additional content as the user scrolls down a page, providing a continuous browsing experience.
Auto-suggestions: Providing real-time search suggestions as users type in search queries.
Updating Content: Dynamically updating content like news feeds, weather information, or sports scores without requiring manual page refreshes.
Fetching Weather Data with OpenWeatherMap API using AJAX
In this example, we'll demonstrate how to use AJAX (Asynchronous JavaScript and XML) to fetch weather information from the OpenWeatherMap API and display it on a web page.
Introduction
The OpenWeatherMap API is a powerful tool for retrieving weather information for locations around the world. This example demonstrates how to use the API to fetch current weather data for a specific city and display it in your application or documentation.
API Key
Before getting started, you need to sign up for an API key from OpenWeatherMap to access their weather data API. Replace 'YOUR_API_KEY' in the code below with your actual API key.
const apiKey = 'YOUR_API_KEY';
Simple Weather App HTML
In this example, we'll provide the HTML structure for a simple weather app that fetches and displays weather data from the OpenWeatherMap API.
HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather App</title>
</head>
<body>
<h1>Weather Report</h1>
<button id="fetchButton">Fetch Data</button>
<div id="weather-info">
<!-- Data will be displayed here -->
</div>
<script src="script.js"></script>
</body>
</html>
JavaScript (script.js)
Create a JavaScript file named script.js to handle the AJAX request and update the weather data on the page:
// API Endpoint and Location
const apiUrl = 'https://api.openweathermap.org/data/2.5/weather';
const location = 'New York'; // Replace with your desired location
// Fetch Weather Data
const xhr = new XMLHttpRequest();
xhr.open('GET', `${apiUrl}?q=${location}&appid=${apiKey}`, true);
xhr.onload = function () {
if (xhr.status === 200) {
const weatherInfo = document.getElementById('weather-info');
const data = JSON.parse(xhr.responseText);
const temperature = (data.main.temp - 273.15).toFixed(2); // Convert from Kelvin to Celsius
const html = `
<p><strong>Location:</strong> ${data.name}, ${data.sys.country}</p>
<p><strong>Temperature:</strong> ${temperature} °C</p>
<p><strong>Weather:</strong> ${data.weather[0].description}</p>
`;
weatherInfo.innerHTML = html;
} else {
const weatherInfo = document.getElementById('weather-info');
weatherInfo.innerHTML = '<p>Failed to fetch weather data.</p>';
}
};
xhr.send();
Result
When you open the HTML file in a web browser, it will display the weather information for the specified location (New York in this case). Make sure to replace 'YOUR_API_KEY' with your actual OpenWeatherMap API key.
This example demonstrates how to fetch weather data from the OpenWeatherMap API using AJAX and display it on a simple web page. Remember to host your HTML and JavaScript files on a web server if you plan to access the API from a live website. That's it! You've successfully fetched and displayed weather data using the OpenWeatherMap API and AJAX.
APIs play a crucial role in modern software development by enabling applications to collaborate and share data effectively. Understanding how to use APIs and integrate them into your projects is fundamental for building feature-rich and interconnected software.
AJAX is a foundational technology in modern web development that empowers developers to create dynamic and responsive web applications. While the name suggests XML, AJAX is compatible with various data formats, making it a versatile tool for enhancing the user experience and creating interactive web applications.
Building and Deploying JavaScript Applications
Developing and deploying a JavaScript application involves a series of steps that range from setting up the development environment to deploying the application on a web server or hosting platform. The following is a detailed guide to assist individuals through this process:
Setting up the Development Environment
Before commencing the development process, it is essential for the developer to ensure that Node.js and npm (Node Package Manager) are installed on their system. These vital tools can be acquired from the official website of Node.js Node.js. Additionally, the developer should select an appropriate code editor or Integrated Development Environment (IDE) for JavaScript development. Some of the popular choices include Visual Studio Code, Sublime Text, and WebStorm.
The installation of Node.js and npm provides access to the essential tools and libraries required for JavaScript development. The careful selection of the appropriate code editor or IDE can substantially enhance productivity and code quality.
Choosing a JavaScript Framework or Library
The choice of a JavaScript framework or library is contingent upon the specific requirements of the project at hand. Developers can opt to work with well-established frameworks such as React, Angular, Vue.js, or adhere to the use of vanilla JavaScript, depending on the complexity and demands of the project. The selection is fundamentally guided by the need for structure and pre-built components that can expedite the development process and bolster maintainability.
Creating the Project
Project initiation is facilitated by the utilization of a package manager such as npm or yarn to establish a new project. For instance, the execution of the command npm init can be employed to set up a new Node.js project. The adoption of a package manager during project initiation ensures the establishment of a standardized project structure and streamlines the management of dependencies. This approach significantly aids in maintaining project organization and manageability.
Application Development
Throughout the process of coding the JavaScript application, the developer is advised to diligently organize modules and components. This practice is crucial for enabling ease of maintenance in the future. The development of organized and modular code is pivotal for ensuring the application remains easily maintainable and straightforward to debug. Additionally, this approach fosters code reusability and encourages collaboration among developers working on the project.
Application Testing
The developer is encouraged to create unit tests and integration tests employing testing frameworks such as Jest, Mocha, or Jasmine. This practice serves the purpose of verifying that the application functions in accordance with its intended objectives. The creation of tests serves as a proactive measure to identify and preemptively address any potential bugs, thereby instilling confidence in the reliability of the application.
Building the Application
To optimize the JavaScript code, CSS, and assets for production, it is recommended to employ a suitable build tool such as Webpack, Parcel, or Rollup. These tools bundle and optimize code and assets, leading to reduced loading times and improved performance. Furthermore, they contribute to the organization of code and facilitate the segregation of concerns within the application.
Deployment Configuration
The developer should make a well-informed decision regarding the deployment location. Deployment options encompass traditional web hosting, cloud services such as AWS or Google Cloud, or platforms like Netlify, Vercel, or GitHub Pages. The choice of deployment platform should be in alignment with the project's specific requirements and budget constraints. Different platforms offer varying levels of scalability, security, and ease of use.
Creating a Production Build
Generating a production-ready version of the application entails executing the build process. This typically involves code minification and optimization, resulting in reduced bandwidth usage and an enhanced user experience. Furthermore, a production build ensures that the application performs optimally in production environments.
Deploying the Application
The deployment process necessitates strict adherence to the instructions provided by the hosting platform. This may involve utilizing FTP, SSH, or platform-specific deployment tools. Adhering to best practices during deployment is crucial for ensuring seamless user access to the application. Deployment can be accomplished through various means, including manual uploads or automated deployment pipelines.
Domain and DNS Setup (if applicable)
For those utilizing custom domains, configuring DNS settings to direct traffic to the hosting provider or server is a requisite step. This configuration enables users to access the application through a user-friendly domain name, thereby enhancing branding and accessibility.
Continuous Integration and Deployment (CI/CD)
The developer may opt to establish a Continuous Integration and Continuous Deployment (CI/CD) pipeline. This can be achieved through the utilization of CI/CD tools such as Jenkins, Travis CI, CircleCI, or GitHub Actions. The automation of testing and deployment processes in response to code changes minimizes the potential for human error and ensures that code alterations undergo rigorous testing before reaching the production environment. This approach significantly elevates code quality and reliability.
Monitoring and Maintenance
Post-deployment, vigilance is required to monitor the application for errors, performance issues, and security vulnerabilities. Regularly updating dependencies is instrumental in enhancing security and leveraging new features. This proactive approach guarantees that the application retains its reliability, security, and performance over time.
Scaling (if necessary)
In scenarios where the application experiences growth and an increase in traffic and workload, scaling the infrastructure may become imperative. Cloud service providers offer solutions designed to accommodate such scaling requirements. These solutions enable the application to seamlessly manage heightened loads while preserving performance and availability.
Backup and Disaster Recovery (if necessary)
The implementation of backup and disaster recovery strategies is indispensable to safeguard the application's data in the event of unforeseen disruptions. These strategies are instrumental in ensuring business continuity and mitigating the risk of data loss during unexpected events.
Callback Functions in JavaScript
Callback functions are a fundamental concept in JavaScript, enabling asynchronous and event-driven programming. This Markdown document provides an in-depth explanation of callback functions, their purpose, and how to use them effectively.
What is a Callback Function?
- A callback function is a JavaScript function that is passed as an argument to another function.
- It is typically invoked or executed at a later time, often after some asynchronous operation or event.
- Callbacks are essential for handling tasks like data retrieval, event handling, and dealing with asynchronous behavior.
Why Use Callback Functions?
- Asynchronous Operations: Callbacks are crucial for managing asynchronous operations like file reading, API requests, and timers.
- Event Handling: They are used to respond to events like button clicks, user input, or network responses.
- Modular Code: Callbacks help write modular and reusable code by separating concerns and promoting the single-responsibility principle.
Anatomy of a Callback Function
A typical callback function has the following structure:
function callbackFunction(arg1, arg2, ..., callback) {
// Perform some operations
// ...
// Call the callback function when done
callback(result);
}
- callbackFunction is the function that takes a callback as an argument.It may perform some operations asynchronously.
- It eventually calls the callback function, passing it a result or an error.
Handling Errors in Callbacks
In JavaScript, callback functions can handle errors by convention. It's common to pass an error object as the first argument or use the second argument to represent errors. Developers should check for errors and handle them appropriately within the callback function.
Alternative Approaches to Callbacks
Promises: Promises offer a structured way to handle asynchronous code and errors. They have three states: pending, fulfilled, and rejected. Promises use the
.then()and.catch()methods to handle success and error scenarios.Async/Await: Async/await is a more recent addition to JavaScript. It simplifies asynchronous code by allowing developers to write it in a more synchronous style. It's built on top of Promises and is especially useful for handling asynchronous operations with a more linear code flow.
Event Emitters: In Node.js, the
EventEmitterclass allows you to create custom event-driven architectures for handling asynchronous tasks.
Callback Hell (Callback Pyramid)
Callback hell, also known as the "pyramid of doom," is a common issue in JavaScript when working with deeply nested callback functions. This phenomenon occurs when multiple asynchronous operations are chained one after the other, making the code difficult to read and maintain. This Markdown document explains callback hell and provides a simple example.
What is Callback Hell?
- Callback hell occurs when asynchronous functions are nested within each other, leading to deeply indented code structures.
- It makes code harder to understand, debug, and maintain due to excessive indentation levels.
- Callback hell often results from handling multiple asynchronous operations sequentially, such as making API requests or reading/writing files.
Example
asyncOperation1(function (result1) {
// Callback 1
asyncOperation2(result1, function (result2) {
// Callback 2
asyncOperation3(result2, function (result3) {
// Callback 3
asyncOperation4(result3, function (result4) {
// Callback 4
asyncOperation5(result4, function (result5) {
// Callback 5
// ... and so on
});
});
});
});
});
Problems with Callback Hell
Readability: Callback hell leads to deeply indented code, making it challenging to read and understand. This can hinder code reviews and collaboration.
Maintainability: As more asynchronous operations are added, callback hell makes the codebase difficult to maintain. Modifying existing functionality or adding new features becomes error-prone.
Error Handling: Managing errors becomes complex in nested callbacks. Handling exceptions and propagating errors to higher levels can be challenging.
Mitigating Callback Hell
1. Named Functions
- Break down callback functions into separate, named functions. This improves code readability by giving meaningful names to individual functions.
2. Promises
- Promises provide a more structured way to handle asynchronous code. They allow you to chain asynchronous operations, making the code more linear and easier to read.
3. Async/Await
- Async/await is a more recent addition to JavaScript. It simplifies asynchronous code by allowing you to write it in a more synchronous style. It is built on top of Promises and is especially useful for handling asynchronous operations with a more linear code flow.
4. Modularization
- Organize code into smaller, reusable modules. This reduces the complexity of individual functions and makes it easier to manage asynchronous operations.
Effective error handling is crucial in asynchronous programming. Callbacks can handle errors by convention, but alternative approaches like Promises, async/await, and event emitters provide more structured and readable ways to manage asynchronous code. The choice of which approach to use depends on the specific requirements and coding style preferences. Callback hell is a common issue in JavaScript when working with deeply nested callback functions for handling asynchronous operations. It can lead to code that is challenging to read, maintain, and debug. Mitigation strategies, such as using named functions, Promises, async/await, or modularization, can significantly improve code structure and readability when dealing with asynchronous tasks, making your code more maintainable and error-resistant.
Currying
Currying is an advanced technique in functional programming that consists of transforming a function with multiple arguments into a sequence of nesting functions. It transforms a function from callable f(a,b,c) into callable as f(a)(b)(c). It doesn’t call a function instead it transforms it.
To get a better understanding of currying let’s create a simple add function add that takes three arguments and returns the sum of them. Then, we create a addCurry function that takes a single input and returns a series of functions with its sum.
// Noncurried version
const add = (a, b, c)=>{
return a+ b + c
}
console.log(add(2, 3, 5)) // 10
// Curried version
const addCurry = (a) => {
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
console.log(addCurry(2)(3)(5)) // 10
Here, we can see that both the curried and noncurried versions returned the same result. Currying can be beneficial for many reasons, some of which are mentioned here.
- It helps to avoid passing the same variable again and again.
- It divides the function into smaller chunks with a single responsibility, making the function less error-prone.
- It is used in functional programming to create a high-order function.
Debugging
In programming, errors can occur while writing code. It could be due to syntactical or logical errors. The process of finding errors can be time-consuming and tricky and is called code debugging.
Fortunately, most modern browsers come with built-in debuggers. These debuggers can be switched on and off, forcing errors to be reported. It is also possible to set up breakpoints during the execution of code to stop execution and examine variables. For this one has to open a debugging window and place the debugger keyword in the JavaScript code. The code execution is stopped in each breakpoint, allowing developers to examine the JavaScript values and, resume the execution of code.
One can also use the console.log() method to print the JavaScript values in the debugger window.
const a = 5, b = 6;
const c = a + b;
console.log(c);
// Result : c = 11;
Browser Developer Tools
Modern browsers come equipped with powerful developer tools that aid in debugging JavaScript, inspecting HTML and CSS, and monitoring network requests. Here's a brief overview of some essential tools:
Chrome DevTools: Google Chrome's developer tools offer a wide range of features for debugging web applications.
Firefox DevTools: Mozilla Firefox's developer tools are another excellent option, offering similar capabilities.
Microsoft Edge DevTools: For Microsoft Edge users, the built-in developer tools provide essential debugging features.
Safari Web Inspector: Safari's Web Inspector is a robust toolset for debugging and profiling web applications.
Using Breakpoints
Modern browsers offer developer tools with debugging capabilities. Setting breakpoints pauses code execution and helps to inspect variables and call stacks.
Browser Developer Tools
Browsers provide a set of developer tools that allow you to inspect HTML, CSS, and JavaScript.
We can access them by right-clicking on a web page and selecting "Inspect" or by pressing F12 or Ctrl+Shift+I.
Key features include:
Console: View and interact with console output.
Elements: Inspect and modify the DOM.
Sources: Debug JavaScript with breakpoints and watch expressions.
Network: Monitor network requests and responses. Using debugger Statement
We can insert the debugger statement in the code to create breakpoints programmatically. When the code encounters debugger, it will pause execution and open the browser's developer tools.
ECMAScript (ES)
ECMAScript, commonly abbreviated as ES, is a standardized scripting language specification. It serves as the foundation for several programming languages, with JavaScript being the most well-known and widely used implementation. This Markdown document provides an overview of ECMAScript, its history, features, and its role in web development.
What is ECMAScript?
Standardized Language: ECMAScript is a standardized scripting language specification maintained by ECMA International. It defines the syntax and semantics of the language to ensure consistency and interoperability.
JavaScript Implementation: JavaScript is the most prominent implementation of ECMAScript, but other languages like ActionScript also use this specification as a foundation.
History of ECMAScript
ES1 (ECMAScript 1): Released in 1997, ES1 laid the foundation for JavaScript as we know it today.
ES3 (ECMAScript 3): Released in 1999, ES3 introduced significant improvements and is considered the version that brought JavaScript into mainstream web development.
ES5 (ECMAScript 5): Released in 2009, ES5 added new features and improved existing ones, making JavaScript more robust.
ES6 (ECMAScript 2015): Released in 2015, ES6 was a major milestone, introducing significant changes such as arrow functions, classes, modules, and more.
ESNext: Refers to the ongoing development of ECMAScript, where new features and improvements are continually proposed and added.
Why ECMAScript (ES) is Standardized for JavaScript
This section of the document elaborates on why ECMAScript is crucial for JavaScript, its role in standardization, and its benefits for the language.
The Need for Standardization
Language Consistency: JavaScript, as a widely used programming language for web development, needed a standardized specification to ensure consistency across various implementations and environments.
Interoperability: Different web browsers and engines may have their own interpretations of JavaScript. A standard helps ensure that JavaScript code behaves consistently across all platforms.
The Role of ECMAScript
Defining the Language: ECMAScript defines the core features of JavaScript, including its syntax, data types, functions, and fundamental objects.
Standardization Body: ECMAScript is maintained and developed by ECMA International (European Computer Manufacturers Association), a standards organization. This organization ensures that JavaScript remains a well-defined and stable language.
Version Evolution: ECMAScript introduces new language features and improvements in each new version, keeping JavaScript up-to-date with the needs of modern web development.
Benefits of ECMAScript Standardization
Consistency: Standardization ensures that JavaScript behaves consistently across different platforms and browsers, reducing compatibility issues.
Interoperability: Developers can write JavaScript code with confidence, knowing that it will work as intended across various environments.
Innovation: ECMAScript's ongoing development allows for the introduction of new language features and improvements, enabling JavaScript to evolve with the ever-changing web landscape.
Cross-Platform Development: Standardization makes it easier for developers to write code that works on both client and server-side environments.
JavaScript Implementations
Major Browsers: Popular web browsers like Chrome, Firefox, Safari, and Edge all implement ECMAScript to execute JavaScript code.
Node.js: Node.js, a server-side JavaScript runtime, also adheres to ECMAScript standards, enabling JavaScript to be used for server-side programming.
Key Features of ECMAScript
Arrow Functions: Provide concise syntax for defining functions and lexical binding of
this.Classes: Introduced class syntax for object-oriented programming.
Modules: Added native support for module imports and exports.
Promises: Introduced Promises for improved handling of asynchronous operations.
Async/Await: Simplified asynchronous code with the introduction of async functions.
let and const: Block-scoped variables with
letand constants withconst.Destructuring: Allows for easy extraction of values from arrays and objects.
Template Literals: Introduced template literals for more flexible string interpolation.
The Role of ECMAScript in Web Development
Client-Side Scripting: ECMAScript is the foundation for client-side scripting in web development. It powers interactive web applications.
Compatibility: While modern browsers support the latest ECMAScript features, developers need to consider backward compatibility for older browsers.
Transpilers: Tools like Babel can transpile newer ECMAScript code into older versions for wider browser support.
TypeScript: TypeScript, a superset of ECMAScript, adds static typing for enhanced tooling and code safety.
Some Examples of ECMAScript Syntax
Arrow Function Syntax:
const x = (x, y) => x + y;Class Syntax:
class ClassName { constructor() { ... } }Promise Syntax:
const promiseA = new Promise(myExecutorFunc);const promiseB = promiseA.then(handleFulfilled1, handleRejected1);Spread Operator:
const year = [...q1, ...q2, ...q3, ...q4];Map:
const x = new Map([ ["a", 500],["b", 300],["c", 200] ]);
ECMAScript is a fundamental part of web development, shaping how we create dynamic and interactive web applications. Staying informed about the latest ECMAScript features is essential for modern JavaScript development. ECMAScript plays a crucial role in providing a standardized foundation for JavaScript, ensuring consistency, interoperability, and continuous improvement of the language. This standardization allows developers to write JavaScript code with confidence, knowing that it will work reliably across different platforms and environments.
Global footprint
If you are developing a module, which might be running on a web page, which also runs other modules, then you must beware of the variable name overlapping.
Suppose we are developing a counter module:
let myCounter = {
number: 0,
plusPlus: function () {
this.number = this.number + 1;
},
isGreaterThanTen: function () {
return this.number > 10;
},
};
Note: This technique is often used with closures, to make the internal state immutable from the outside.
The module now takes only one variable name — myCounter. If any other module on the page makes use of such names like number or isGreaterThanTen then it's perfectly safe because we will not override each other's values.
Hoisting
Hosting is a default behavior in JavaScript of moving declarations at the top. While executing a code, it creates a global execution context: creation and execution. In the creation phase, JavaScript moves the variable and function declaration to the top of the page, which is known as hoisting.
// variable hoisting
console.log(counter);
let counter = 1; // throws ReferenceError: Cannot access 'counter' before initialization
Although the counter is present in the heap memory but hasn't been initialized so, it throws an error. This happens because of hoisting, the counter variable is hoisted here.
// function hoisting
const x = 20,
y = 10;
let result = add(x,y); // ❌ Uncaught ReferenceError: add is not defined
console.log(result);
let add = (x, y) => x + y;
Here, the add function is hoisted and initialized with undefined in heap memory in the creation phase of the global execution context. Thus, throwing an error.
Linked List
It is a common data structure found in all programming languages. A linked list is very similar to a normal array in Javascript, it just acts a little bit differently.
Here each element in the list is a separate object containing a link or a pointer to the next. There is no built-in method or function for linked lists in Javascript so one has to implement it. An example of a linked list is shown below.
["one", "two", "three", "four"]
Types of Linked Lists
There are three different types of linked lists:
- Singly Linked Lists: Each node contains only one pointer to the next node.
- Doubly Linked Lists: There are two pointers at each node, one to the next node and one to the previous node.
- Circular Linked Lists: A circular linked list forms a loop by having the last node pointing to the first node or any other node before it.
Add
The add method is created here to add value to a linked list.
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
class LinkedList {
constructor(head) {
this.head = head
}
append = (value) => {
const newNode = new Node(value)
let current = this.head
if (!this.head) {
this.head = newNode
return
}
while (current.next) {
current = current.next
}
current.next = newNode
}
}
Pop
Here, a pop method is created to remove a value from the linked list.
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
class LinkedList {
constructor(head) {
this.head = head
}
pop = () => {
let current = this.head
while (current.next.next) {
current = current.next
}
current.next = current.next.next
}
}
Prepend
Here, a prepend method is created to add a value before the first child of the linked list.
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
class LinkedList {
constructor(head) {
this.head = head
}
prepend = (value) => {
const newNode = new Node(value)
if (!this.head) {
this.head = newNode
}
else {
newNode.next = this.head
this.head = newNode
}
}
}
Shift
Here, the shift method is created to remove the first element of the Linked List.
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
class LinkedList {
constructor(head) {
this.head = head
}
shift = () => {
this.head = this.head.next
}
}
Polyfills and Transpilers
JavaScript evolves every now and then. Regularly, new language proposals are submitted, analyzed, and added to https://tc39.github.io/ecma262/ and then incorporated into the specification. There may be differences in how it is implemented in JavaScript engines depending on the browser. Some may implement the draft proposals, while others wait until the whole specification is released. Backward compatibility issues arise as new things are introduced.
To support the modern code in old browsers we use two tools: transpilers and polyfills.
Transpilers
It is a program that translates modern code and rewrites it using older syntax constructs so, that the older engine can understand it. For example, "nullish coalescing operator" ?? was introduced in 2020, and outdated browsers can’t understand it.
Now, it’s the transpiler's job to make the nullish coalescing operator” ?? understandable to the old browsers.
// before running the transpiler
height = height ?? 200;
// after running the transpiler
height = (height !== undefined && height !== null) ? height: 200;
Polyfills
There are times when new functionality isn't available in outdated browser engines. In this case, the code that uses the new functionality won’t work. To fill the gaps, we add the missing functionality which is called a polyfill. For example, the filter() method was introduced in ES5 and is not supported in some of the old browsers. This method accepts a function and returns an array containing only the values of the original array for which the function returns true.
const arr = [1, 2, 3, 4, 5, 6];
const filtered = arr.filter((e) => e % 2 === 0); // filter outs the even number
console.log(filtered);
// [2, 4, 6]
The polyfill for the filter is.
Array.prototype.filter = function (callback) {
// Store the new array
const result = [];
for (let i = 0; i < this.length; i++) {
// call the callback with the current element, index, and context.
//if it passes the test then add the element in the new array.
if (callback(this[i], i, this)) {
result.push(this[i]);
}
}
//return the array
return result
}
caniuse shows the updated functionality and syntax supported by different browser engines.
Single-Threaded Nature of JavaScript
JavaScript is a single-threaded programming language, executing code sequentially in one main thread. It relies on non-blocking asynchronous patterns to handle tasks efficiently without blocking the main thread, ensuring responsiveness in web applications. While simplifying concurrency, it requires effective use of callbacks and event-driven programming.
Understanding Single-Threaded JavaScript
Here are some key points to understand about JavaScript's single-threaded execution:
One Thread, One Task: JavaScript operates within a single execution thread, which means it can perform only one task at a time. This thread is often referred to as the "main thread" or the "event loop."
Blocking vs. Non-Blocking: JavaScript code is inherently non-blocking. This means that when a time-consuming operation (such as a network request or file read) is encountered, JavaScript does not wait for it to complete. Instead, it delegates the task to another part of the environment (e.g., the browser or Node.js runtime) and continues executing other code.
Asynchronous Programming: To handle potentially time-consuming operations without blocking the main thread, JavaScript relies heavily on asynchronous programming patterns. Functions like callbacks, Promises, and async/await allow developers to work with asynchronous operations effectively.
Event-Driven: JavaScript is often described as "event-driven." This means that it listens for and responds to events, such as user interactions (clicks, keystrokes), timers, or network responses. When an event occurs, a corresponding callback function is executed.
Concurrency Model: While JavaScript runs in a single thread, the concurrency model enables concurrent execution of code. This is achieved through mechanisms like the event loop, which manages the execution of asynchronous tasks in a way that ensures responsiveness and non-blocking behavior.
Browser and Environment Interaction: In web development, JavaScript interacts with the browser's Document Object Model (DOM) and other browser APIs. To maintain a responsive user interface, JavaScript code must execute quickly and efficiently and delegate time-consuming operations to separate threads when necessary.
Example
// Simulating an asynchronous operation with a callback
function simulateAsyncOperation(callback) {
setTimeout(function () {
console.log("Async operation completed.");
callback();
}, 2000); // Simulating a 2-second delay
}
console.log("Start of the program");
// Initiating an asynchronous operation
simulateAsyncOperation(function () {
console.log("Callback executed: Handling the result.");
});
console.log("End of the program");
In this example, we demonstrate the single-threaded nature of JavaScript and how it handles asynchronous operations using callbacks.
Code Explanation:
We define a function
simulateAsyncOperationthat simulates an asynchronous operation usingsetTimeout. This function takes a callback as an argument, which will be executed when the asynchronous operation is completed.We start the program by logging "Start of the program."
We initiate the asynchronous operation using
simulateAsyncOperation, passing in a callback function. This function will be executed after the 2-second delay.Immediately after initiating the asynchronous operation, we log "End of the program."
Execution Flow:
When you run this code, you'll notice that even though the asynchronous operation takes 2 seconds to complete, the program does not block. The "End of the program" message is logged immediately after initiating the asynchronous operation, demonstrating JavaScript's non-blocking behavior.
After the 2-second delay, the "Async operation completed." message is logged, followed by "Callback executed: Handling the result," indicating that the callback function was executed when the asynchronous operation finished.
Key Takeaways:
JavaScript operates in a single thread, and asynchronous operations are handled through callbacks.
The single-threaded nature allows JavaScript to remain responsive even during time-consuming tasks.
Callbacks are a fundamental mechanism for working with asynchronous code in JavaScript.
Benefits and Challenges
Benefits:
- Simplicity: Single-threaded execution simplifies the programming model and reduces the risk of complex concurrency-related bugs.
- Predictability: The single-threaded nature makes it easier to reason about the order of execution and the state of your program.
Challenges:
- Blocking Operations: Long-running operations can potentially block the main thread, leading to a poor user experience, especially in web applications.
- Callback Hell: Excessive use of callbacks (often referred to as "callback hell") can make the code harder to read and maintain.
- Concurrency Bottleneck: CPU-bound tasks cannot fully utilize multi-core processors because JavaScript runs in a single thread.
JavaScript's single-threaded nature is a defining feature of the language. While it simplifies certain aspects of programming, it also presents challenges in terms of handling asynchronous tasks and ensuring responsive applications. Effective use of asynchronous patterns and understanding the event-driven model are essential for JavaScript developers.
Template Literals
Template literals are literals delaminated with backtick (``) and are used in variable and expression interpolation into strings.
let text = `Hello World!`;
// template literals with both single and double code inside a single string
let text = `He's often called "Johnny"`;
// template literals with multiline strings
let text =
`The quick
brown fox
jumps over
the lazy dog`;
// template literals with variable interpolation
const firstName = "John";
const lastName = "Doe";
const welcomeText = `Welcome ${firstName}, ${lastName}!`;
// template literals with expression interpolation
const price = 10;
const VAT = 0.25;
const total = `Total: ${(price * (1 + VAT)).toFixed(2)}`;
Unit Testing
Unit testing is a fundamental practice in web development. It involves testing individual components or functions to ensure they work as expected. This practice is included in a software development approach called test-driven development(TDD) which consists in writing unit tests out of the software requirements first then write code after.Let's now see why we should write Unit tests.
Why write Unit Tests?
Unit testing is widely used because of the following reasons :
- In TDD,the software requirements are turned into specific test cases. The software is then improved to pass the new tests.
- In other to verify that individual parts of the codebase is working correctly.
- It allows code modification without affecting the functionality of other units or the software
- It provides a safety net when refactoring or making changes.
- It helps document the expected behavior of functions and components.
- It helps to no repetition of modules because each module has it own responsibility
Benefits of Unit Tests
The main advantage of unit tests is their laser-sharp focus. Since they test a single function, they give precise feedback. If a unit test fails, in most cases, you can be sure that the specific function being tested is the problem.
- Unit tests help to find and fix bugs quickly and easily.
- Unit tests contribute to higher code quality.
- Unit tests contribute to better application architecture.
- Unit tests act as documentation.
Best Practices for creating Unit Tests
To ensure that you have good Unit tests follow this best practices :
- Write tests during development, not after
- Test cases should not duplicate the implementation logic.
- Test cases should exhibit the same behavior as long as their code is unchanged.
- Use a consistent naming convention for the test cases
- Avoid using conditional logic expressions in Unit tests
- If possible automate Unit tests
Testing Frameworks
Testing frameworks streamline the process of writing and running tests. Two popular frameworks in JavaScript are Jest and Mocha.
Jest
Jest is a zero-config, all-in-one popular testing framework. It's suitable for both unit and integration testing. Let's see how to get started with Jest.
Install Jest using npm or yarn:
npm install --save-dev jest
Create a test file (e.g., myFunction.test.js) for the function you want to test.
Write a test case using Jest's test function:
const myFunction = require('./myFunction');
test('should return the sum of two numbers', () => {
expect(myFunction(2, 3)).toBe(5);
});
Run tests using the jest command:
npx jest
Mocha
Mocha is a flexible testing framework. It provides the structure for running tests but requires additional libraries for assertions and mocking.
Getting Started with Mocha
Install Mocha and an assertion library like Chai:
npm install --save-dev mocha chai
Create a test directory and add your test files.
Write your test cases using Mocha's describe and it functions and Chai's assertion functions.
const chai = require('chai');
const expect = chai.expect;
const myFunction = require('./myFunction');
describe('myFunction', () => {
it('should return the sum of two numbers', () => {
expect(myFunction(2, 3)).to.equal(5);
});
});
We explored the fundamentals of testing in web development and discussed the significance of Unit testing and other testing frameworks and tools that are vital for any web developer. With consistent practice and access to the right set of tools, one can write dependable code and ensure that applications perform optimally.