JavaScript Evolution: From Scripting Language to Modern Development Platform
From Browser Script to Web Platform
The Evolution of JavaScript

JavaScript was built in a hurry in 1995 to make web pages do things. It was standardized slowly, and by 2009 it still had rough edges that developers worked around every day.
In this article, we are going to start at the beginning. We will look at those rough edges, so that when the newer versions fix them later in the series, we know what they were fixing.
The Same Task, Before and After#
Here is the kind of difference the whole series is about: one small task, written before and after the language moved on. Pick a milestone, compare the two versions, and run both against the same data. Notice that the output is identical; only the ceremony changes.
The task: take a list of items and return just their names. The data is [{ name: "Ada" }, { name: "Linus" }], and both versions return ["Ada", "Linus"].
The ES5 way (2009), with a full function and a return for the callback:
function labelsOld(items) {
return items.map(function (item) {
return item.name;
});
}
The ES2015 way, with an arrow function:
const labelsNew = (items) => items.map((item) => item.name);
The result is the same. The newer syntax is less ceremony, not different behavior. One real difference is that an arrow function keeps the this of the code around it (called lexical this), and it cannot be used as a constructor with new.
Try it in your head first: if you called both with that data, would any difference show up in the output? (None does. The next sections show where old JavaScript genuinely hurt.)
Introduction: Why JavaScript History Matters#
Before diving into the technical details, let's clarify some terminology. ECMAScript is the official specification that defines how JavaScript should work, while JavaScript is the implementation of this standard. Think of ECMAScript as the blueprint and JavaScript as the actual building constructed from those plans.
Understanding JavaScript's evolution isn't just a history lesson—it's essential knowledge for modern developers. The features you use daily, from arrow functions to optional chaining, all have origin stories that explain their design decisions and use cases. By understanding this history, you'll gain deeper insight into why JavaScript works the way it does, helping you write more effective code.
The Birth of JavaScript: A 10-Day Wonder#
In 1995, Netscape Navigator dominated the browser market, but the web was largely static. Netscape needed a lightweight scripting language to make web pages more dynamic and interactive. Enter Brendan Eich, who was tasked with creating this new language in just ten days.
Originally named "Mocha," then briefly "LiveScript," before finally becoming "JavaScript" (partially for marketing reasons to capitalize on Java's popularity), this new language had a rushed design that led to quirks and limitations that still echo through web development today.
Despite its flaws, JavaScript quickly gained adoption as the only way to add interactivity to web pages. Microsoft responded with its own implementation called JScript, sparking the first browser wars and creating compatibility nightmares that would haunt developers for years to come.
The Road to Standardization: ECMA-262#
To address cross-browser compatibility issues, Netscape submitted JavaScript to the European Computer Manufacturers Association (ECMA) for standardization in 1996. This led to the formation of the ECMA-262 committee and the release of the first standardized version, ECMAScript 1, in 1997.
ECMAScript 2 (1998) and ECMAScript 3 (1999) followed quickly, making incremental improvements. Then came a long and complicated period in JavaScript's history. An ambitious ECMAScript 4 proposal aimed to transform JavaScript into a bigger, enterprise-ready language with classes, modules, and static typing. However, disagreements about the language's direction led to the proposal's failure.
From these ashes emerged a compromise: ECMAScript 5, released in 2009—a full decade after ES3. This lengthy gap represents a challenging period in web development, where developers had to rely heavily on libraries and workarounds to build modern applications.
ES5: The Last "Old JavaScript"#
ECMAScript 5 represents an important transition point—the last version of what we might call "old JavaScript" before the big changes of ES6. While smaller than later versions, ES5 introduced several key features that laid the groundwork for modern JavaScript development:
-
Strict mode: A way to opt into a restricted variant of JavaScript that eliminated silent errors and "bad parts" of the language
-
JSON support: Native methods for parsing and serializing JSON data
-
New array methods:
forEach(),map(),filter(),reduce(), and others that enabled functional programming approaches -
Object methods:
Object.create(),Object.defineProperty(), andObject.keys()for better object manipulation -
Function binding: The
bind()method for explicitly setting thethisvalue in functions
These additions made JavaScript more capable and helped reduce reliance on external libraries for basic functionality. However, developers still faced significant pain points when building complex applications.
Life Before ES6: The Pain Points#
To appreciate the change that would come with ES6, it's important to understand the challenges developers faced with pre-ES6 JavaScript:
-
Callback hell: Nested callbacks were the only way to handle asynchronous operations, leading to deeply nested, hard-to-maintain code
-
Scope issues: The lack of block scoping (only having function scope) caused unexpected variable behaviors
-
The
thisconundrum: The value ofthischanged depending on how a function was called, leading to countless bugs -
Verbose syntax: Functions and object manipulations required more code than necessary
-
No native modules: A module is a file that keeps its own variables private and shares only what it chooses. JavaScript had no built-in way to do that, so developers used workarounds: an IIFE (an "immediately invoked function expression", a function that is defined and called at once so its variables stay inside it), or the community formats CommonJS (used by Node.js) and AMD (loading modules asynchronously in browsers)
-
Limited tooling: The language lacked native features that made large-scale development manageable
These limitations led to heavy reliance on libraries like jQuery, Underscore, and Backbone.js to provide missing functionality and normalize behavior across browsers.
One of the most dramatic improvements in JavaScript has been in handling asynchronous operations. This interactive example demonstrates the evolution from deeply nested callbacks (the infamous "callback hell") to the cleaner Promise syntax of ES6, and finally to the elegant async/await pattern introduced in ES8. Try clicking each button to see how the same operations are handled across different JavaScript eras, and observe how readability and maintainability have improved with each iteration.
---html
<h1>Callback Hell vs. Promises (ES5 vs. ES6)</h1>
<div class="card">
<h2>The Problem: Callback Hell (ES5)</h2>
<p>Before ES6, handling asynchronous operations led to deeply nested callbacks, making code hard to read and maintain.</p>
<div class="code-block">
<pre><span class="comment">// ES5 style - "Callback Hell" or "Pyramid of Doom"</span>
getUser(userId, function(user) {
<span class="comment">// First callback</span>
getFriends(user, function(friends) {
<span class="comment">// Second callback, nested deeper</span>
getPhotos(friends, function(photos) {
<span class="comment">// Third callback, even deeper</span>
sortPhotos(photos, function(sorted) {
<span class="comment">// Fourth callback, this is getting ridiculous</span>
displayPhotos(sorted, function() {
<span class="comment">// Finally done, but five levels deep!</span>
console.log("Photos displayed!");
});
});
});
});
});</pre>
</div>
<button id="runCallback">Run Callback Example</button>
<div id="callbackResult" class="result"></div>
<div id="callbackLoader" class="loader"></div>
</div>
<div class="card">
<h2>The Solution: Promises (ES6)</h2>
<p>ES6 introduced Promises, allowing for a cleaner way to handle asynchronous operations with better error handling.</p>
<div class="code-block">
<pre><span class="comment">// ES6 style - Using Promises</span>
getUser(userId)
.then(user => getFriends(user))
.then(friends => getPhotos(friends))
.then(photos => sortPhotos(photos))
.then(sorted => displayPhotos(sorted))
.then(() => console.log("Photos displayed!"))
.catch(error => console.error("Error:", error));</pre>
</div>
<button id="runPromise">Run Promise Example</button>
<div id="promiseResult" class="result"></div>
<div id="promiseLoader" class="loader"></div>
</div>
<div class="card">
<h2>Even Better: Async/Await (ES8)</h2>
<p>ES8 took it further with async/await, making asynchronous code look and behave like synchronous code.</p>
<div class="code-block">
<pre><span class="comment">// ES8 style - Using async/await</span>
async function displayUserPhotos(userId) {
try {
const user = await getUser(userId);
const friends = await getFriends(user);
const photos = await getPhotos(friends);
const sorted = await sortPhotos(photos);
await displayPhotos(sorted);
console.log("Photos displayed!");
} catch (error) {
console.error("Error:", error);
}
}</pre>
</div>
<button id="runAsync">Run Async/Await Example</button>
<div id="asyncResult" class="result"></div>
<div id="asyncLoader" class="loader"></div>
</div>
---css
/* Dark mode by default with light mode via media query */
:root {
--bg-color: #0d1117;
--text-color: #e6edf3;
--card-color: #161b22;
--border-color: #30363d;
--accent-color: #58a6ff;
--button-hover: #1f6feb;
--code-bg: #1e2428;
--comment-color: #8b949e;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: #f6f8fa;
--text-color: #24292e;
--card-color: #ffffff;
--border-color: #d0d7de;
--accent-color: #0969da;
--button-hover: #0366d6;
--code-bg: #f6f8fa;
--comment-color: #6e7781;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
max-width: 800px;
margin: 0 auto;
transition: all 0.3s ease;
}
h1,
h2 {
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
}
.card {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 6px;
overflow-x: auto;
margin: 15px 0;
border: 1px solid var(--border-color);
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
}
.comment {
color: var(--comment-color);
}
button {
background-color: var(--accent-color);
color: var(--text-color);
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: var(--button-hover);
}
.result {
margin-top: 15px;
padding: 10px;
background-color: var(--code-bg);
border-radius: 6px;
border: 1px solid var(--border-color);
}
.loader {
display: none;
border: 3px solid var(--border-color);
border-top: 3px solid var(--accent-color);
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
margin: 10px auto;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
---js
// Simulate asynchronous operations
function simulateAsyncOp(data, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(data), delay);
});
}
// Callback version
document.getElementById("runCallback").addEventListener("click", function () {
const resultDiv = document.getElementById("callbackResult");
const loader = document.getElementById("callbackLoader");
resultDiv.textContent = "";
loader.style.display = "block";
// Simulate the callback pyramid
setTimeout(() => {
resultDiv.textContent += "Getting user...\n";
setTimeout(() => {
resultDiv.textContent += "Getting friends...\n";
setTimeout(() => {
resultDiv.textContent += "Getting photos...\n";
setTimeout(() => {
resultDiv.textContent += "Sorting photos...\n";
setTimeout(() => {
resultDiv.textContent += "Displaying photos!\n";
resultDiv.textContent += "✅ Operation completed (callback style)";
loader.style.display = "none";
}, 300);
}, 300);
}, 300);
}, 300);
}, 300);
});
// Promise version
document.getElementById("runPromise").addEventListener("click", function () {
const resultDiv = document.getElementById("promiseResult");
const loader = document.getElementById("promiseLoader");
resultDiv.textContent = "";
loader.style.display = "block";
// Using promises
simulateAsyncOp("Getting user...", 300).
then(result => {
resultDiv.textContent += result + "\n";
return simulateAsyncOp("Getting friends...", 300);
}).
then(result => {
resultDiv.textContent += result + "\n";
return simulateAsyncOp("Getting photos...", 300);
}).
then(result => {
resultDiv.textContent += result + "\n";
return simulateAsyncOp("Sorting photos...", 300);
}).
then(result => {
resultDiv.textContent += result + "\n";
return simulateAsyncOp("Displaying photos!", 300);
}).
then(result => {
resultDiv.textContent += result + "\n";
resultDiv.textContent += "✅ Operation completed (promise style)";
loader.style.display = "none";
}).
catch(error => {
resultDiv.textContent += "❌ Error: " + error;
loader.style.display = "none";
});
});
// Async/Await version
document.
getElementById("runAsync").
addEventListener("click", async function () {
const resultDiv = document.getElementById("asyncResult");
const loader = document.getElementById("asyncLoader");
resultDiv.textContent = "";
loader.style.display = "block";
try {
// Using async/await
resultDiv.textContent +=
(await simulateAsyncOp("Getting user...", 300)) + "\n";
resultDiv.textContent +=
(await simulateAsyncOp("Getting friends...", 300)) + "\n";
resultDiv.textContent +=
(await simulateAsyncOp("Getting photos...", 300)) + "\n";
resultDiv.textContent +=
(await simulateAsyncOp("Sorting photos...", 300)) + "\n";
resultDiv.textContent +=
(await simulateAsyncOp("Displaying photos!", 300)) + "\n";
resultDiv.textContent += "✅ Operation completed (async/await style)";
} catch (error) {
resultDiv.textContent += "❌ Error: " + error;
} finally {
loader.style.display = "none";
}
});
Before ES6, JavaScript developers had to contend with the quirks of var declarations and function scoping. Two terms matter here. Scope is the part of the code where a variable can be seen: var is visible in the whole function, while let and const are visible only inside the nearest { } block. Hoisting means JavaScript registers a variable's name before it runs the code, so a var read too early gives undefined, while a let or const read too early throws an error. That error window, from the start of the block until the declaration line, is called the temporal dead zone.
The introduction of let and const in ES6 addressed many pain points by providing block scope, a clear error in the temporal dead zone, and preventing variable redeclaration. This example demonstrates the critical differences between these declaration methods and why modern JavaScript strongly favors let and const over var. Experiment with each tab to see how scoping, hoisting, and redeclaration work differently across these variable declarations.
---html
<h1>Variable Declaration Evolution (var vs. let vs. const)</h1>
<div class="card">
<h2>Problem 1: Function vs. Block Scope</h2>
<div class="tabs">
<button class="tab active" onclick="switchTab(event, 'var-scope')">var (ES5)</button>
<button class="tab" onclick="switchTab(event, 'let-scope')">let (ES6)</button>
</div>
<div id="var-scope" class="tab-content active">
<p><code>var</code> variables are function-scoped, not block-scoped. This can lead to unexpected behavior.</p>
<div class="code-block">
<pre><span class="comment">// Using var (ES5)</span>
function varExample() {
for (var i = 0; i < 3; i++) {
<span class="comment">// i is defined throughout the function</span>
console.log('Inside loop:', i);
}
console.log('Outside loop:', i); <span class="comment">// i is still accessible here!</span>
return i;
}</pre>
</div>
<button onclick="runVarScopeExample()">Run var Example</button>
<div id="var-scope-result" class="result"></div>
</div>
<div id="let-scope" class="tab-content">
<p><code>let</code> variables are block-scoped, which prevents variables from "leaking" outside their intended scope.</p>
<div class="code-block">
<pre><span class="comment">// Using let (ES6)</span>
function letExample() {
for (let i = 0; i < 3; i++) {
<span class="comment">// i is only defined within this block</span>
console.log('Inside loop:', i);
}
<span class="comment">// The next line would cause a ReferenceError</span>
<span class="comment">// console.log('Outside loop:', i);</span>
<span class="comment">// Instead we need to define i outside if needed</span>
let result = 'i is not accessible here!';
return result;
}</pre>
</div>
<button onclick="runLetScopeExample()">Run let Example</button>
<div id="let-scope-result" class="result"></div>
</div>
</div>
<div class="card">
<h2>Problem 2: Variable Redeclaration</h2>
<div class="tabs">
<button class="tab active" onclick="switchTab(event, 'var-redeclaration')">var (ES5)</button>
<button class="tab" onclick="switchTab(event, 'let-redeclaration')">let/const (ES6)</button>
</div>
<div id="var-redeclaration" class="tab-content active">
<p><code>var</code> allows redeclaring the same variable multiple times without errors.</p>
<div class="code-block">
<pre><span class="comment">// Using var (ES5)</span>
function varRedeclarationExample() {
var x = 1;
console.log(x); <span class="comment">// 1</span>
var x = 2; <span class="comment">// No error, just redeclares x</span>
console.log(x); <span class="comment">// 2</span>
return 'You can redeclare the same variable with var!';
}</pre>
</div>
<button onclick="runVarRedeclarationExample()">Run var Redeclaration Example</button>
<div id="var-redeclaration-result" class="result"></div>
</div>
<div id="let-redeclaration" class="tab-content">
<p><code>let</code> and <code>const</code> prevent redeclaration of the same variable in the same scope.</p>
<div class="code-block">
<pre><span class="comment">// Using let/const (ES6)</span>
function letConstRedeclarationExample() {
let x = 1;
console.log(x); <span class="comment">// 1</span>
<span class="comment">// let x = 2; // This would cause a SyntaxError</span>
<span class="comment">// We can reassign (but not redeclare) with let</span>
x = 2;
console.log(x); <span class="comment">// 2</span>
<span class="comment">// With const, we can't even reassign</span>
const y = 3;
console.log(y); <span class="comment">// 3</span>
<span class="comment">// y = 4; // This would cause a TypeError</span>
return 'With let/const, redeclaration is not allowed, preventing potential bugs!';
}</pre>
</div>
<button onclick="runLetConstRedeclarationExample()">Run let/const Example</button>
<div id="let-redeclaration-result" class="result"></div>
</div>
</div>
<div class="card">
<h2>Problem 3: Temporal Dead Zone</h2>
<p>The "Temporal Dead Zone" refers to the period between entering scope and declaration where let/const variables cannot be accessed.</p>
<div class="tabs">
<button class="tab active" onclick="switchTab(event, 'var-tdz')">var (ES5)</button>
<button class="tab" onclick="switchTab(event, 'let-tdz')">let/const (ES6)</button>
</div>
<div id="var-tdz" class="tab-content active">
<p><code>var</code> variables are hoisted and initialized with <code>undefined</code>.</p>
<div class="code-block">
<pre><span class="comment">// Using var (ES5)</span>
function varHoistingExample() {
console.log(x); <span class="comment">// undefined (hoisted but not initialized)</span>
var x = 5;
console.log(x); <span class="comment">// 5</span>
<span class="comment">// This is equivalent to:</span>
<span class="comment">// var x;</span>
<span class="comment">// console.log(x);</span>
<span class="comment">// x = 5;</span>
<span class="comment">// console.log(x);</span>
return 'var variables are hoisted and initialized with undefined';
}</pre>
</div>
<button onclick="runVarHoistingExample()">Run var Hoisting Example</button>
<div id="var-tdz-result" class="result"></div>
</div>
<div id="let-tdz" class="tab-content">
<p><code>let</code> and <code>const</code> variables are hoisted but not initialized, creating a "Temporal Dead Zone".</p>
<div class="code-block">
<pre><span class="comment">// Using let/const (ES6)</span>
function letConstHoistingExample() {
<span class="comment">// This would cause ReferenceError: Cannot access 'y' before initialization</span>
<span class="comment">// console.log(y);</span>
let y = 5;
console.log(y); <span class="comment">// 5</span>
return 'let/const variables are hoisted but not initialized, creating a "Temporal Dead Zone"';
}</pre>
</div>
<button onclick="runLetConstHoistingExample()">Run let/const Example</button>
<button onclick="demonstrateTDZ()">Demonstrate Temporal Dead Zone Error</button>
<div id="let-tdz-result" class="result"></div>
</div>
</div>
---css
/* Dark mode by default with light mode via media query */
:root {
--bg-color: #0d1117;
--text-color: #e6edf3;
--card-color: #161b22;
--border-color: #30363d;
--accent-color: #58a6ff;
--button-hover: #1f6feb;
--code-bg: #1e2428;
--comment-color: #8b949e;
--alert-color: #f85149;
--success-color: #3fb950;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: #f6f8fa;
--text-color: #24292e;
--card-color: #ffffff;
--border-color: #d0d7de;
--accent-color: #0969da;
--button-hover: #0366d6;
--code-bg: #f6f8fa;
--comment-color: #6e7781;
--alert-color: #cf222e;
--success-color: #1a7f37;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
max-width: 800px;
margin: 0 auto;
transition: all 0.3s ease;
}
h1,
h2 {
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
}
.card {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 6px;
overflow-x: auto;
margin: 15px 0;
border: 1px solid var(--border-color);
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
}
.comment {
color: var(--comment-color);
}
button {
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
}
button:hover {
background-color: var(--button-hover);
}
.result {
margin-top: 15px;
padding: 10px;
background-color: var(--code-bg);
border-radius: 6px;
border: 1px solid var(--border-color);
white-space: pre-wrap;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}
.highlight-success {
color: var(--success-color);
}
.highlight-error {
color: var(--alert-color);
}
.tabs {
display: flex;
margin-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.tab {
padding: 8px 16px;
cursor: pointer;
background-color: transparent;
border: none;
color: var(--text-color);
border-bottom: 2px solid transparent;
}
.tab.active {
border-bottom: 2px solid var(--accent-color);
font-weight: bold;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
---js
// Switch tabs
function switchTab(event, tabId) {
// Hide all tab contents
const tabContents = document.getElementsByClassName("tab-content");
for (let i = 0; i < tabContents.length; i++) {
tabContents[i].classList.remove("active");
}
// Remove active class from all tabs
const tabs = document.getElementsByClassName("tab");
for (let i = 0; i < tabs.length; i++) {
tabs[i].classList.remove("active");
}
// Show the selected tab content and mark the button as active
document.getElementById(tabId).classList.add("active");
event.currentTarget.classList.add("active");
}
// Problem 1 - Function vs Block Scope
function runVarScopeExample() {
const resultDiv = document.getElementById("var-scope-result");
resultDiv.innerHTML = "";
try {
function varExample() {
let result = "";
for (var i = 0; i < 3; i++) {
result += `Inside loop: ${i}\n`;
}
result += `Outside loop: ${i}\n`;
return result;
}
resultDiv.innerHTML =
varExample() +
"<span class='highlight-success'>\n✓ Note how 'i' is still accessible after the loop finishes!</span>";
} catch (error) {
resultDiv.innerHTML = `<span class='highlight-error'>Error: ${error.message}</span>`;
}
}
function runLetScopeExample() {
const resultDiv = document.getElementById("let-scope-result");
resultDiv.innerHTML = "";
try {
function letExample() {
let result = "";
for (let i = 0; i < 3; i++) {
result += `Inside loop: ${i}\n`;
}
// The next line would cause a ReferenceError
// result += `Outside loop: ${i}\n`;
result += `\nTrying to access 'i' outside the loop would cause a ReferenceError`;
return result;
}
resultDiv.innerHTML =
letExample() +
"<span class='highlight-success'>\n✓ With 'let', variables are properly scoped to their blocks!</span>";
} catch (error) {
resultDiv.innerHTML = `<span class='highlight-error'>Error: ${error.message}</span>`;
}
}
// Problem 2 - Variable Redeclaration
function runVarRedeclarationExample() {
const resultDiv = document.getElementById("var-redeclaration-result");
resultDiv.innerHTML = "";
try {
function varRedeclarationExample() {
let result = "";
var x = 1;
result += `x initialized to: ${x}\n`;
var x = 2; // No error, just redeclares x
result += `x after redeclaration: ${x}\n`;
return result;
}
resultDiv.innerHTML =
varRedeclarationExample() +
"<span class='highlight-error'>\n⚠️ With 'var', you can accidentally redeclare variables!</span>";
} catch (error) {
resultDiv.innerHTML = `<span class='highlight-error'>Error: ${error.message}</span>`;
}
}
function runLetConstRedeclarationExample() {
const resultDiv = document.getElementById("let-redeclaration-result");
resultDiv.innerHTML = "";
try {
function letConstRedeclarationExample() {
let result = "";
let x = 1;
result += `x initialized to: ${x}\n`;
// let x = 2; // This would cause a SyntaxError
// We can reassign (but not redeclare) with let
x = 2;
result += `x after reassignment: ${x}\n`;
// With const, we can't even reassign
const y = 3;
result += `y (const) initialized to: ${y}\n`;
// y = 4; // This would cause a TypeError
result += `Attempting to reassign 'y' would cause a TypeError\n`;
return result;
}
resultDiv.innerHTML =
letConstRedeclarationExample() +
"<span class='highlight-success'>\n✓ With 'let/const', redeclaration is prevented, avoiding bugs!</span>";
} catch (error) {
resultDiv.innerHTML = `<span class='highlight-error'>Error: ${error.message}</span>`;
}
}
// Problem 3 - Temporal Dead Zone
function runVarHoistingExample() {
const resultDiv = document.getElementById("var-tdz-result");
resultDiv.innerHTML = "";
try {
function varHoistingExample() {
let result = "";
result += `Value of x before declaration: ${x}\n`;
var x = 5;
result += `Value of x after declaration: ${x}\n`;
return result;
}
// Need to define x in this scope for the example to work
var x;
resultDiv.innerHTML =
varHoistingExample() +
"<span class='highlight-error'>\n⚠️ 'var' hoisting can lead to unexpected 'undefined' values!</span>";
} catch (error) {
resultDiv.innerHTML = `<span class='highlight-error'>Error: ${error.message}</span>`;
}
}
function runLetConstHoistingExample() {
const resultDiv = document.getElementById("let-tdz-result");
resultDiv.innerHTML = "";
try {
function letConstHoistingExample() {
let result = "";
// This would cause ReferenceError: Cannot access 'y' before initialization
// result += `Value of y before declaration: ${y}\n`;
result += `Trying to access 'y' before declaration would cause a ReferenceError\n`;
let y = 5;
result += `Value of y after declaration: ${y}\n`;
return result;
}
resultDiv.innerHTML =
letConstHoistingExample() +
"<span class='highlight-success'>\n✓ The Temporal Dead Zone prevents accessing variables before declaration!</span>";
} catch (error) {
resultDiv.innerHTML = `<span class='highlight-error'>Error: ${error.message}</span>`;
}
}
function demonstrateTDZ() {
const resultDiv = document.getElementById("let-tdz-result");
resultDiv.innerHTML = "";
try {
// This will cause an error - intentionally
function demonstrateTDZ() {
console.log(z); // ReferenceError
let z = 10;
return z;
}
demonstrateTDZ();
} catch (error) {
resultDiv.innerHTML =
`<span class='highlight-error'>Error demonstrated: ${error.message}</span>` +
"<span class='highlight-success'>\n\n✓ This is the Temporal Dead Zone in action! Unlike 'var', accessing 'let' or 'const' variables before declaration throws an error.</span>";
}
}
// Initialize with the first example
runVarScopeExample();
runVarRedeclarationExample();
runVarHoistingExample();
Predict, then run: the var loop#
The scope rule has a famous consequence. This loop schedules three messages for later with setTimeout. By the time any message runs, the loop has already finished:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
// prints: 3, 3, 3
Because var has one i for the whole function, all three callbacks read the same variable, and it is 3 when the loop ends. With let, every pass through the loop gets its own i:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
// prints: 0, 1, 2
Coding challenge: Fix the loop
Change one keyword so the loop prints the numbers in order.
Expected result: the console shows 0, 1, 2 instead of 3, 3, 3.
---js
// TODO: change one keyword so this prints 0, 1, 2
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
String manipulation before ES6 was cumbersome, especially for multi-line strings or when embedding variables. Template literals, introduced in ES6, transformed how we work with strings, making code more readable and intuitive. This interactive example contrasts the old string concatenation approach with modern template literals. Try entering your own values to see how template literals simplify string operations, support multi-line strings without escape characters, and elegantly handle string interpolation.
---html
<h1>String Evolution: Template Literals</h1>
<div class="card">
<h2>String Concatenation: Before and After ES6</h2>
<div class="grid">
<div>
<h3>ES5 String Concatenation</h3>
<div class="code-block">
<pre><span class="comment">// ES5 style concatenation</span>
<span class="keyword">var</span> name = <span class="string">'Alice'</span>;
<span class="keyword">var</span> age = 32;
<span class="keyword">var</span> job = <span class="string">'Developer'</span>;
<span class="comment">// Building complex strings with concatenation</span>
<span class="keyword">var</span> greeting = <span class="string">'Hello, my name is '</span> + name +
<span class="string">'.\nI am '</span> + age + <span class="string">' years old and work as a '</span> +
job + <span class="string">'.'</span>;
<span class="comment">// Multi-line strings required concat or escape chars</span>
<span class="keyword">var</span> multiLine = <span class="string">'This is line one.\n'</span> +
<span class="string">'This is line two.\n'</span> +
<span class="string">'This is line three.'</span>;</pre>
</div>
</div>
<div>
<h3>ES6 Template Literals</h3>
<div class="code-block">
<pre><span class="comment">// ES6 template literals</span>
<span class="keyword">const</span> name = <span class="string">'Alice'</span>;
<span class="keyword">const</span> age = 32;
<span class="keyword">const</span> job = <span class="string">'Developer'</span>;
<span class="comment">// Building strings with template literals</span>
<span class="keyword">const</span> greeting = <span class="string">`Hello, my name is ${name}.
I am ${age} years old and work as a ${job}.`</span>;
<span class="comment">// Multi-line strings are easy with backticks</span>
<span class="keyword">const</span> multiLine = <span class="string">`This is line one.
This is line two.
This is line three.`</span>;</pre>
</div>
</div>
</div>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" value="Alice">
</div>
<div class="form-group">
<label for="age">Age:</label>
<input type="number" id="age" value="32">
</div>
<div class="form-group">
<label for="job">Job:</label>
<input type="text" id="job" value="Developer">
</div>
<button id="runES5">Run ES5 Example</button>
<button id="runES6">Run ES6 Example</button>
<div class="result" id="stringResult"></div>
</div>
<div class="card">
<h2>Advanced Template Literals</h2>
<h3>Expression Evaluation</h3>
<div class="code-block">
<pre><span class="comment">// Template literals can evaluate expressions</span>
<span class="keyword">const</span> a = 5;
<span class="keyword">const</span> b = 10;
<span class="keyword">const</span> result = <span class="string">`The sum of ${a} and ${b} is ${a + b}.</span>
<span class="string">The product of ${a} and ${b} is ${a * b}.</span>
<span class="string">Is ${a} greater than ${b}? ${a > b ? 'Yes' : 'No'}.`</span>;</pre>
</div>
<div class="form-group">
<label for="number1">First Number:</label>
<input type="number" id="number1" value="5">
</div>
<div class="form-group">
<label for="number2">Second Number:</label>
<input type="number" id="number2" value="10">
</div>
<button id="runExpressions">Run Expressions Example</button>
<div class="result" id="expressionResult"></div>
<h3>Tagged Templates</h3>
<div class="code-block">
<pre><span class="comment">// Tagged templates allow processing template literals</span>
<span class="keyword">function</span> highlight(strings, ...values) {
<span class="keyword">let</span> result = <span class="string">''</span>;
<span class="comment">// Loop through all the pieces</span>
strings.forEach((string, i) => {
<span class="keyword">let</span> value = values[i] || <span class="string">''</span>;
<span class="comment">// Highlight all values in the template</span>
<span class="keyword">if</span> (value) {
value = <span class="string">`<span class="highlight">${value}</span>`</span>;
}
result += string + value;
});
<span class="keyword">return</span> result;
}
<span class="keyword">const</span> product = <span class="string">'Laptop'</span>;
<span class="keyword">const</span> price = 999.99;
<span class="keyword">const</span> stock = 42;
<span class="keyword">const</span> productInfo = highlight<span class="string">`Product: ${product}
Price: $${price}
In stock: ${stock} units`</span>;</pre>
</div>
<div class="form-group">
<label for="product">Product:</label>
<input type="text" id="product" value="Laptop">
</div>
<div class="form-group">
<label for="price">Price:</label>
<input type="number" id="price" value="999.99" step="0.01">
</div>
<div class="form-group">
<label for="stock">Stock:</label>
<input type="number" id="stock" value="42">
</div>
<button id="runTagged">Run Tagged Templates Example</button>
<div class="result" id="taggedResult"></div>
</div>
<div class="card">
<h2>Real-World Application: HTML Template Generation</h2>
<div class="code-block">
<pre><span class="comment">// ES5 way of generating HTML (cumbersome)</span>
<span class="keyword">function</span> createUserCardES5(user) {
<span class="keyword">var</span> html = <span class="string">'<div class="user-card">\n'</span> +
<span class="string">' <img src="'</span> + user.avatar + <span class="string">'" alt="'</span> + user.name + <span class="string">'" />\n'</span> +
<span class="string">' <h3>'</span> + user.name + <span class="string">'</h3>\n'</span> +
<span class="string">' <p>Email: '</span> + user.email + <span class="string">'</p>\n'</span> +
<span class="string">' <p>Role: '</span> + user.role + <span class="string">'</p>\n'</span> +
<span class="string">' <p>Member since: '</span> + user.memberSince + <span class="string">'</p>\n'</span>;
<span class="keyword">if</span> (user.verified) {
html += <span class="string">' <p class="verified">✓ Verified Account</p>\n'</span>;
}
html += <span class="string">'</div>'</span>;
<span class="keyword">return</span> html;
}
<span class="comment">// ES6 way of generating HTML (cleaner)</span>
<span class="keyword">function</span> createUserCardES6(user) {
<span class="keyword">return</span> <span class="string">`<div class="user-card">
<img src="${user.avatar}" alt="${user.name}" />
<h3>${user.name}</h3>
<p>Email: ${user.email}</p>
<p>Role: ${user.role}</p>
<p>Member since: ${user.memberSince}</p>
${user.verified ? `<p class="verified">✓ Verified Account</p>` : ''}
</div>`</span>;
}</pre>
</div>
<div class="form-group">
<label for="username">Name:</label>
<input type="text" id="username" value="Sarah Johnson">
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="text" id="email" value="sarah.j@example.com">
</div>
<div class="form-group">
<label for="role">Role:</label>
<input type="text" id="role" value="Senior Developer">
</div>
<div class="form-group">
<label for="since">Member Since:</label>
<input type="text" id="since" value="January 2020">
</div>
<div class="form-group">
<label for="verified">
<input type="checkbox" id="verified" checked> Verified Account
</label>
</div>
<button id="generateES5">Generate HTML with ES5</button>
<button id="generateES6">Generate HTML with ES6</button>
<div class="result" id="htmlResult"></div>
<div id="cardPreview" style="margin-top: 20px;"></div>
</div>
---css
/* Dark mode by default with light mode via media query */
:root {
--bg-color: #0d1117;
--text-color: #e6edf3;
--card-color: #161b22;
--border-color: #30363d;
--accent-color: #58a6ff;
--button-hover: #1f6feb;
--code-bg: #1e2428;
--comment-color: #8b949e;
--string-color: #a5d6ff;
--variable-color: #ff7b72;
--keyword-color: #ff7b72;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: #f6f8fa;
--text-color: #24292e;
--card-color: #ffffff;
--border-color: #d0d7de;
--accent-color: #0969da;
--button-hover: #0366d6;
--code-bg: #f6f8fa;
--comment-color: #6e7781;
--string-color: #0a3069;
--variable-color: #953800;
--keyword-color: #cf222e;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
max-width: 800px;
margin: 0 auto;
transition: all 0.3s ease;
}
h1,
h2,
h3 {
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
}
.card {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 6px;
overflow-x: auto;
margin: 15px 0;
border: 1px solid var(--border-color);
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
position: relative;
}
.comment {
color: var(--comment-color);
}
.string {
color: var(--string-color);
}
.variable {
color: var(--variable-color);
}
.keyword {
color: var(--keyword-color);
}
button {
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
}
button:hover {
background-color: var(--button-hover);
}
.result {
margin-top: 15px;
padding: 10px;
background-color: var(--code-bg);
border-radius: 6px;
border: 1px solid var(--border-color);
white-space: pre-wrap;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}
.highlight {
background-color: rgba(255, 123, 114, 0.1);
border-radius: 3px;
padding: 2px 4px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"],
input[type="number"],
textarea {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--code-bg);
color: var(--text-color);
font-family: inherit;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 700px) {
.grid {
grid-template-columns: 1fr;
}
}
---js
// String concatenation example
document.getElementById("runES5").addEventListener("click", function () {
const name = document.getElementById("name").value;
const age = document.getElementById("age").value;
const job = document.getElementById("job").value;
const resultDiv = document.getElementById("stringResult");
// ES5 style
var greeting =
"Hello, my name is " +
name +
".\nI am " +
age +
" years old and work as a " +
job +
".";
var multiLine =
"This is line one.\n" + "This is line two.\n" + "This is line three.";
resultDiv.textContent =
"Greeting:\n" + greeting + "\n\nMulti-line string:\n" + multiLine;
});
document.getElementById("runES6").addEventListener("click", function () {
const name = document.getElementById("name").value;
const age = document.getElementById("age").value;
const job = document.getElementById("job").value;
const resultDiv = document.getElementById("stringResult");
// ES6 style
const greeting = `Hello, my name is ${name}.
I am ${age} years old and work as a ${job}.`;
const multiLine = `This is line one.
This is line two.
This is line three.`;
resultDiv.textContent =
"Greeting:\n" + greeting + "\n\nMulti-line string:\n" + multiLine;
});
// Expression evaluation example
document.
getElementById("runExpressions").
addEventListener("click", function () {
const a = parseFloat(document.getElementById("number1").value);
const b = parseFloat(document.getElementById("number2").value);
const resultDiv = document.getElementById("expressionResult");
const result = `The sum of ${a} and ${b} is ${a + b}.
The product of ${a} and ${b} is ${a * b}.
Is ${a} greater than ${b}? ${a > b ? "Yes" : "No"}.`;
resultDiv.textContent = result;
});
// Tagged templates example
document.getElementById("runTagged").addEventListener("click", function () {
const product = document.getElementById("product").value;
const price = parseFloat(document.getElementById("price").value);
const stock = parseInt(document.getElementById("stock").value);
const resultDiv = document.getElementById("taggedResult");
function highlight(strings, ...values) {
let result = "";
strings.forEach((string, i) => {
const value = values[i] !== undefined ? values[i] : "";
// For demonstration, we'll add some styling to the values
const highlightedValue =
value !== "" ?
`<span style="background-color: rgba(255, 123, 114, 0.2); padding: 2px 4px; border-radius: 3px;">${value}</span>` :
"";
result += string + highlightedValue;
});
return result;
}
const productInfo = highlight`Product: ${product}
Price: $${price}
In stock: ${stock} units`;
resultDiv.innerHTML = productInfo;
});
// HTML generation example
document.getElementById("generateES5").addEventListener("click", function () {
generateUserCard("ES5");
});
document.getElementById("generateES6").addEventListener("click", function () {
generateUserCard("ES6");
});
function generateUserCard(version) {
const user = {
name: document.getElementById("username").value,
email: document.getElementById("email").value,
role: document.getElementById("role").value,
memberSince: document.getElementById("since").value,
verified: document.getElementById("verified").checked,
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(
document.getElementById("username").value)
}&background=random` };
const resultDiv = document.getElementById("htmlResult");
const previewDiv = document.getElementById("cardPreview");
let html = "";
if (version === "ES5") {
// ES5 style
html = createUserCardES5(user);
resultDiv.textContent = html;
} else {
// ES6 style
html = createUserCardES6(user);
resultDiv.textContent = html;
}
// Also show rendered HTML
previewDiv.innerHTML = `
<style>
.user-card {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
background-color: var(--card-color);
max-width: 300px;
text-align: center;
}
.user-card img {
width: 100px;
height: 100px;
border-radius: 50%;
margin-bottom: 10px;
}
.user-card h3 {
margin: 10px 0;
color: var(--text-color);
}
.user-card p {
margin: 5px 0;
color: var(--text-color);
}
.verified {
color: #3fb950;
font-weight: bold;
}
</style>
${html}
<p style="margin-top: 20px; font-style: italic;">Generated using ${version} string concatenation style</p>
`;
}
function createUserCardES5(user) {
var html =
'<div class="user-card">\n' +
' <img src="' +
user.avatar +
'" alt="' +
user.name +
'" />\n' +
" <h3>" +
user.name +
"</h3>\n" +
" <p>Email: " +
user.email +
"</p>\n" +
" <p>Role: " +
user.role +
"</p>\n" +
" <p>Member since: " +
user.memberSince +
"</p>\n";
if (user.verified) {
html += ' <p class="verified">✓ Verified Account</p>\n';
}
html += "</div>";
return html;
}
function createUserCardES6(user) {
return `<div class="user-card">
<img src="${user.avatar}" alt="${user.name}" />
<h3>${user.name}</h3>
<p>Email: ${user.email}</p>
<p>Role: ${user.role}</p>
<p>Member since: ${user.memberSince}</p>
${user.verified ? `<p class="verified">✓ Verified Account</p>` : ""}
</div>`;
}
The Transition Period: Setting the Stage for Modern JavaScript#
The gap between ES5 (2009) and ES6 (2015) might seem like a dormant period, but it was actually a time of immense innovation in the JavaScript community. Developers weren't waiting for language improvements—they were creating their own solutions:
-
Transpilers like Babel (a transpiler rewrites code from one version of a language into another) allowed developers to write "future JavaScript" and convert it to ES5-compatible code
-
Module bundlers like Browserify and eventually Webpack (a bundler combines many module files into one file a browser can load) enabled modular development approaches
-
Task runners like Grunt and Gulp automated common development workflows
-
Polyfills (small scripts that add a missing feature by writing it in older JavaScript) provided implementations of new features for older browsers
This period also saw the TC39 committee (responsible for ECMAScript specifications) shift to a more transparent, feature-based development approach. Rather than releasing massive updates every several years, they moved toward yearly releases with smaller sets of new features.
JavaScript's journey from a simple scripting language to a platform for complex applications required better ways to organize code. Module systems evolved from workarounds like IIFEs and namespacing patterns to standardized formats like CommonJS and AMD, finally culminating in native ES6 modules. This interactive example traces this evolution through the years, showing how each approach addressed code organization challenges. The example concludes with a complete calculator implementation using ES6 modules, demonstrating how modern JavaScript facilitates clean separation of concerns and maintainable code architecture.
---html
<h1>The Evolution of JavaScript Modules</h1>
<div class="card">
<h2>The Problem: Code Organization Before Modules</h2>
<p>In the early days of JavaScript, there was no native way to organize code into modules. Developers had to use various patterns to avoid polluting the global scope and organize their code.</p>
<div class="code-block">
<pre><span class="comment">// Global variables - the original way of sharing code</span>
<span class="keyword">var</span> userName = <span class="string">"Alice"</span>;
<span class="keyword">var</span> userAge = 30;
<span class="keyword">function</span> greetUser() {
alert(<span class="string">"Hello, "</span> + userName);
}
<span class="comment">// Problem: Easy name collisions</span>
<span class="keyword">var</span> userName = <span class="string">"Bob"</span>; <span class="comment">// Oops! Overwrote the previous variable</span></pre>
</div>
<h3>Common Pre-Module Patterns</h3>
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" onclick="switchTab('iife-tab')">IIFE Pattern</button>
<button class="tab-button" onclick="switchTab('namespace-tab')">Namespace Pattern</button>
<button class="tab-button" onclick="switchTab('constructor-tab')">Constructor Pattern</button>
</div>
<div id="iife-tab" class="tab-content active">
<h4>Immediately Invoked Function Expression (IIFE)</h4>
<p>The IIFE pattern creates a private scope for variables and functions, exposing only what you choose through the window object or a return value.</p>
<div class="code-block">
<pre><span class="comment">// IIFE Pattern (Immediately Invoked Function Expression)</span>
<span class="keyword">var</span> userModule = (<span class="keyword">function</span>() {
<span class="comment">// Private variables and functions</span>
<span class="keyword">var</span> userName = <span class="string">"Alice"</span>;
<span class="keyword">var</span> userAge = 30;
<span class="keyword">function</span> calculateBirthYear() {
<span class="keyword">var</span> currentYear = new Date().getFullYear();
<span class="keyword">return</span> currentYear - userAge;
}
<span class="comment">// Public API</span>
<span class="keyword">return</span> {
getName: <span class="keyword">function</span>() {
<span class="keyword">return</span> userName;
},
getAge: <span class="keyword">function</span>() {
<span class="keyword">return</span> userAge;
},
getBirthYear: calculateBirthYear
};
})();
<span class="comment">// Usage</span>
console.log(userModule.getName()); <span class="comment">// "Alice"</span>
console.log(userModule.getBirthYear()); <span class="comment">// Current year - 30</span>
console.log(userName); <span class="comment">// undefined (private to the module)</span></pre>
</div>
</div>
<div id="namespace-tab" class="tab-content">
<h4>Namespace Pattern</h4>
<p>The namespace pattern organizes code under a single global object to avoid polluting the global scope.</p>
<div class="code-block">
<pre><span class="comment">// Namespace Pattern</span>
<span class="keyword">var</span> APP = APP || {};
<span class="comment">// User module</span>
APP.user = {
name: <span class="string">"Alice"</span>,
age: 30,
getName: <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="keyword">this</span>.name;
},
getAge: <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="keyword">this</span>.age;
},
getBirthYear: <span class="keyword">function</span>() {
<span class="keyword">var</span> currentYear = new Date().getFullYear();
<span class="keyword">return</span> currentYear - <span class="keyword">this</span>.age;
}
};
<span class="comment">// Another module</span>
APP.settings = {
theme: <span class="string">"dark"</span>,
language: <span class="string">"en"</span>
};
<span class="comment">// Usage</span>
console.log(APP.user.getName()); <span class="comment">// "Alice"</span>
console.log(APP.settings.theme); <span class="comment">// "dark"</span></pre>
</div>
</div>
<div id="constructor-tab" class="tab-content">
<h4>Constructor Pattern</h4>
<p>The constructor pattern uses constructor functions to create objects with methods and properties.</p>
<div class="code-block">
<pre><span class="comment">// Constructor Pattern</span>
<span class="keyword">function</span> User(name, age) {
<span class="keyword">this</span>.name = name;
<span class="keyword">this</span>.age = age;
<span class="keyword">this</span>.getName = <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="keyword">this</span>.name;
};
<span class="keyword">this</span>.getBirthYear = <span class="keyword">function</span>() {
<span class="keyword">var</span> currentYear = new Date().getFullYear();
<span class="keyword">return</span> currentYear - <span class="keyword">this</span>.age;
};
}
<span class="comment">// Usage</span>
<span class="keyword">var</span> alice = <span class="keyword">new</span> User(<span class="string">"Alice"</span>, 30);
<span class="keyword">var</span> bob = <span class="keyword">new</span> User(<span class="string">"Bob"</span>, 25);
console.log(alice.getName()); <span class="comment">// "Alice"</span>
console.log(bob.getName()); <span class="comment">// "Bob"</span></pre>
</div>
</div>
</div>
</div>
<div class="card">
<h2>The Evolution of JavaScript Modules</h2>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-year">2009</div>
<div class="timeline-content">
<div class="timeline-title">CommonJS</div>
<p>Developed for server-side JavaScript (Node.js), CommonJS introduced a synchronous module format.</p>
<div class="code-block">
<pre><span class="comment">// math.js</span>
<span class="keyword">function</span> add(a, b) {
<span class="keyword">return</span> a + b;
}
<span class="keyword">function</span> subtract(a, b) {
<span class="keyword">return</span> a - b;
}
<span class="comment">// Export functions</span>
module.exports = {
add: add,
subtract: subtract
};
<span class="comment">// main.js</span>
<span class="keyword">const</span> math = require(<span class="string">'./math'</span>);
console.log(math.add(5, 3)); <span class="comment">// 8</span>
console.log(math.subtract(5, 3)); <span class="comment">// 2</span></pre>
</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-year">2011</div>
<div class="timeline-content">
<div class="timeline-title">AMD (Asynchronous Module Definition)</div>
<p>Developed for browser environments, AMD allows asynchronous loading of modules, which is crucial for the web.</p>
<div class="code-block">
<pre><span class="comment">// math.js</span>
define([], <span class="keyword">function</span>() {
<span class="keyword">function</span> add(a, b) {
<span class="keyword">return</span> a + b;
}
<span class="keyword">function</span> subtract(a, b) {
<span class="keyword">return</span> a - b;
}
<span class="keyword">return</span> {
add: add,
subtract: subtract
};
});
<span class="comment">// main.js</span>
require([<span class="string">'math'</span>], <span class="keyword">function</span>(math) {
console.log(math.add(5, 3)); <span class="comment">// 8</span>
console.log(math.subtract(5, 3)); <span class="comment">// 2</span>
});</pre>
</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-year">2012</div>
<div class="timeline-content">
<div class="timeline-title">UMD (Universal Module Definition)</div>
<p>UMD combines CommonJS and AMD to create a universal pattern that works in both Node.js and browsers.</p>
<div class="code-block">
<pre><span class="comment">// math.js</span>
(<span class="keyword">function</span>(root, factory) {
<span class="keyword">if</span> (<span class="keyword">typeof</span> define === <span class="string">'function'</span> && define.amd) {
<span class="comment">// AMD. Register as an anonymous module.</span>
define([], factory);
} <span class="keyword">else if</span> (<span class="keyword">typeof</span> module === <span class="string">'object'</span> && module.exports) {
<span class="comment">// Node. Export as module</span>
module.exports = factory();
} <span class="keyword">else</span> {
<span class="comment">// Browser globals (root is window)</span>
root.math = factory();
}
}(<span class="keyword">this</span>, <span class="keyword">function</span>() {
<span class="keyword">function</span> add(a, b) {
<span class="keyword">return</span> a + b;
}
<span class="keyword">function</span> subtract(a, b) {
<span class="keyword">return</span> a - b;
}
<span class="keyword">return</span> {
add: add,
subtract: subtract
};
}));</pre>
</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-year">2015</div>
<div class="timeline-content">
<div class="timeline-title">ES6 Modules (ES2015)</div>
<p>Native JavaScript modules were standardized in ES6, providing a unified, official way to create modules.</p>
<div class="code-block">
<pre><span class="comment">// math.js</span>
<span class="keyword">export function</span> add(a, b) {
<span class="keyword">return</span> a + b;
}
<span class="keyword">export function</span> subtract(a, b) {
<span class="keyword">return</span> a - b;
}
<span class="comment">// main.js</span>
<span class="keyword">import</span> { add, subtract } <span class="keyword">from</span> <span class="string">'./math.js'</span>;
console.log(add(5, 3)); <span class="comment">// 8</span>
console.log(subtract(5, 3)); <span class="comment">// 2</span>
<span class="comment">// Alternatively, import everything</span>
<span class="keyword">import</span> * <span class="keyword">as</span> math <span class="keyword">from</span> <span class="string">'./math.js'</span>;
console.log(math.add(5, 3)); <span class="comment">// 8</span></pre>
</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-year">2020+</div>
<div class="timeline-content">
<div class="timeline-title">Dynamic Imports (ES2020)</div>
<p>ES2020 introduced dynamic imports, allowing modules to be loaded on demand.</p>
<div class="code-block">
<pre><span class="comment">// Using dynamic imports</span>
<span class="keyword">async function</span> loadMathModule() {
<span class="keyword">const</span> math = <span class="keyword">await import</span>(<span class="string">'./math.js'</span>);
console.log(math.add(5, 3)); <span class="comment">// 8</span>
console.log(math.subtract(5, 3)); <span class="comment">// 2</span>
}
<span class="comment">// Load the module when needed</span>
document.getElementById(<span class="string">'loadButton'</span>).addEventListener(<span class="string">'click'</span>, loadMathModule);</pre>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<h2>ES6 Modules Deep Dive</h2>
<h3>Basic Module Structures</h3>
<div class="grid">
<div>
<h4>Named Exports/Imports</h4>
<div class="code-block">
<pre><span class="comment">// utils.js</span>
<span class="keyword">export function</span> formatDate(date) {
<span class="keyword">return</span> new Date(date).toLocaleDateString();
}
<span class="keyword">export function</span> formatCurrency(amount) {
<span class="keyword">return</span> <span class="string">`$${amount.toFixed(2)}`</span>;
}
<span class="keyword">export const</span> PI = 3.14159;
<span class="comment">// app.js</span>
<span class="keyword">import</span> { formatDate, formatCurrency } <span class="keyword">from</span> <span class="string">'./utils.js'</span>;
console.log(formatDate(<span class="string">'2023-10-05'</span>));
console.log(formatCurrency(42.5));</pre>
</div>
</div>
<div>
<h4>Default Exports/Imports</h4>
<div class="code-block">
<pre><span class="comment">// user.js</span>
<span class="keyword">class</span> User {
constructor(name, age) {
<span class="keyword">this</span>.name = name;
<span class="keyword">this</span>.age = age;
}
getProfile() {
<span class="keyword">return</span> <span class="string">`${this.name}, ${this.age}`</span>;
}
}
<span class="keyword">export default</span> User;
<span class="comment">// app.js</span>
<span class="keyword">import</span> User <span class="keyword">from</span> <span class="string">'./user.js'</span>;
<span class="keyword">const</span> alice = <span class="keyword">new</span> User(<span class="string">'Alice'</span>, 30);
console.log(alice.getProfile());</pre>
</div>
</div>
</div>
<h3>Mixed Default and Named Exports</h3>
<div class="code-block">
<pre><span class="comment">// api.js</span>
<span class="keyword">export default class</span> API {
<span class="keyword">static</span> get(url) {
<span class="keyword">return</span> fetch(url).then(res => res.json());
}
<span class="keyword">static</span> post(url, data) {
<span class="keyword">return</span> fetch(url, {
method: <span class="string">'POST'</span>,
headers: { <span class="string">'Content-Type'</span>: <span class="string">'application/json'</span> },
body: JSON.stringify(data)
}).then(res => res.json());
}
}
<span class="keyword">export const</span> API_URL = <span class="string">'https://api.example.com'</span>;
<span class="keyword">export const</span> API_VERSION = <span class="string">'v1'</span>;
<span class="comment">// app.js</span>
<span class="keyword">import</span> API, { API_URL, API_VERSION } <span class="keyword">from</span> <span class="string">'./api.js'</span>;
<span class="keyword">const</span> usersEndpoint = <span class="string">`${API_URL}/${API_VERSION}/users`</span>;
API.get(usersEndpoint).then(users => console.log(users));</pre>
</div>
<h3>Module Re-exports</h3>
<div class="code-block">
<pre><span class="comment">// math.js</span>
<span class="keyword">export function</span> add(a, b) { <span class="keyword">return</span> a + b; }
<span class="keyword">export function</span> subtract(a, b) { <span class="keyword">return</span> a - b; }
<span class="comment">// utils.js</span>
<span class="keyword">export function</span> formatDate(date) { <span class="keyword">return</span> new Date(date).toLocaleDateString(); }
<span class="comment">// index.js - Re-exports from multiple modules</span>
<span class="keyword">export</span> { add, subtract } <span class="keyword">from</span> <span class="string">'./math.js'</span>;
<span class="keyword">export</span> { formatDate } <span class="keyword">from</span> <span class="string">'./utils.js'</span>;
<span class="keyword">export const</span> VERSION = <span class="string">'1.0.0'</span>;
<span class="comment">// app.js - Imports everything from a single file</span>
<span class="keyword">import</span> { add, subtract, formatDate, VERSION } <span class="keyword">from</span> <span class="string">'./index.js'</span>;</pre>
</div>
<h3>Dynamic Imports</h3>
<div class="code-block">
<pre><span class="comment">// app.js</span>
<span class="keyword">const</span> loadModule = <span class="keyword">async</span> (moduleName) => {
<span class="keyword">try</span> {
<span class="keyword">if</span> (moduleName === <span class="string">'math'</span>) {
<span class="keyword">const</span> math = <span class="keyword">await import</span>(<span class="string">'./math.js'</span>);
<span class="keyword">return</span> math;
} <span class="keyword">else if</span> (moduleName === <span class="string">'utils'</span>) {
<span class="keyword">const</span> utils = <span class="keyword">await import</span>(<span class="string">'./utils.js'</span>);
<span class="keyword">return</span> utils;
}
} <span class="keyword">catch</span> (error) {
console.error(<span class="string">`Failed to load module: ${moduleName}`</span>, error);
<span class="keyword">return</span> null;
}
};
<span class="comment">// Load modules on demand</span>
document.getElementById(<span class="string">'loadMath'</span>).addEventListener(<span class="string">'click'</span>, <span class="keyword">async</span> () => {
<span class="keyword">const</span> math = <span class="keyword">await</span> loadModule(<span class="string">'math'</span>);
<span class="keyword">if</span> (math) {
console.log(math.add(5, 3));
}
});</pre>
</div>
<h3>Module Organization Patterns</h3>
<div class="code-block">
<pre><span class="comment">// Basic structure of a modern JavaScript application</span>
/src
/components
Button.js <span class="comment">// export default Button</span>
Card.js <span class="comment">// export default Card</span>
index.js <span class="comment">// re-export: export { default as Button } from './Button.js'</span>
<span class="comment">// re-export: export { default as Card } from './Card.js'</span>
/utils
format.js <span class="comment">// export { formatDate, formatCurrency }</span>
api.js <span class="comment">// export default API; export const API_URL</span>
index.js <span class="comment">// re-export utilities</span>
/modules
user.js <span class="comment">// user module</span>
cart.js <span class="comment">// cart module</span>
index.js <span class="comment">// application entry point</span>
<span class="comment">// Using barrel exports with index.js files</span>
<span class="keyword">import</span> { Button, Card } <span class="keyword">from</span> <span class="string">'./components'</span>;
<span class="keyword">import</span> { formatDate, API } <span class="keyword">from</span> <span class="string">'./utils'</span>;</pre>
</div>
</div>
<div class="card">
<h2>Complete Example: Building a Calculator</h2>
<p>This example demonstrates how to build a simple calculator using ES6 modules.</p>
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" onclick="switchTab('structure-tab')">Project Structure</button>
<button class="tab-button" onclick="switchTab('math-module-tab')">Math Module</button>
<button class="tab-button" onclick="switchTab('ui-module-tab')">UI Module</button>
<button class="tab-button" onclick="switchTab('main-tab')">Main Script</button>
<button class="tab-button" onclick="switchTab('html-tab')">HTML</button>
</div>
<div id="structure-tab" class="tab-content active">
<h4>Project Structure</h4>
<pre>
calculator/
├── index.html
├── css/
│ └── style.css
└── js/
├── main.js # Main entry point
├── modules/
│ ├── math.js # Math operations
│ └── ui.js # UI handling
└── utils/
└── formatter.js # Number formatting
</pre>
</div>
<div id="math-module-tab" class="tab-content">
<h4>Math Module (math.js)</h4>
<div class="code-block">
<pre><span class="comment">// js/modules/math.js</span>
<span class="comment">/**
* Basic mathematical operations
*/</span>
<span class="keyword">export function</span> add(a, b) {
<span class="keyword">return</span> a + b;
}
<span class="keyword">export function</span> subtract(a, b) {
<span class="keyword">return</span> a - b;
}
<span class="keyword">export function</span> multiply(a, b) {
<span class="keyword">return</span> a * b;
}
<span class="keyword">export function</span> divide(a, b) {
<span class="keyword">if</span> (b === 0) {
<span class="keyword">throw new</span> Error(<span class="string">"Division by zero"</span>);
}
<span class="keyword">return</span> a / b;
}
<span class="comment">// Used internally</span>
<span class="keyword">function</span> validateNumbers(a, b) {
<span class="keyword">if</span> (isNaN(a) || isNaN(b)) {
<span class="keyword">throw new</span> Error(<span class="string">"Please enter valid numbers"</span>);
}
}
<span class="comment">// Export a function that performs the operation</span>
<span class="keyword">export function</span> calculate(a, b, operation) {
<span class="comment">// Parse string inputs to numbers</span>
a = parseFloat(a);
b = parseFloat(b);
<span class="comment">// Validate inputs</span>
validateNumbers(a, b);
<span class="keyword">switch</span> (operation) {
<span class="keyword">case</span> <span class="string">'add'</span>:
<span class="keyword">return</span> add(a, b);
<span class="keyword">case</span> <span class="string">'subtract'</span>:
<span class="keyword">return</span> subtract(a, b);
<span class="keyword">case</span> <span class="string">'multiply'</span>:
<span class="keyword">return</span> multiply(a, b);
<span class="keyword">case</span> <span class="string">'divide'</span>:
<span class="keyword">return</span> divide(a, b);
<span class="keyword">default</span>:
<span class="keyword">throw new</span> Error(<span class="string">"Invalid operation"</span>);
}
}</pre>
</div>
<h4>Formatter Utility (formatter.js)</h4>
<div class="code-block">
<pre><span class="comment">// js/utils/formatter.js</span>
<span class="comment">/**
* Utility for formatting numbers
*/</span>
<span class="keyword">export function</span> formatNumber(num) {
<span class="comment">// Format number to 2 decimal places</span>
<span class="keyword">return</span> parseFloat(num.toFixed(2)).toString();
}
<span class="keyword">export function</span> formatCurrency(num) {
<span class="keyword">return</span> <span class="string">`$${formatNumber(num)}`</span>;
}
<span class="keyword">export default</span> {
formatNumber,
formatCurrency
};</pre>
</div>
</div>
<div id="ui-module-tab" class="tab-content">
<h4>UI Module (ui.js)</h4>
<div class="code-block">
<pre><span class="comment">// js/modules/ui.js</span>
<span class="comment">/**
* Handle UI interactions for the calculator
*/</span>
<span class="keyword">import</span> { calculate } <span class="keyword">from</span> <span class="string">'./math.js'</span>;
<span class="keyword">import</span> { formatNumber } <span class="keyword">from</span> <span class="string">'../utils/formatter.js'</span>;
<span class="comment">// Private variables for the module</span>
<span class="keyword">let</span> resultElement;
<span class="keyword">let</span> num1Element;
<span class="keyword">let</span> num2Element;
<span class="keyword">let</span> operationElement;
<span class="comment">// Initialize UI elements</span>
<span class="keyword">export function</span> initUI() {
<span class="comment">// Get DOM elements</span>
resultElement = document.getElementById(<span class="string">'result'</span>);
num1Element = document.getElementById(<span class="string">'num1'</span>);
num2Element = document.getElementById(<span class="string">'num2'</span>);
operationElement = document.getElementById(<span class="string">'operation'</span>);
<span class="comment">// Add click event listeners</span>
document.getElementById(<span class="string">'calculate'</span>).addEventListener(<span class="string">'click'</span>, handleCalculation);
document.getElementById(<span class="string">'clear'</span>).addEventListener(<span class="string">'click'</span>, clearInputs);
<span class="comment">// Set default values</span>
clearInputs();
<span class="keyword">return</span> {
updateResult
};
}
<span class="comment">// Handle calculation when the button is clicked</span>
<span class="keyword">function</span> handleCalculation() {
<span class="keyword">try</span> {
<span class="keyword">const</span> num1 = num1Element.value;
<span class="keyword">const</span> num2 = num2Element.value;
<span class="keyword">const</span> operation = operationElement.value;
<span class="comment">// Perform calculation using the math module</span>
<span class="keyword">const</span> result = calculate(num1, num2, operation);
<span class="comment">// Update result with formatted number</span>
updateResult(formatNumber(result));
} <span class="keyword">catch</span> (error) {
updateResult(<span class="string">`Error: ${error.message}`</span>);
}
}
<span class="comment">// Update the result display</span>
<span class="keyword">function</span> updateResult(text) {
resultElement.textContent = text;
}
<span class="comment">// Clear all inputs and reset result</span>
<span class="keyword">function</span> clearInputs() {
num1Element.value = <span class="string">''</span>;
num2Element.value = <span class="string">''</span>;
operationElement.value = <span class="string">'add'</span>;
updateResult(<span class="string">'0'</span>);
}
<span class="comment">// Export the things we want to make public</span>
<span class="keyword">export default</span> {
initUI,
updateResult
};</pre>
</div>
</div>
<div id="main-tab" class="tab-content">
<h4>Main Script (main.js)</h4>
<div class="code-block">
<pre><span class="comment">// js/main.js</span>
<span class="comment">/**
* Main entry point for the calculator application
*/</span>
<span class="keyword">import</span> { calculate } <span class="keyword">from</span> <span class="string">'./modules/math.js'</span>;
<span class="keyword">import</span> UI <span class="keyword">from</span> <span class="string">'./modules/ui.js'</span>;
<span class="comment">// When DOM is fully loaded</span>
document.addEventListener(<span class="string">'DOMContentLoaded'</span>, () => {
console.log(<span class="string">'Calculator app starting...'</span>);
<span class="comment">// Initialize UI</span>
<span class="keyword">const</span> ui = UI.initUI();
<span class="comment">// Example calculation (for debugging)</span>
<span class="keyword">try</span> {
console.log(<span class="string">`5 + 3 = ${calculate(5, 3, 'add')}`</span>);
console.log(<span class="string">`10 - 4 = ${calculate(10, 4, 'subtract')}`</span>);
console.log(<span class="string">`7 * 6 = ${calculate(7, 6, 'multiply')}`</span>);
console.log(<span class="string">`20 / 5 = ${calculate(20, 5, 'divide')}`</span>);
} <span class="keyword">catch</span> (error) {
console.error(<span class="string">`Calculation error: ${error.message}`</span>);
}
});</pre>
</div>
</div>
<div id="html-tab" class="tab-content">
<h4>HTML Structure (index.html)</h4>
<div class="code-block">
<pre><span class="comment"><!-- index.html --></span>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ES6 Module Calculator</title>
<link rel="stylesheet" href="css/style.css">
<span class="comment"><!-- Important: type="module" allows ES6 modules to work --></span>
<script type="module" src="js/main.js"></script>
</head>
<body>
<div class="calculator">
<h1>ES6 Module Calculator</h1>
<div class="result-display">
<div id="result">0</div>
</div>
<div class="inputs">
<div class="input-group">
<label for="num1">First Number:</label>
<input type="number" id="num1" placeholder="Enter first number">
</div>
<div class="input-group">
<label for="operation">Operation:</label>
<select id="operation">
<option value="add">Addition (+)</option>
<option value="subtract">Subtraction (-)</option>
<option value="multiply">Multiplication (*)</option>
<option value="divide">Division (/)</option>
</select>
</div>
<div class="input-group">
<label for="num2">Second Number:</label>
<input type="number" id="num2" placeholder="Enter second number">
</div>
</div>
<div class="buttons">
<button id="calculate">Calculate</button>
<button id="clear">Clear</button>
</div>
<div class="info">
<p>This calculator demonstrates ES6 modules:</p>
<ul>
<li>math.js - Mathematical operations</li>
<li>ui.js - User interface handling</li>
<li>formatter.js - Number formatting</li>
</ul>
</div>
</div>
</body>
</html></pre>
</div>
</div>
</div>
<div class="demo-container">
<h3>Key Benefits of ES6 Modules</h3>
<ul>
<li><strong>Native browser support</strong> - No need for external libraries for module loading</li>
<li><strong>Static imports</strong> - Better static analysis and optimization by tools</li>
<li><strong>Encapsulation</strong> - Private variables and functions stay private to the module</li>
<li><strong>Named and default exports</strong> - Flexible ways to expose functionality</li>
<li><strong>Single instance</strong> - Modules are only evaluated once, even when imported multiple times</li>
<li><strong>Tree shaking</strong> - Bundlers can remove unused exports for smaller bundles</li>
</ul>
</div>
</div>
<div class="card">
<h2>Browser Support and Practical Usage</h2>
<div class="grid">
<div>
<h3>Browser Support</h3>
<p>ES6 modules have excellent browser support as of 2025, but there are some considerations:</p>
<ul>
<li>Modern browsers (Chrome, Firefox, Safari, Edge) all support ES6 modules</li>
<li>The <code><script type="module"></code> attribute is required</li>
<li>Modules are always deferred by default</li>
<li>Modules use strict mode automatically</li>
<li>Modules have their own scope (unlike regular scripts)</li>
<li>CORS must be enabled for modules loaded from different origins</li>
</ul>
</div>
<div>
<h3>Module Bundlers</h3>
<p>For production applications, module bundlers are still commonly used to:</p>
<ul>
<li>Combine multiple modules into fewer files</li>
<li>Implement tree shaking (dead code elimination)</li>
<li>Handle older browser compatibility</li>
<li>Process non-JavaScript assets</li>
<li>Optimize loading performance</li>
</ul>
<p>Popular module bundlers:</p>
<ul>
<li>Webpack</li>
<li>Rollup</li>
<li>Parcel</li>
<li>esbuild</li>
<li>Vite</li>
</ul>
</div>
</div>
<h3>Module Patterns and Best Practices</h3>
<table>
<thead>
<tr>
<th>Pattern</th>
<th>Description</th>
<th>Use Case</th>
</tr>
</thead>
<tbody>
<tr>
<td>Named Exports</td>
<td>Export multiple functions or values by name</td>
<td>Utility libraries, collections of related functions</td>
</tr>
<tr>
<td>Default Export</td>
<td>Export a single main value from the module</td>
<td>Classes, main functions, primary components</td>
</tr>
<tr>
<td>Mixed Exports</td>
<td>Combine default and named exports</td>
<td>A main class with related utilities</td>
</tr>
<tr>
<td>Barrel Files</td>
<td>Re-export from multiple modules in an index.js</td>
<td>Creating convenient public APIs, hiding internal structure</td>
</tr>
<tr>
<td>Facade Pattern</td>
<td>Create a simplified API over complex modules</td>
<td>Exposing only necessary functionality to consumers</td>
</tr>
</tbody>
</table>
<h3>Dependency Management</h3>
<div class="code-block">
<pre><span class="comment">// Use top-level imports for static dependencies</span>
<span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;
<span class="keyword">import</span> { useState, useEffect } <span class="keyword">from</span> <span class="string">'react'</span>;
<span class="comment">// Use dynamic imports for code splitting and conditional loading</span>
<span class="keyword">const</span> loadFeature = <span class="keyword">async</span> (featureName) => {
<span class="keyword">if</span> (featureName === <span class="string">'analytics'</span>) {
<span class="keyword">const</span> { initAnalytics } = <span class="keyword">await import</span>(<span class="string">'./features/analytics.js'</span>);
initAnalytics();
} <span class="keyword">else if</span> (featureName === <span class="string">'editor'</span>) {
<span class="keyword">const</span> { Editor } = <span class="keyword">await import</span>(<span class="string">'./features/editor.js'</span>);
<span class="keyword">return</span> Editor;
}
};
<span class="comment">// Load large features on demand</span>
document.querySelector(<span class="string">'.load-editor'</span>).addEventListener(<span class="string">'click'</span>, <span class="keyword">async</span> () => {
<span class="keyword">const</span> Editor = <span class="keyword">await</span> loadFeature(<span class="string">'editor'</span>);
<span class="keyword">new</span> Editor(<span class="string">'#editor-container'</span>);
});</pre>
</div>
</div>
---css
/* Dark mode by default with light mode via media query */
:root {
--bg-color: #0d1117;
--text-color: #e6edf3;
--card-color: #161b22;
--border-color: #30363d;
--accent-color: #58a6ff;
--button-hover: #1f6feb;
--code-bg: #1e2428;
--comment-color: #8b949e;
--string-color: #a5d6ff;
--variable-color: #ff7b72;
--keyword-color: #ff7b72;
--success-color: #56d364;
--warning-color: #e3b341;
--danger-color: #f85149;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: #f6f8fa;
--text-color: #24292e;
--card-color: #ffffff;
--border-color: #d0d7de;
--accent-color: #0969da;
--button-hover: #0366d6;
--code-bg: #f6f8fa;
--comment-color: #6e7781;
--string-color: #0a3069;
--variable-color: #953800;
--keyword-color: #cf222e;
--success-color: #1a7f37;
--warning-color: #9a6700;
--danger-color: #cf222e;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
max-width: 800px;
margin: 0 auto;
transition: all 0.3s ease;
}
h1,
h2,
h3 {
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
}
.card {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 6px;
overflow-x: auto;
margin: 15px 0;
border: 1px solid var(--border-color);
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
position: relative;
}
.comment {
color: var(--comment-color);
}
.string {
color: var(--string-color);
}
.variable {
color: var(--variable-color);
}
.keyword {
color: var(--keyword-color);
}
.tab-container {
margin-bottom: 15px;
}
.tab-buttons {
display: flex;
margin-bottom: -1px;
}
.tab-button {
padding: 8px 16px;
border: 1px solid var(--border-color);
border-bottom: none;
border-radius: 6px 6px 0 0;
background-color: var(--bg-color);
color: var(--text-color);
cursor: pointer;
font-size: 14px;
margin-right: 5px;
opacity: 0.7;
}
.tab-button.active {
background-color: var(--card-color);
opacity: 1;
font-weight: bold;
}
.tab-content {
border: 1px solid var(--border-color);
border-radius: 0 6px 6px 6px;
padding: 15px;
background-color: var(--card-color);
display: none;
}
.tab-content.active {
display: block;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 700px) {
.grid {
grid-template-columns: 1fr;
}
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th,
td {
padding: 8px 12px;
text-align: left;
border: 1px solid var(--border-color);
}
th {
background-color: rgba(255, 255, 255, 0.05);
}
.timeline {
position: relative;
padding: 20px 0;
}
.timeline::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 20px;
width: 2px;
background-color: var(--border-color);
}
.timeline-item {
position: relative;
margin-bottom: 30px;
padding-left: 50px;
}
.timeline-dot {
position: absolute;
left: 11px;
top: 0;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: var(--accent-color);
}
.timeline-content {
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 6px;
background-color: var(--card-color);
}
.timeline-year {
position: absolute;
left: 50px;
top: -5px;
font-weight: bold;
font-size: 18px;
}
.timeline-title {
margin-top: 15px;
font-weight: bold;
border-bottom: 1px solid var(--border-color);
padding-bottom: 5px;
margin-bottom: 10px;
}
.file {
color: var(--text-color);
font-weight: bold;
margin-bottom: 5px;
}
.file-content {
margin-left: 15px;
margin-bottom: 15px;
}
.demo-container {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 15px;
margin: 20px 0;
}
.demo-output {
background-color: var(--code-bg);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 10px;
margin-top: 15px;
min-height: 50px;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
white-space: pre-wrap;
}
.success {
color: var(--success-color);
}
.warning {
color: var(--warning-color);
}
.danger {
color: var(--danger-color);
}
---js
function switchTab(tabId) {
// Hide all tab contents
const tabContents = document.querySelectorAll(".tab-content");
tabContents.forEach(tab => {
tab.classList.remove("active");
});
// Remove active class from all tab buttons
const tabButtons = document.querySelectorAll(".tab-button");
tabButtons.forEach(button => {
button.classList.remove("active");
});
// Show the selected tab content and mark its button as active
document.getElementById(tabId).classList.add("active");
event.currentTarget.classList.add("active");
}
// Simple module example to demonstrate how ES6 modules work
document.addEventListener("DOMContentLoaded", () => {
console.log("Module example loaded");
// Create buttons to demonstrate the module pattern evolution
const createDemoBtn = (id, label, handler) => {
const btn = document.createElement("button");
btn.id = id;
btn.textContent = label;
btn.addEventListener("click", handler);
return btn;
};
// Create a simple demo area
if (!document.querySelector(".demo-output")) {
const demoContainer = document.querySelector(".demo-container");
if (demoContainer) {
// Create output area
const outputDiv = document.createElement("div");
outputDiv.className = "demo-output";
outputDiv.textContent = "Module demo output will appear here...";
// Create buttons
const globalBtn = createDemoBtn("globalBtn", "Global Variables", () => {
outputDiv.textContent = "Using global variables (pre-modules):\n";
// Simulate global variables pattern
const userName = "Alice";
const userAge = 30;
outputDiv.textContent += `User: ${userName}, Age: ${userAge}\n`;
outputDiv.textContent +=
"Problem: Variables are in global scope and can be overwritten!";
});
const iifeBtn = createDemoBtn("iifeBtn", "IIFE Pattern", () => {
outputDiv.textContent = "Using IIFE pattern:\n";
// Simulate IIFE pattern
const userModule = function () {
// Private variables
const userName = "Alice";
const userAge = 30;
// Public API
return {
getName: function () {
return userName;
},
getAge: function () {
return userAge;
} };
}();
outputDiv.textContent += `User: ${userModule.getName()}, Age: ${userModule.getAge()}\n`;
outputDiv.textContent +=
"Better: Variables are encapsulated but still requires manual pattern setup.";
});
const es6Btn = createDemoBtn("es6Btn", "ES6 Module (simulated)", () => {
outputDiv.textContent = "Using ES6 modules (simulated):\n";
// Simulate ES6 module pattern
const simulatedModule = {
// Exported functions
getName() {
return "Alice";
},
getAge() {
return 30;
} };
outputDiv.textContent += `User: ${simulatedModule.getName()}, Age: ${simulatedModule.getAge()}\n`;
outputDiv.textContent +=
"Best: Native language support for encapsulation and explicit exports.";
});
// Add buttons and output to demo container
const buttonContainer = document.createElement("div");
buttonContainer.style.marginTop = "20px";
buttonContainer.appendChild(globalBtn);
buttonContainer.appendChild(iifeBtn);
buttonContainer.appendChild(es6Btn);
demoContainer.appendChild(document.createElement("hr"));
demoContainer.appendChild(document.createElement("h3")).textContent =
"Interactive Demo";
demoContainer.appendChild(buttonContainer);
demoContainer.appendChild(outputDiv);
}
}
});
Objects are fundamental to JavaScript, and working with them has evolved dramatically over time. Pre-ES6, tasks like copying objects, extracting properties, or safely accessing nested values required verbose, error-prone approaches. Modern JavaScript has introduced powerful features like the spread operator, destructuring, and optional chaining that change how we work with objects. This example demonstrates how these newer features simplify common object operations while making code more readable and safer. Experiment with different patterns to see how object handling has evolved from ES5 to ES11.
---html
<h1>The Evolution of Object Manipulation in JavaScript</h1>
<div class="card">
<h2>Object Creation and Manipulation</h2>
<div class="grid">
<div>
<h3>ES5 Object Handling</h3>
<div class="code-block">
<pre><span class="comment">// ES5 - Creating objects</span>
<span class="keyword">var</span> person = {
firstName: <span class="string">'John'</span>,
lastName: <span class="string">'Doe'</span>,
age: 30
};
<span class="comment">// Adding properties</span>
person.email = <span class="string">'john.doe@example.com'</span>;
<span class="comment">// Copying objects (shallow)</span>
<span class="keyword">function</span> cloneObject(obj) {
<span class="keyword">var</span> copy = {};
<span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> obj) {
<span class="keyword">if</span> (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
<span class="keyword">return</span> copy;
}
<span class="comment">// Getting object keys</span>
<span class="keyword">var</span> keys = [];
<span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> person) {
<span class="keyword">if</span> (person.hasOwnProperty(key)) {
keys.push(key);
}
}
<span class="comment">// Checking if property exists</span>
<span class="keyword">var</span> hasEmail = person.hasOwnProperty(<span class="string">'email'</span>);</pre>
</div>
</div>
<div>
<h3>ES6+ Object Handling</h3>
<div class="code-block">
<pre><span class="comment">// ES6+ - Creating objects</span>
<span class="keyword">const</span> person = {
firstName: <span class="string">'John'</span>,
lastName: <span class="string">'Doe'</span>,
age: 30
};
<span class="comment">// Adding properties</span>
<span class="keyword">const</span> updatedPerson = { ...person, email: <span class="string">'john.doe@example.com'</span> };
<span class="comment">// Copying objects (shallow)</span>
<span class="keyword">const</span> clone = { ...person };
<span class="comment">// Or</span>
<span class="keyword">const</span> anotherClone = Object.assign({}, person);
<span class="comment">// Getting object keys</span>
<span class="keyword">const</span> keys = Object.keys(person);
<span class="comment">// Checking if property exists</span>
<span class="keyword">const</span> hasEmail = <span class="string">'email'</span> in person;</pre>
</div>
</div>
</div>
<textarea id="objectData" rows="5" placeholder="Enter JSON object data to experiment with">
{
"firstName": "John",
"lastName": "Doe",
"age": 30,
"skills": ["JavaScript", "HTML", "CSS"],
"address": {
"city": "New York",
"state": "NY",
"zip": "10001"
}
}
</textarea>
<button id="cloneES5">Clone with ES5</button>
<button id="cloneES6">Clone with ES6</button>
<button id="listKeys">Get Object Keys</button>
<button id="checkProp">Check for Property</button>
<input type="text" id="propName" placeholder="Enter property name to check" value="address">
<div class="result" id="objectResult"></div>
</div>
<div class="card">
<h2>Object Methods</h2>
<table>
<thead>
<tr>
<th>Method</th>
<th>Description</th>
<th>Introduced</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Object.keys()</code></td>
<td>Returns an array of a given object's own enumerable property names</td>
<td>ES5</td>
</tr>
<tr>
<td><code>Object.values()</code></td>
<td>Returns an array of a given object's own enumerable property values</td>
<td>ES8 (ES2017)</td>
</tr>
<tr>
<td><code>Object.entries()</code></td>
<td>Returns an array of a given object's own enumerable property [key, value] pairs</td>
<td>ES8 (ES2017)</td>
</tr>
<tr>
<td><code>Object.assign()</code></td>
<td>Copies all enumerable own properties from one or more source objects to a target object</td>
<td>ES6 (ES2015)</td>
</tr>
<tr>
<td><code>Object.fromEntries()</code></td>
<td>Transforms a list of key-value pairs into an object</td>
<td>ES10 (ES2019)</td>
</tr>
</tbody>
</table>
<h3>Interactive Example</h3>
<div class="code-block">
<pre><span class="comment">// Example object</span>
<span class="keyword">const</span> product = {
name: <span class="string">'Laptop'</span>,
price: 999.99,
inStock: true,
specs: {
cpu: <span class="string">'i7'</span>,
ram: <span class="string">'16GB'</span>,
storage: <span class="string">'512GB SSD'</span>
}
};
<span class="comment">// Object.keys() - ES5</span>
<span class="keyword">const</span> keys = Object.keys(product);
console.log(keys); <span class="comment">// ['name', 'price', 'inStock', 'specs']</span>
<span class="comment">// Object.values() - ES8 (ES2017)</span>
<span class="keyword">const</span> values = Object.values(product);
console.log(values); <span class="comment">// ['Laptop', 999.99, true, {cpu: 'i7', ram: '16GB', storage: '512GB SSD'}]</span>
<span class="comment">// Object.entries() - ES8 (ES2017)</span>
<span class="keyword">const</span> entries = Object.entries(product);
console.log(entries); <span class="comment">// [['name', 'Laptop'], ['price', 999.99], ...]</span>
<span class="comment">// Object.fromEntries() - ES10 (ES2019)</span>
<span class="keyword">const</span> modifiedEntries = entries.map(([key, value]) => {
<span class="keyword">if</span> (key === <span class="string">'price'</span>) {
<span class="keyword">return</span> [key, value * 0.9]; <span class="comment">// 10% discount</span>
}
<span class="keyword">return</span> [key, value];
});
<span class="keyword">const</span> discountedProduct = Object.fromEntries(modifiedEntries);
console.log(discountedProduct);</pre>
</div>
<button id="runKeys">Run Object.keys()</button>
<button id="runValues">Run Object.values()</button>
<button id="runEntries">Run Object.entries()</button>
<button id="runFromEntries">Run Object.fromEntries()</button>
<div class="result" id="methodsResult"></div>
</div>
<div class="card">
<h2>Object Destructuring</h2>
<div class="grid">
<div>
<h3>ES5 Property Access</h3>
<div class="code-block">
<pre><span class="comment">// ES5 way to access properties</span>
<span class="keyword">var</span> user = {
name: <span class="string">'Alice'</span>,
role: <span class="string">'Admin'</span>,
permissions: {
read: true,
write: true,
delete: false
}
};
<span class="comment">// Extracting values</span>
<span class="keyword">var</span> name = user.name;
<span class="keyword">var</span> role = user.role;
<span class="keyword">var</span> canWrite = user.permissions.write;
<span class="comment">// Function parameters</span>
<span class="keyword">function</span> displayUser(user) {
<span class="keyword">var</span> name = user.name;
<span class="keyword">var</span> role = user.role;
<span class="keyword">return</span> name + <span class="string">' ('</span> + role + <span class="string">')'</span>;
}</pre>
</div>
</div>
<div>
<h3>ES6 Destructuring</h3>
<div class="code-block">
<pre><span class="comment">// ES6 object destructuring</span>
<span class="keyword">const</span> user = {
name: <span class="string">'Alice'</span>,
role: <span class="string">'Admin'</span>,
permissions: {
read: true,
write: true,
delete: false
}
};
<span class="comment">// Extracting values</span>
<span class="keyword">const</span> { name, role } = user;
<span class="keyword">const</span> { permissions: { write: canWrite } } = user;
<span class="comment">// Function parameters</span>
<span class="keyword">function</span> displayUser({ name, role }) {
<span class="keyword">return</span> `${name} (${role})`;
}
<span class="comment">// With default values</span>
<span class="keyword">const</span> { age = 30 } = user; <span class="comment">// age gets default value if not in user</span>
<span class="comment">// Renaming variables</span>
<span class="keyword">const</span> { name: userName, role: userRole } = user;</pre>
</div>
</div>
</div>
<textarea id="userData" rows="7" placeholder="Enter JSON user data to experiment with">
{
"name": "Alice Smith",
"role": "Admin",
"permissions": {
"read": true,
"write": true,
"delete": false
},
"department": "Engineering"
}
</textarea>
<button id="extractES5">Extract with ES5</button>
<button id="extractES6">Extract with ES6</button>
<button id="nestedExtracting">Nested Destructuring</button>
<button id="defaultParams">Destructuring with Defaults</button>
<div class="result" id="destructuringResult"></div>
</div>
<div class="card">
<h2>ES11 Optional Chaining & Nullish Coalescing</h2>
<div class="grid">
<div>
<h3>ES5 Defensive Coding</h3>
<div class="code-block">
<pre><span class="comment">// ES5 - Defensive property access</span>
<span class="keyword">function</span> getUserCity(user) {
<span class="keyword">var</span> city = <span class="string">'Unknown'</span>;
<span class="keyword">if</span> (user &&
user.address &&
user.address.city) {
city = user.address.city;
}
<span class="keyword">return</span> <span class="string">'City: '</span> + city;
}
<span class="comment">// ES5 - Default values</span>
<span class="keyword">function</span> getUserPreferences(user) {
<span class="keyword">var</span> theme = (user && user.preferences &&
user.preferences.theme) || <span class="string">'default'</span>;
<span class="comment">// Problem: '' or 0 would also fall back to default</span>
<span class="keyword">var</span> fontSize = (user && user.preferences &&
user.preferences.fontSize) || 16;
<span class="keyword">return</span> {
theme: theme,
fontSize: fontSize
};
}</pre>
</div>
</div>
<div>
<h3>ES11 Optional Chaining & Nullish Coalescing</h3>
<div class="code-block">
<pre><span class="comment">// ES11 - Optional Chaining</span>
<span class="keyword">function</span> getUserCity(user) {
<span class="keyword">const</span> city = user?.address?.city ?? <span class="string">'Unknown'</span>;
<span class="keyword">return</span> `City: ${city}`;
}
<span class="comment">// ES11 - Nullish Coalescing</span>
<span class="keyword">function</span> getUserPreferences(user) {
<span class="comment">// Only falls back if specifically null/undefined</span>
<span class="keyword">const</span> theme = user?.preferences?.theme ?? <span class="string">'default'</span>;
<span class="keyword">const</span> fontSize = user?.preferences?.fontSize ?? 16;
<span class="comment">// These will preserve '' and 0 as valid values</span>
<span class="keyword">return</span> {
theme,
fontSize
};
}</pre>
</div>
</div>
</div>
<textarea id="nestedUserData" rows="10" placeholder="Enter JSON user data with nested properties">
{
"name": "Bob Johnson",
"address": {
"street": "123 Main St",
"city": "Chicago",
"state": "IL",
"zip": "60601"
},
"preferences": {
"theme": "dark",
"fontSize": 14,
"notifications": true
}
}
</textarea>
<button id="es5Defensive">ES5 Defensive Access</button>
<button id="es11OptionalChaining">ES11 Optional Chaining</button>
<button id="es5DefaultValues">ES5 Default Values</button>
<button id="es11NullishCoalescing">ES11 Nullish Coalescing</button>
<button id="testEdgeCases">Test Edge Cases</button>
<div class="result" id="optionalChainingResult"></div>
</div>
---css
/* Dark mode by default with light mode via media query */
:root {
--bg-color: #0d1117;
--text-color: #e6edf3;
--card-color: #161b22;
--border-color: #30363d;
--accent-color: #58a6ff;
--button-hover: #1f6feb;
--code-bg: #1e2428;
--comment-color: #8b949e;
--string-color: #a5d6ff;
--variable-color: #ff7b72;
--keyword-color: #ff7b72;
--success-color: #56d364;
--warning-color: #e3b341;
--danger-color: #f85149;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: #f6f8fa;
--text-color: #24292e;
--card-color: #ffffff;
--border-color: #d0d7de;
--accent-color: #0969da;
--button-hover: #0366d6;
--code-bg: #f6f8fa;
--comment-color: #6e7781;
--string-color: #0a3069;
--variable-color: #953800;
--keyword-color: #cf222e;
--success-color: #1a7f37;
--warning-color: #9a6700;
--danger-color: #cf222e;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
max-width: 800px;
margin: 0 auto;
transition: all 0.3s ease;
}
h1, h2, h3 {
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
}
.card {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 6px;
overflow-x: auto;
margin: 15px 0;
border: 1px solid var(--border-color);
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
position: relative;
}
.comment {
color: var(--comment-color);
}
.string {
color: var(--string-color);
}
.variable {
color: var(--variable-color);
}
.keyword {
color: var(--keyword-color);
}
button {
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
}
button:hover {
background-color: var(--button-hover);
}
.result {
margin-top: 15px;
padding: 15px;
background-color: var(--code-bg);
border-radius: 6px;
border: 1px solid var(--border-color);
white-space: pre-wrap;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
max-height: 200px;
overflow-y: auto;
}
.success {
color: var(--success-color);
}
.warning {
color: var(--warning-color);
}
.danger {
color: var(--danger-color);
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 700px) {
.grid {
grid-template-columns: 1fr;
}
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 8px 12px;
text-align: left;
border: 1px solid var(--border-color);
}
th {
background-color: rgba(255, 255, 255, 0.05);
}
textarea, input {
background-color: var(--code-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 8px;
width: 100%;
font-family: inherit;
}
textarea {
min-height: 100px;
resize: vertical;
}
---js
// Object Creation and Manipulation
document.getElementById('cloneES5').addEventListener('click', function () {
const resultDiv = document.getElementById('objectResult');
const objectData = getObjectData();
if (!objectData) return;
try {
// ES5 Clone
function cloneObjectES5(obj) {
var copy = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
return copy;
}
const clone = cloneObjectES5(objectData);
resultDiv.innerHTML = `<span class="success">✓ ES5 Cloning Successful</span>\n\n${formatObject(clone)}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('cloneES6').addEventListener('click', function () {
const resultDiv = document.getElementById('objectResult');
const objectData = getObjectData();
if (!objectData) return;
try {
// ES6 Clone
const clone = { ...objectData };
resultDiv.innerHTML = `<span class="success">✓ ES6 Spread Operator Cloning Successful</span>\n\n${formatObject(clone)}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('listKeys').addEventListener('click', function () {
const resultDiv = document.getElementById('objectResult');
const objectData = getObjectData();
if (!objectData) return;
try {
// ES5 way
let es5Keys = [];
for (let key in objectData) {
if (objectData.hasOwnProperty(key)) {
es5Keys.push(key);
}
}
// ES6 way
const es6Keys = Object.keys(objectData);
resultDiv.innerHTML = `ES5 Keys: ${JSON.stringify(es5Keys)}\n\nES6 Keys: ${JSON.stringify(es6Keys)}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('checkProp').addEventListener('click', function () {
const resultDiv = document.getElementById('objectResult');
const objectData = getObjectData();
const propName = document.getElementById('propName').value;
if (!objectData) return;
try {
// ES5 way
const es5HasProp = objectData.hasOwnProperty(propName);
// ES6+ way
const es6HasProp = (propName in objectData);
resultDiv.innerHTML = `Checking for property "${propName}":\n\nES5 (hasOwnProperty): ${es5HasProp}\nES6+ (in operator): ${es6HasProp}`;
if (es5HasProp) {
resultDiv.innerHTML += `\n\nValue: ${formatObject(objectData[propName])}`;
}
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Object Methods
document.getElementById('runKeys').addEventListener('click', function () {
const resultDiv = document.getElementById('methodsResult');
const objectData = getObjectData();
if (!objectData) return;
try {
const keys = Object.keys(objectData);
resultDiv.innerHTML = `<span class="success">Object.keys() (ES5)</span>\n\n${JSON.stringify(keys, null, 2)}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('runValues').addEventListener('click', function () {
const resultDiv = document.getElementById('methodsResult');
const objectData = getObjectData();
if (!objectData) return;
try {
// Check if Object.values is available
if (typeof Object.values !== 'function') {
resultDiv.innerHTML = `<span class="warning">⚠ Object.values() is not available in your browser (ES8/ES2017 feature)</span>`;
return;
}
const values = Object.values(objectData);
resultDiv.innerHTML = `<span class="success">Object.values() (ES8/ES2017)</span>\n\n${formatObject(values)}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('runEntries').addEventListener('click', function () {
const resultDiv = document.getElementById('methodsResult');
const objectData = getObjectData();
if (!objectData) return;
try {
// Check if Object.entries is available
if (typeof Object.entries !== 'function') {
resultDiv.innerHTML = `<span class="warning">⚠ Object.entries() is not available in your browser (ES8/ES2017 feature)</span>`;
return;
}
const entries = Object.entries(objectData);
resultDiv.innerHTML = `<span class="success">Object.entries() (ES8/ES2017)</span>\n\n${formatObject(entries)}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('runFromEntries').addEventListener('click', function () {
const resultDiv = document.getElementById('methodsResult');
const objectData = getObjectData();
if (!objectData) return;
try {
// Check if Object.entries and fromEntries are available
if (typeof Object.entries !== 'function' || typeof Object.fromEntries !== 'function') {
resultDiv.innerHTML = `<span class="warning">⚠ Object.fromEntries() is not available in your browser (ES10/ES2019 feature)</span>`;
return;
}
const entries = Object.entries(objectData);
// Transform some entries
const modifiedEntries = entries.map(([key, value]) => {
// If it's a number, double it
if (typeof value === 'number') {
return [key, value * 2];
}
// If it's a string, uppercase it
if (typeof value === 'string') {
return [key, value.toUpperCase()];
}
return [key, value];
});
const newObject = Object.fromEntries(modifiedEntries);
resultDiv.innerHTML = `<span class="success">Object.fromEntries() (ES10/ES2019)</span>\n\nOriginal object:\n${formatObject(objectData)}\n\nModified object:\n${formatObject(newObject)}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Object Destructuring
document.getElementById('extractES5').addEventListener('click', function () {
const resultDiv = document.getElementById('destructuringResult');
const userData = getUserData();
if (!userData) return;
try {
// ES5 way to extract properties
var name = userData.name;
var role = userData.role;
var department = userData.department;
resultDiv.innerHTML = `<span class="success">ES5 Property Access</span>\n\nname: ${name}\nrole: ${role}\ndepartment: ${department}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('extractES6').addEventListener('click', function () {
const resultDiv = document.getElementById('destructuringResult');
const userData = getUserData();
if (!userData) return;
try {
// ES6 destructuring
const { name, role, department } = userData;
resultDiv.innerHTML = `<span class="success">ES6 Destructuring</span>\n\nname: ${name}\nrole: ${role}\ndepartment: ${department}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('nestedExtracting').addEventListener('click', function () {
const resultDiv = document.getElementById('destructuringResult');
const userData = getUserData();
if (!userData) return;
try {
// ES5 nested property access
var es5Read = userData.permissions && userData.permissions.read;
var es5Write = userData.permissions && userData.permissions.write;
var es5Delete = userData.permissions && userData.permissions.delete;
// ES6 nested destructuring
const {
permissions: {
read,
write,
delete: canDelete // rename because 'delete' is a reserved word
} } =
userData;
resultDiv.innerHTML = `<span class="success">ES6 Nested Destructuring</span>\n\nES5 way:\nread: ${es5Read}\nwrite: ${es5Write}\ndelete: ${es5Delete}\n\nES6 way:\nread: ${read}\nwrite: ${write}\ndelete (renamed to canDelete): ${canDelete}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('defaultParams').addEventListener('click', function () {
const resultDiv = document.getElementById('destructuringResult');
const userData = getUserData();
if (!userData) return;
try {
// ES6 destructuring with defaults
const {
name,
role,
age = 30,
location = 'Remote',
preferences = { theme: 'light' } } =
userData;
resultDiv.innerHTML = `<span class="success">ES6 Destructuring with Default Values</span>\n\nname: ${name}\nrole: ${role}\nage (default value): ${age}\nlocation (default value): ${location}\npreferences.theme (default object): ${preferences.theme}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Optional Chaining & Nullish Coalescing
document.getElementById('es5Defensive').addEventListener('click', function () {
const resultDiv = document.getElementById('optionalChainingResult');
const userData = getNestedUserData();
try {
// ES5 defensive coding
function getUserCity(user) {
var city = 'Unknown';
if (user &&
user.address &&
user.address.city) {
city = user.address.city;
}
return 'City: ' + city;
}
const result = getUserCity(userData);
resultDiv.innerHTML = `<span class="success">ES5 Defensive Property Access</span>\n\n${result}`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('es11OptionalChaining').addEventListener('click', function () {
const resultDiv = document.getElementById('optionalChainingResult');
const userData = getNestedUserData();
try {
// Check if optional chaining is available
try {
// Test if optional chaining is supported
eval('const test = {}?.prop');
// ES11 optional chaining
function getUserCity(user) {
const city = user?.address?.city ?? 'Unknown';
return `City: ${city}`;
}
const result = getUserCity(userData);
resultDiv.innerHTML = `<span class="success">ES11 Optional Chaining</span>\n\n${result}`;
} catch (error) {
resultDiv.innerHTML = `<span class="warning">⚠ Optional chaining (?.) is not available in your browser (ES11/ES2020 feature)</span>`;
}
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('es5DefaultValues').addEventListener('click', function () {
const resultDiv = document.getElementById('optionalChainingResult');
const userData = getNestedUserData();
try {
// ES5 default values with ||
function getUserPreferences(user) {
var theme = user && user.preferences &&
user.preferences.theme || 'default';
// Problem: '' or 0 would also fall back to default
var fontSize = user && user.preferences &&
user.preferences.fontSize || 16;
return {
theme: theme,
fontSize: fontSize };
}
const result = getUserPreferences(userData);
resultDiv.innerHTML = `<span class="success">ES5 Default Values with || (Logical OR)</span>\n\nTheme: ${result.theme}\nFont Size: ${result.fontSize}\n\n<span class="warning">⚠ Note: The || operator also treats '' (empty string), 0, false, NaN as falsy values that trigger the default</span>`;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('es11NullishCoalescing').addEventListener('click', function () {
const resultDiv = document.getElementById('optionalChainingResult');
const userData = getNestedUserData();
try {
// Check if nullish coalescing is available
try {
// Test if nullish coalescing is supported
eval('const test = null ?? "default"');
// ES11 nullish coalescing
function getUserPreferences(user) {
const theme = user?.preferences?.theme ?? 'default';
const fontSize = user?.preferences?.fontSize ?? 16;
return {
theme,
fontSize };
}
const result = getUserPreferences(userData);
resultDiv.innerHTML = `<span class="success">ES11 Nullish Coalescing (??)</span>\n\nTheme: ${result.theme}\nFont Size: ${result.fontSize}\n\n<span class="success">✓ Note: The ?? operator only uses the default for null/undefined, preserving other falsy values like '' and 0</span>`;
} catch (error) {
resultDiv.innerHTML = `<span class="warning">⚠ Nullish coalescing (??) is not available in your browser (ES11/ES2020 feature)</span>`;
}
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById('testEdgeCases').addEventListener('click', function () {
const resultDiv = document.getElementById('optionalChainingResult');
try {
// Create a test object with some edge cases
const testObj = {
emptyString: '',
zero: 0,
nullValue: null,
undefinedValue: undefined,
falseValue: false };
// ES5 vs ES11 comparison
let output = '<span class="success">Comparing || vs ?? with falsy values</span>\n\n';
// Test each value
for (const [key, value] of Object.entries(testObj)) {
const orResult = value || `default (${key})`;
// Use try-catch for nullish coalescing to handle browser compatibility
let nullishResult;
try {
nullishResult = eval(`${key === 'undefinedValue' ? 'undefined' : JSON.stringify(value)} ?? "default (${key})"`);
} catch (e) {
nullishResult = '(Not supported in this browser)';
}
output += `${key}:\n Original value: ${value === undefined ? 'undefined' : value === null ? 'null' : JSON.stringify(value)}\n With || (OR): ${orResult}\n With ?? (Nullish): ${nullishResult}\n\n`;
}
resultDiv.innerHTML = output;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Helper functions
function getObjectData() {
const objectDataText = document.getElementById('objectData').value.trim();
try {
return JSON.parse(objectDataText);
} catch (error) {
document.getElementById('objectResult').innerHTML = `<span class="danger">✗ Invalid JSON: ${error.message}</span>`;
return null;
}
}
function getUserData() {
const userDataText = document.getElementById('userData').value.trim();
try {
return JSON.parse(userDataText);
} catch (error) {
document.getElementById('destructuringResult').innerHTML = `<span class="danger">✗ Invalid JSON: ${error.message}</span>`;
return null;
}
}
function getNestedUserData() {
const userDataText = document.getElementById('nestedUserData').value.trim();
try {
return JSON.parse(userDataText);
} catch (error) {
document.getElementById('optionalChainingResult').innerHTML = `<span class="danger">✗ Invalid JSON: ${error.message}</span>`;
return null;
}
}
function formatObject(obj) {
return JSON.stringify(obj, null, 2);
}
Array manipulation has undergone a profound transformation with the introduction of functional programming patterns in JavaScript. Where developers once relied on verbose for loops and temporary variables, modern JavaScript offers expressive, chainable methods that make code more declarative and maintainable. This example walks through the progression from traditional loops to methods like map(), filter(), and reduce(), to newer features like flatMap() and array destructuring. Try each example to experience how modern array methods can dramatically reduce code complexity while increasing readability.
---html
<h1>The Evolution of Array Methods in JavaScript</h1>
<div class="card">
<h2>Timeline of Array Methods</h2>
<table>
<thead>
<tr>
<th>ECMAScript Version</th>
<th>Year</th>
<th>Added Array Methods</th>
</tr>
</thead>
<tbody>
<tr>
<td>ES5</td>
<td>2009</td>
<td>
<code>forEach()</code>, <code>map()</code>, <code>filter()</code>, <code>reduce()</code>,
<code>reduceRight()</code>, <code>every()</code>, <code>some()</code>, <code>indexOf()</code>,
<code>lastIndexOf()</code>
</td>
</tr>
<tr>
<td>ES6 (ES2015)</td>
<td>2015</td>
<td>
<code>find()</code>, <code>findIndex()</code>, <code>from()</code>, <code>of()</code>,
<code>keys()</code>, <code>values()</code>, <code>entries()</code>, <code>copyWithin()</code>,
<code>fill()</code>
</td>
</tr>
<tr>
<td>ES7 (ES2016)</td>
<td>2016</td>
<td><code>includes()</code></td>
</tr>
<tr>
<td>ES10 (ES2019)</td>
<td>2019</td>
<td><code>flat()</code>, <code>flatMap()</code></td>
</tr>
<tr>
<td>ES11 (ES2020)</td>
<td>2020</td>
<td>Getting and setting with <code>??</code> (nullish coalescing)</td>
</tr>
<tr>
<td>ES12+ (ES2021+)</td>
<td>2021+</td>
<td><code>at()</code>, <code>findLast()</code>, <code>findLastIndex()</code></td>
</tr>
</tbody>
</table>
</div>
<div class="card">
<h2>From Loops to Functional Programming</h2>
<div class="grid">
<div>
<h3>Traditional Loops (Pre-ES5)</h3>
<div class="code-block">
<pre><span class="comment">// Working with arrays before ES5</span>
<span class="keyword">var</span> numbers = [1, 2, 3, 4, 5];
<span class="comment">// Transforming array (similar to map)</span>
<span class="keyword">var</span> doubled = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
<span class="comment">// Filtering array</span>
<span class="keyword">var</span> evens = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < numbers.length; i++) {
<span class="keyword">if</span> (numbers[i] % 2 === 0) {
evens.push(numbers[i]);
}
}
<span class="comment">// Checking if value exists</span>
<span class="keyword">function</span> includes(array, value) {
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < array.length; i++) {
<span class="keyword">if</span> (array[i] === value) {
<span class="keyword">return</span> true;
}
}
<span class="keyword">return</span> false;
}
<span class="comment">// Summing array values</span>
<span class="keyword">var</span> sum = 0;
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < numbers.length; i++) {
sum += numbers[i];
}</pre>
</div>
</div>
<div>
<h3>Functional Array Methods (ES5+)</h3>
<div class="code-block">
<pre><span class="comment">// Working with arrays using ES5+ methods</span>
<span class="keyword">const</span> numbers = [1, 2, 3, 4, 5];
<span class="comment">// Transforming array with map</span>
<span class="keyword">const</span> doubled = numbers.map(n => n * 2);
<span class="comment">// Filtering array</span>
<span class="keyword">const</span> evens = numbers.filter(n => n % 2 === 0);
<span class="comment">// Checking if value exists</span>
<span class="keyword">const</span> hasThree = numbers.includes(3); <span class="comment">// ES7</span>
<span class="comment">// Or for ES5:</span>
<span class="keyword">const</span> hasThreeES5 = numbers.indexOf(3) !== -1;
<span class="comment">// Summing array values</span>
<span class="keyword">const</span> sum = numbers.reduce((acc, n) => acc + n, 0);
<span class="comment">// Multiple operations in a chain</span>
<span class="keyword">const</span> sumOfDoubledEvens = numbers
.filter(n => n % 2 === 0) <span class="comment">// Get even numbers</span>
.map(n => n * 2) <span class="comment">// Double them</span>
.reduce((acc, n) => acc + n, 0); <span class="comment">// Sum</span></pre>
</div>
</div>
</div>
<textarea id="arrayData" rows="3" placeholder="Enter comma-separated numbers to experiment with">1, 2, 3, 4, 5, 6, 7, 8, 9, 10</textarea>
<button id="runTraditional">Run Traditional For Loops</button>
<button id="runFunctional">Run Functional Methods</button>
<button id="runChain">Run Method Chain</button>
<div class="result" id="loopsResult"></div>
</div>
<div class="card">
<h2>Finding Array Elements</h2>
<div class="grid">
<div>
<h3>ES5 Methods</h3>
<div class="code-block">
<pre><span class="comment">// ES5 methods for finding elements</span>
<span class="keyword">var</span> fruits = ['apple', 'banana', 'orange', 'apple'];
<span class="comment">// Finding index of an element</span>
<span class="keyword">var</span> index = fruits.indexOf('banana'); <span class="comment">// 1</span>
<span class="keyword">var</span> lastIndex = fruits.lastIndexOf('apple'); <span class="comment">// 3</span>
<span class="comment">// Checking existence</span>
<span class="keyword">var</span> hasOrange = fruits.indexOf('orange') !== -1; <span class="comment">// true</span>
<span class="comment">// Finding with a condition (workaround)</span>
<span class="keyword">var</span> findLongFruit = <span class="keyword">function</span>(array) {
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < array.length; i++) {
<span class="keyword">if</span> (array[i].length > 5) {
<span class="keyword">return</span> array[i];
}
}
<span class="keyword">return</span> undefined;
};
<span class="keyword">var</span> longFruit = findLongFruit(fruits);</pre>
</div>
</div>
<div>
<h3>ES6+ Methods</h3>
<div class="code-block">
<pre><span class="comment">// ES6+ methods for finding elements</span>
<span class="keyword">const</span> fruits = ['apple', 'banana', 'orange', 'apple'];
<span class="comment">// Finding by value</span>
<span class="keyword">const</span> hasOrange = fruits.includes('orange'); <span class="comment">// ES7</span>
<span class="comment">// Finding with a condition</span>
<span class="keyword">const</span> longFruit = fruits.find(f => f.length > 5); <span class="comment">// ES6</span>
<span class="keyword">const</span> longFruitIndex = fruits.findIndex(f => f.length > 5); <span class="comment">// ES6</span>
<span class="comment">// Finding the last matching element</span>
<span class="keyword">const</span> lastLongFruit = fruits.findLast(f => f.length > 5); <span class="comment">// ES2023</span>
<span class="keyword">const</span> lastLongIndex = fruits.findLastIndex(f => f.length > 5); <span class="comment">// ES2023</span>
<span class="comment">// Getting element at a specific index (positive or negative)</span>
<span class="keyword">const</span> lastFruit = fruits.at(-1); <span class="comment">// Same as fruits[fruits.length-1]</span></pre>
</div>
</div>
</div>
<textarea id="fruitsArray" rows="3" placeholder="Enter comma-separated fruits to experiment with">apple, banana, orange, mango, strawberry, blueberry, kiwi</textarea>
<div class="grid" style="grid-template-columns: 2fr 1fr;">
<div>
<input type="text" id="searchFruit" placeholder="Fruit to search for" value="banana">
</div>
<div>
<input type="number" id="minLength" placeholder="Min length for find()" value="6">
</div>
</div>
<button id="runES5Find">Run ES5 Finding Methods</button>
<button id="runES6Find">Run ES6+ Finding Methods</button>
<button id="runES2023Find">Try ES2023 Methods</button>
<div class="result" id="findResult"></div>
</div>
<div class="card">
<h2>Array Creation and Manipulation</h2>
<div class="grid">
<div>
<h3>ES5 and Earlier</h3>
<div class="code-block">
<pre><span class="comment">// Creating arrays in ES5</span>
<span class="keyword">var</span> numbers = [1, 2, 3];
<span class="comment">// Array from string</span>
<span class="keyword">var</span> chars = 'hello'.split('');
<span class="comment">// Array from array-like object</span>
<span class="keyword">function</span> getArgs() {
<span class="keyword">var</span> args = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
<span class="keyword">return</span> args;
}
<span class="comment">// Creating sequence</span>
<span class="keyword">function</span> range(start, end) {
<span class="keyword">var</span> result = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = start; i <= end; i++) {
result.push(i);
}
<span class="keyword">return</span> result;
}
<span class="comment">// Flattening nested arrays</span>
<span class="keyword">function</span> flatten(arrays) {
<span class="keyword">var</span> result = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < arrays.length; i++) {
<span class="keyword">for</span> (<span class="keyword">var</span> j = 0; j < arrays[i].length; j++) {
result.push(arrays[i][j]);
}
}
<span class="keyword">return</span> result;
}</pre>
</div>
</div>
<div>
<h3>ES6 and Beyond</h3>
<div class="code-block">
<pre><span class="comment">// Creating arrays in ES6+</span>
<span class="keyword">const</span> numbers = [1, 2, 3];
<span class="comment">// Array from string</span>
<span class="keyword">const</span> chars = Array.from('hello'); <span class="comment">// ES6</span>
<span class="comment">// Array from array-like object</span>
<span class="keyword">function</span> getArgs(...args) {
<span class="keyword">return</span> args; <span class="comment">// Rest parameter</span>
}
<span class="comment">// Creating sequence with Array.from</span>
<span class="keyword">const</span> range = (start, end) =>
Array.from({ length: end - start + 1 }, (_, i) => start + i);
<span class="comment">// Alternative with Array(n).keys()</span>
<span class="keyword">const</span> rangeAlt = (start, end) =>
[...Array(end - start + 1).keys()].map(i => i + start);
<span class="comment">// Flattening nested arrays</span>
<span class="keyword">const</span> flatten = arrays => arrays.flat(); <span class="comment">// ES2019</span>
<span class="comment">// Flatten and map in one step</span>
<span class="keyword">const</span> flatAndDouble = arrays =>
arrays.flatMap(arr => arr.map(n => n * 2)); <span class="comment">// ES2019</span></pre>
</div>
</div>
</div>
<div class="grid">
<div>
<label for="stringToArray">String to convert to array:</label>
<input type="text" id="stringToArray" placeholder="Enter a string" value="Hello">
</div>
<div>
<label for="sequenceRange">Sequence range (start,end):</label>
<input type="text" id="sequenceRange" placeholder="e.g., 1,10" value="1,10">
</div>
</div>
<textarea id="nestedArrays" rows="3" placeholder="Enter nested arrays as JSON">[[1, 2], [3, 4], [5, 6]]</textarea>
<button id="runES5Creation">Run ES5 Array Creation</button>
<button id="runES6Creation">Run ES6+ Array Creation</button>
<button id="runFlatten">Run Flatten Methods</button>
<div class="result" id="creationResult"></div>
</div>
<div class="card">
<h2>Real-World Example: Data Processing</h2>
<div class="code-block">
<pre><span class="comment">// User data processing example</span>
<span class="keyword">const</span> users = [
{ id: 1, name: 'Alice', age: 28, active: true, hobbies: ['reading', 'hiking'] },
{ id: 2, name: 'Bob', age: 35, active: false, hobbies: ['gaming', 'cooking'] },
{ id: 3, name: 'Charlie', age: 22, active: true, hobbies: ['painting', 'yoga', 'coding'] },
{ id: 4, name: 'Diana', age: 31, active: true, hobbies: ['photography', 'travel'] },
{ id: 5, name: 'Evan', age: 40, active: false, hobbies: ['music', 'gardening'] }
];
<span class="comment">/* ES5 style */</span>
<span class="keyword">function</span> processUsersES5(users) {
<span class="comment">// Get active users</span>
<span class="keyword">var</span> activeUsers = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < users.length; i++) {
<span class="keyword">if</span> (users[i].active) {
activeUsers.push(users[i]);
}
}
<span class="comment">// Get names of active users</span>
<span class="keyword">var</span> activeNames = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < activeUsers.length; i++) {
activeNames.push(activeUsers[i].name);
}
<span class="comment">// Get all hobbies from all users</span>
<span class="keyword">var</span> allHobbies = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < users.length; i++) {
<span class="keyword">for</span> (<span class="keyword">var</span> j = 0; j < users[i].hobbies.length; j++) {
allHobbies.push(users[i].hobbies[j]);
}
}
<span class="comment">// Get unique hobbies</span>
<span class="keyword">var</span> uniqueHobbies = [];
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < allHobbies.length; i++) {
<span class="keyword">if</span> (uniqueHobbies.indexOf(allHobbies[i]) === -1) {
uniqueHobbies.push(allHobbies[i]);
}
}
<span class="comment">// Calculate average age</span>
<span class="keyword">var</span> totalAge = 0;
<span class="keyword">for</span> (<span class="keyword">var</span> i = 0; i < users.length; i++) {
totalAge += users[i].age;
}
<span class="keyword">var</span> averageAge = totalAge / users.length;
<span class="keyword">return</span> {
activeNames: activeNames,
uniqueHobbies: uniqueHobbies,
averageAge: averageAge
};
}
<span class="comment">/* ES6+ style */</span>
<span class="keyword">function</span> processUsersES6(users) {
<span class="comment">// Get active users' names</span>
<span class="keyword">const</span> activeNames = users
.filter(user => user.active)
.map(user => user.name);
<span class="comment">// Get all unique hobbies</span>
<span class="keyword">const</span> uniqueHobbies = [
...new Set(users.flatMap(user => user.hobbies))
];
<span class="comment">// Calculate average age</span>
<span class="keyword">const</span> averageAge = users
.reduce((sum, user) => sum + user.age, 0) / users.length;
<span class="keyword">return</span> {
activeNames,
uniqueHobbies,
averageAge
};
}</pre>
</div>
<textarea id="usersData" rows="12" placeholder="Enter users data as JSON">
[
{ "id": 1, "name": "Alice", "age": 28, "active": true, "hobbies": ["reading", "hiking"] },
{ "id": 2, "name": "Bob", "age": 35, "active": false, "hobbies": ["gaming", "cooking"] },
{ "id": 3, "name": "Charlie", "age": 22, "active": true, "hobbies": ["painting", "yoga", "coding"] },
{ "id": 4, "name": "Diana", "age": 31, "active": true, "hobbies": ["photography", "travel"] },
{ "id": 5, "name": "Evan", "age": 40, "active": false, "hobbies": ["music", "gardening"] }
]
</textarea>
<button id="runES5Processing">Process with ES5</button>
<button id="runES6Processing">Process with ES6+</button>
<div class="result" id="processingResult"></div>
</div>
---css
/* Dark mode by default with light mode via media query */
:root {
--bg-color: #0d1117;
--text-color: #e6edf3;
--card-color: #161b22;
--border-color: #30363d;
--accent-color: #58a6ff;
--button-hover: #1f6feb;
--code-bg: #1e2428;
--comment-color: #8b949e;
--string-color: #a5d6ff;
--variable-color: #ff7b72;
--keyword-color: #ff7b72;
--success-color: #56d364;
--warning-color: #e3b341;
--danger-color: #f85149;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: #f6f8fa;
--text-color: #24292e;
--card-color: #ffffff;
--border-color: #d0d7de;
--accent-color: #0969da;
--button-hover: #0366d6;
--code-bg: #f6f8fa;
--comment-color: #6e7781;
--string-color: #0a3069;
--variable-color: #953800;
--keyword-color: #cf222e;
--success-color: #1a7f37;
--warning-color: #9a6700;
--danger-color: #cf222e;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
max-width: 800px;
margin: 0 auto;
transition: all 0.3s ease;
}
h1,
h2,
h3 {
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
}
.card {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 6px;
overflow-x: auto;
margin: 15px 0;
border: 1px solid var(--border-color);
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
position: relative;
}
.comment {
color: var(--comment-color);
}
.string {
color: var(--string-color);
}
.variable {
color: var(--variable-color);
}
.keyword {
color: var(--keyword-color);
}
button {
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
}
button:hover {
background-color: var(--button-hover);
}
.result {
margin-top: 15px;
padding: 15px;
background-color: var(--code-bg);
border-radius: 6px;
border: 1px solid var(--border-color);
white-space: pre-wrap;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
max-height: 300px;
overflow-y: auto;
}
.success {
color: var(--success-color);
}
.warning {
color: var(--warning-color);
}
.danger {
color: var(--danger-color);
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 700px) {
.grid {
grid-template-columns: 1fr;
}
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th,
td {
padding: 8px 12px;
text-align: left;
border: 1px solid var(--border-color);
}
th {
background-color: rgba(255, 255, 255, 0.05);
}
textarea,
input {
background-color: var(--code-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 8px;
width: 100%;
font-family: inherit;
}
textarea {
min-height: 100px;
resize: vertical;
}
---js
// From Loops to Functional Programming
document.
getElementById("runTraditional").
addEventListener("click", function () {
const resultDiv = document.getElementById("loopsResult");
const arrayData = getArrayData();
if (!arrayData) return;
try {
// Traditional loops
let result = "Traditional Loops Results:\n\n";
// Transforming array (similar to map)
var doubled = [];
for (var i = 0; i < arrayData.length; i++) {
doubled.push(arrayData[i] * 2);
}
result += "Doubled: " + JSON.stringify(doubled) + "\n\n";
// Filtering array
var evens = [];
for (var i = 0; i < arrayData.length; i++) {
if (arrayData[i] % 2 === 0) {
evens.push(arrayData[i]);
}
}
result += "Evens: " + JSON.stringify(evens) + "\n\n";
// Checking if value exists
function includes(array, value) {
for (var i = 0; i < array.length; i++) {
if (array[i] === value) {
return true;
}
}
return false;
}
result += "Includes 3: " + includes(arrayData, 3) + "\n\n";
// Summing array values
var sum = 0;
for (var i = 0; i < arrayData.length; i++) {
sum += arrayData[i];
}
result += "Sum: " + sum;
resultDiv.textContent = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById("runFunctional").addEventListener("click", function () {
const resultDiv = document.getElementById("loopsResult");
const arrayData = getArrayData();
if (!arrayData) return;
try {
// Functional methods
let result = "Functional Methods Results:\n\n";
// Transforming array with map
const doubled = arrayData.map(n => n * 2);
result += "map() - Doubled: " + JSON.stringify(doubled) + "\n\n";
// Filtering array
const evens = arrayData.filter(n => n % 2 === 0);
result += "filter() - Evens: " + JSON.stringify(evens) + "\n\n";
// Checking if value exists
const hasThree = arrayData.includes(3);
const hasThreeES5 = arrayData.indexOf(3) !== -1;
result += "includes() - Has 3: " + hasThree + "\n";
result += "indexOf() - Has 3: " + hasThreeES5 + "\n\n";
// Summing array values
const sum = arrayData.reduce((acc, n) => acc + n, 0);
result += "reduce() - Sum: " + sum;
resultDiv.textContent = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById("runChain").addEventListener("click", function () {
const resultDiv = document.getElementById("loopsResult");
const arrayData = getArrayData();
if (!arrayData) return;
try {
// Method chaining
let result = "Method Chaining Results:\n\n";
// Sum of doubled evens
const sumOfDoubledEvens = arrayData.
filter(n => n % 2 === 0) // Get even numbers
.map(n => n * 2) // Double them
.reduce((acc, n) => acc + n, 0); // Sum
result += "Filter → Map → Reduce:\n";
result += "Input: " + JSON.stringify(arrayData) + "\n";
result +=
"Even numbers: " +
JSON.stringify(arrayData.filter(n => n % 2 === 0)) +
"\n";
result +=
"Doubled evens: " +
JSON.stringify(arrayData.filter(n => n % 2 === 0).map(n => n * 2)) +
"\n";
result += "Sum of doubled evens: " + sumOfDoubledEvens + "\n\n";
// Find, filter, includes
result += "Other Chains:\n";
const firstNumberOver5 = arrayData.find(n => n > 5);
result += "First number > 5: " + firstNumberOver5 + "\n";
const hasEvenNumber = arrayData.some(n => n % 2 === 0);
result += "Has even numbers: " + hasEvenNumber + "\n";
const allPositive = arrayData.every(n => n > 0);
result += "All numbers positive: " + allPositive;
resultDiv.textContent = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Finding Array Elements
document.getElementById("runES5Find").addEventListener("click", function () {
const resultDiv = document.getElementById("findResult");
const fruitsArray = getFruitsArray();
const searchFruit = document.getElementById("searchFruit").value;
const minLength = parseInt(document.getElementById("minLength").value);
if (!fruitsArray) return;
try {
// ES5 methods
let result = "ES5 Finding Methods:\n\n";
// Finding index of an element
const index = fruitsArray.indexOf(searchFruit);
result += `indexOf('${searchFruit}'): ${index}\n`;
const lastIndex = fruitsArray.lastIndexOf(searchFruit);
result += `lastIndexOf('${searchFruit}'): ${lastIndex}\n\n`;
// Checking existence
const hasFruit = fruitsArray.indexOf(searchFruit) !== -1;
result += `Has '${searchFruit}': ${hasFruit}\n\n`;
// Finding with a condition (workaround)
function findLongFruit(array, minLength) {
for (var i = 0; i < array.length; i++) {
if (array[i].length >= minLength) {
return array[i];
}
}
return undefined;
}
const longFruit = findLongFruit(fruitsArray, minLength);
result += `First fruit with length >= ${minLength}: ${longFruit || "None"}\n`;
// Finding all long fruits
function filterLongFruits(array, minLength) {
var results = [];
for (var i = 0; i < array.length; i++) {
if (array[i].length >= minLength) {
results.push(array[i]);
}
}
return results;
}
const allLongFruits = filterLongFruits(fruitsArray, minLength);
result += `All fruits with length >= ${minLength}: ${JSON.stringify(
allLongFruits)
}`;
resultDiv.textContent = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById("runES6Find").addEventListener("click", function () {
const resultDiv = document.getElementById("findResult");
const fruitsArray = getFruitsArray();
const searchFruit = document.getElementById("searchFruit").value;
const minLength = parseInt(document.getElementById("minLength").value);
if (!fruitsArray) return;
try {
// ES6+ methods
let result = "ES6+ Finding Methods:\n\n";
// Test for includes method (ES7)
if (typeof Array.prototype.includes === "function") {
const hasFruit = fruitsArray.includes(searchFruit);
result += `includes('${searchFruit}'): ${hasFruit}\n\n`;
} else {
result += "includes() method not supported in this browser (ES7)\n\n";
}
// Finding with a condition (ES6)
if (typeof Array.prototype.find === "function") {
const longFruit = fruitsArray.find(f => f.length >= minLength);
result += `find(length >= ${minLength}): ${longFruit || "None"}\n`;
const longFruitIndex = fruitsArray.findIndex(f => f.length >= minLength);
result += `findIndex(length >= ${minLength}): ${longFruitIndex}\n\n`;
} else {
result +=
"find()/findIndex() methods not supported in this browser (ES6)\n\n";
}
// Get using at() method (ES2022)
if (typeof Array.prototype.at === "function") {
const firstFruit = fruitsArray.at(0);
result += `First fruit (at(0)): ${firstFruit}\n`;
const lastFruit = fruitsArray.at(-1);
result += `Last fruit (at(-1)): ${lastFruit}\n`;
const secondToLast = fruitsArray.at(-2);
result += `Second to last fruit (at(-2)): ${secondToLast}\n`;
} else {
result += "at() method not supported in this browser (ES2022)\n";
// Fallback for at() using slice
const lastFruit = fruitsArray[fruitsArray.length - 1];
result += `Last fruit (traditional): ${lastFruit}\n`;
}
resultDiv.textContent = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById("runES2023Find").addEventListener("click", function () {
const resultDiv = document.getElementById("findResult");
const fruitsArray = getFruitsArray();
const minLength = parseInt(document.getElementById("minLength").value);
if (!fruitsArray) return;
try {
// ES2023 methods
let result = "ES2023 Finding Methods:\n\n";
// Test for findLast and findLastIndex (ES2023)
if (
typeof Array.prototype.findLast === "function" &&
typeof Array.prototype.findLastIndex === "function")
{
const lastLongFruit = fruitsArray.findLast(f => f.length >= minLength);
result += `findLast(length >= ${minLength}): ${lastLongFruit || "None"}\n`;
const lastLongIndex = fruitsArray.findLastIndex(
f => f.length >= minLength);
result += `findLastIndex(length >= ${minLength}): ${lastLongIndex}\n`;
} else {
result +=
"<span class='warning'>findLast() and findLastIndex() are not supported in this browser (ES2023)</span>\n\n";
// Polyfill demonstration
result += "Polyfill demonstration:\n";
// Reverse array and use find
const reversed = [...fruitsArray].reverse();
const lastLongFruit = reversed.find(f => f.length >= minLength);
const lastLongIndex = lastLongFruit ?
fruitsArray.lastIndexOf(lastLongFruit) :
-1;
result += `Last fruit with length >= ${minLength}: ${
lastLongFruit || "None"
}\n`;
result += `Index of last fruit with length >= ${minLength}: ${lastLongIndex}\n`;
}
// Show the evolution of array finding methods
result += "\nEvolution of Array Finding Methods:\n";
result += "- ES5 (2009): indexOf(), lastIndexOf()\n";
result += "- ES6 (2015): find(), findIndex()\n";
result += "- ES7 (2016): includes()\n";
result += "- ES2022: at()\n";
result += "- ES2023: findLast(), findLastIndex()\n";
resultDiv.innerHTML = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Array Creation and Manipulation
document.
getElementById("runES5Creation").
addEventListener("click", function () {
const resultDiv = document.getElementById("creationResult");
const stringToArray = document.getElementById("stringToArray").value;
const sequenceRange = document.
getElementById("sequenceRange").
value.split(",").
map(Number);
const nestedArraysText = document.getElementById("nestedArrays").value;
try {
let nestedArrays = JSON.parse(nestedArraysText);
// ES5 array creation
let result = "ES5 Array Creation:\n\n";
// Array from string
const chars = stringToArray.split("");
result += `String to array (split): ${JSON.stringify(chars)}\n\n`;
// Creating sequence
function range(start, end) {
var result = [];
for (var i = start; i <= end; i++) {
result.push(i);
}
return result;
}
const rangeArray = range(sequenceRange[0], sequenceRange[1]);
result += `Sequence ${sequenceRange[0]}-${
sequenceRange[1]
}: ${JSON.stringify(rangeArray)}\n\n`;
// Flattening nested arrays
function flatten(arrays) {
var result = [];
for (var i = 0; i < arrays.length; i++) {
for (var j = 0; j < arrays[i].length; j++) {
result.push(arrays[i][j]);
}
}
return result;
}
const flatArray = flatten(nestedArrays);
result += `Flattened array: ${JSON.stringify(flatArray)}`;
resultDiv.textContent = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.
getElementById("runES6Creation").
addEventListener("click", function () {
const resultDiv = document.getElementById("creationResult");
const stringToArray = document.getElementById("stringToArray").value;
const sequenceRange = document.
getElementById("sequenceRange").
value.split(",").
map(Number);
try {
// ES6+ array creation
let result = "ES6+ Array Creation:\n\n";
// Test for Array.from (ES6)
if (typeof Array.from === "function") {
// Array from string
const chars = Array.from(stringToArray);
result += `String to array (Array.from): ${JSON.stringify(chars)}\n\n`;
// Creating sequence with Array.from
const range = (start, end) =>
Array.from({ length: end - start + 1 }, (_, i) => start + i);
const rangeArray = range(sequenceRange[0], sequenceRange[1]);
result += `Sequence ${sequenceRange[0]}-${
sequenceRange[1]
} (Array.from): ${JSON.stringify(rangeArray)}\n\n`;
// Alternative with spread and keys
const rangeAlt = [
...Array(sequenceRange[1] - sequenceRange[0] + 1).keys()].
map(i => i + sequenceRange[0]);
result += `Sequence (spread + keys): ${JSON.stringify(rangeAlt)}\n\n`;
} else {
result += "Array.from() is not supported in this browser (ES6)\n\n";
}
// Array creation with spread
const spreadExample = [...stringToArray];
result += `String to array (spread): ${JSON.stringify(spreadExample)}\n\n`;
// Array.of demonstration (ES6)
if (typeof Array.of === "function") {
const arrayOf = Array.of(1, 2, 3, 4, 5);
result += `Array.of(1, 2, 3, 4, 5): ${JSON.stringify(arrayOf)}\n`;
result +=
"Compare with Array(5): " +
JSON.stringify(Array(5)) +
" (sparse array with 5 empty slots)\n";
} else {
result += "Array.of() is not supported in this browser (ES6)\n";
}
resultDiv.textContent = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.getElementById("runFlatten").addEventListener("click", function () {
const resultDiv = document.getElementById("creationResult");
const nestedArraysText = document.getElementById("nestedArrays").value;
try {
let nestedArrays = JSON.parse(nestedArraysText);
// ES2019 flat and flatMap
let result = "ES2019 flat() and flatMap():\n\n";
// Test for flat and flatMap (ES2019)
if (
typeof Array.prototype.flat === "function" &&
typeof Array.prototype.flatMap === "function")
{
// Basic flat usage
const flatArray = nestedArrays.flat();
result += `flat(): ${JSON.stringify(flatArray)}\n\n`;
// Deeper nesting example
const deeperArrays = [1, [2, [3, [4]]]];
result += `Deep nested array: ${JSON.stringify(deeperArrays)}\n`;
result += `flat(1): ${JSON.stringify(deeperArrays.flat())}\n`;
result += `flat(2): ${JSON.stringify(deeperArrays.flat(2))}\n`;
result += `flat(Infinity): ${JSON.stringify(
deeperArrays.flat(Infinity))
}\n\n`;
// flatMap example
const duplicateNumbers = [1, 2, 3, 4, 5];
const duplicated = duplicateNumbers.flatMap(n => [n, n]);
result += `flatMap(n => [n, n]): ${JSON.stringify(duplicated)}\n`;
// Equivalent of flatMap with map + flat
const mapThenFlat = duplicateNumbers.map(n => [n, n]).flat();
result += `map(n => [n, n]).flat(): ${JSON.stringify(mapThenFlat)}`;
} else {
result +=
"<span class='warning'>flat() and flatMap() are not supported in this browser (ES2019)</span>\n\n";
// Manual implementation
function flatten(arrays) {
var result = [];
for (var i = 0; i < arrays.length; i++) {
if (Array.isArray(arrays[i])) {
for (var j = 0; j < arrays[i].length; j++) {
result.push(arrays[i][j]);
}
} else {
result.push(arrays[i]);
}
}
return result;
}
const flatArray = flatten(nestedArrays);
result += `Manual flatten: ${JSON.stringify(flatArray)}\n\n`;
// Pre-ES2019 flatMap equivalent
function flatMap(array, callback) {
return flatten(array.map(callback));
}
const duplicateNumbers = [1, 2, 3, 4, 5];
const duplicated = flatMap(duplicateNumbers, n => [n, n]);
result += `Manual flatMap: ${JSON.stringify(duplicated)}`;
}
resultDiv.innerHTML = result;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Data Processing Example
document.
getElementById("runES5Processing").
addEventListener("click", function () {
const resultDiv = document.getElementById("processingResult");
const usersData = getUsersData();
if (!usersData) return;
try {
// ES5 style processing
function processUsersES5(users) {
// Get active users
var activeUsers = [];
for (var i = 0; i < users.length; i++) {
if (users[i].active) {
activeUsers.push(users[i]);
}
}
// Get names of active users
var activeNames = [];
for (var i = 0; i < activeUsers.length; i++) {
activeNames.push(activeUsers[i].name);
}
// Get all hobbies from all users
var allHobbies = [];
for (var i = 0; i < users.length; i++) {
for (var j = 0; j < users[i].hobbies.length; j++) {
allHobbies.push(users[i].hobbies[j]);
}
}
// Get unique hobbies
var uniqueHobbies = [];
for (var i = 0; i < allHobbies.length; i++) {
if (uniqueHobbies.indexOf(allHobbies[i]) === -1) {
uniqueHobbies.push(allHobbies[i]);
}
}
// Calculate average age
var totalAge = 0;
for (var i = 0; i < users.length; i++) {
totalAge += users[i].age;
}
var averageAge = totalAge / users.length;
return {
activeNames: activeNames,
uniqueHobbies: uniqueHobbies,
averageAge: averageAge };
}
const result = processUsersES5(usersData);
// Format the output
let output = "ES5 Data Processing:\n\n";
output += "Active Users: " + JSON.stringify(result.activeNames) + "\n\n";
output += "Unique Hobbies: " + JSON.stringify(result.uniqueHobbies) + "\n\n";
output += "Average Age: " + result.averageAge.toFixed(1);
resultDiv.textContent = output;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
document.
getElementById("runES6Processing").
addEventListener("click", function () {
const resultDiv = document.getElementById("processingResult");
const usersData = getUsersData();
if (!usersData) return;
try {
// ES6+ style processing
function processUsersES6(users) {
// Check for required methods
const hasFlatMap = typeof Array.prototype.flatMap === "function";
const hasSet = typeof Set === "function";
// Get active users' names
const activeNames = users.
filter(user => user.active).
map(user => user.name);
// Get all unique hobbies
let uniqueHobbies;
if (hasFlatMap && hasSet) {
uniqueHobbies = [...new Set(users.flatMap(user => user.hobbies))];
} else {
// Fallback for browsers without flatMap or Set
const allHobbies = [];
users.forEach(user => {
user.hobbies.forEach(hobby => {
allHobbies.push(hobby);
});
});
uniqueHobbies = allHobbies.filter(
(hobby, index, self) => self.indexOf(hobby) === index);
}
// Calculate average age
const averageAge =
users.reduce((sum, user) => sum + user.age, 0) / users.length;
return {
activeNames,
uniqueHobbies,
averageAge,
hasModernFeatures: hasFlatMap && hasSet };
}
const result = processUsersES6(usersData);
// Format the output
let output = "ES6+ Data Processing:\n\n";
output += "Active Users: " + JSON.stringify(result.activeNames) + "\n\n";
output += "Unique Hobbies: " + JSON.stringify(result.uniqueHobbies) + "\n\n";
output += "Average Age: " + result.averageAge.toFixed(1) + "\n\n";
// Line count comparison
output += "Code Comparison:\n";
output += "- ES5: ~40 lines of code\n";
output += "- ES6+: ~10 lines of code (75% reduction)\n\n";
if (!result.hasModernFeatures) {
output +=
"<span class='warning'>Note: Your browser doesn't support some modern JavaScript features (Set or flatMap).</span>";
} else {
output +=
"<span class='success'>Your browser supports modern JavaScript features!</span>";
}
resultDiv.innerHTML = output;
} catch (error) {
resultDiv.innerHTML = `<span class="danger">✗ Error: ${error.message}</span>`;
}
});
// Helper functions
function getArrayData() {
const arrayDataText = document.getElementById("arrayData").value;
try {
// Parse comma-separated values
return arrayDataText.split(",").map(item => parseInt(item.trim()));
} catch (error) {
document.getElementById(
"loopsResult").
innerHTML = `<span class="danger">✗ Invalid input: ${error.message}</span>`;
return null;
}
}
function getFruitsArray() {
const fruitsArrayText = document.getElementById("fruitsArray").value;
try {
// Parse comma-separated values
return fruitsArrayText.split(",").map(item => item.trim());
} catch (error) {
document.getElementById(
"findResult").
innerHTML = `<span class="danger">✗ Invalid input: ${error.message}</span>`;
return null;
}
}
function getUsersData() {
const usersDataText = document.getElementById("usersData").value;
try {
return JSON.parse(usersDataText);
} catch (error) {
document.getElementById(
"processingResult").
innerHTML = `<span class="danger">✗ Invalid JSON: ${error.message}</span>`;
return null;
}
}
Conclusion: Setting the Stage for ES6#
ECMAScript 5 marked both the culmination of early JavaScript development and the foundation for the modern JavaScript era. While it addressed some pain points, it left many others unresolved—which set up the changes that came with ES6.
In the next article, we will look at how ES6 (2015) answered these problems with let and const, arrow functions, classes, promises, and modules.
What was your experience with pre-ES6 JavaScript? Do you remember the workarounds and libraries that were once essential to everyday development? Share your stories in the comments below!
Comments require JavaScript.