JavaScript with Web
By Kouam Michel Leroi
• This tutorial assumes you have a basic understanding of
JavaScript (variables, data types, loops, functions, etc.).
We'll dive into advanced concepts, modern ES6+
features, and best practices to make you a proficient
JavaScript developer.
Closures and Scope
Key Concepts
• Scope: Determines variable accessibility (global, function,
block).
• Closure: A function that retains access to its outer lexical
scope even after the outer function has finished
executing.
e.g;
function outer() {
let count = 0; Why it matters:
return function inner() { Closures enable data encapsulation,
count++;
private variables, and functional
[Link](count);
}; programming patterns.
}
const counter = outer();
counter(); // 1
counter(); // 2
Prototypes and Inheritance
In JavaScript, Prototypes are the mechanism by which
objects inherit features from one another. It's the core
concept behind how inheritance works in the language.
Key Concepts
• Prototype Chain: Objects inherit properties from other
objects via [[Prototype]] (accessible via __proto__ or
[Link]()).
• Constructor Functions: Traditional way to create objects.
• ES6 Classes: Syntactic sugar over prototypes.
What are Prototypes?
• Every object in JavaScript has an internal property called [[Prototype]]. This property points
to another object, which is known as its prototype.
• The Chain:
When you try to access a property or method on an object, JavaScript first checks if the property exists
directly on that object. If it doesn't, it looks in the object's prototype. If it's still not found, it continues up
the chain of prototypes (the prototype chain) until it reaches the end, which is typically the
[Link].
• [Link]:
This is the root of almost all prototype chains. It's an object that has null for its own
prototype. It contains common methods like toString(), hasOwnProperty(), and valueOf().
Accessing the Prototype:
• You can access the prototype object via the non-standard
but widely supported property __proto__.
• The standard way to access it is using
[Link](obj).
How Inheritance Works (Prototypal Inheritance)
• JavaScript uses prototypal inheritance, meaning that objects
inherit directly from other objects, rather than from classes (as in
classical inheritance).
Constructor Functions
Before ES6 classes, developers used constructor functions and the
new keyword to create objects and set up inheritance:
function Animal(name) {
[Link] = name;
}
// Add a method to the Animal's prototype.
// ALL objects created with 'new Animal()' will inherit this method.
[Link] = function() {
[Link](`${[Link]} makes a noise.`);
};
const dog = new Animal('Buddy');
[Link](); // Output: Buddy makes a noise.
ES6 Classes (Syntactical Sugar)
The introduction of the class keyword in ES6 provides a much cleaner, more
familiar syntax for setting up inheritance, but under the hood, it still uses
prototypes. class Dog extends Pet {
constructor(name, breed) {
class Pet { super(name); // Calls Pet constructor
constructor(name) { [Link] = breed;
[Link] = name; }
} // Overrides the inherited method
// This method is automatically speak() {
placed on [Link] [Link](`${[Link]}, the ${[Link]},
speak() { barks!`);
[Link](`${[Link]} makes a }
noise.`); }
}
} const poodle = new Dog('Poodle', 'Standard');
[Link](); // Output: Poodle, the Standard,
barks!
Asynchronous JavaScript
Key Concepts
• Event Loop: Handles asynchronous operations (non-
blocking).
• Callbacks: Functions passed as arguments to async
operations.
• Promises: Objects representing eventual
completion/failure of async operations.
• Async/Await: Syntactic sugar for promises.
• Asynchronous JavaScript is the way the language handles operations that
may take an unknown amount of time to complete, such as fetching data
from a network, reading files, or setting timeouts. Because JavaScript is
single-threaded, if these long-running tasks were handled synchronously
(one after the other), they would block the main thread, causing the user
interface to freeze.
• Asynchronous programming allows the main thread to continue executing
other tasks while waiting for the long operation to finish, executing a specific
function (a callback) only when the result is available.
Callbacks (The Old Way)
• The earliest method involved passing a function as an argument that would be executed later
when the result was ready.
• Problem: This often led to "Callback Hell" or the "Pyramid of Doom," where multiple nested
callbacks made the code difficult to read, debug, and maintain.
// Example of Callback Hell (conceptual)
getData(function(data) {
processData(data, function(processed) {
saveData(processed, function(result)
{
[Link](result);
});
});
});
Promises (The Foundation)
A Promise is an object representing the eventual
// Promise Example
completion (or failure) of an asynchronous fetch('api/data')
.then(response => [Link]()) // Successfully
operation and its resulting value.
resolved
A Promise can be in one of three states: .then(data => {
[Link](data); // Handle the data
• Pending: Initial state, neither fulfilled nor })
rejected. .catch(error => {
[Link]('Fetch failed:', error); // Handle errors
• Fulfilled (Resolved): The operation });
completed successfully.
• Rejected: The operation failed.
You handle the results using the .then() for
success and .catch() for errors, which allows for
chaining of asynchronous operations, improving
readability.
Async/Await (The Modern Approach)
• The async/await keywords, built on top of Promises, provide a clean, syntax-
friendly way to write asynchronous code that looks and behaves like
synchronous code.
• async function: A function declared with async always returns a Promise. If
the function returns a non-Promise value, JavaScript automatically wraps it in
a resolved Promise.
• await keyword: This can only be used inside an async function. It pauses the
execution of the async function until the Promise it's placed in front of is
resolved.
async function fetchUserData() {
try {
// Pauses execution until the fetch Promise resolves
const response = await fetch('api/user');
// Pauses execution until the json Promise resolves
const userData = await [Link]();
[Link](userData);
return userData;
} catch (error) {
// Catch any errors that occurred during the awaits
[Link]('Error fetching user data:', error);
}
}
fetchUserData();
Event Loop and Concurrency
The magic behind non-blocking asynchronous JavaScript lies in the Event Loop, which is part of the
JavaScript runtime environment (like [Link] or the browser).
Main Thread (Call Stack): Synchronous code is executed here.
Web APIs/Node APIs: When an asynchronous operation (like a network request or setTimeout) is
encountered, it's handed off to the appropriate API (outside the JS engine).
Callback Queue (Task Queue): Once the asynchronous operation completes, its associated callback
function is placed here.
Event Loop: Continuously checks two things:
• Is the Call Stack empty (i.e., is the main thread idle)?
• Are there any callbacks in the Callback Queue?
If the Call Stack is empty, the Event Loop takes the first callback from the queue and pushes it onto the
Call Stack for execution. This ensures the main thread is never blocked, allowing the UI to remain
responsive.
ES6+ Features
Variables: let and const
• let: Block-scoped, mutable.
• const: Block-scoped, immutable (reference).
Arrow Functions
const add = (a, b) => a + b;