JavaScript Evolution: From Scripting Language to Modern Development Platform
Nullish Coalescing, Optional Chaining, and BigInts
Understanding the changes brought with ES11 & ES12

A user object arrives without an address. The next line reads user.address.city, and the page stops with a TypeError. We could guard every step by hand, but there is a shorter way.
In this article, we are going to read deep values safely, choose defaults without throwing away a real 0 or an empty string, and do exact math with very big whole numbers. Those come from ES11 (2020) and ES12 (2021).
What's here: ?. and ?? (2020), then Promise.allSettled(), BigInt, dynamic import() and globalThis (2020), then replaceAll(), numeric separators, logical assignment and WeakRef (2021). WeakRef and dynamic import() are advanced and optional; skip them on a first read.
What these two editions fix#
ES6 gave us arrow functions, classes, promises and modules. The editions after it were smaller. ES11 and ES12 went after everyday annoyances: missing values, wrong defaults, and numbers that are too big.
ES11/ECMAScript 2020: Quality-of-Life Improvements#
Optional Chaining (?.)#
Reading a property of undefined or null throws a TypeError, so reaching into nested data used to need a guard at every step. Here is the failure first:
const user = {}; // this user has no address
console.log(user.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')
The old fix was to check each step with &&:
const user = {};
const city = user && user.address && user.address.city;
console.log(city); // undefined
Optional chaining (?.) does the same check in one short form. If the value before ?. is null or undefined, the whole expression stops and gives undefined instead of throwing:
const user = {};
console.log(user?.address?.city); // undefined
// It works with function calls too
const settings = { save() { return 'saved'; } };
console.log(settings.save?.()); // 'saved'
console.log(settings.reset?.()); // undefined, because there is no reset method
// And with array access
const list = null;
console.log(list?.[0]); // undefined
This makes code more readable and prevents the "Cannot read properties of undefined" errors that have plagued JavaScript developers for years. Use it where a missing value is a normal case. It will also hide a real bug if you add it everywhere, because a typo quietly gives undefined.
Nullish Coalescing: Smarter Default Values#
The nullish coalescing operator (??) provides a more precise way to handle default values compared to the logical OR operator (||).
First, a word we need. A value is falsy when JavaScript treats it as false in a condition. The falsy values are false, 0, -0, 0n, "" (the empty string), null, undefined, and NaN. Everything else, including "0" and [], is truthy. The || operator falls back to the default for any falsy value, so a real 0 gets thrown away. The ?? operator falls back only for null and undefined, the two values that mean "nothing here".
// Consider these values
const count = 0;
const text = "";
const nullValue = null;
const undefinedValue = undefined;
// Logical OR vs Nullish Coalescing
console.log(count || 10); // 10 (undesired!)
console.log(count ?? 10); // 0 (correct!)
console.log(text || "default"); // "default" (undesired!)
console.log(text ?? "default"); // "" (correct!)
console.log(nullValue ?? "default"); // "default"
console.log(undefinedValue ?? "default"); // "default"
---html
<div class="container">
<h1>Optional Chaining & Nullish Coalescing</h1>
<div class="description">
<p>ES11 introduced two powerful features that make code more resilient when dealing with potentially undefined values:</p>
<ul>
<li><span class="highlight">Optional Chaining (?.):</span> Safely access nested object properties without error</li>
<li><span class="highlight">Nullish Coalescing (??):</span> Provide default values only when the value is null or undefined</li>
</ul>
</div>
<div class="demo-area">
<h3>User Data Explorer</h3>
<p>This demo shows how optional chaining and nullish coalescing make code more robust when dealing with varying data structures.</p>
<div id="objectDisplay">
<pre><code id="currentObject"></code></pre>
</div>
<div class="controls">
<button id="completeUser">Complete User</button>
<button id="missingAddress">User Without Address</button>
<button id="missingUser">Undefined User</button>
<button id="zeroValues">User With Zero Values</button>
</div>
<div class="code-area">
<div class="code-block">
<h3>Old Way (Pre-ES11)</h3>
<pre><code>// Access nested property
function getCity(user) {
if (user && user.address &&
user.address.city) {
return user.address.city;
}
return 'Unknown city';
}
// Default values
function getPostCount(user) {
let count = user && user.stats &&
user.stats.posts;
return count || 'No posts'; // Issue with 0!
}</code></pre>
<button id="runOldWay">Run Old Way</button>
<div class="output" id="oldWayOutput"></div>
</div>
<div class="code-block">
<h3>Modern Way (ES11+)</h3>
<pre><code>// Access nested property
function getCity(user) {
return user?.address?.city ?? 'Unknown city';
}
// Default values - respects 0 as valid
function getPostCount(user) {
return user?.stats?.posts ?? 'No posts';
}</code></pre>
<button id="runModernWay">Run Modern Way</button>
<div class="output" id="modernWayOutput"></div>
</div>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #f5f5f5;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #e9ecef;
--border-color: #dee2e6;
--highlight: #8a2be2;
--red: #dc3545;
--green: #28a745;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #2d2d2d;
--border-color: #444;
--highlight: #bb86fc;
--red: #f07178;
--green: #c3e88d;
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
h1 {
color: var(--highlight);
margin-bottom: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.demo-area {
margin-bottom: 30px;
}
.code-area {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.code-block {
flex: 1;
min-width: 300px;
}
pre {
background-color: var(--code-bg);
padding: 15px;
border-radius: 5px;
overflow-x: auto;
border: 1px solid var(--border-color);
}
code {
font-family: 'Fira Code', monospace;
font-size: 14px;
}
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-bottom: 15px;
margin-right: 10px;
}
button:hover {
opacity: 0.9;
}
.output {
background-color: var(--output-bg);
padding: 15px;
border-radius: 5px;
margin-top: 10px;
min-height: 120px;
border: 1px solid var(--border-color);
}
.description {
margin-bottom: 25px;
}
.highlight {
color: var(--highlight);
font-weight: bold;
}
.success {
color: var(--green);
}
.error {
color: var(--red);
}
.controls {
margin-bottom: 15px;
}
#objectDisplay {
margin-bottom: 20px;
}
---js
// User data samples
const users = {
complete: {
name: 'Alice',
address: {
street: '123 Main St',
city: 'Techville',
zip: '12345' },
stats: {
posts: 42,
followers: 1024 } },
missingAddress: {
name: 'Bob',
stats: {
posts: 17,
followers: 512 } },
missingUser: undefined,
zeroValues: {
name: 'Charlie',
address: {
street: '456 Tech Ave',
city: 'Codeburg',
zip: '67890' },
stats: {
posts: 0,
followers: 0 } } };
let currentUser = users.complete;
// Update display of current object
function updateObjectDisplay() {
const display = document.getElementById('currentObject');
display.textContent = currentUser ?
JSON.stringify(currentUser, null, 2) :
'undefined';
}
// Button handlers to change current user
document.getElementById('completeUser').addEventListener('click', () => {
currentUser = users.complete;
updateObjectDisplay();
});
document.getElementById('missingAddress').addEventListener('click', () => {
currentUser = users.missingAddress;
updateObjectDisplay();
});
document.getElementById('missingUser').addEventListener('click', () => {
currentUser = users.missingUser;
updateObjectDisplay();
});
document.getElementById('zeroValues').addEventListener('click', () => {
currentUser = users.zeroValues;
updateObjectDisplay();
});
// Old way example
document.getElementById('runOldWay').addEventListener('click', function () {
const output = document.getElementById('oldWayOutput');
output.innerHTML = '';
const log = message => {
output.innerHTML += message + '<br>';
};
try {
// Old way of accessing nested properties
function getCity(user) {
if (user && user.address && user.address.city) {
return user.address.city;
}
return 'Unknown city';
}
// Old way of providing defaults (issue with 0)
function getPostCount(user) {
let count = user && user.stats && user.stats.posts;
return count || 'No posts'; // This will return 'No posts' for 0!
}
const city = getCity(currentUser);
const posts = getPostCount(currentUser);
log(`<strong>City:</strong> ${city}`);
log(`<strong>Posts:</strong> ${posts}`);
if (currentUser && currentUser.stats && currentUser.stats.posts === 0 && posts === 'No posts') {
log(`<span class="error">Note: 0 posts treated as falsy value!</span>`);
}
} catch (error) {
log(`<span class="error">Error: ${error.message}</span>`);
}
});
// Modern way example
document.getElementById('runModernWay').addEventListener('click', function () {
const output = document.getElementById('modernWayOutput');
output.innerHTML = '';
const log = message => {
output.innerHTML += message + '<br>';
};
try {
// Modern way with optional chaining and nullish coalescing
function getCity(user) {
return user?.address?.city ?? 'Unknown city';
}
// Correctly handles 0 values
function getPostCount(user) {
return user?.stats?.posts ?? 'No posts';
}
const city = getCity(currentUser);
const posts = getPostCount(currentUser);
log(`<strong>City:</strong> ${city}`);
log(`<strong>Posts:</strong> ${posts}`);
if (currentUser?.stats?.posts === 0 && posts === 0) {
log(`<span class="success">Note: 0 posts correctly preserved!</span>`);
}
} catch (error) {
log(`<span class="error">Error: ${error.message}</span>`);
}
});
// Initialize
updateObjectDisplay();
The key difference is that nullish coalescing only falls back to the default value when the left-hand side is null or undefined, making it perfect for cases where 0 or empty strings are valid values.
Promise.allSettled(): Better Promise Handling#
A promise is a value that arrives later and either succeeds (fulfilled) or fails (rejected). Promise.all() gives up as soon as one promise rejects, so you lose the results of the others. Promise.allSettled() waits for every promise and reports each outcome:
const promises = [
Promise.resolve('users loaded'),
Promise.reject(new Error('products failed')),
Promise.resolve('orders loaded')
];
const results = await Promise.allSettled(promises);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} succeeded:`, result.value);
} else {
console.log(`Promise ${index} failed:`, result.reason.message);
}
});
// Promise 0 succeeded: users loaded
// Promise 1 failed: products failed
// Promise 2 succeeded: orders loaded
The top-level await here works in a module or in the Node REPL. Inside a regular script, put the code in an async function.
---html
<div class="container">
<h1>Promise Evolution Explorer</h1>
<p>See how JavaScript's approach to asynchronous programming has evolved from callbacks to Promises to async/await. This timeline shows how handling asynchronous operations has become more concise and readable over time.</p>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-marker"></div>
<div>Pre-ES6</div>
<div><small>Callback Hell</small></div>
</div>
<div class="timeline-item">
<div class="timeline-marker"></div>
<div>ES6 (2015)</div>
<div><small>Promises</small></div>
</div>
<div class="timeline-item">
<div class="timeline-marker"></div>
<div>ES8 (2017)</div>
<div><small>async/await</small></div>
</div>
<div class="timeline-item">
<div class="timeline-marker"></div>
<div>ES11 (2020)</div>
<div><small>Promise.allSettled</small></div>
</div>
<div class="timeline-item">
<div class="timeline-marker"></div>
<div>ES13 (2022)</div>
<div><small>Top-level await</small></div>
</div>
</div>
<div class="card">
<h2>Basic Async Operation Comparison</h2>
<p>See how the same asynchronous operation is handled with different approaches.</p>
<div class="code-tabs">
<div class="code-tab active" data-tab="callback">Callbacks</div>
<div class="code-tab" data-tab="promise">Promises</div>
<div class="code-tab" data-tab="async">async/await</div>
</div>
<div class="tab-content active" id="callback-tab">
<div class="code-block">
<pre><code><span class="highlight-callback">// Using callbacks (Pre-ES6)
function fetchUserData(userId, callback) {
// Simulate API call
setTimeout(() => {
const userData = {
id: userId,
name: 'User ' + userId
};
callback(null, userData);
// callback(new Error('Failed to fetch')); // Error case
}, 1000);
}
function fetchUserPosts(userId, callback) {
setTimeout(() => {
const posts = [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' }
];
callback(null, posts);
}, 1000);
}</span></code></pre>
<button id="runCallback">Run Callback Example</button>
</div>
<div class="code-block">
<pre><code><span class="highlight-callback">// Using the fetched data with callbacks
function displayUserWithPosts(userId) {
fetchUserData(userId, (error, user) => {
if (error) {
console.error('Error fetching user:', error);
return;
}
console.log('User:', user);
fetchUserPosts(user.id, (error, posts) => {
if (error) {
console.error('Error fetching posts:', error);
return;
}
console.log('Posts:', posts);
// Imagine more nested callbacks here!
// This is what we call "callback hell"
});
});
}</span></code></pre>
</div>
</div>
<div class="tab-content" id="promise-tab">
<div class="code-block">
<pre><code><span class="highlight-promise">// Using Promises (ES6)
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
// Simulate API call
setTimeout(() => {
const userData = {
id: userId,
name: 'User ' + userId
};
resolve(userData);
// reject(new Error('Failed to fetch')); // Error case
}, 1000);
});
}
function fetchUserPosts(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const posts = [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' }
];
resolve(posts);
}, 1000);
});
}</span></code></pre>
<button id="runPromise">Run Promise Example</button>
</div>
<div class="code-block">
<pre><code><span class="highlight-promise">// Using the fetched data with Promises
function displayUserWithPosts(userId) {
fetchUserData(userId)
.then(user => {
console.log('User:', user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log('Posts:', posts);
// Chain can continue...
})
.catch(error => {
console.error('Error:', error);
});
}
// Promise.all to fetch user and posts in parallel
function fetchUserAndPostsParallel(userId) {
Promise.all([
fetchUserData(userId),
fetchUserPosts(userId)
])
.then(([user, posts]) => {
console.log('User:', user);
console.log('Posts:', posts);
})
.catch(error => {
console.error('Error:', error);
});
}</span></code></pre>
</div>
</div>
<div class="tab-content" id="async-tab">
<div class="code-block">
<pre><code><span class="highlight-async">// Using async/await (ES8)
async function displayUserWithPosts(userId) {
try {
// Sequential execution
const user = await fetchUserData(userId);
console.log('User:', user);
const posts = await fetchUserPosts(user.id);
console.log('Posts:', posts);
// Code continues here in a flat structure
// No more nesting or .then() chains!
} catch (error) {
console.error('Error:', error);
}
}
// Parallel execution with async/await
async function fetchUserAndPostsParallel(userId) {
try {
const [user, posts] = await Promise.all([
fetchUserData(userId),
fetchUserPosts(userId)
]);
console.log('User:', user);
console.log('Posts:', posts);
} catch (error) {
console.error('Error:', error);
}
}</span></code></pre>
<button id="runAsync">Run Async/Await Example</button>
</div>
<div class="code-block">
<pre><code><span class="highlight-async">// ES13: Top-level await (no async function wrapper)
// Only works at module scope!
// This code would be at the top level of a module
try {
const userId = 123;
const user = await fetchUserData(userId);
const posts = await fetchUserPosts(userId);
console.log('User:', user);
console.log('Posts:', posts);
// Use the data for module initialization
// without needing an async IIFE wrapper
} catch (error) {
console.error('Error during module init:', error);
}
// Before Top-level await (ES13), we needed:
(async function init() {
// Same code as above
})();</span></code></pre>
</div>
</div>
<div class="output" id="asyncOutput">
<div class="info">Select a tab and click the "Run" button to see the output here.</div>
</div>
</div>
<div class="card">
<h2>Modern Promise Features (ES11+)</h2>
<p>Explore newer Promise features that help manage multiple async operations more effectively.</p>
<div class="code-block">
<pre><code>// Create different simulated API calls with varying outcomes
function successfulAPI(id) {
return new Promise((resolve) => {
setTimeout(() => resolve(`Success ${id}`), 1000 + Math.random() * 1000);
});
}
function failingAPI(id) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Failed ${id}`)), 1000 + Math.random() * 1000);
});
}
// Create an array of promises with mixed results
const promises = [
successfulAPI(1),
failingAPI(2),
successfulAPI(3)
];</code></pre>
</div>
<div class="code-tabs">
<div class="code-tab active" data-tab="promise-all">Promise.all</div>
<div class="code-tab" data-tab="promise-allsettled">Promise.allSettled</div>
<div class="code-tab" data-tab="promise-any">Promise.any</div>
</div>
<div class="tab-content active" id="promise-all-tab">
<div class="code-block">
<pre><code>// Promise.all (ES6) - fails fast if any promise rejects
Promise.all(promises)
.then(results => {
console.log('All succeeded!');
console.log(results);
})
.catch(error => {
console.error('At least one failed:');
console.error(error);
});</code></pre>
<button id="runPromiseAll">Run Promise.all</button>
</div>
</div>
<div class="tab-content" id="promise-allsettled-tab">
<div class="code-block">
<pre><code>// Promise.allSettled (ES11) - wait for all promises regardless of outcome
Promise.allSettled(promises)
.then(results => {
console.log('All settled!');
// Process each result based on its status
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index + 1} succeeded:`, result.value);
} else {
console.log(`Promise ${index + 1} failed:`, result.reason);
}
});
});</code></pre>
<button id="runPromiseAllSettled">Run Promise.allSettled</button>
</div>
</div>
<div class="tab-content" id="promise-any-tab">
<div class="code-block">
<pre><code>// Promise.any (ES12) - returns first fulfilled promise, ignores rejections
Promise.any(promises)
.then(firstSuccess => {
console.log('First success:', firstSuccess);
})
.catch(error => {
console.error('All promises failed:', error);
});</code></pre>
<button id="runPromiseAny">Run Promise.any</button>
</div>
</div>
<div class="output" id="promiseFeaturesOutput">
<div class="info">Click a button to see how different Promise combinators handle multiple asynchronous operations.</div>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #f5f5f5;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #e9ecef;
--border-color: #dee2e6;
--highlight: #8a2be2;
--green: #28a745;
--yellow: #ffc107;
--red: #dc3545;
--blue: #0d6efd;
--orange: #fd7e14;
--purple: #6f42c1;
--card-bg: #ffffff;
--timeline-bg: rgba(0, 0, 0, 0.03);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #2d2d2d;
--border-color: #444;
--highlight: #bb86fc;
--green: #c3e88d;
--yellow: #ffcb6b;
--red: #f07178;
--blue: #82aaff;
--orange: #f78c6c;
--purple: #c792ea;
--card-bg: #1e1e1e;
--timeline-bg: rgba(255, 255, 255, 0.05);
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
h1, h2, h3 {
color: var(--highlight);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 30px;
}
.timeline {
display: flex;
justify-content: space-between;
margin: 30px 0;
position: relative;
padding: 20px 0;
background-color: var(--timeline-bg);
border-radius: 8px;
}
.timeline::before {
content: '';
position: absolute;
top: 50%;
left: 50px;
right: 50px;
height: 4px;
background-color: var(--border-color);
transform: translateY(-50%);
z-index: 1;
}
.timeline-item {
position: relative;
z-index: 2;
text-align: center;
padding: 0 10px;
}
.timeline-marker {
width: 20px;
height: 20px;
background-color: var(--highlight);
border-radius: 50%;
margin: 0 auto 10px auto;
}
.code-area {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.code-block {
flex: 1;
min-width: 280px;
background-color: var(--code-bg);
padding: 15px;
border-radius: 5px;
border: 1px solid var(--border-color);
}
pre {
margin: 0;
overflow-x: auto;
font-size: 14px;
}
code {
font-family: 'Fira Code', monospace;
}
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-top: 10px;
margin-right: 10px;
}
button:hover {
opacity: 0.9;
}
.output {
background-color: var(--output-bg);
padding: 15px;
border-radius: 5px;
margin-top: 15px;
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--border-color);
}
.loading {
display: inline-block;
width: 15px;
height: 15px;
border: 3px solid var(--border-color);
border-radius: 50%;
border-top-color: var(--highlight);
animation: spin 1s linear infinite;
margin-right: 8px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.code-tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 15px;
}
.code-tab {
padding: 8px 15px;
cursor: pointer;
background-color: transparent;
border: 1px solid transparent;
border-bottom: none;
border-radius: 4px 4px 0 0;
margin-right: 5px;
}
.code-tab.active {
background-color: var(--button-bg);
color: var(--button-text);
border-color: var(--border-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.controls {
margin: 15px 0;
}
.success {
color: var(--green);
font-weight: bold;
}
.error {
color: var(--red);
font-weight: bold;
}
.info {
color: var(--blue);
font-weight: bold;
}
.pending {
color: var(--yellow);
font-weight: bold;
}
.highlight-callback {
color: var(--orange);
}
.highlight-promise {
color: var(--blue);
}
.highlight-async {
color: var(--purple);
}
---js
// Tab switching for code examples
document.querySelectorAll('.code-tab').forEach(tab => {
tab.addEventListener('click', () => {
// Find the tabs container
const tabsContainer = tab.closest('.code-tabs');
// Update active tab in this container
tabsContainer.querySelectorAll('.code-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Find the corresponding tab content container
const contentContainer = tabsContainer.nextElementSibling.closest('.card');
// Hide all tab contents in this container
contentContainer.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Show the selected tab content
const tabName = tab.dataset.tab;
contentContainer.querySelector(`#${tabName}-tab`).classList.add('active');
});
});
// Function to create custom logging that outputs to the specified element
function createCustomLogger(outputElement) {
return {
log: function (...args) {
const message = args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2);
}
return arg.toString();
}).join(' ');
outputElement.innerHTML += `<div>${message}</div>`;
},
error: function (...args) {
const message = args.map(arg => {
if (typeof arg === 'object' && arg instanceof Error) {
return arg.message;
}
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2);
}
return arg.toString();
}).join(' ');
outputElement.innerHTML += `<div class="error">${message}</div>`;
},
clear: function () {
outputElement.innerHTML = '';
} };
}
// Async operation examples
const asyncOutput = document.getElementById('asyncOutput');
const customConsole = createCustomLogger(asyncOutput);
// Callback implementation
function fetchUserDataCallback(userId, callback) {
customConsole.log(`<div class="pending"><span class="loading"></span>Fetching user ${userId}...</div>`);
setTimeout(() => {
const userData = { id: userId, name: 'User ' + userId };
customConsole.log(`<div class="success">User data received for ID ${userId}</div>`);
callback(null, userData);
}, 1500);
}
function fetchUserPostsCallback(userId, callback) {
customConsole.log(`<div class="pending"><span class="loading"></span>Fetching posts for user ${userId}...</div>`);
setTimeout(() => {
const posts = [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' }];
customConsole.log(`<div class="success">Posts received for user ${userId}</div>`);
callback(null, posts);
}, 1500);
}
// Promise implementation
function fetchUserDataPromise(userId) {
customConsole.log(`<div class="pending"><span class="loading"></span>Fetching user ${userId}...</div>`);
return new Promise(resolve => {
setTimeout(() => {
const userData = { id: userId, name: 'User ' + userId };
customConsole.log(`<div class="success">User data received for ID ${userId}</div>`);
resolve(userData);
}, 1500);
});
}
function fetchUserPostsPromise(userId) {
customConsole.log(`<div class="pending"><span class="loading"></span>Fetching posts for user ${userId}...</div>`);
return new Promise(resolve => {
setTimeout(() => {
const posts = [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' }];
customConsole.log(`<div class="success">Posts received for user ${userId}</div>`);
resolve(posts);
}, 1500);
});
}
// Button event handlers
document.getElementById('runCallback').addEventListener('click', () => {
customConsole.clear();
customConsole.log(`<div class="info">Running callback example...</div>`);
fetchUserDataCallback(123, (error, user) => {
if (error) {
customConsole.error(error);
return;
}
customConsole.log(`User: ${JSON.stringify(user)}`);
fetchUserPostsCallback(user.id, (error, posts) => {
if (error) {
customConsole.error(error);
return;
}
customConsole.log(`Posts: ${JSON.stringify(posts)}`);
customConsole.log(`<div class="info">Notice the nested callbacks (callback hell)!</div>`);
});
});
});
document.getElementById('runPromise').addEventListener('click', () => {
customConsole.clear();
customConsole.log(`<div class="info">Running Promise example...</div>`);
fetchUserDataPromise(123).
then(user => {
customConsole.log(`User: ${JSON.stringify(user)}`);
return fetchUserPostsPromise(user.id);
}).
then(posts => {
customConsole.log(`Posts: ${JSON.stringify(posts)}`);
customConsole.log(`<div class="info">Notice the flat chain structure compared to callbacks!</div>`);
}).
catch(error => {
customConsole.error(error);
});
});
document.getElementById('runAsync').addEventListener('click', async () => {
customConsole.clear();
customConsole.log(`<div class="info">Running async/await example...</div>`);
try {
const user = await fetchUserDataPromise(123);
customConsole.log(`User: ${JSON.stringify(user)}`);
const posts = await fetchUserPostsPromise(user.id);
customConsole.log(`Posts: ${JSON.stringify(posts)}`);
customConsole.log(`<div class="info">Notice the clean, synchronous-like code structure!</div>`);
} catch (error) {
customConsole.error(error);
}
});
// Modern Promise features examples
const promiseFeaturesOutput = document.getElementById('promiseFeaturesOutput');
const promiseConsole = createCustomLogger(promiseFeaturesOutput);
function successfulAPI(id) {
return new Promise(resolve => {
const delay = 1000 + Math.random() * 1000;
promiseConsole.log(`<div class="pending"><span class="loading"></span>API ${id} running (will succeed in ${delay.toFixed(0)}ms)...</div>`);
setTimeout(() => {
promiseConsole.log(`<div class="success">API ${id} succeeded</div>`);
resolve(`Success ${id}`);
}, delay);
});
}
function failingAPI(id) {
return new Promise((_, reject) => {
const delay = 1000 + Math.random() * 1000;
promiseConsole.log(`<div class="pending"><span class="loading"></span>API ${id} running (will fail in ${delay.toFixed(0)}ms)...</div>`);
setTimeout(() => {
promiseConsole.log(`<div class="error">API ${id} failed</div>`);
reject(new Error(`Failed ${id}`));
}, delay);
});
}
document.getElementById('runPromiseAll').addEventListener('click', () => {
promiseConsole.clear();
promiseConsole.log(`<div class="info">Running Promise.all example...</div>`);
promiseConsole.log(`<div class="info">Promise.all will fail if ANY promise rejects</div>`);
const promises = [
successfulAPI(1),
failingAPI(2),
successfulAPI(3)];
Promise.all(promises).
then(results => {
promiseConsole.log(`<div class="success">All succeeded!</div>`);
promiseConsole.log(results);
}).
catch(error => {
promiseConsole.log(`<div class="error">At least one failed!</div>`);
promiseConsole.error(error);
});
});
document.getElementById('runPromiseAllSettled').addEventListener('click', () => {
promiseConsole.clear();
promiseConsole.log(`<div class="info">Running Promise.allSettled example...</div>`);
promiseConsole.log(`<div class="info">Promise.allSettled waits for ALL promises regardless of outcome</div>`);
const promises = [
successfulAPI(1),
failingAPI(2),
successfulAPI(3)];
Promise.allSettled(promises).
then(results => {
promiseConsole.log(`<div class="success">All settled!</div>`);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
promiseConsole.log(`Promise ${index + 1} succeeded: ${result.value}`);
} else {
promiseConsole.log(`Promise ${index + 1} failed: ${result.reason.message}`);
}
});
});
});
document.getElementById('runPromiseAny').addEventListener('click', () => {
promiseConsole.clear();
promiseConsole.log(`<div class="info">Running Promise.any example...</div>`);
promiseConsole.log(`<div class="info">Promise.any returns first fulfilled promise, ignores rejections</div>`);
const promises = [
successfulAPI(1),
failingAPI(2),
successfulAPI(3)];
Promise.any(promises).
then(firstSuccess => {
promiseConsole.log(`<div class="success">First success:</div>`);
promiseConsole.log(firstSuccess);
}).
catch(error => {
promiseConsole.log(`<div class="error">All promises failed!</div>`);
promiseConsole.error(error);
});
});
Unlike Promise.all() which fails fast if any promise rejects, Promise.allSettled() always waits for all promises to complete, giving you full control over how to handle both successes and failures.
BigInt: Handling Large Numbers#
A regular JavaScript number is a 64-bit floating-point value, so whole numbers are only exact up to Number.MAX_SAFE_INTEGER (9007199254740991). Past that, neighboring integers collapse together:
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992, the same value, which is wrong
BigInt is a separate type for whole numbers of any size. Add an n after the digits, or call BigInt():
const bigNumber = 9007199254740991n; // the 'n' suffix makes it a BigInt
const alsoBig = BigInt("9007199254740991");
console.log(bigNumber + 1n); // 9007199254740992n
console.log(bigNumber + 2n); // 9007199254740993n, still exact
console.log(typeof bigNumber); // 'bigint'
// Common use case: large IDs
const userId = 9007199254740995n;
console.log(userId + 1n); // 9007199254740996n
BigInt and regular numbers are different types, and JavaScript refuses to guess how to combine them:
console.log(1n + 1);
// TypeError: Cannot mix BigInt and other types, use explicit conversions
Convert one side explicitly. Use BigInt(1) to stay exact, or Number(1n) if you accept a regular number again:
console.log(1n + BigInt(1)); // 2n
console.log(Number(1n) + 1); // 2
console.log(7n / 2n); // 3n, BigInt division drops the remainder
---html
<div class="container">
<h1>BigInt Operations Explorer</h1>
<p>ES11 introduced the BigInt primitive type to JavaScript, enabling developers to represent and manipulate integers larger than 2^53 - 1 (Number.MAX_SAFE_INTEGER). This interactive demo showcases BigInt operations and how they differ from regular numbers.</p>
<div class="tabs">
<div class="tab active" data-tab="basics">Basics & Operations</div>
<div class="tab" data-tab="comparison">Comparing BigInt & Number</div>
<div class="tab" data-tab="realworld">Real-World Examples</div>
</div>
<!-- BigInt Basics Tab -->
<div id="basics-tab" class="tab-content active">
<div class="card">
<h2>BigInt Basics</h2>
<p>BigInt is a built-in object that provides a way to represent whole numbers larger than 2^53 - 1 (9,007,199,254,740,991), which is the largest number JavaScript can reliably represent with the Number primitive.</p>
<div class="code-block">
<pre><code>// Creating BigInts
const a = 9007199254740991n; // Literal with 'n' suffix
const b = BigInt(9007199254740991); // Using BigInt() function
const c = BigInt("9007199254740991"); // From string
// Basic operations
const sum = a + b;
const product = a * b;
const quotient = a / b;
const remainder = a % b;
const power = a ** 2n;</code></pre>
</div>
<h3>Try Basic Operations</h3>
<div class="flex-container">
<div class="flex-item">
<div class="form-group">
<label for="bigint1">BigInt Value 1:</label>
<input type="text" id="bigint1" value="9007199254740991">
</div>
<div class="form-group">
<label for="bigint2">BigInt Value 2:</label>
<input type="text" id="bigint2" value="9007199254740992">
</div>
<button id="runBasicOps">Run Basic Operations</button>
</div>
<div class="flex-item">
<div class="output" id="basicsOutput"></div>
</div>
</div>
<h3>BigInt Limitations</h3>
<ul>
<li>Cannot mix BigInt with Number in operations</li>
<li>No support for Math object methods</li>
<li>Cannot use unary plus (+) operator</li>
<li>Decimal points are truncated when converting to BigInt</li>
</ul>
<button id="showLimitations">Show Limitations Examples</button>
<div class="output" id="limitationsOutput"></div>
</div>
</div>
<!-- BigInt vs Number Comparison Tab -->
<div id="comparison-tab" class="tab-content">
<div class="card">
<h2>BigInt vs. Number</h2>
<p>See the difference between BigInt and Number when working with large integers. The demonstration below shows where regular numbers start to lose precision, while BigInt maintains accuracy.</p>
<h3>Integer Precision Demonstration</h3>
<div class="code-block">
<pre><code>// Adding 1 to MAX_SAFE_INTEGER
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (Incorrect!)
console.log(Number.MAX_SAFE_INTEGER + 3); // 9007199254740994 (Incorrect!)
// With BigInt, precision is maintained
console.log(BigInt(Number.MAX_SAFE_INTEGER)); // 9007199254740991n
console.log(BigInt(Number.MAX_SAFE_INTEGER) + 1n); // 9007199254740992n
console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n); // 9007199254740993n
console.log(BigInt(Number.MAX_SAFE_INTEGER) + 3n); // 9007199254740994n</code></pre>
</div>
<button id="runPrecisionDemo">Run Precision Demo</button>
<div class="output" id="precisionOutput"></div>
<h3>Arithmetic Operation Comparison</h3>
<div class="flex-container">
<div class="flex-item">
<div class="form-group">
<label for="largeNumber">Enter a Large Number:</label>
<input type="text" id="largeNumber" value="9007199254740991">
</div>
<div class="form-group">
<label for="operation">Operation:</label>
<select id="operation" style="padding: 8px; border-radius: 4px; border: 1px solid var(--border-color); width: 100%; background-color: var(--bg-color); color: var(--text-color);">
<option value="add">Add</option>
<option value="subtract">Subtract</option>
<option value="multiply">Multiply</option>
<option value="divide">Divide</option>
<option value="power">Power</option>
</select>
</div>
<div class="form-group">
<label for="operand">Operand:</label>
<input type="number" id="operand" value="123">
</div>
<button id="runComparisonOps">Run Comparison</button>
</div>
<div class="flex-item">
<div class="output" id="comparisonOutput"></div>
</div>
</div>
</div>
</div>
<!-- Real-World Usage Tab -->
<div id="realworld-tab" class="tab-content">
<div class="card">
<h2>Real-World BigInt Applications</h2>
<p>BigInt is particularly useful in scenarios where precise integer calculations with large numbers are crucial.</p>
<div class="example">
<div class="example-title">Example 1: ID Management in Large Systems</div>
<div class="code-block">
<pre><code>// Working with database IDs larger than MAX_SAFE_INTEGER
const userId = 9007199254740995n;
const nextId = userId + 1n;
// Formatting for display (converts to string)
console.log(`User ID: ${userId}`);
console.log(`Next available ID: ${nextId}`);</code></pre>
</div>
<button class="runExample" data-example="ids">Run Example</button>
</div>
<div class="example">
<div class="example-title">Example 2: Financial Calculations</div>
<div class="code-block">
<pre><code>// Financial calculations in cents to avoid floating point issues
const accountBalance = BigInt(1000000000000000); // $10 trillion in cents
const interestRate = 5n; // 5%
const years = 10n;
// Simple interest calculation
const interest = (accountBalance * interestRate * years) / 100n;
const newBalance = accountBalance + interest;
// Format for display (converting back to dollars)
function formatAsMoney(bigIntCents) {
const centString = bigIntCents.toString();
const dollars = centString.slice(0, -2) || '0';
const cents = centString.slice(-2).padStart(2, '0');
return `$${dollars}.${cents}`;
}</code></pre>
</div>
<button class="runExample" data-example="finance">Run Example</button>
</div>
<div class="example">
<div class="example-title">Example 3: Cryptography & Hashing</div>
<div class="code-block">
<pre><code>// Working with cryptographic values
const prime1 = 9007199254740881n;
const prime2 = 9007199254740991n;
const composite = prime1 * prime2;
// Simple modular exponentiation (used in cryptography)
function modPow(base, exponent, modulus) {
let result = 1n;
base = base % modulus;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % modulus;
}
exponent = exponent / 2n;
base = (base * base) % modulus;
}
return result;
}</code></pre>
</div>
<button class="runExample" data-example="crypto">Run Example</button>
</div>
<div class="output" id="realworldOutput"></div>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #f5f5f5;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #e9ecef;
--border-color: #dee2e6;
--highlight: #8a2be2;
--green: #28a745;
--yellow: #ffc107;
--red: #dc3545;
--blue: #0d6efd;
--section-bg: rgba(0, 0, 0, 0.03);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #2d2d2d;
--border-color: #444;
--highlight: #bb86fc;
--green: #4caf50;
--yellow: #ffeb3b;
--red: #f44336;
--blue: #2196f3;
--section-bg: rgba(255, 255, 255, 0.05);
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
h1, h2, h3 {
color: var(--highlight);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.card {
background-color: var(--section-bg);
border-radius: 8px;
padding: 20px;
margin-bottom: 25px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
margin-right: 5px;
border-radius: 5px 5px 0 0;
}
.tab.active {
background-color: var(--button-bg);
color: var(--button-text);
border-color: var(--border-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 5px;
overflow-x: auto;
border: 1px solid var(--border-color);
margin-bottom: 15px;
}
pre {
margin: 0;
font-size: 14px;
}
code {
font-family: 'Fira Code', monospace;
}
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
margin-bottom: 10px;
}
button:hover {
opacity: 0.9;
}
.output {
background-color: var(--output-bg);
padding: 15px;
border-radius: 5px;
margin-top: 15px;
min-height: 100px;
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border-color);
}
.highlight {
color: var(--highlight);
font-weight: bold;
}
.success {
color: var(--green);
}
.warning {
color: var(--yellow);
}
.error {
color: var(--red);
}
.info {
color: var(--blue);
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="number"], input[type="text"] {
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
width: 100%;
background-color: var(--bg-color);
color: var(--text-color);
}
.flex-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.flex-item {
flex: 1;
min-width: 250px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
padding: 8px 12px;
text-align: left;
border: 1px solid var(--border-color);
}
th {
background-color: var(--section-bg);
font-weight: bold;
}
.example {
margin-bottom: 20px;
padding: 15px;
background-color: var(--section-bg);
border-radius: 5px;
}
.example-title {
font-weight: bold;
margin-bottom: 10px;
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${tab.dataset.tab}-tab`).classList.add('active');
});
});
// Helper function to create logger for output divs
function createLogger(outputElement) {
return {
log: function (message, className = '') {
const div = document.createElement('div');
div.innerHTML = message;
if (className) div.className = className;
outputElement.appendChild(div);
},
clear: function () {
outputElement.innerHTML = '';
} };
}
// Basics tab functionality
document.getElementById('runBasicOps').addEventListener('click', function () {
const output = document.getElementById('basicsOutput');
const logger = createLogger(output);
logger.clear();
try {
// Get user inputs
const val1 = document.getElementById('bigint1').value;
const val2 = document.getElementById('bigint2').value;
// Convert to BigInt
const a = BigInt(val1);
const b = BigInt(val2);
logger.log(`<b>Input Values:</b>`);
logger.log(`a = ${a}`);
logger.log(`b = ${b}`);
logger.log(`<br><b>Basic Operations:</b>`);
logger.log(`Addition (a + b): ${a + b}`);
logger.log(`Subtraction (a - b): ${a - b}`);
logger.log(`Multiplication (a * b): ${a * b}`);
logger.log(`Division (a / b): ${a / b} <span class="info">(note: division truncates toward zero)</span>`);
logger.log(`Remainder (a % b): ${a % b}`);
logger.log(`Exponentiation (a ** 2n): ${a ** 2n}`);
logger.log(`<br><b>Creating BigInts:</b>`);
logger.log(`Using n suffix: ${1234567890123456789012345678901234567890n}`);
logger.log(`Using BigInt(): ${BigInt("1234567890123456789012345678901234567890")}`);
} catch (error) {
logger.log(`<span class="error">Error: ${error.message}</span>`);
logger.log(`<span class="info">Make sure your inputs are valid integers without decimals</span>`);
}
});
document.getElementById('showLimitations').addEventListener('click', function () {
const output = document.getElementById('limitationsOutput');
const logger = createLogger(output);
logger.clear();
logger.log(`<b>Limitation 1: Cannot mix BigInt and Number</b>`);
try {
const big = 10n;
const num = 10;
const result = big + num; // This will throw a TypeError
} catch (error) {
logger.log(`<code>const big = 10n;\nconst num = 10;\nconst result = big + num;</code>`);
logger.log(`<span class="error">Error: ${error.message}</span>`);
}
logger.log(`<span class="success">Correct way: Explicit conversion</span>`);
logger.log(`<code>const result = big + BigInt(num);</code>`);
logger.log(`Result: ${10n + BigInt(10)}`);
logger.log(`<br><b>Limitation 2: No Math object methods</b>`);
try {
const big = 10n;
const result = Math.sqrt(big); // This will not work as expected
} catch (error) {
logger.log(`<code>const big = 10n;\nconst result = Math.sqrt(big);</code>`);
logger.log(`<span class="error">Error: ${error.message}</span>`);
}
logger.log(`<span class="info">Workaround: Convert to Number (if safe) or implement custom functions</span>`);
logger.log(`<code>// Custom square root approximation for BigInt\nfunction bigIntSqrt(value) {\n if (value < 0n) throw new Error("Square root of negative numbers not supported");\n if (value < 2n) return value;\n \n // Initial approximation\n let x = value / 2n;\n let y = (x + value / x) / 2n;\n \n while (y < x) {\n x = y;\n y = (x + value / x) / 2n;\n }\n \n return x;\n}</code>`);
logger.log(`<br><b>Limitation 3: Decimal truncation</b>`);
logger.log(`<code>const truncated = BigInt(123.45);</code>`);
try {
const truncated = BigInt(123.45);
logger.log(`<span class="error">This throws a RangeError: ${new RangeError("The number 123.45 cannot be converted to a BigInt because it is not an integer").message}</span>`);
} catch (error) {
logger.log(`<span class="error">Error: ${error.message}</span>`);
}
logger.log(`<br><b>Limitation 4: No unary plus</b>`);
try {
const big = 10n;
const result = +big; // Will throw TypeError
} catch (error) {
logger.log(`<code>const big = 10n;\nconst result = +big;</code>`);
logger.log(`<span class="error">Error: ${error.message}</span>`);
}
});
// Comparison tab functionality
document.getElementById('runPrecisionDemo').addEventListener('click', function () {
const output = document.getElementById('precisionOutput');
const logger = createLogger(output);
logger.clear();
const MAX_SAFE = Number.MAX_SAFE_INTEGER;
logger.log(`<b>Number.MAX_SAFE_INTEGER:</b> ${MAX_SAFE}`);
logger.log(`<br><b>Using regular Number:</b>`);
logger.log(`MAX_SAFE_INTEGER + 1: ${MAX_SAFE + 1}`);
logger.log(`MAX_SAFE_INTEGER + 2: ${MAX_SAFE + 2} <span class="error">(Precision lost!)</span>`);
logger.log(`MAX_SAFE_INTEGER + 3: ${MAX_SAFE + 3} <span class="error">(Precision lost!)</span>`);
logger.log(`MAX_SAFE_INTEGER + 4: ${MAX_SAFE + 4} <span class="error">(Precision lost!)</span>`);
logger.log(`<br><b>Using BigInt:</b>`);
const MAX_SAFE_BIG = BigInt(MAX_SAFE);
logger.log(`BigInt(MAX_SAFE_INTEGER): ${MAX_SAFE_BIG}`);
logger.log(`BigInt(MAX_SAFE_INTEGER) + 1n: ${MAX_SAFE_BIG + 1n} <span class="success">(Precise!)</span>`);
logger.log(`BigInt(MAX_SAFE_INTEGER) + 2n: ${MAX_SAFE_BIG + 2n} <span class="success">(Precise!)</span>`);
logger.log(`BigInt(MAX_SAFE_INTEGER) + 3n: ${MAX_SAFE_BIG + 3n} <span class="success">(Precise!)</span>`);
logger.log(`BigInt(MAX_SAFE_INTEGER) + 4n: ${MAX_SAFE_BIG + 4n} <span class="success">(Precise!)</span>`);
// Large multiplication comparison
const largeNum = 12345678901234567n;
logger.log(`<br><b>Large Multiplication:</b>`);
logger.log(`BigInt: ${largeNum} ร ${largeNum} = ${largeNum * largeNum}`);
const approxNum = Number(largeNum);
logger.log(`Number: ${approxNum} ร ${approxNum} = ${approxNum * approxNum} <span class="error">(Precision lost!)</span>`);
});
document.getElementById('runComparisonOps').addEventListener('click', function () {
const output = document.getElementById('comparisonOutput');
const logger = createLogger(output);
logger.clear();
try {
const largeNumber = document.getElementById('largeNumber').value;
const operation = document.getElementById('operation').value;
const operand = document.getElementById('operand').value;
// Convert to both number and BigInt
const asNumber = Number(largeNumber);
const asBigInt = BigInt(largeNumber);
const operandNum = Number(operand);
const operandBig = BigInt(operand);
logger.log(`<b>Input:</b> ${largeNumber} ${operation} ${operand}`);
logger.log(`<b>As Number:</b> ${asNumber}`);
logger.log(`<b>As BigInt:</b> ${asBigInt}`);
let numberResult, bigIntResult;
switch (operation) {
case 'add':
numberResult = asNumber + operandNum;
bigIntResult = asBigInt + operandBig;
break;
case 'subtract':
numberResult = asNumber - operandNum;
bigIntResult = asBigInt - operandBig;
break;
case 'multiply':
numberResult = asNumber * operandNum;
bigIntResult = asBigInt * operandBig;
break;
case 'divide':
numberResult = asNumber / operandNum;
bigIntResult = asBigInt / operandBig;
break;
case 'power':
numberResult = asNumber ** operandNum;
bigIntResult = asBigInt ** operandBig;
break;}
logger.log(`<br><b>Result as Number:</b> ${numberResult}`);
logger.log(`<b>Result as BigInt:</b> ${bigIntResult}`);
// Check if precision was lost
if (String(numberResult).replace('.', '') !== String(bigIntResult).replace('n', '')) {
logger.log(`<span class="warning">Number and BigInt results differ! This likely indicates precision loss with the Number type.</span>`);
} else {
logger.log(`<span class="success">Results match! Both types handled this calculation accurately.</span>`);
}
} catch (error) {
logger.log(`<span class="error">Error: ${error.message}</span>`);
}
});
// Real-world examples
document.querySelectorAll('.runExample').forEach(button => {
button.addEventListener('click', function () {
const exampleType = button.dataset.example;
const output = document.getElementById('realworldOutput');
const logger = createLogger(output);
logger.clear();
switch (exampleType) {
case 'ids':
// ID management example
const userId = 9007199254740995n;
const nextId = userId + 1n;
logger.log(`<b>ID Management Example:</b>`);
logger.log(`User ID: ${userId}`);
logger.log(`Next available ID: ${nextId}`);
// Compare with Number
logger.log(`<br><b>Comparing with Number type:</b>`);
const userIdAsNumber = Number(userId);
const nextIdAsNumber = userIdAsNumber + 1;
logger.log(`User ID as Number: ${userIdAsNumber}`);
logger.log(`Next ID as Number: ${nextIdAsNumber}`);
if (String(nextIdAsNumber) !== String(nextId).replace('n', '')) {
logger.log(`<span class="error">PRECISION LOSS DETECTED!</span> The Number type cannot accurately represent this ID increment.`);
}
break;
case 'finance':
// Financial calculations
const accountBalance = BigInt(1000000000000000); // $10 trillion in cents
const interestRate = 5n; // 5%
const years = 10n;
// Simple interest calculation
const interest = accountBalance * interestRate * years / 100n;
const newBalance = accountBalance + interest;
logger.log(`<b>Financial Calculation Example:</b>`);
logger.log(`Initial balance: $${(accountBalance / 100n).toString()}.00`);
logger.log(`Interest rate: ${interestRate}%`);
logger.log(`Time period: ${years} years`);
logger.log(`Interest earned: $${(interest / 100n).toString()}.00`);
logger.log(`New balance: $${(newBalance / 100n).toString()}.00`);
// Compare with floating point
logger.log(`<br><b>Comparing with Number type:</b>`);
const balanceAsNumber = Number(accountBalance) / 100; // Convert to dollars
const interestAsNumber = balanceAsNumber * 0.05 * 10;
const newBalanceAsNumber = balanceAsNumber + interestAsNumber;
logger.log(`Interest calculated with Number: $${interestAsNumber.toFixed(2)}`);
logger.log(`New balance with Number: $${newBalanceAsNumber.toFixed(2)}`);
if (newBalanceAsNumber.toString().split('.')[0] !== (newBalance / 100n).toString()) {
logger.log(`<span class="error">PRECISION DIFFERENCE DETECTED!</span> The results are slightly different due to floating-point precision issues.`);
}
break;
case 'crypto':
// Cryptography example
const prime1 = 9007199254740881n;
const prime2 = 9007199254740991n;
const composite = prime1 * prime2;
// Simple modular exponentiation function
function modPow(base, exponent, modulus) {
let result = 1n;
base = base % modulus;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = result * base % modulus;
}
exponent = exponent / 2n;
base = base * base % modulus;
}
return result;
}
logger.log(`<b>Cryptography Example:</b>`);
logger.log(`Prime 1: ${prime1}`);
logger.log(`Prime 2: ${prime2}`);
logger.log(`Composite (product): ${composite}`);
// Calculate a simple public/private key operation
const publicKey = 65537n; // Common RSA public exponent
const message = 12345678901234567890n;
const encrypted = modPow(message, publicKey, composite);
logger.log(`<br><b>Message:</b> ${message}`);
logger.log(`<b>Public Key:</b> ${publicKey}`);
logger.log(`<b>Encrypted:</b> ${encrypted}`);
logger.log(`<br><span class="info">This is a simplified example. Real cryptographic operations are more complex, but BigInt makes them possible in JavaScript.</span>`);
// Compare with Number
logger.log(`<br><b>Why Number type isn't suitable:</b>`);
logger.log(`Composite value length: ${composite.toString().length} digits`);
logger.log(`Maximum safe integer in JavaScript: ${Number.MAX_SAFE_INTEGER} (${Number.MAX_SAFE_INTEGER.toString().length} digits)`);
logger.log(`<span class="error">These calculations would lose precision with Number type.</span>`);
break;}
});
});
Dynamic Imports: Loading Code On Demand#
Advanced, optional. A normal import at the top of a file always loads that module up front. The import() function loads one later, when your code asks for it, and returns a promise for the module:
// Works in Node ESM or a browser module
const path = await import('node:path');
console.log(typeof path.join); // 'function'
That lets you load heavy code only when it is needed, for example only for users who choose a dark theme:
// ./themes/dark.js is a placeholder for a file you would write
async function loadTheme(theme) {
try {
if (theme === 'dark') {
const { darkTheme } = await import('./themes/dark.js');
darkTheme.apply();
}
} catch (error) {
console.error('Error loading theme:', error);
}
}
---html
<div class="container">
<h1>Dynamic vs Static Imports Performance Explorer</h1>
<p>This interactive demo showcases the performance benefits of dynamic imports (introduced in ES11) compared to traditional static imports. Explore how and when to use dynamic imports for optimized loading and better user experience.</p>
<div class="tabs">
<div class="tab active" data-tab="concept">Concept & Basics</div>
<div class="tab" data-tab="demo">Interactive Demo</div>
<div class="tab" data-tab="realworld">Real-World Scenarios</div>
</div>
<!-- Concept & Basics Tab -->
<div id="concept-tab" class="tab-content active">
<div class="card">
<h2>Understanding Import Types</h2>
<div class="flex-container">
<div class="flex-item">
<h3>Static Imports</h3>
<div class="code-block">
<pre><code>// Static imports are loaded and evaluated at parse time
import { sum } from './math.js';
import * as utils from './utils.js';
import Chart from './chart.js';
// Code execution starts after all imports are loaded
console.log('Application started');
sum(5, 10);</code></pre>
</div>
<p>Static imports:</p>
<ul>
<li>Load before any code executes</li>
<li>Block execution until all dependencies are loaded</li>
<li>Cannot be conditionally imported</li>
<li>Are always hoisted to the top</li>
</ul>
</div>
<div class="flex-item">
<h3>Dynamic Imports</h3>
<div class="code-block">
<pre><code>// Dynamic imports are loaded on demand
console.log('Application started');
// Import only when needed
if (userClickedChart) {
import('./chart.js')
.then(module => {
const Chart = module.default;
const chart = new Chart();
chart.render();
})
.catch(error => {
console.error('Failed to load chart module');
});
}</code></pre>
</div>
<p>Dynamic imports:</p>
<ul>
<li>Load on demand when the import() is executed</li>
<li>Return a Promise that resolves to the module</li>
<li>Can be used conditionally (if/else)</li>
<li>Allow parallel loading of independent modules</li>
</ul>
</div>
</div>
<h3>How Import Types Affect Loading</h3>
<div class="module-box">
<h4>Static Import Loading Sequence</h4>
<ol>
<li>Browser discovers all import statements during parsing</li>
<li>All imports are loaded and evaluated sequentially</li>
<li>Application code execution begins only after all imports are complete</li>
<li>If an import fails, the entire application fails to load</li>
</ol>
<div class="code-block">
<pre><code>import moduleA from './moduleA.js'; // 200ms load time
import moduleB from './moduleB.js'; // 500ms load time
import moduleC from './moduleC.js'; // 300ms load time
// This code waits 1000ms (total) before executing
console.log('Application ready');</code></pre>
</div>
</div>
<div class="module-box">
<h4>Dynamic Import Loading Sequence</h4>
<ol>
<li>Application code begins executing immediately</li>
<li>Modules are loaded only when import() is called</li>
<li>Multiple dynamic imports can load in parallel</li>
<li>Critical path code doesn't wait for non-critical modules</li>
</ol>
<div class="code-block">
<pre><code>// Application starts immediately
console.log('Application started');
// Load only what's needed for the initial view
import('./moduleA.js')
.then(moduleA => {
// Initialize core functionality
});
// These modules load only when needed
button.addEventListener('click', () => {
import('./moduleB.js')
.then(moduleB => {
// Feature activated on demand
});
});</code></pre>
</div>
</div>
</div>
<div class="card">
<h2>Performance Comparison</h2>
<div class="feature-comparison">
<div class="feature-box">
<div class="feature-title">Static Imports</div>
<ul class="feature-list">
<li>Simpler syntax for essential dependencies</li>
<li>Better for small applications with few dependencies</li>
<li>Enables static analysis and better tree-shaking</li>
<li>Dependencies are guaranteed to be available when needed</li>
<li>Better IDE support with auto-completion and type checking</li>
</ul>
</div>
<div class="feature-box">
<div class="feature-title">Dynamic Imports</div>
<ul class="feature-list">
<li>Faster initial page load (load only what's needed)</li>
<li>Reduced memory usage (load only when required)</li>
<li>Enables code-splitting and lazy loading</li>
<li>Allows conditional loading based on runtime conditions</li>
<li>Isolates module loading errors to specific features</li>
</ul>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Metric</th>
<th>Static Imports</th>
<th>Dynamic Imports</th>
</tr>
</thead>
<tbody>
<tr>
<td>Initial Page Load</td>
<td>Slower (loads everything upfront)</td>
<td>Faster (loads only essentials)</td>
</tr>
<tr>
<td>Memory Usage</td>
<td>Higher (all modules loaded)</td>
<td>Lower (only loaded when needed)</td>
</tr>
<tr>
<td>Feature Access Speed</td>
<td>Faster (already loaded)</td>
<td>Slightly delayed (load on demand)</td>
</tr>
<tr>
<td>Code Organization</td>
<td>Simpler for small applications</td>
<td>Better for large applications</td>
</tr>
<tr>
<td>Error Isolation</td>
<td>Poor (one failed import breaks everything)</td>
<td>Good (errors isolated to specific features)</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Interactive Demo Tab -->
<div id="demo-tab" class="tab-content">
<div class="card">
<h2>Interactive Performance Comparison</h2>
<p>This demo simulates loading a web application with different modules using both static and dynamic imports. Watch how loading times and rendering speed differ between the approaches.</p>
<div class="scenario-selector">
<label for="scenario">Choose a scenario:</label>
<select id="scenario">
<option value="balanced">Balanced Application (5 modules)</option>
<option value="large">Large Application (10 modules)</option>
<option value="critical">Critical Path Rendering (3 core + 7 optional modules)</option>
</select>
</div>
<div class="flex-container">
<div class="flex-item">
<h3>Static Import Approach</h3>
<button id="runStatic">Run Static Import Simulation</button>
<div class="module-box" id="staticModules">
<h4>Modules to Import</h4>
<div id="staticModulesList">Select a scenario to see modules</div>
</div>
<div>
<p><strong>Progress:</strong></p>
<div class="progress-container">
<div class="progress-bar" id="staticProgress">
<span class="progress-text">0%</span>
</div>
</div>
</div>
<div class="output" id="staticOutput"></div>
</div>
<div class="flex-item">
<h3>Dynamic Import Approach</h3>
<button id="runDynamic">Run Dynamic Import Simulation</button>
<div class="module-box" id="dynamicModules">
<h4>Modules to Import</h4>
<div id="dynamicModulesList">Select a scenario to see modules</div>
</div>
<div>
<p><strong>Progress:</strong></p>
<div class="progress-container">
<div class="progress-bar" id="dynamicProgress">
<span class="progress-text">0%</span>
</div>
</div>
</div>
<div class="output" id="dynamicOutput"></div>
</div>
</div>
<div>
<h3>Performance Results</h3>
<button id="compareResults" disabled>Compare Results</button>
<div class="chart-container" id="resultsChart">
<div class="bar" id="staticTimeBar" style="left: 140px; background-color: var(--chart-color-1);">
<div class="bar-value" id="staticTimeValue">0ms</div>
<div class="bar-label">Total Load Time</div>
</div>
<div class="bar" id="dynamicTimeBar" style="left: 280px; background-color: var(--chart-color-2);">
<div class="bar-value" id="dynamicTimeValue">0ms</div>
<div class="bar-label">Total Load Time</div>
</div>
<div class="bar" id="staticRenderBar" style="left: 420px; background-color: var(--chart-color-3);">
<div class="bar-value" id="staticRenderValue">0ms</div>
<div class="bar-label">Time to First Render</div>
</div>
<div class="bar" id="dynamicRenderBar" style="left: 560px; background-color: var(--chart-color-4);">
<div class="bar-value" id="dynamicRenderValue">0ms</div>
<div class="bar-label">Time to First Render</div>
</div>
</div>
<div class="chart-legend">
<div class="legend-item">
<div class="legend-color" style="background-color: var(--chart-color-1);"></div>
<span>Static Total Time</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: var(--chart-color-2);"></div>
<span>Dynamic Total Time</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: var(--chart-color-3);"></div>
<span>Static Render Time</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: var(--chart-color-4);"></div>
<span>Dynamic Render Time</span>
</div>
</div>
</div>
</div>
</div>
<!-- Real-World Scenarios Tab -->
<div id="realworld-tab" class="tab-content">
<div class="card">
<h2>Real-World Use Cases</h2>
<p>Dynamic imports shine in several common web development scenarios. Here are some practical examples of when and how to use them:</p>
<div class="module-box">
<h4>1. Route-Based Code Splitting</h4>
<p>Load code only for the current route in a single-page application.</p>
<div class="code-block">
<pre><code>// router.js
const routes = {
'/home': () => import('./pages/home.js'),
'/about': () => import('./pages/about.js'),
'/products': () => import('./pages/products.js'),
'/contact': () => import('./pages/contact.js')
};
async function renderPage(path) {
try {
const page = await routes[path]();
page.default.render();
} catch (error) {
console.error('Failed to load page:', error);
// Show error page
}
}</code></pre>
</div>
<p><strong>Benefits:</strong> Each page loads only its required code, reducing initial bundle size. Users download only what they need.</p>
</div>
<div class="module-box">
<h4>2. Feature-Based Loading</h4>
<p>Load advanced features only when users access them.</p>
<div class="code-block">
<pre><code>// In a dashboard application
document.querySelector('#showCharts').addEventListener('click', async () => {
try {
// Load chart library and data processing modules on demand
const [chartModule, dataModule] = await Promise.all([
import('./components/charts.js'),
import('./data/processor.js')
]);
const { renderChart } = chartModule;
const { processData } = dataModule;
const data = await processData();
renderChart(data);
} catch (error) {
console.error('Failed to load chart feature:', error);
showFallbackView();
}
});</code></pre>
</div>
<p><strong>Benefits:</strong> Heavy features load only when needed, keeping the core application lightweight and fast.</p>
</div>
<div class="module-box">
<h4>3. Conditional Loading Based on User Device/Browser</h4>
<p>Load different implementations based on browser support or device type.</p>
<div class="code-block">
<pre><code>// Conditionally load modules based on browser support
async function loadAppropriateVideoPlayer() {
if (supportsWebRTC()) {
const { WebRTCPlayer } = await import('./players/webrtc-player.js');
return new WebRTCPlayer();
} else if (supportsHLS()) {
const { HLSPlayer } = await import('./players/hls-player.js');
return new HLSPlayer();
} else {
const { FallbackPlayer } = await import('./players/fallback-player.js');
return new FallbackPlayer();
}
}</code></pre>
</div>
<p><strong>Benefits:</strong> Users only download code relevant to their device or browser, reducing bundle size and improving performance.</p>
</div>
<div class="module-box">
<h4>4. Loading Resources On User Interaction</h4>
<p>Load heavy components only after user interaction.</p>
<div class="code-block">
<pre><code>// Load rich text editor only when user focuses on comment field
const commentField = document.querySelector('#commentField');
commentField.addEventListener('focus', async () => {
if (!window.richEditor) {
const editorModule = await import('./editor/rich-editor.js');
window.richEditor = new editorModule.RichEditor(commentField);
}
});</code></pre>
</div>
<p><strong>Benefits:</strong> Initial page load is faster as complex components load only when needed.</p>
</div>
<div class="module-box">
<h4>5. Progressive Enhancement</h4>
<p>Enhance the application with non-critical features after core functionality is loaded.</p>
<div class="code-block">
<pre><code>// Load critical path first
import { renderPage, setupNavigation } from './core.js';
// Initialize core application immediately
renderPage();
setupNavigation();
// Then enhance with non-critical features
if (navigator.onLine) {
// These won't block initial rendering
Promise.all([
import('./features/analytics.js'),
import('./features/social-sharing.js'),
import('./features/theme-customizer.js')
]).then(([analytics, social, themes]) => {
analytics.init();
social.setupSharingButtons();
themes.initThemeSelector();
});
}</code></pre>
</div>
<p><strong>Benefits:</strong> Core functionality loads quickly while enhancements load in the background without delaying the initial experience.</p>
</div>
</div>
<div class="card">
<h2>Best Practices</h2>
<div class="flex-container">
<div class="flex-item">
<h3>When to Use Static Imports</h3>
<ul>
<li>For small applications with few dependencies</li>
<li>For critical modules needed on every page</li>
<li>When you need static analysis and tree-shaking</li>
<li>For utility functions used throughout the application</li>
<li>When IDE auto-completion is important for development</li>
</ul>
</div>
<div class="flex-item">
<h3>When to Use Dynamic Imports</h3>
<ul>
<li>For route-based code splitting in SPAs</li>
<li>For large libraries only used in specific features</li>
<li>For conditionally loaded features</li>
<li>When optimizing for fast initial page load</li>
<li>When different users need different modules</li>
</ul>
</div>
</div>
<div class="module-box">
<h4>Performance Tips</h4>
<ol>
<li><strong>Preload critical dynamic imports</strong> using the <code><link rel="preload"></code> tag for modules you're confident will be needed soon.</li>
<li><strong>Use Promise.all</strong> for parallel loading of multiple independent modules.</li>
<li><strong>Consider a hybrid approach</strong> - use static imports for core functionality and dynamic imports for features.</li>
<li><strong>Add error handling</strong> for dynamic imports to gracefully recover from network failures.</li>
<li><strong>Monitor module sizes</strong> and split large modules into smaller ones that can be loaded independently.</li>
</ol>
</div>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #f5f5f5;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #e9ecef;
--border-color: #dee2e6;
--highlight: #8a2be2;
--green: #28a745;
--yellow: #ffc107;
--red: #dc3545;
--blue: #0d6efd;
--card-bg: #ffffff;
--section-bg: rgba(0, 0, 0, 0.03);
--chart-color-1: #4285f4;
--chart-color-2: #34a853;
--chart-color-3: #fbbc05;
--chart-color-4: #ea4335;
--chart-color-5: #8a2be2;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #2d2d2d;
--border-color: #444;
--highlight: #bb86fc;
--green: #4caf50;
--yellow: #ffeb3b;
--red: #f44336;
--blue: #2196f3;
--card-bg: #1e1e1e;
--section-bg: rgba(255, 255, 255, 0.05);
--chart-color-1: #8ab4f8;
--chart-color-2: #81c995;
--chart-color-3: #fde293;
--chart-color-4: #f28b82;
--chart-color-5: #d7aefb;
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
h1, h2, h3 {
color: var(--highlight);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 30px;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
margin-right: 5px;
border-radius: 5px 5px 0 0;
background-color: var(--section-bg);
}
.tab.active {
background-color: var(--button-bg);
color: var(--button-text);
}
.tab-content {
display: none;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.tab-content.active {
display: block;
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin-bottom: 20px;
border: 1px solid var(--border-color);
}
pre {
margin: 0;
font-size: 14px;
}
code {
font-family: 'Fira Code', monospace;
}
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
margin-bottom: 10px;
transition: opacity 0.2s;
}
button:hover {
opacity: 0.9;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.output {
background-color: var(--output-bg);
padding: 15px;
border-radius: 5px;
margin-top: 15px;
min-height: 100px;
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border-color);
}
.highlight {
color: var(--highlight);
font-weight: bold;
}
.success {
color: var(--green);
font-weight: bold;
}
.warning {
color: var(--yellow);
font-weight: bold;
}
.error {
color: var(--red);
font-weight: bold;
}
.info {
color: var(--blue);
font-weight: bold;
}
.flex-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.flex-item {
flex: 1;
min-width: 300px;
}
.chart-container {
width: 100%;
height: 300px;
margin: 20px 0;
position: relative;
}
.bar {
position: absolute;
bottom: 0;
width: 80px;
background-color: var(--button-bg);
transition: height 0.5s;
border-radius: 5px 5px 0 0;
}
.bar-label {
position: absolute;
top: -25px;
width: 100%;
text-align: center;
font-size: 14px;
}
.bar-value {
position: absolute;
top: -50px;
width: 100%;
text-align: center;
font-weight: bold;
font-size: 16px;
}
.chart-legend {
display: flex;
justify-content: center;
margin-top: 20px;
}
.legend-item {
display: flex;
align-items: center;
margin: 0 10px;
}
.legend-color {
width: 15px;
height: 15px;
margin-right: 5px;
border-radius: 3px;
}
.progress-container {
width: 100%;
height: 20px;
background-color: var(--section-bg);
border-radius: 10px;
margin: 10px 0;
overflow: hidden;
}
.progress-bar {
height: 100%;
width: 0;
background-color: var(--button-bg);
transition: width 0.3s;
position: relative;
}
.progress-text {
position: absolute;
right: 10px;
color: white;
font-size: 12px;
line-height: 20px;
}
.module-box {
padding: 15px;
margin: 15px 0;
border-radius: 5px;
background-color: var(--section-bg);
}
.module-box h4 {
margin-top: 0;
}
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 10px;
text-align: left;
border: 1px solid var(--border-color);
}
th {
background-color: var(--section-bg);
}
.feature-comparison {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 20px 0;
}
.feature-box {
background-color: var(--section-bg);
padding: 15px;
border-radius: 5px;
}
.feature-title {
font-weight: bold;
margin-bottom: 10px;
color: var(--highlight);
}
.feature-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.feature-list li {
padding: 5px 0;
position: relative;
padding-left: 25px;
}
.feature-list li:before {
content: 'โ';
color: var(--green);
position: absolute;
left: 5px;
}
.scenario-selector {
margin-bottom: 20px;
}
select {
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--bg-color);
color: var(--text-color);
}
.loading-spinner {
display: inline-block;
width: 15px;
height: 15px;
border: 2px solid rgba(0, 0, 0, 0.1);
border-top-color: var(--highlight);
border-radius: 50%;
animation: spin 1s infinite linear;
margin-right: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (prefers-color-scheme: dark) {
.loading-spinner {
border-color: rgba(255, 255, 255, 0.1);
border-top-color: var(--highlight);
}
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${tab.dataset.tab}-tab`).classList.add('active');
});
});
// Helper function to create logger for output divs
function createLogger(outputElement) {
return {
log: function (message, className = '') {
const div = document.createElement('div');
div.innerHTML = message;
if (className) div.className = className;
outputElement.appendChild(div);
// Auto-scroll to bottom
outputElement.scrollTop = outputElement.scrollHeight;
},
clear: function () {
outputElement.innerHTML = '';
} };
}
// Demo scenarios data
const scenarios = {
balanced: {
modules: [
{ name: 'core.js', size: 200, critical: true },
{ name: 'utils.js', size: 100, critical: true },
{ name: 'ui-components.js', size: 300, critical: true },
{ name: 'data-manager.js', size: 250, critical: false },
{ name: 'analytics.js', size: 150, critical: false }] },
large: {
modules: [
{ name: 'core.js', size: 200, critical: true },
{ name: 'utils.js', size: 100, critical: true },
{ name: 'ui-components.js', size: 300, critical: true },
{ name: 'data-manager.js', size: 250, critical: false },
{ name: 'analytics.js', size: 150, critical: false },
{ name: 'chart-library.js', size: 500, critical: false },
{ name: 'form-validation.js', size: 200, critical: false },
{ name: 'user-profile.js', size: 350, critical: false },
{ name: 'social-sharing.js', size: 180, critical: false },
{ name: 'theme-manager.js', size: 120, critical: false }] },
critical: {
modules: [
{ name: 'core.js', size: 200, critical: true },
{ name: 'utils.js', size: 100, critical: true },
{ name: 'ui-components.js', size: 300, critical: true },
{ name: 'data-manager.js', size: 250, critical: false },
{ name: 'analytics.js', size: 150, critical: false },
{ name: 'chart-library.js', size: 500, critical: false },
{ name: 'form-validation.js', size: 200, critical: false },
{ name: 'user-profile.js', size: 350, critical: false },
{ name: 'social-sharing.js', size: 180, critical: false },
{ name: 'theme-manager.js', size: 120, critical: false }] } };
// Initialize variables for performance metrics
let staticTotalTime = 0;
let dynamicTotalTime = 0;
let staticRenderTime = 0;
let dynamicRenderTime = 0;
// Update modules list based on selected scenario
document.getElementById('scenario').addEventListener('change', function () {
const scenario = this.value;
const modules = scenarios[scenario].modules;
// Update static modules list
const staticList = document.getElementById('staticModulesList');
staticList.innerHTML = '';
modules.forEach(module => {
const moduleElement = document.createElement('div');
moduleElement.innerHTML = `${module.name} <span class="info">(${module.size}ms)</span> ${module.critical ? '<span class="highlight">Critical</span>' : ''}`;
staticList.appendChild(moduleElement);
});
// Update dynamic modules list (same modules but will be loaded differently)
const dynamicList = document.getElementById('dynamicModulesList');
dynamicList.innerHTML = '';
// Critical modules first
modules.filter(m => m.critical).forEach(module => {
const moduleElement = document.createElement('div');
moduleElement.innerHTML = `${module.name} <span class="info">(${module.size}ms)</span> <span class="highlight">Critical</span>`;
dynamicList.appendChild(moduleElement);
});
// Then non-critical
modules.filter(m => !m.critical).forEach(module => {
const moduleElement = document.createElement('div');
moduleElement.innerHTML = `${module.name} <span class="info">(${module.size}ms)</span> <span class="warning">On Demand</span>`;
dynamicList.appendChild(moduleElement);
});
// Reset performance results
resetResults();
});
// Trigger the change event to initialize the first scenario
document.getElementById('scenario').dispatchEvent(new Event('change'));
// Reset performance results
function resetResults() {
staticTotalTime = 0;
dynamicTotalTime = 0;
staticRenderTime = 0;
dynamicRenderTime = 0;
document.getElementById('staticTimeBar').style.height = '0';
document.getElementById('dynamicTimeBar').style.height = '0';
document.getElementById('staticRenderBar').style.height = '0';
document.getElementById('dynamicRenderBar').style.height = '0';
document.getElementById('staticTimeValue').textContent = '0ms';
document.getElementById('dynamicTimeValue').textContent = '0ms';
document.getElementById('staticRenderValue').textContent = '0ms';
document.getElementById('dynamicRenderValue').textContent = '0ms';
document.getElementById('compareResults').disabled = true;
// Reset progress bars
document.getElementById('staticProgress').style.width = '0%';
document.getElementById('staticProgress').querySelector('.progress-text').textContent = '0%';
document.getElementById('dynamicProgress').style.width = '0%';
document.getElementById('dynamicProgress').querySelector('.progress-text').textContent = '0%';
}
// Run static import simulation
document.getElementById('runStatic').addEventListener('click', function () {
const staticOutput = document.getElementById('staticOutput');
const logger = createLogger(staticOutput);
logger.clear();
const scenario = document.getElementById('scenario').value;
const modules = scenarios[scenario].modules;
// Disable buttons during simulation
this.disabled = true;
document.getElementById('runDynamic').disabled = true;
logger.log('Starting static import simulation...', 'info');
logger.log('With static imports, all modules must load before any code executes.', 'highlight');
let loadedModules = 0;
let totalModules = modules.length;
let totalSize = modules.reduce((sum, module) => sum + module.size, 0);
let loadedSize = 0;
// Start the loading simulation
let startTime = Date.now();
function loadNextModule(index) {
if (index >= modules.length) {
// All modules loaded
const totalTime = Date.now() - startTime;
staticTotalTime = totalTime;
staticRenderTime = totalTime; // With static imports, render time equals total time
logger.log(`All modules loaded in ${totalTime}ms`, 'success');
logger.log('Application rendering can now begin...', 'highlight');
logger.log('Rendering application...', 'info');
// Update progress to 100%
document.getElementById('staticProgress').style.width = '100%';
document.getElementById('staticProgress').querySelector('.progress-text').textContent = '100%';
// Enable the compare button if both simulations are done
if (dynamicTotalTime > 0) {
document.getElementById('compareResults').disabled = false;
}
// Re-enable buttons
document.getElementById('runStatic').disabled = false;
document.getElementById('runDynamic').disabled = false;
return;
}
const module = modules[index];
// Simulate module loading time
setTimeout(() => {
loadedModules++;
loadedSize += module.size;
const progress = Math.round(loadedSize / totalSize * 100);
document.getElementById('staticProgress').style.width = `${progress}%`;
document.getElementById('staticProgress').querySelector('.progress-text').textContent = `${progress}%`;
logger.log(`Loaded <span class="info">${module.name}</span> (${module.size}ms) [${loadedModules}/${totalModules}]`,
module.critical ? 'success' : '');
// Load next module
loadNextModule(index + 1);
}, module.size);
}
// Start loading modules
loadNextModule(0);
});
// Run dynamic import simulation
document.getElementById('runDynamic').addEventListener('click', function () {
const dynamicOutput = document.getElementById('dynamicOutput');
const logger = createLogger(dynamicOutput);
logger.clear();
const scenario = document.getElementById('scenario').value;
const modules = scenarios[scenario].modules;
const criticalModules = modules.filter(m => m.critical);
const nonCriticalModules = modules.filter(m => !m.critical);
// Disable buttons during simulation
this.disabled = true;
document.getElementById('runStatic').disabled = true;
logger.log('Starting dynamic import simulation...', 'info');
logger.log('With dynamic imports, critical modules load first, then the application renders, then non-critical modules load on demand.', 'highlight');
let loadedModules = 0;
let totalModules = modules.length;
let totalSize = modules.reduce((sum, module) => sum + module.size, 0);
let loadedSize = 0;
let firstRenderDone = false;
// Start the loading simulation
let startTime = Date.now();
// First load critical modules
function loadCriticalModules(index) {
if (index >= criticalModules.length) {
// Critical modules loaded, application can render
const renderTime = Date.now() - startTime;
dynamicRenderTime = renderTime;
logger.log(`Critical modules loaded in ${renderTime}ms`, 'success');
logger.log('Application can now render its initial view!', 'highlight');
firstRenderDone = true;
// Simulate user interaction after 500ms
setTimeout(() => {
logger.log('<hr>');
logger.log('User interacts with the application, triggering on-demand loading...', 'info');
// Start loading non-critical modules
loadNonCriticalModules(0);
}, 500);
return;
}
const module = criticalModules[index];
// Simulate module loading time
setTimeout(() => {
loadedModules++;
loadedSize += module.size;
const progress = Math.round(loadedSize / totalSize * 100);
document.getElementById('dynamicProgress').style.width = `${progress}%`;
document.getElementById('dynamicProgress').querySelector('.progress-text').textContent = `${progress}%`;
logger.log(`Loaded critical module <span class="info">${module.name}</span> (${module.size}ms) [${loadedModules}/${totalModules}]`, 'success');
// Load next critical module
loadCriticalModules(index + 1);
}, module.size);
}
// Then load non-critical modules on demand
function loadNonCriticalModules(index) {
if (index >= nonCriticalModules.length) {
// All modules loaded
const totalTime = Date.now() - startTime;
dynamicTotalTime = totalTime;
logger.log(`All modules loaded in ${totalTime}ms`, 'success');
// Update progress to 100%
document.getElementById('dynamicProgress').style.width = '100%';
document.getElementById('dynamicProgress').querySelector('.progress-text').textContent = '100%';
// Enable the compare button if both simulations are done
if (staticTotalTime > 0) {
document.getElementById('compareResults').disabled = false;
}
// Re-enable buttons
document.getElementById('runStatic').disabled = false;
document.getElementById('runDynamic').disabled = false;
return;
}
const module = nonCriticalModules[index];
// Simulate module loading time
setTimeout(() => {
loadedModules++;
loadedSize += module.size;
const progress = Math.round(loadedSize / totalSize * 100);
document.getElementById('dynamicProgress').style.width = `${progress}%`;
document.getElementById('dynamicProgress').querySelector('.progress-text').textContent = `${progress}%`;
logger.log(`Loaded on-demand module <span class="info">${module.name}</span> (${module.size}ms) [${loadedModules}/${totalModules}]`);
// Load next non-critical module
loadNonCriticalModules(index + 1);
}, module.size);
}
// Start loading critical modules
loadCriticalModules(0);
});
// Compare results
document.getElementById('compareResults').addEventListener('click', function () {
const maxHeight = 250; // Maximum bar height in pixels
const maxTime = Math.max(staticTotalTime, dynamicTotalTime, staticRenderTime, dynamicRenderTime);
// Calculate heights proportionally
const staticTimeHeight = Math.round(staticTotalTime / maxTime * maxHeight);
const dynamicTimeHeight = Math.round(dynamicTotalTime / maxTime * maxHeight);
const staticRenderHeight = Math.round(staticRenderTime / maxTime * maxHeight);
const dynamicRenderHeight = Math.round(dynamicRenderTime / maxTime * maxHeight);
// Update bars
document.getElementById('staticTimeBar').style.height = `${staticTimeHeight}px`;
document.getElementById('dynamicTimeBar').style.height = `${dynamicTimeHeight}px`;
document.getElementById('staticRenderBar').style.height = `${staticRenderHeight}px`;
document.getElementById('dynamicRenderBar').style.height = `${dynamicRenderHeight}px`;
// Update values
document.getElementById('staticTimeValue').textContent = `${staticTotalTime}ms`;
document.getElementById('dynamicTimeValue').textContent = `${dynamicTotalTime}ms`;
document.getElementById('staticRenderValue').textContent = `${staticRenderTime}ms`;
document.getElementById('dynamicRenderValue').textContent = `${dynamicRenderTime}ms`;
// Calculate improvement percentages
const totalImprovement = Math.round((staticTotalTime - dynamicTotalTime) / staticTotalTime * 100);
const renderImprovement = Math.round((staticRenderTime - dynamicRenderTime) / staticRenderTime * 100);
// Get outputs
const staticOutput = document.getElementById('staticOutput');
const dynamicOutput = document.getElementById('dynamicOutput');
const staticLogger = createLogger(staticOutput);
const dynamicLogger = createLogger(dynamicOutput);
// Add improvement notes
staticLogger.log('<hr>');
dynamicLogger.log('<hr>');
if (totalImprovement > 0) {
dynamicLogger.log(`Dynamic imports were <span class="success">${totalImprovement}% faster</span> in total load time!`, 'highlight');
} else {
staticLogger.log(`Static imports were <span class="success">${-totalImprovement}% faster</span> in total load time!`, 'highlight');
}
if (renderImprovement > 0) {
dynamicLogger.log(`Dynamic imports were <span class="success">${renderImprovement}% faster</span> to first render!`, 'highlight');
} else {
staticLogger.log(`Static imports were <span class="success">${-renderImprovement}% faster</span> to first render!`, 'highlight');
}
});
This feature is perfect for code splitting, lazy loading, and conditionally loading features based on user preferences or device capabilities.
globalThis: Consistent Global Reference#
The globalThis object provides a standard way to access the global object across different JavaScript environments:
// Before globalThis
const getGlobalObject = () => {
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof self !== 'undefined') return self;
throw new Error('Unable to locate global object');
};
// With globalThis
console.log(globalThis); // Points to window, global, or self depending on environment
console.log(typeof globalThis); // 'object'
---html
<div class="container">
<h1>globalThis Behavior in Browser Context</h1>
<p>The <code>globalThis</code> property provides a standard way to access the global object across different JavaScript environments. This demo explores how it behaves in a browser context and how it compares to other global object references.</p>
<div class="card">
<h2>Global Object References in Browser</h2>
<p>In browsers, the global object has traditionally been accessible through several different references, depending on the context:</p>
<table>
<thead>
<tr>
<th>Reference</th>
<th>Description</th>
<th>Context</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>window</code></td>
<td>The traditional global object in browsers</td>
<td>Available in browser window contexts</td>
</tr>
<tr>
<td><code>self</code></td>
<td>Reference to the current window</td>
<td>Window and Web Worker contexts</td>
</tr>
<tr>
<td><code>frames</code></td>
<td>Array-like object of frame elements</td>
<td>Window context</td>
</tr>
<tr>
<td><code>this</code> (global)</td>
<td>In global scope, refers to global object</td>
<td>Depends on execution context</td>
</tr>
<tr>
<td><code>globalThis</code></td>
<td>Standardized reference (ES2020)</td>
<td>All JavaScript environments</td>
</tr>
</tbody>
</table>
<div class="info-box">
<p><strong>Note:</strong> <code>globalThis</code> was introduced in ES2020 (ES11) to provide a standard way to access the global object across all JavaScript environments (browsers, Node.js, Web Workers, etc.) without having to detect the environment.</p>
</div>
</div>
<div class="card">
<h2>Interactive Demo</h2>
<p>Let's explore how <code>globalThis</code> behaves in the browser context and how it relates to other global object references:</p>
<div class="tab-container">
<div class="tabs">
<div class="tab active" data-tab="equality">Equality Tests</div>
<div class="tab" data-tab="properties">Global Properties</div>
<div class="tab" data-tab="modules">Module Context</div>
</div>
<!-- Equality Tests Tab -->
<div id="equality-tab" class="tab-content active">
<p>This test checks if <code>globalThis</code> is equal to other global object references in the browser:</p>
<button id="runEqualityTest">Run Equality Test</button>
<div class="output" id="equalityOutput"></div>
<h3>Code:</h3>
<div class="code-block">
<pre><code>// Check equality between globalThis and other global references
console.log('globalThis === window:', globalThis === window);
console.log('globalThis === self:', globalThis === self);
console.log('globalThis === frames:', globalThis === frames);
console.log('globalThis === this (in global scope):', globalThis === this);
// Check if references have circular references to themselves
console.log('globalThis.globalThis === globalThis:', globalThis.globalThis === globalThis);
console.log('window.window === window:', window.window === window);
console.log('window.globalThis === globalThis:', window.globalThis === globalThis);
console.log('globalThis.window === window:', globalThis.window === window);</code></pre>
</div>
</div>
<!-- Global Properties Tab -->
<div id="properties-tab" class="tab-content">
<p>This test examines which properties are accessible via <code>globalThis</code> compared to <code>window</code>:</p>
<div class="flex-container">
<div class="flex-item">
<h3>Check Common Properties:</h3>
<button id="checkCommonProps">Check Common Properties</button>
<div class="output" id="commonPropsOutput"></div>
</div>
<div class="flex-item">
<h3>Add Custom Global Property:</h3>
<button id="addGlobalProp">Add Property & Check Access</button>
<div class="output" id="customPropOutput"></div>
</div>
</div>
<h3>Code:</h3>
<div class="code-block">
<pre><code>// Check common browser globals
const commonProperties = [
'document', 'location', 'navigator', 'console',
'setTimeout', 'fetch', 'localStorage', 'history'
];
commonProperties.forEach(prop => {
console.log(`globalThis.${prop} exists:`, prop in globalThis);
console.log(`window.${prop} === globalThis.${prop}:`, window[prop] === globalThis[prop]);
});
// Test adding a custom global property
window.customProp = "Added via window";
console.log('globalThis.customProp:', globalThis.customProp);
globalThis.anotherProp = "Added via globalThis";
console.log('window.anotherProp:', window.anotherProp);</code></pre>
</div>
</div>
<!-- Module Context Tab -->
<div id="modules-tab" class="tab-content">
<p>This test demonstrates how <code>globalThis</code> behaves in a module context, where <code>this</code> isn't the global object:</p>
<button id="testModuleContext">Test Module Context</button>
<div class="output" id="moduleOutput"></div>
<div class="info-box">
<p>In JavaScript modules (<code><script type="module"></code>), the top-level <code>this</code> is <code>undefined</code> instead of the global object. <code>globalThis</code> provides a consistent way to access the global object even in modules.</p>
</div>
<h3>Code:</h3>
<div class="code-block">
<pre><code>// Regular script
console.log('In regular script, this === window:', this === window);
console.log('In regular script, this === globalThis:', this === globalThis);
// Module script
// <script type="module">
// console.log('In module, this === window:', this === window);
// console.log('In module, this === undefined:', this === undefined);
// console.log('In module, globalThis === window:', globalThis === window);
// </script></code></pre>
</div>
</div>
</div>
</div>
<div class="card">
<h2>Practical Use Cases</h2>
<p>Here are some practical scenarios where <code>globalThis</code> is particularly useful:</p>
<div class="code-block">
<pre><code>// 1. Environment-agnostic code (works in Node.js and browsers)
function getGlobalStorage() {
if ('localStorage' in globalThis) {
return globalThis.localStorage;
} else {
// Fallback for non-browser environments
return {
getItem: (key) => this._storage[key],
setItem: (key, value) => { this._storage[key] = value },
_storage: {}
};
}
}
// 2. Polyfill pattern (pre-ES2020)
// Before globalThis, you'd need this kind of detection
const getGlobalThis = () => {
if (typeof globalThis !== 'undefined') return globalThis;
if (typeof self !== 'undefined') return self;
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
// Fallback - not 100% reliable
return Function('return this')();
};
// 3. Working with cross-origin iframes where window.parent might be restricted
function safePostMessage(message) {
try {
// This works even with cross-origin frames
globalThis.parent.postMessage(message, '*');
} catch (e) {
console.error('Cannot communicate with parent frame:', e);
}
}</code></pre>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #f5f5f5;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #e9ecef;
--border-color: #dee2e6;
--heading-color: #8a2be2;
--card-bg: #ffffff;
--true-color: #28a745;
--false-color: #dc3545;
--highlight-color: #fd7e14;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #2d2d2d;
--border-color: #444;
--heading-color: #bb86fc;
--card-bg: #1e1e1e;
--true-color: #4caf50;
--false-color: #f44336;
--highlight-color: #ff9800;
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
h1, h2, h3 {
color: var(--heading-color);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 30px;
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin-bottom: 20px;
border: 1px solid var(--border-color);
}
pre {
margin: 0;
font-size: 14px;
}
code {
font-family: 'Fira Code', monospace;
}
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-top: 10px;
margin-right: 10px;
transition: opacity 0.2s;
}
button:hover {
opacity: 0.9;
}
.output {
background-color: var(--output-bg);
padding: 15px;
border-radius: 5px;
margin-top: 15px;
min-height: 100px;
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border-color);
white-space: pre-wrap;
font-family: 'Fira Code', monospace;
font-size: 14px;
}
.output-line {
margin-bottom: 8px;
}
.true {
color: var(--true-color);
}
.false {
color: var(--false-color);
}
.highlight {
background-color: rgba(255, 165, 0, 0.2);
padding: 2px 4px;
border-radius: 2px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 10px;
border: 1px solid var(--border-color);
text-align: left;
}
th {
background-color: rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
th {
background-color: rgba(255, 255, 255, 0.05);
}
}
.flex-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.flex-item {
flex: 1;
min-width: 300px;
}
.tab-container {
margin-top: 20px;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
}
.tab {
padding: 10px 15px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
border-radius: 5px 5px 0 0;
background-color: rgba(0, 0, 0, 0.03);
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
.tab {
background-color: rgba(255, 255, 255, 0.03);
}
}
.tab.active {
background-color: var(--button-bg);
color: var(--button-text);
}
.tab-content {
display: none;
padding: 15px 0;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.tab-content.active {
display: block;
}
.info-box {
background-color: rgba(13, 110, 253, 0.1);
border-left: 4px solid var(--button-bg);
padding: 15px;
margin: 15px 0;
border-radius: 0 5px 5px 0;
}
@media (prefers-color-scheme: dark) {
.info-box {
background-color: rgba(13, 110, 253, 0.2);
}
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
const tabId = `${tab.dataset.tab}-tab`;
document.getElementById(tabId).classList.add('active');
});
});
// Helper function to create formatted output
function formatOutput(message, value, highlight = false) {
const line = document.createElement('div');
line.className = 'output-line';
// Format the message and value
let text = message;
if (typeof value !== 'undefined') {
const valueClass = value === true ? 'true' : value === false ? 'false' : '';
text += ` <span class="${valueClass}">${value}</span>`;
}
if (highlight) {
line.innerHTML = `<span class="highlight">${text}</span>`;
} else {
line.innerHTML = text;
}
return line;
}
// Equality test
document.getElementById('runEqualityTest').addEventListener('click', () => {
const output = document.getElementById('equalityOutput');
output.innerHTML = '';
// Basic equality tests
output.appendChild(formatOutput('globalThis === window:', globalThis === window));
output.appendChild(formatOutput('globalThis === self:', globalThis === self));
output.appendChild(formatOutput('globalThis === frames:', globalThis === frames));
output.appendChild(formatOutput('globalThis === this (in global scope):', globalThis === this));
output.appendChild(document.createElement('br'));
// Circular reference tests
output.appendChild(formatOutput('globalThis.globalThis === globalThis:', globalThis.globalThis === globalThis));
output.appendChild(formatOutput('window.window === window:', window.window === window));
output.appendChild(formatOutput('window.globalThis === globalThis:', window.globalThis === globalThis));
output.appendChild(formatOutput('globalThis.window === window:', globalThis.window === window));
output.appendChild(document.createElement('br'));
// Constructor tests
output.appendChild(formatOutput('Object.prototype.toString.call(globalThis):', Object.prototype.toString.call(globalThis)));
output.appendChild(formatOutput('Object.prototype.toString.call(window):', Object.prototype.toString.call(window)));
});
// Common properties test
document.getElementById('checkCommonProps').addEventListener('click', () => {
const output = document.getElementById('commonPropsOutput');
output.innerHTML = '';
const commonProperties = [
'document', 'location', 'navigator', 'console',
'setTimeout', 'fetch', 'localStorage', 'history'];
commonProperties.forEach(prop => {
output.appendChild(formatOutput(`globalThis.${prop} exists:`, prop in globalThis));
const areEqual = window[prop] === globalThis[prop];
output.appendChild(formatOutput(`window.${prop} === globalThis.${prop}:`, areEqual, !areEqual));
output.appendChild(document.createElement('br'));
});
});
// Custom property test
document.getElementById('addGlobalProp').addEventListener('click', () => {
const output = document.getElementById('customPropOutput');
output.innerHTML = '';
// Test adding a property via window
window.customProp = "Added via window";
output.appendChild(formatOutput('Added: window.customProp = "Added via window"'));
output.appendChild(formatOutput('globalThis.customProp:', globalThis.customProp));
output.appendChild(document.createElement('br'));
// Test adding a property via globalThis
globalThis.anotherProp = "Added via globalThis";
output.appendChild(formatOutput('Added: globalThis.anotherProp = "Added via globalThis"'));
output.appendChild(formatOutput('window.anotherProp:', window.anotherProp));
output.appendChild(document.createElement('br'));
// Delete the properties to keep the environment clean
delete window.customProp;
delete globalThis.anotherProp;
output.appendChild(formatOutput('Properties cleaned up', undefined, true));
});
// Module context test (simulation)
document.getElementById('testModuleContext').addEventListener('click', () => {
const output = document.getElementById('moduleOutput');
output.innerHTML = '';
// Regular script context
output.appendChild(formatOutput('In regular script, this === window:', this === window));
output.appendChild(formatOutput('In regular script, this === globalThis:', this === globalThis));
output.appendChild(document.createElement('br'));
// Simulate module context with an IIFE
output.appendChild(formatOutput('Simulating module context with a function:', undefined, true));
// Function context example (not exactly like a module, but shows difference in 'this')
const moduleSimulation = function () {
// In a module 'this' would be undefined, here it's not window
output.appendChild(formatOutput('In function, this === window:', this === window));
output.appendChild(formatOutput('In function, this === undefined:', this === undefined));
output.appendChild(formatOutput('In function, globalThis === window:', globalThis === window));
output.appendChild(formatOutput('globalThis still accessible as expected', undefined, true));
};
moduleSimulation();
output.appendChild(document.createElement('br'));
output.appendChild(formatOutput('Note: To see true module behavior, use a script with type="module"', undefined, true));
});
// Dynamically add a module script to show true module behavior
document.getElementById('testModuleContext').addEventListener('click', () => {
// Remove any existing module test script
const existingScript = document.getElementById('module-test-script');
if (existingScript) {
existingScript.remove();
}
// Create a module script
const moduleScript = document.createElement('script');
moduleScript.id = 'module-test-script';
moduleScript.type = 'module';
moduleScript.textContent = `
const moduleOutput = document.getElementById('moduleOutput');
moduleOutput.appendChild(document.createElement('br'));
const header = document.createElement('div');
header.innerHTML = '<span class="highlight">Real module output:</span>';
moduleOutput.appendChild(header);
// Create formatted output elements
function createOutputLine(message, value) {
const line = document.createElement('div');
line.className = 'output-line';
// Format the message and value
let text = message;
if (typeof value !== 'undefined') {
const valueClass = value === true ? 'true' : value === false ? 'false' : '';
text += \` <span class="\${valueClass}">\${value}</span>\`;
}
line.innerHTML = text;
return line;
}
// This is undefined in a module's top level
moduleOutput.appendChild(createOutputLine('In real module, this === window:', this === window));
moduleOutput.appendChild(createOutputLine('In real module, this === undefined:', this === undefined));
moduleOutput.appendChild(createOutputLine('In real module, globalThis === window:', globalThis === window));
`;
document.body.appendChild(moduleScript);
});
This eliminates the need for environment detection code when accessing global variables or features.
ES12/ECMAScript 2021: Polishing the Language#
String.prototype.replaceAll(): Better String Manipulation#
Before ES12, replacing all occurrences of a string required using a regular expression with the global flag:
const string = "Hello World! Hello JavaScript!";
// Without the /g flag, replace() changes only the first match
console.log(string.replace("Hello", "Hi")); // "Hi World! Hello JavaScript!"
// Before replaceAll: a regular expression with the global flag
console.log(string.replace(/Hello/g, "Hi")); // "Hi World! Hi JavaScript!"
// With replaceAll
console.log(string.replaceAll("Hello", "Hi")); // "Hi World! Hi JavaScript!"
---html
<div class="container">
<h1>String Replacement Comparison</h1>
<p>Explore the differences between JavaScript's <code>String.prototype.replace()</code> method with RegExp and the newer <code>String.prototype.replaceAll()</code> method introduced in ES12 (ECMAScript 2021).</p>
<div class="tabs">
<div class="tab active" data-tab="basics">Basics & Syntax</div>
<div class="tab" data-tab="interactive">Interactive Demo</div>
<div class="tab" data-tab="benchmark">Performance Benchmark</div>
<div class="tab" data-tab="realworld">Real-World Examples</div>
</div>
<!-- Basics & Syntax Tab -->
<div id="basics-tab" class="tab-content active">
<div class="card">
<h2>Understanding the Difference</h2>
<p>Before ES12, if you wanted to replace all occurrences of a substring in JavaScript, you had to use regular expressions with the global flag (<code>/pattern/g</code>) or resort to workarounds. With <code>replaceAll()</code>, this becomes much simpler.</p>
<div class="code-comparison">
<div>
<div class="code-header">Traditional Approach with replace()</div>
<div class="code-block code-block-with-header">
<pre><code>// Using String.prototype.replace() with RegExp
const str = "The quick brown fox jumps over the lazy dog";
// Replace all occurrences of 'o' with '*'
const result = str.replace(/o/g, '*');
console.log(result);
// "The quick br*wn f*x jumps *ver the lazy d*g"
// Without the global flag, only the first match is replaced
const singleResult = str.replace(/o/, '*');
console.log(singleResult);
// "The quick br*wn fox jumps over the lazy dog"</code></pre>
</div>
</div>
<div>
<div class="code-header">Modern Approach with replaceAll()</div>
<div class="code-block code-block-with-header">
<pre><code>// Using String.prototype.replaceAll()
const str = "The quick brown fox jumps over the lazy dog";
// Replace all occurrences of 'o' with '*'
const result = str.replaceAll('o', '*');
console.log(result);
// "The quick br*wn f*x jumps *ver the lazy d*g"
// You can also use RegExp with replaceAll()
// (must have global flag or it will throw an error)
const regexResult = str.replaceAll(/o/g, '*');
console.log(regexResult);
// "The quick br*wn f*x jumps *ver the lazy d*g"</code></pre>
</div>
</div>
</div>
<h3>Key Differences</h3>
<table class="comparison-table">
<thead>
<tr>
<th>Feature</th>
<th>replace() with RegExp</th>
<th>replaceAll()</th>
</tr>
</thead>
<tbody>
<tr>
<td>Syntax</td>
<td>Requires regex with global flag: <code>str.replace(/pattern/g, replacement)</code></td>
<td>Simple string parameters: <code>str.replaceAll('pattern', replacement)</code></td>
</tr>
<tr>
<td>Regular Expression Support</td>
<td>Yes, and required for global replacement</td>
<td>Yes, but global flag is mandatory when using regex</td>
</tr>
<tr>
<td>String Literals</td>
<td>Need to escape special regex characters when using string literals</td>
<td>No escaping needed for string literals, simplifying the code</td>
</tr>
<tr>
<td>Performance</td>
<td>Slightly faster for complex patterns due to optimization</td>
<td>Comparable performance for simple replacements</td>
</tr>
<tr>
<td>Browser Support</td>
<td>All browsers</td>
<td>Modern browsers (ES12/ES2021+), needs polyfill for older browsers</td>
</tr>
</tbody>
</table>
<div class="regex-explanation">
<h3>A Note on Special Characters in Regular Expressions</h3>
<p>When using <code>replace()</code> with a string pattern that contains special regex characters, you need to escape them manually. With <code>replaceAll()</code>, you can use string literals directly:</p>
<div class="code-comparison">
<div class="code-block">
<pre><code>// Replacing dots (.) which are special in regex
const version = "1.2.3";
// With replace() - need to escape the dots
const escaped = version.replace(/\./g, "-");
console.log(escaped); // "1-2-3"
// With replaceAll() - no escaping needed with string parameter
const simple = version.replaceAll(".", "-");
console.log(simple); // "1-2-3"</code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- Interactive Demo Tab -->
<div id="interactive-tab" class="tab-content">
<div class="card">
<h2>Interactive Replacement Demo</h2>
<p>Try both methods side by side to see the differences in action. Edit the input text, pattern, and replacement to see the results update in real-time.</p>
<div class="form-group">
<label for="inputText">Input Text:</label>
<textarea id="inputText">The quick brown fox jumps over the lazy dog. The fox is quick and the dog is lazy.</textarea>
</div>
<div class="form-group">
<label for="pattern">Pattern to Replace:</label>
<input type="text" id="pattern" value="the">
</div>
<div class="form-group">
<label for="replacement">Replacement:</label>
<input type="text" id="replacement" value="THE">
</div>
<div class="form-group">
<label for="regexOptions">Regular Expression Options:</label>
<div>
<label><input type="checkbox" id="globalFlag" checked> Global (g)</label>
<label><input type="checkbox" id="caseInsensitiveFlag"> Case Insensitive (i)</label>
</div>
</div>
<button id="runReplacement">Run Replacement</button>
<h3>Results:</h3>
<div class="code-comparison">
<div>
<div class="code-header">replace() Result</div>
<div class="code-block code-block-with-header">
<pre id="replaceOutput">Results will appear here...</pre>
</div>
<div id="replaceCode" class="code-block" style="margin-top: 10px;">
<pre><code>// Code used:
const result = inputText.replace(/the/g, 'THE');</code></pre>
</div>
</div>
<div>
<div class="code-header">replaceAll() Result</div>
<div class="code-block code-block-with-header">
<pre id="replaceAllOutput">Results will appear here...</pre>
</div>
<div id="replaceAllCode" class="code-block" style="margin-top: 10px;">
<pre><code>// Code used:
const result = inputText.replaceAll('the', 'THE');</code></pre>
</div>
</div>
</div>
<div id="replaceDiff" class="regex-explanation" style="display: none;">
<h3>Why are the results different?</h3>
<div id="diffExplanation"></div>
</div>
</div>
</div>
<!-- Performance Benchmark Tab -->
<div id="benchmark-tab" class="tab-content">
<div class="card">
<h2>Performance Benchmark</h2>
<p>Let's benchmark the performance of <code>replace()</code> with RegExp versus <code>replaceAll()</code> for different scenarios.</p>
<div class="benchmark-controls">
<div class="benchmark-control">
<div class="form-group">
<label for="benchmarkScenario">Scenario:</label>
<select id="benchmarkScenario">
<option value="simple">Simple Text Replacement</option>
<option value="special">Special Characters</option>
<option value="long">Long Text (500 replacements)</option>
</select>
</div>
</div>
<div class="benchmark-control">
<div class="form-group">
<label for="iterations">Iterations:</label>
<select id="iterations">
<option value="1000">1,000</option>
<option value="10000" selected>10,000</option>
<option value="100000">100,000</option>
</select>
</div>
</div>
</div>
<button id="runBenchmark">Run Benchmark</button>
<div id="benchmarkResult" class="output" style="display: none;">
<p>Running benchmark...</p>
</div>
<div class="chart-container" id="chartContainer" style="display: none;">
<div class="chart-y-axis">
<div class="chart-y-label">100ms</div>
<div class="chart-y-label">75ms</div>
<div class="chart-y-label">50ms</div>
<div class="chart-y-label">25ms</div>
<div class="chart-y-label">0ms</div>
</div>
<div class="bar" id="replaceBar" style="left: 100px; background-color: var(--chart-color-1);">
<div class="bar-value" id="replaceTime">0ms</div>
<div class="bar-label">replace() with RegExp</div>
</div>
<div class="bar" id="replaceAllBar" style="left: 300px; background-color: var(--chart-color-2);">
<div class="bar-value" id="replaceAllTime">0ms</div>
<div class="bar-label">replaceAll()</div>
</div>
</div>
<div class="chart-legend">
<div class="legend-item">
<div class="legend-color" style="background-color: var(--chart-color-1);"></div>
<span>replace() with RegExp</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: var(--chart-color-2);"></div>
<span>replaceAll()</span>
</div>
</div>
<h3>Benchmark Analysis</h3>
<div id="benchmarkAnalysis" class="regex-explanation" style="display: none;">
<p>Analysis will appear after running the benchmark.</p>
</div>
</div>
</div>
<!-- Real-World Examples Tab -->
<div id="realworld-tab" class="tab-content">
<div class="card">
<h2>Real-World Examples</h2>
<p>Here are some practical examples of using <code>replace()</code> with RegExp and <code>replaceAll()</code> for common string manipulation tasks.</p>
<div class="code-header">Example 1: Format Phone Numbers</div>
<div class="code-block code-block-with-header">
<pre><code>const formatPhoneNumber = (phoneNumber) => {
// Remove all non-digit characters first
// Using replace() with RegExp
const cleanedWithReplace = phoneNumber.replace(/\D/g, '');
// Using replaceAll() - would need multiple calls for different characters
const cleanedWithReplaceAll = phoneNumber
.replaceAll(' ', '')
.replaceAll('-', '')
.replaceAll('(', '')
.replaceAll(')', '')
.replaceAll('.', '');
// Format as (XXX) XXX-XXXX
const formatted = cleanedWithReplace.replace(
/(\d{3})(\d{3})(\d{4})/,
'($1) $2-$3'
);
return formatted;
};
// Example
const phone = "(123) 456-7890";
const formattedPhone = formatPhoneNumber(phone);
console.log(formattedPhone); // (123) 456-7890
// Example with messy input
const messyPhone = "123.456.7890";
console.log(formatPhoneNumber(messyPhone)); // (123) 456-7890</code></pre>
</div>
<div class="code-header">Example 2: Sanitize URL Parameters</div>
<div class="code-block code-block-with-header">
<pre><code>const sanitizeUrlParam = (param) => {
// Using replace() with RegExp
// Replace spaces with dashes and remove special characters
const sanitizedWithReplace = param
.replace(/\s+/g, '-')
.replace(/[^a-zA-Z0-9-]/g, '')
.toLowerCase();
// Using replaceAll() for spaces, then replace() for special chars
const sanitizedWithReplaceAll = param
.replaceAll(' ', '-')
.replace(/[^a-zA-Z0-9-]/g, '') // Still need RegExp for character classes
.toLowerCase();
return sanitizedWithReplace;
};
// Example
const title = "My Cool Article: With Some Funky Characters!";
const urlParam = sanitizeUrlParam(title);
console.log(urlParam); // my-cool-article-with-some-funky-characters</code></pre>
</div>
<div class="code-header">Example 3: Template String Replacement</div>
<div class="code-block code-block-with-header">
<pre><code>const template = (str, data) => {
let result = str;
// Using replace() with RegExp
for (const [key, value] of Object.entries(data)) {
const regex = new RegExp(`{{${key}}}`, 'g');
result = result.replace(regex, value);
}
/* Using replaceAll() - simpler syntax
for (const [key, value] of Object.entries(data)) {
result = result.replaceAll(`{{${key}}}`, value);
}
*/
return result;
};
// Example
const emailTemplate = "Hello {{name}}, Your order #{{orderId}} has been confirmed. Your total is ${{total}}.";
const emailData = {
name: "John",
orderId: "12345",
total: "99.99"
};
const email = template(emailTemplate, emailData);
console.log(email);
// "Hello John, Your order #12345 has been confirmed. Your total is $99.99."</code></pre>
</div>
<h3>Best Practices</h3>
<div class="regex-explanation">
<h4>When to use <code>replace()</code> with RegExp:</h4>
<ul>
<li>When you need pattern matching (like <code>\d</code> for digits, <code>\s</code> for whitespace)</li>
<li>When you need capture groups with <code>$1</code>, <code>$2</code> references</li>
<li>When you need to use a replacement function</li>
<li>When you need to support older browsers</li>
</ul>
<h4>When to use <code>replaceAll()</code>:</h4>
<ul>
<li>When replacing simple fixed strings (no regex needed)</li>
<li>When you want cleaner, more readable code</li>
<li>When working with strings that contain regex special characters</li>
<li>When you want to avoid the common mistake of forgetting the global flag</li>
</ul>
</div>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #f5f5f5;
--button-bg: #0d6efd;
--button-hover: #0b5ed7;
--button-text: white;
--output-bg: #e9ecef;
--border-color: #dee2e6;
--highlight: #8a2be2;
--chart-color-1: #4285f4;
--chart-color-2: #34a853;
--card-bg: #ffffff;
--error-color: #dc3545;
--success-color: #198754;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--button-bg: #0d6efd;
--button-hover: #0b5ed7;
--button-text: white;
--output-bg: #2d2d2d;
--border-color: #444;
--highlight: #bb86fc;
--chart-color-1: #8ab4f8;
--chart-color-2: #81c995;
--card-bg: #1e1e1e;
--error-color: #f44336;
--success-color: #4caf50;
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
h1, h2, h3 {
color: var(--highlight);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 30px;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
margin-right: 5px;
border-radius: 5px 5px 0 0;
background-color: rgba(0, 0, 0, 0.03);
}
@media (prefers-color-scheme: dark) {
.tab {
background-color: rgba(255, 255, 255, 0.05);
}
}
.tab.active {
background-color: var(--button-bg);
color: var(--button-text);
border-color: var(--border-color);
}
.tab-content {
display: none;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.tab-content.active {
display: block;
}
.code-comparison {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.code-block {
flex: 1;
min-width: 300px;
background-color: var(--code-bg);
padding: 15px;
border-radius: 5px;
overflow-x: auto;
border: 1px solid var(--border-color);
}
pre {
margin: 0;
font-size: 14px;
}
code {
font-family: 'Fira Code', monospace;
}
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-top: 10px;
margin-right: 10px;
transition: background-color 0.2s;
}
button:hover {
background-color: var(--button-hover);
}
.output {
background-color: var(--output-bg);
padding: 15px;
border-radius: 5px;
margin-top: 15px;
min-height: 100px;
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border-color);
}
.error {
color: var(--error-color);
}
.success {
color: var(--success-color);
}
.highlight {
color: var(--highlight);
font-weight: bold;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input, textarea, select {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--bg-color);
color: var(--text-color);
font-family: inherit;
}
textarea {
resize: vertical;
min-height: 100px;
}
.comparison-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.comparison-table th, .comparison-table td {
padding: 10px;
border: 1px solid var(--border-color);
text-align: left;
}
.comparison-table th {
background-color: rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
.comparison-table th {
background-color: rgba(255, 255, 255, 0.05);
}
}
.chart-container {
width: 100%;
height: 250px;
position: relative;
margin: 30px 0;
}
.bar {
position: absolute;
bottom: 30px;
width: 80px;
background-color: var(--button-bg);
transition: height 0.5s;
border-radius: 5px 5px 0 0;
}
.bar-label {
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
padding: 5px 0;
}
.bar-value {
position: absolute;
top: -25px;
width: 100%;
text-align: center;
font-weight: bold;
}
.chart-y-axis {
position: absolute;
left: -50px;
top: 0;
bottom: 30px;
width: 50px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
}
.chart-y-label {
padding-right: 10px;
}
.chart-x-axis {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 30px;
display: flex;
}
.chart-legend {
display: flex;
justify-content: center;
margin-top: 20px;
}
.legend-item {
display: flex;
align-items: center;
margin: 0 10px;
}
.legend-color {
width: 15px;
height: 15px;
margin-right: 5px;
border-radius: 3px;
}
.highlighted-diff {
background-color: rgba(138, 43, 226, 0.2);
padding: 2px;
border-radius: 3px;
}
@media (prefers-color-scheme: dark) {
.highlighted-diff {
background-color: rgba(187, 134, 252, 0.2);
}
}
.benchmark-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.benchmark-control {
flex: 1;
min-width: 200px;
}
.code-header {
background-color: rgba(0, 0, 0, 0.05);
padding: 8px 15px;
border-radius: 5px 5px 0 0;
font-weight: bold;
border: 1px solid var(--border-color);
border-bottom: none;
}
@media (prefers-color-scheme: dark) {
.code-header {
background-color: rgba(255, 255, 255, 0.05);
}
}
.code-block-with-header {
border-radius: 0 0 5px 5px;
margin-top: 0;
}
.regex-explanation {
margin-top: 15px;
padding: 10px;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 5px;
}
@media (prefers-color-scheme: dark) {
.regex-explanation {
background-color: rgba(255, 255, 255, 0.05);
}
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${tab.dataset.tab}-tab`).classList.add('active');
});
});
// Interactive Demo
const runReplacement = () => {
const inputText = document.getElementById('inputText').value;
const pattern = document.getElementById('pattern').value;
const replacement = document.getElementById('replacement').value;
const globalFlag = document.getElementById('globalFlag').checked;
const caseInsensitiveFlag = document.getElementById('caseInsensitiveFlag').checked;
let flags = '';
if (globalFlag) flags += 'g';
if (caseInsensitiveFlag) flags += 'i';
// Update code samples
let replaceCode = `// Code used:\nconst result = inputText.replace(`;
let replaceAllCode = `// Code used:\n`;
// Try using replace()
try {
const regex = new RegExp(pattern, flags);
const replaceResult = inputText.replace(regex, replacement);
document.getElementById('replaceOutput').textContent = replaceResult;
replaceCode += `/${pattern}/${flags}, '${replacement}');`;
} catch (error) {
document.getElementById('replaceOutput').textContent = `Error: ${error.message}`;
replaceCode += `/${pattern}/${flags}, '${replacement}'); // Error: ${error.message}`;
}
// Try using replaceAll()
try {
let replaceAllResult;
// Check if we're using regex or string
if (pattern.includes('\\') || pattern.match(/[\.\*\+\?\^\$\{\}\(\)\|\[\]\/\\]/)) {
// Likely a regex pattern, use RegExp object (must include global flag)
const regex = new RegExp(pattern, flags.includes('g') ? flags : flags + 'g');
replaceAllResult = inputText.replaceAll(regex, replacement);
replaceAllCode += `const result = inputText.replaceAll(/${pattern}/${flags.includes('g') ? flags : flags + 'g'}, '${replacement}');`;
} else {
// Simple string, use direct string parameter
replaceAllResult = inputText.replaceAll(pattern, replacement);
replaceAllCode += `const result = inputText.replaceAll('${pattern}', '${replacement}');`;
}
document.getElementById('replaceAllOutput').textContent = replaceAllResult;
} catch (error) {
document.getElementById('replaceAllOutput').textContent = `Error: ${error.message}`;
replaceAllCode += `const result = inputText.replaceAll('${pattern}', '${replacement}'); // Error: ${error.message}`;
}
// Update code samples
document.getElementById('replaceCode').innerHTML = `<pre><code>${replaceCode}</code></pre>`;
document.getElementById('replaceAllCode').innerHTML = `<pre><code>${replaceAllCode}</code></pre>`;
// Compare results
const replaceOutput = document.getElementById('replaceOutput').textContent;
const replaceAllOutput = document.getElementById('replaceAllOutput').textContent;
if (replaceOutput !== replaceAllOutput && !replaceOutput.startsWith('Error') && !replaceAllOutput.startsWith('Error')) {
document.getElementById('replaceDiff').style.display = 'block';
// Try to explain the difference
let explanation = '';
if (!globalFlag) {
explanation = `<p>The results are different because <code>replace()</code> without the global flag only replaces the first occurrence, while <code>replaceAll()</code> replaces all occurrences.</p>`;
} else {
explanation = `<p>The results may differ because of how the pattern is interpreted. With <code>replace()</code>, the pattern is treated as a regular expression with flags <code>${flags}</code>, while with <code>replaceAll()</code>, the pattern may be treated as a literal string if it doesn't contain special RegExp characters.</p>`;
}
document.getElementById('diffExplanation').innerHTML = explanation;
} else {
document.getElementById('replaceDiff').style.display = 'none';
}
};
document.getElementById('runReplacement').addEventListener('click', runReplacement);
// Run once on page load
runReplacement();
// Performance Benchmark
const runBenchmark = () => {
const iterations = parseInt(document.getElementById('iterations').value);
const scenario = document.getElementById('benchmarkScenario').value;
// Show benchmark result container
document.getElementById('benchmarkResult').style.display = 'block';
document.getElementById('benchmarkResult').innerHTML = '<p>Running benchmark...</p>';
// Set up benchmark scenarios
let inputText, pattern, replacement;
switch (scenario) {
case 'simple':
inputText = "The quick brown fox jumps over the lazy dog. The fox is quick and the dog is lazy.";
pattern = "the";
replacement = "THE";
break;
case 'special':
inputText = "price: $10.99, discount: $2.50, total: $8.49";
pattern = "\\.";
replacement = ",";
break;
case 'long':
// Generate a long text with many occurrences
inputText = Array(500).fill("The quick brown fox jumps over the lazy dog.").join(" ");
pattern = "o";
replacement = "*";
break;}
// Give the UI time to update
setTimeout(() => {
// Benchmark replace() with RegExp
const startReplace = performance.now();
for (let i = 0; i < iterations; i++) {
try {
if (scenario === 'special') {
// Special characters need explicit RegExp
inputText.replace(/\./g, replacement);
} else {
// Use dynamic RegExp
const regex = new RegExp(pattern, 'g');
inputText.replace(regex, replacement);
}
} catch (e) {
// Ignore errors
}
}
const endReplace = performance.now();
const replaceTime = endReplace - startReplace;
// Benchmark replaceAll()
const startReplaceAll = performance.now();
for (let i = 0; i < iterations; i++) {
try {
if (scenario === 'special') {
// For special characters, replaceAll can use string directly
inputText.replaceAll('.', replacement);
} else {
inputText.replaceAll(pattern, replacement);
}
} catch (e) {
// Ignore errors
}
}
const endReplaceAll = performance.now();
const replaceAllTime = endReplaceAll - startReplaceAll;
// Display results
const resultText = `
<h3>Benchmark Results (${iterations.toLocaleString()} iterations)</h3>
<p><strong>Scenario:</strong> ${scenario === 'simple' ? 'Simple Text Replacement' :
scenario === 'special' ? 'Special Characters' :
'Long Text (500 replacements)'}</p>
<p><strong>replace() with RegExp:</strong> ${replaceTime.toFixed(2)}ms</p>
<p><strong>replaceAll():</strong> ${replaceAllTime.toFixed(2)}ms</p>
<p><strong>Difference:</strong> ${Math.abs(replaceTime - replaceAllTime).toFixed(2)}ms
(${replaceTime < replaceAllTime ? 'replace() is faster' : 'replaceAll() is faster'} by
${(Math.abs(replaceTime - replaceAllTime) / Math.min(replaceTime, replaceAllTime) * 100).toFixed(2)}%)</p>
`;
document.getElementById('benchmarkResult').innerHTML = resultText;
// Update chart
document.getElementById('chartContainer').style.display = 'block';
const maxTime = Math.max(replaceTime, replaceAllTime);
const scale = Math.min(200 / maxTime, 1); // Scale to fit in 200px height max
document.getElementById('replaceBar').style.height = `${replaceTime * scale}px`;
document.getElementById('replaceAllBar').style.height = `${replaceAllTime * scale}px`;
document.getElementById('replaceTime').textContent = `${replaceTime.toFixed(2)}ms`;
document.getElementById('replaceAllTime').textContent = `${replaceAllTime.toFixed(2)}ms`;
// Add analysis
document.getElementById('benchmarkAnalysis').style.display = 'block';
let analysis = '';
if (scenario === 'simple') {
analysis = `<p>For simple text replacements, ${replaceTime < replaceAllTime ? '<code>replace()</code> with RegExp' : '<code>replaceAll()</code>'}
performed better in this test. The difference is relatively small, showing that both methods are efficient for basic replacements.</p>
<p>For everyday use, <code>replaceAll()</code> is often preferred for its simplicity and readability, while <code>replace()</code> with RegExp
offers more flexibility for complex patterns.</p>`;
} else if (scenario === 'special') {
analysis = `<p>When dealing with special characters, ${replaceTime < replaceAllTime ? '<code>replace()</code> with RegExp' : '<code>replaceAll()</code>'}
performed better. With <code>replaceAll()</code>, you don't need to escape special regex characters
when using string literals, which makes the code cleaner and less error-prone.</p>
<p>This is one of the key advantages of <code>replaceAll()</code> - it simplifies working with text that contains characters
that would otherwise need escaping in regular expressions.</p>`;
} else {
analysis = `<p>For long texts with many replacements, ${replaceTime < replaceAllTime ? '<code>replace()</code> with RegExp' : '<code>replaceAll()</code>'}
showed better performance. The JavaScript engine's regex optimization may play a role in these results.</p>
<p>For applications dealing with large volumes of text processing, this performance difference could be significant, though
modern JavaScript engines continue to optimize both methods.</p>`;
}
document.getElementById('benchmarkAnalysis').innerHTML = analysis;
}, 50);
};
document.getElementById('runBenchmark').addEventListener('click', runBenchmark);
This method provides a cleaner, more intuitive way to perform global string replacements without needing to understand regular expressions.
Numeric Separators: Readable Numbers#
Numeric separators make large numbers more readable by allowing underscores as separators:
// Hard to read
const billion = 1000000000;
// Easy to read, and exactly the same number
const readableBillion = 1_000_000_000;
console.log(billion === readableBillion); // true
// Works with decimals
const pi = 3.141_592_653_589;
console.log(pi); // 3.141592653589
// And hex and binary
const hex = 0xFF_FF_FF; // 16777215
const binary = 0b0101_0101; // 85
---html
<div class="container">
<h1>Numeric Separators in JavaScript</h1>
<p>Numeric separators, introduced in ES12 (ECMAScript 2021), make large numbers more readable by allowing underscores (<code>_</code>) as visual separators between digits. This demo explores how they work with different number formats.</p>
<div class="card">
<h2>Introduction</h2>
<p>Compare these two numbers:</p>
<div class="code-block">
<pre>const budget = 1000000000; // One billion
const budget = 1_000_000_000; // Clearly one billion</pre>
</div>
<p>The underscores are ignored by the JavaScript engine but make the numbers much more readable for developers. They can be placed anywhere between digits.</p>
<div class="example">
<div class="example-title">Key Rules:</div>
<ul>
<li>Underscores must be between digits (not at beginning/end)</li>
<li>Multiple underscores in a row are allowed (but unusual)</li>
<li>Works with all numeric literals: decimal, binary, hex, octal, and scientific notation</li>
<li>Completely ignored by the JavaScript engine at runtime</li>
</ul>
</div>
</div>
<div class="card">
<h2>Interactive Demo</h2>
<div class="tabs">
<div class="tab active" data-tab="playground">Playground</div>
<div class="tab" data-tab="converter">Number Converter</div>
<div class="tab" data-tab="examples">Real-world Examples</div>
</div>
<!-- Playground Tab -->
<div id="playground-tab" class="tab-content active">
<p>Enter a number with numeric separators and see how JavaScript interprets it:</p>
<div class="form-group">
<label for="playgroundInput">Number with separators:</label>
<input type="text" id="playgroundInput" value="1_234_567.890_123" placeholder="Enter a number with separators (e.g., 1_000_000)">
</div>
<button id="evaluateNumber">Evaluate</button>
<div id="playgroundOutput" class="output"></div>
<div class="example">
<div class="example-title">Try these examples:</div>
<ul>
<li><code>1_234_567.890_123</code> - Decimal with separators</li>
<li><code>0x1234_ABCD</code> - Hexadecimal with separators</li>
<li><code>0b1010_0001_1000</code> - Binary with separators</li>
<li><code>0o1234_5670</code> - Octal with separators</li>
<li><code>1_000_000.123_456e-3</code> - Scientific notation with separators</li>
</ul>
</div>
</div>
<!-- Converter Tab -->
<div id="converter-tab" class="tab-content">
<p>Enter a number and see how it looks with numeric separators in different formats:</p>
<div class="form-group">
<label for="converterInput">Number (without separators):</label>
<input type="text" id="converterInput" value="1000000" placeholder="Enter a number (e.g., 1000000)">
</div>
<div class="slider-container">
<label for="groupSize">Grouping Size: <span id="groupSizeValue">3</span> digits</label>
<input type="range" id="groupSize" class="range-slider" min="1" max="6" value="3">
</div>
<button id="convertNumber">Convert</button>
<div id="converterOutput" class="output"></div>
</div>
<!-- Examples Tab -->
<div id="examples-tab" class="tab-content">
<p>Here are some real-world examples showing how numeric separators improve readability:</p>
<table>
<thead>
<tr>
<th>Use Case</th>
<th>Without Separators</th>
<th>With Separators</th>
</tr>
</thead>
<tbody>
<tr>
<td>Budget Allocation</td>
<td><code>const budget = 1000000000;</code></td>
<td><code>const budget = 1_000_000_000;</code></td>
</tr>
<tr>
<td>Color Value (Hex)</td>
<td><code>const color = 0xff00ff;</code></td>
<td><code>const color = 0xff_00_ff;</code></td>
</tr>
<tr>
<td>Binary Flags</td>
<td><code>const flags = 0b1010010110;</code></td>
<td><code>const flags = 0b10_1001_0110;</code></td>
</tr>
<tr>
<td>Unix Timestamp</td>
<td><code>const timestamp = 1617364042000;</code></td>
<td><code>const timestamp = 1_617_364_042_000;</code></td>
</tr>
<tr>
<td>Credit Card</td>
<td><code>const cardNum = 4123567891234567;</code></td>
<td><code>const cardNum = 4123_5678_9123_4567;</code></td>
</tr>
</tbody>
</table>
<div class="flex-container" style="margin-top: 20px;">
<div class="flex-item">
<h3>Before ES12</h3>
<div class="code-block">
<pre>// Hard to quickly verify the number of zeros
const billion = 1000000000;
// Hex colors are hard to read
const purple = 0xff00ff;
// Hard to visually parse binary patterns
const binaryData = 0b1010110111;
// Difficult to check if correct
const accountBalance = 123456789.12;</pre>
</div>
</div>
<div class="flex-item">
<h3>With ES12 Numeric Separators</h3>
<div class="code-block">
<pre>// Easier to count zeros
const billion = 1_000_000_000;
// RGB components clearly visible
const purple = 0xff_00_ff;
// Grouped for better comprehension
const binaryData = 0b1010_1101_11;
// Financial amounts with clear grouping
const accountBalance = 123_456_789.12;</pre>
</div>
</div>
</div>
<div id="examplesOutput" class="output" style="margin-top: 20px;">
Click the examples above to see how JavaScript evaluates them.
</div>
<button id="runAllExamples" style="margin-top: 20px;">Run All Examples</button>
</div>
</div>
<div class="card">
<h2>Support & Compatibility</h2>
<p>Numeric separators were introduced in:</p>
<ul>
<li>ES12 / ECMAScript 2021</li>
<li>Supported in all modern browsers (Chrome 75+, Firefox 70+, Safari 13+, Edge 79+)</li>
<li>Node.js 12.5.0+</li>
</ul>
<div class="example">
<div class="example-title">Common Pitfalls:</div>
<ul>
<li>Cannot use separators at the beginning or end of a number: <code>_123</code> or <code>123_</code> are invalid</li>
<li>Cannot use separators next to the decimal point: <code>123_.456</code> or <code>123._456</code> are invalid</li>
<li>Cannot use separators in the exponent part of scientific notation: <code>1e1_0</code> is invalid</li>
<li>Cannot use separators adjacent to the prefix in hex/binary/octal: <code>0_x123</code>, <code>0x_123</code> are invalid</li>
</ul>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #f5f5f5;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #e9ecef;
--border-color: #dee2e6;
--highlight: #8a2be2;
--green: #28a745;
--yellow: #ffc107;
--red: #dc3545;
--blue: #0d6efd;
--gray: #6c757d;
--card-bg: #ffffff;
--card-shadow: rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--button-bg: #0d6efd;
--button-text: white;
--output-bg: #2d2d2d;
--border-color: #444;
--highlight: #bb86fc;
--green: #4caf50;
--yellow: #ffeb3b;
--red: #f44336;
--blue: #2196f3;
--gray: #9e9e9e;
--card-bg: #1e1e1e;
--card-shadow: rgba(0, 0, 0, 0.3);
}
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
h1, h2, h3 {
color: var(--highlight);
}
.container {
max-width: 900px;
margin: 0 auto;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 6px var(--card-shadow);
padding: 20px;
margin-bottom: 30px;
}
.code-block {
background-color: var(--code-bg);
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin-bottom: 20px;
border: 1px solid var(--border-color);
font-family: 'Fira Code', Consolas, monospace;
}
pre {
margin: 0;
font-size: 14px;
font-family: inherit;
}
code {
font-family: 'Fira Code', Consolas, monospace;
}
button {
background-color: var(--button-bg);
color: var(--button-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-top: 10px;
margin-right: 10px;
transition: opacity 0.2s;
}
button:hover {
opacity: 0.9;
}
.output {
background-color: var(--output-bg);
padding: 15px;
border-radius: 5px;
margin-top: 15px;
min-height: 50px;
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border-color);
font-family: 'Fira Code', Consolas, monospace;
font-size: 14px;
white-space: pre-wrap;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea, select {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Fira Code', Consolas, monospace;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
margin-right: 5px;
border-radius: 5px 5px 0 0;
background-color: rgba(0, 0, 0, 0.03);
}
@media (prefers-color-scheme: dark) {
.tab {
background-color: rgba(255, 255, 255, 0.05);
}
}
.tab.active {
background-color: var(--button-bg);
color: var(--button-text);
border-color: var(--border-color);
}
.tab-content {
display: none;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.tab-content.active {
display: block;
}
.result-container {
display: flex;
margin-bottom: 15px;
align-items: center;
}
.result-label {
flex: 1;
font-weight: bold;
}
.result-value {
flex: 2;
font-family: 'Fira Code', Consolas, monospace;
}
.highlight {
color: var(--highlight);
font-weight: bold;
}
.success {
color: var(--green);
}
.warning {
color: var(--yellow);
}
.error {
color: var(--red);
}
.separator {
color: var(--blue);
font-weight: bold;
}
.example {
margin-bottom: 20px;
padding: 15px;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 5px;
border-left: 4px solid var(--highlight);
}
@media (prefers-color-scheme: dark) {
.example {
background-color: rgba(255, 255, 255, 0.05);
}
}
.example-title {
font-weight: bold;
margin-bottom: 10px;
}
.flex-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.flex-item {
flex: 1;
min-width: 250px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 10px;
text-align: left;
border: 1px solid var(--border-color);
}
th {
background-color: rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
th {
background-color: rgba(255, 255, 255, 0.05);
}
}
/* Range slider styles */
.slider-container {
margin: 20px 0;
}
.range-slider {
-webkit-appearance: none;
width: 100%;
height: 10px;
border-radius: 5px;
background: var(--gray);
outline: none;
}
.range-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--highlight);
cursor: pointer;
}
.range-slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--highlight);
cursor: pointer;
}
/* Number format highlighting */
.number-part {
color: var(--blue);
}
.separator-part {
color: var(--highlight);
font-weight: bold;
}
.format-badge {
display: inline-block;
padding: 3px 6px;
border-radius: 3px;
margin-right: 5px;
font-size: 12px;
}
.badge-decimal {
background-color: rgba(13, 110, 253, 0.1);
border: 1px solid var(--blue);
color: var(--blue);
}
.badge-hex {
background-color: rgba(138, 43, 226, 0.1);
border: 1px solid var(--highlight);
color: var(--highlight);
}
.badge-binary {
background-color: rgba(40, 167, 69, 0.1);
border: 1px solid var(--green);
color: var(--green);
}
.badge-octal {
background-color: rgba(255, 193, 7, 0.1);
border: 1px solid var(--yellow);
color: var(--yellow);
}
.badge-scientific {
background-color: rgba(220, 53, 69, 0.1);
border: 1px solid var(--red);
color: var(--red);
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
const tabId = `${tab.dataset.tab}-tab`;
document.getElementById(tabId).classList.add('active');
});
});
// Playground functionality
document.getElementById('evaluateNumber').addEventListener('click', () => {
const input = document.getElementById('playgroundInput').value;
const output = document.getElementById('playgroundOutput');
try {
// Check if it's a valid number with separators
if (!/^[0-9a-fx._b-]+$/i.test(input)) {
throw new Error('Invalid input. Please enter a valid number with separators.');
}
// Try to evaluate the number
let result;
try {
result = eval(input); // Safe here since we're only working with numbers
} catch (e) {
throw new Error(`JavaScript couldn't evaluate this as a number: ${e.message}`);
}
// Format the output based on the input type
let formattedInput = input;
let formatType = '';
if (input.startsWith('0x') || input.startsWith('0X')) {
formatType = 'Hexadecimal (base 16)';
formattedInput = formatWithHighlightedSeparators(input);
} else if (input.startsWith('0b') || input.startsWith('0B')) {
formatType = 'Binary (base 2)';
formattedInput = formatWithHighlightedSeparators(input);
} else if (input.startsWith('0o') || input.startsWith('0O')) {
formatType = 'Octal (base 8)';
formattedInput = formatWithHighlightedSeparators(input);
} else if (input.includes('e') || input.includes('E')) {
formatType = 'Scientific Notation';
formattedInput = formatWithHighlightedSeparators(input);
} else {
formatType = 'Decimal (base 10)';
formattedInput = formatWithHighlightedSeparators(input);
}
// Create output
output.innerHTML = `
<span class="highlight">Number Type:</span> ${formatType}
<span class="highlight">Input With Separators:</span> ${formattedInput}
<span class="highlight">JavaScript Value:</span> ${result}
<span class="highlight">JavaScript Type:</span> ${typeof result}
<span class="highlight">Different Representations:</span>
โข Decimal: ${result}
โข Hexadecimal: 0x${result.toString(16).toUpperCase()}
โข Binary: 0b${result.toString(2)}
โข Octal: 0o${result.toString(8)}
โข Scientific: ${result.toExponential()}`;
} catch (error) {
output.innerHTML = `<span class="error">Error: ${error.message}</span>
Make sure your number format is valid. Remember the rules:
โข Underscores must be between digits
โข Cannot use at the beginning/end of a number
โข Cannot use adjacent to the decimal point
โข Cannot use in the exponent part`;
}
});
// Helper function to highlight separators
function formatWithHighlightedSeparators(input) {
return input.replace(/(_+)/g, '<span class="separator-part">$1</span>');
}
// Converter functionality
document.getElementById('groupSize').addEventListener('input', function () {
document.getElementById('groupSizeValue').textContent = this.value;
});
document.getElementById('convertNumber').addEventListener('click', () => {
const input = document.getElementById('converterInput').value;
const groupSize = parseInt(document.getElementById('groupSize').value);
const output = document.getElementById('converterOutput');
try {
// Validate input
const inputNum = Number(input);
if (isNaN(inputNum)) {
throw new Error('Please enter a valid number.');
}
// Function to add separators based on group size
function addSeparators(numStr, size) {
// Handle the integer part
let intPart = '';
let decPart = '';
if (numStr.includes('.')) {
[intPart, decPart] = numStr.split('.');
} else {
intPart = numStr;
}
// Add separators to integer part
let result = '';
for (let i = 0; i < intPart.length; i++) {
if (i > 0 && (intPart.length - i) % size === 0) {
result += '_';
}
result += intPart[i];
}
// Add decimal part if it exists
if (decPart) {
result += '.';
// Add separators to decimal part
let decResult = '';
for (let i = 0; i < decPart.length; i++) {
if (i > 0 && i % size === 0) {
decResult += '_';
}
decResult += decPart[i];
}
result += decResult;
}
return result;
}
// Generate different formats
const decimal = inputNum.toString();
const hex = inputNum.toString(16).toUpperCase();
const binary = inputNum.toString(2);
const octal = inputNum.toString(8);
const scientific = inputNum.toExponential();
// Format with separators
const decimalWithSeparators = addSeparators(decimal, groupSize);
const hexWithSeparators = addSeparators(hex, groupSize);
const binaryWithSeparators = addSeparators(binary, groupSize);
const octalWithSeparators = addSeparators(octal, groupSize);
// Scientific notation needs special handling
let scientificWithSeparators = '';
if (scientific.includes('e')) {
const [mantissa, exponent] = scientific.split('e');
scientificWithSeparators = addSeparators(mantissa, groupSize) + 'e' + exponent;
} else {
scientificWithSeparators = addSeparators(scientific, groupSize);
}
// Create output with badges
output.innerHTML = `
<div><span class="format-badge badge-decimal">DECIMAL</span> <code>${formatWithHighlightedSeparators(decimalWithSeparators)}</code></div>
<div><span class="format-badge badge-hex">HEXADECIMAL</span> <code>0x${formatWithHighlightedSeparators(hexWithSeparators)}</code></div>
<div><span class="format-badge badge-binary">BINARY</span> <code>0b${formatWithHighlightedSeparators(binaryWithSeparators)}</code></div>
<div><span class="format-badge badge-octal">OCTAL</span> <code>0o${formatWithHighlightedSeparators(octalWithSeparators)}</code></div>
<div><span class="format-badge badge-scientific">SCIENTIFIC</span> <code>${formatWithHighlightedSeparators(scientificWithSeparators)}</code></div>
<div style="margin-top: 15px;"><span class="highlight">JavaScript code:</span></div>
<div><code>const myNumber = ${formatWithHighlightedSeparators(decimalWithSeparators)};</code></div>
<div><code>const myHexNumber = 0x${formatWithHighlightedSeparators(hexWithSeparators)};</code></div>
<div><code>const myBinaryNumber = 0b${formatWithHighlightedSeparators(binaryWithSeparators)};</code></div>`;
} catch (error) {
output.innerHTML = `<span class="error">Error: ${error.message}</span>`;
}
});
// Examples functionality
document.getElementById('runAllExamples').addEventListener('click', () => {
const output = document.getElementById('examplesOutput');
const examples = [
{ label: 'Billion', noSep: '1000000000', withSep: '1_000_000_000' },
{ label: 'Purple Color', noSep: '0xff00ff', withSep: '0xff_00_ff' },
{ label: 'Binary Data', noSep: '0b1010110111', withSep: '0b1010_1101_11' },
{ label: 'Account Balance', noSep: '123456789.12', withSep: '123_456_789.12' }];
let results = '';
examples.forEach(example => {
try {
const noSepValue = eval(example.noSep);
const withSepValue = eval(example.withSep);
const isEqual = noSepValue === withSepValue;
results += `<div><span class="highlight">${example.label}:</span></div>`;
results += `<div>Without separators: <code>${example.noSep}</code> = ${noSepValue}</div>`;
results += `<div>With separators: <code>${formatWithHighlightedSeparators(example.withSep)}</code> = ${withSepValue}</div>`;
results += `<div class="${isEqual ? 'success' : 'error'}">Values are ${isEqual ? 'equal' : 'not equal'}</div>`;
results += `<br>`;
} catch (e) {
results += `<div class="error">Error evaluating ${example.label}: ${e.message}</div>`;
}
});
output.innerHTML = results;
});
// Initialize with default values
document.getElementById('evaluateNumber').click();
document.getElementById('convertNumber').click();
This feature has no runtime impactโit's purely for improving code readability.
Logical Assignment Operators: Combining Operations#
Logical assignment operators combine logical operations with assignment, reducing boilerplate code:
const obj = { prop: 0 };
const newValue = 7;
// Before
if (!obj.prop) obj.prop = "default"; // same as ||=
if (obj.prop) obj.prop = newValue; // same as &&=
if (obj.prop === null || obj.prop === undefined) obj.prop = "default"; // same as ??=
// After: each line assigns only when its test passes
const obj = { prop: 0 };
obj.prop ??= "default"; // prop is 0, not nullish, so it stays 0
console.log(obj.prop); // 0
obj.prop ||= "default"; // 0 is falsy, so this assigns
console.log(obj.prop); // "default"
obj.prop &&= "changed"; // "default" is truthy, so this assigns
console.log(obj.prop); // "changed"
---html
<div class="container">
<h1>Logical Assignment Operators</h1>
<p class="text-center">A visual guide to JavaScript's ES12 logical assignment operators</p>
<div class="card">
<div class="card-header">What are Logical Assignment Operators?</div>
<div class="card-body">
<p>Introduced in ES12 (ECMAScript 2021), logical assignment operators combine logical operators (<code>&&</code>, <code>||</code>, <code>??</code>) with assignment (<code>=</code>) to create shorter, more expressive code.</p>
<p>These operators help eliminate repetitive patterns where a variable appears on both sides of an assignment.</p>
<table class="operators-table">
<thead>
<tr>
<th>Operator</th>
<th>Old Way</th>
<th>New Way</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>||=</code></td>
<td><code>x = x || y</code></td>
<td><code>x ||= y</code></td>
<td>Logical OR assignment: assigns <code>y</code> to <code>x</code> when <code>x</code> is falsy</td>
</tr>
<tr>
<td><code>&&=</code></td>
<td><code>x = x && y</code></td>
<td><code>x &&= y</code></td>
<td>Logical AND assignment: assigns <code>y</code> to <code>x</code> when <code>x</code> is truthy</td>
</tr>
<tr>
<td><code>??=</code></td>
<td><code>x = x ?? y</code></td>
<td><code>x ??= y</code></td>
<td>Nullish coalescing assignment: assigns <code>y</code> to <code>x</code> when <code>x</code> is null or undefined</td>
</tr>
</tbody>
</table>
<div class="note-box">
<strong>Key point:</strong> These operators are shorthand for patterns like <code>x = x || y</code>. They <span class="highlight">only perform the assignment if the condition is met</span>, which is more efficient than the longer form that always performs the assignment.
</div>
</div>
</div>
<div class="tabs">
<div class="tab active" data-tab="or-assign">Logical OR (||=)</div>
<div class="tab" data-tab="and-assign">Logical AND (&&=)</div>
<div class="tab" data-tab="nullish-assign">Nullish Coalescing (??=)</div>
<div class="tab" data-tab="playground">Interactive Demo</div>
<div class="tab" data-tab="real-world">Real-World Examples</div>
</div>
<!-- Logical OR (||=) Tab -->
<div id="or-assign-tab" class="tab-content active">
<h2>Logical OR Assignment (||=)</h2>
<p>The logical OR assignment operator (<code>||=</code>) assigns the right-hand value to the left-hand variable <strong>only if</strong> the left-hand variable is falsy (e.g., <code>false</code>, <code>0</code>, <code>''</code>, <code>null</code>, <code>undefined</code>, <code>NaN</code>).</p>
<div class="code-comparison">
<div class="code-column old-code">
<div class="code-header">
Before ES12
</div>
<div class="code-block">
<pre><span class="code-comment">// Setting default values</span>
<span class="code-keyword">let</span> <span class="code-variable">user</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>
};
<span class="code-comment">// If user.preferences doesn't exist, create it</span>
<span class="code-variable">user.preferences</span> = <span class="code-variable">user.preferences</span> || {};
<span class="code-comment">// Set default theme if it's falsy</span>
<span class="code-variable">user.preferences.theme</span> = <span class="code-variable">user.preferences.theme</span> || <span class="code-string">'light'</span>;
<span class="code-comment">// Initialize counter if it's not set</span>
<span class="code-variable">user.stats</span> = <span class="code-variable">user.stats</span> || {};
<span class="code-variable">user.stats.visits</span> = <span class="code-variable">user.stats.visits</span> || <span class="code-number">0</span>;
<span class="code-function">console.log</span>(<span class="code-variable">user</span>);</pre>
</div>
</div>
<div class="code-column new-code">
<div class="code-header">
With ES12
</div>
<div class="code-block">
<pre><span class="code-comment">// Setting default values</span>
<span class="code-keyword">let</span> <span class="code-variable">user</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>
};
<span class="code-comment">// If user.preferences doesn't exist, create it</span>
<span class="code-variable">user.preferences</span> <span class="code-highlight">||=</span> {};
<span class="code-comment">// Set default theme if it's falsy</span>
<span class="code-variable">user.preferences.theme</span> <span class="code-highlight">||=</span> <span class="code-string">'light'</span>;
<span class="code-comment">// Initialize counter if it's not set</span>
<span class="code-variable">user.stats</span> <span class="code-highlight">||=</span> {};
<span class="code-variable">user.stats.visits</span> <span class="code-highlight">||=</span> <span class="code-number">0</span>;
<span class="code-function">console.log</span>(<span class="code-variable">user</span>);</pre>
</div>
</div>
</div>
<div class="example-card">
<div class="example-title">What happens under the hood?</div>
<p>The logical OR assignment operator <code>x ||= y</code> works like this:</p>
<ul>
<li>If <code>x</code> is falsy, assign <code>y</code> to <code>x</code></li>
<li>If <code>x</code> is truthy, do nothing</li>
</ul>
<p>Importantly, it's <strong>not</strong> the same as <code>x = x || y</code>. It's actually equivalent to:</p>
<pre><code>x || (x = y)</code></pre>
<p>This means the assignment only happens if needed, which is more efficient.</p>
</div>
<button id="runOrDemo" class="btn-primary">Run Example</button>
<div id="orOutput" class="output" style="display: none;"></div>
</div>
<!-- Logical AND (&&=) Tab -->
<div id="and-assign-tab" class="tab-content">
<h2>Logical AND Assignment (&&=)</h2>
<p>The logical AND assignment operator (<code>&&=</code>) assigns the right-hand value to the left-hand variable <strong>only if</strong> the left-hand variable is truthy (not <code>false</code>, <code>0</code>, <code>''</code>, <code>null</code>, <code>undefined</code>, <code>NaN</code>).</p>
<div class="code-comparison">
<div class="code-column old-code">
<div class="code-header">
Before ES12
</div>
<div class="code-block">
<pre><span class="code-comment">// Update object properties conditionally</span>
<span class="code-keyword">let</span> <span class="code-variable">user</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>,
<span class="code-variable">settings</span>: {
<span class="code-variable">notifications</span>: <span class="code-keyword">true</span>,
<span class="code-variable">theme</span>: <span class="code-string">'dark'</span>
}
};
<span class="code-keyword">let</span> <span class="code-variable">userToUpdate</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>,
<span class="code-variable">isPremium</span>: <span class="code-keyword">true</span>
};
<span class="code-comment">// Only disable notifications if settings exists</span>
<span class="code-keyword">if</span> (<span class="code-variable">user.settings</span>) {
<span class="code-variable">user.settings.notifications</span> = <span class="code-keyword">false</span>;
}
<span class="code-comment">// Alternative using logical AND</span>
<span class="code-variable">user.settings</span> = <span class="code-variable">user.settings</span> && {
...<span class="code-variable">user.settings</span>,
<span class="code-variable">notifications</span>: <span class="code-keyword">false</span>
};
<span class="code-comment">// Only update premium status if it exists</span>
<span class="code-variable">userToUpdate.isPremium</span> = <span class="code-variable">userToUpdate.isPremium</span> && <span class="code-keyword">false</span>;
<span class="code-function">console.log</span>(<span class="code-variable">user</span>);
<span class="code-function">console.log</span>(<span class="code-variable">userToUpdate</span>);</pre>
</div>
</div>
<div class="code-column new-code">
<div class="code-header">
With ES12
</div>
<div class="code-block">
<pre><span class="code-comment">// Update object properties conditionally</span>
<span class="code-keyword">let</span> <span class="code-variable">user</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>,
<span class="code-variable">settings</span>: {
<span class="code-variable">notifications</span>: <span class="code-keyword">true</span>,
<span class="code-variable">theme</span>: <span class="code-string">'dark'</span>
}
};
<span class="code-keyword">let</span> <span class="code-variable">userToUpdate</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>,
<span class="code-variable">isPremium</span>: <span class="code-keyword">true</span>
};
<span class="code-comment">// Only disable notifications if settings exists</span>
<span class="code-variable">user.settings</span>?.<span class="code-variable">notifications</span> <span class="code-highlight">&&=</span> <span class="code-keyword">false</span>;
<span class="code-comment">// Alternative approach with object spread</span>
<span class="code-variable">user.settings</span> <span class="code-highlight">&&=</span> {
...<span class="code-variable">user.settings</span>,
<span class="code-variable">notifications</span>: <span class="code-keyword">false</span>
};
<span class="code-comment">// Only update premium status if it exists</span>
<span class="code-variable">userToUpdate.isPremium</span> <span class="code-highlight">&&=</span> <span class="code-keyword">false</span>;
<span class="code-function">console.log</span>(<span class="code-variable">user</span>);
<span class="code-function">console.log</span>(<span class="code-variable">userToUpdate</span>);</pre>
</div>
</div>
</div>
<div class="example-card">
<div class="example-title">What happens under the hood?</div>
<p>The logical AND assignment operator <code>x &&= y</code> works like this:</p>
<ul>
<li>If <code>x</code> is truthy, assign <code>y</code> to <code>x</code></li>
<li>If <code>x</code> is falsy, do nothing</li>
</ul>
<p>It's not exactly the same as <code>x = x && y</code>. It's equivalent to:</p>
<pre><code>x && (x = y)</code></pre>
<p>This means the assignment only happens if the left side is truthy, which is more efficient.</p>
</div>
<button id="runAndDemo" class="btn-primary">Run Example</button>
<div id="andOutput" class="output" style="display: none;"></div>
</div>
<!-- Nullish Coalescing (??=) Tab -->
<div id="nullish-assign-tab" class="tab-content">
<h2>Nullish Coalescing Assignment (??=)</h2>
<p>The nullish coalescing assignment operator (<code>??=</code>) assigns the right-hand value to the left-hand variable <strong>only if</strong> the left-hand variable is nullish (<code>null</code> or <code>undefined</code>).</p>
<p>This is especially useful for setting default values because it doesn't treat other falsy values like <code>0</code>, <code>''</code>, or <code>false</code> as missing.</p>
<div class="code-comparison">
<div class="code-column old-code">
<div class="code-header">
Before ES12
</div>
<div class="code-block">
<pre><span class="code-keyword">let</span> <span class="code-variable">user</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>,
<span class="code-variable">settings</span>: {
<span class="code-variable">theme</span>: <span class="code-string">'dark'</span>,
<span class="code-variable">notifications</span>: <span class="code-keyword">false</span>, <span class="code-comment">// falsy but valid</span>
<span class="code-variable">volume</span>: <span class="code-number">0</span>, <span class="code-comment">// falsy but valid</span>
<span class="code-variable">vibration</span>: <span class="code-keyword">null</span> <span class="code-comment">// nullish</span>
}
};
<span class="code-comment">// Set defaults for missing values</span>
<span class="code-keyword">if</span> (<span class="code-variable">user.settings.vibration</span> === <span class="code-keyword">undefined</span> ||
<span class="code-variable">user.settings.vibration</span> === <span class="code-keyword">null</span>) {
<span class="code-variable">user.settings.vibration</span> = <span class="code-keyword">true</span>;
}
<span class="code-comment">// Using nullish coalescing operator</span>
<span class="code-variable">user.settings.vibration</span> = <span class="code-variable">user.settings.vibration</span> ?? <span class="code-keyword">true</span>;
<span class="code-comment">// With logical OR, this would override the valid 'false'</span>
<span class="code-variable">user.settings.notifications</span> = <span class="code-variable">user.settings.notifications</span> || <span class="code-keyword">true</span>;
<span class="code-variable">user.settings.volume</span> = <span class="code-variable">user.settings.volume</span> || <span class="code-number">50</span>;
<span class="code-function">console.log</span>(<span class="code-variable">user.settings</span>);</pre>
</div>
</div>
<div class="code-column new-code">
<div class="code-header">
With ES12
</div>
<div class="code-block">
<pre><span class="code-keyword">let</span> <span class="code-variable">user</span> = {
<span class="code-variable">name</span>: <span class="code-string">'John'</span>,
<span class="code-variable">settings</span>: {
<span class="code-variable">theme</span>: <span class="code-string">'dark'</span>,
<span class="code-variable">notifications</span>: <span class="code-keyword">false</span>, <span class="code-comment">// falsy but valid</span>
<span class="code-variable">volume</span>: <span class="code-number">0</span>, <span class="code-comment">// falsy but valid</span>
<span class="code-variable">vibration</span>: <span class="code-keyword">null</span> <span class="code-comment">// nullish</span>
}
};
<span class="code-comment">// Set defaults for missing values</span>
<span class="code-variable">user.settings.vibration</span> <span class="code-highlight">??=</span> <span class="code-keyword">true</span>;
<span class="code-comment">// With ||= this would override false/0</span>
<span class="code-variable">user.settings.notifications</span> <span class="code-highlight">||=</span> <span class="code-keyword">true</span>; <span class="code-comment">// Will override false!</span>
<span class="code-variable">user.settings.volume</span> <span class="code-highlight">||=</span> <span class="code-number">50</span>; <span class="code-comment">// Will override 0!</span>
<span class="code-comment">// Using ??= preserves false and 0 values</span>
<span class="code-variable">user.settings.notifications</span> <span class="code-highlight">??=</span> <span class="code-keyword">true</span>; <span class="code-comment">// No change</span>
<span class="code-variable">user.settings.volume</span> <span class="code-highlight">??=</span> <span class="code-number">50</span>; <span class="code-comment">// No change</span>
<span class="code-function">console.log</span>(<span class="code-variable">user.settings</span>);</pre>
</div>
</div>
</div>
<div class="example-card">
<div class="example-title">When to choose ??= over ||=</div>
<p>The key difference between <code>||=</code> and <code>??=</code> is how they treat falsy values:</p>
<ul>
<li><code>||=</code> assigns the right side when the left is <strong>any falsy value</strong> (<code>false</code>, <code>0</code>, <code>''</code>, <code>null</code>, <code>undefined</code>, <code>NaN</code>)</li>
<li><code>??=</code> assigns the right side <strong>only when</strong> the left is <code>null</code> or <code>undefined</code></li>
</ul>
<p>Use <code>??=</code> when you want to preserve falsy values like <code>false</code>, <code>0</code>, or <code>''</code> that might be legitimate values.</p>
</div>
<button id="runNullishDemo" class="btn-primary">Run Example</button>
<div id="nullishOutput" class="output" style="display: none;"></div>
</div>
<!-- Interactive Demo Tab -->
<div id="playground-tab" class="tab-content">
<h2>Interactive Playground</h2>
<p>Experiment with logical assignment operators by changing the values and seeing the results.</p>
<div class="interactive-demo">
<div class="form-group">
<label for="leftValue">Left-hand value:</label>
<select id="leftValue">
<option value="'hello'">String: "hello"</option>
<option value="''">Empty string: ""</option>
<option value="42">Number: 42</option>
<option value="0">Number: 0</option>
<option value="true">Boolean: true</option>
<option value="false">Boolean: false</option>
<option value="undefined">undefined</option>
<option value="null">null</option>
<option value="NaN">NaN</option>
<option value="[]">Empty array: []</option>
<option value="{}">Empty object: {}</option>
</select>
</div>
<div class="form-group">
<label>Operator:</label>
<div>
<label><input type="radio" name="operator" value="||=" checked> Logical OR (||=)</label>
<label><input type="radio" name="operator" value="&&="> Logical AND (&&=)</label>
<label><input type="radio" name="operator" value="??="> Nullish coalescing (??=)</label>
</div>
</div>
<div class="form-group">
<label for="rightValue">Right-hand value:</label>
<select id="rightValue">
<option value="'default'">String: "default"</option>
<option value="''">Empty string: ""</option>
<option value="42">Number: 42</option>
<option value="0">Number: 0</option>
<option value="true">Boolean: true</option>
<option value="false">Boolean: false</option>
<option value="undefined">undefined</option>
<option value="null">null</option>
<option value="[]">Empty array: []</option>
<option value="{}">Empty object: {}</option>
</select>
</div>
<button id="runPlayground" class="btn-primary">Test Operation</button>
<div class="result-container" id="playgroundResult" style="display: none;">
<div class="code-block">
<pre id="playgroundCode"></pre>
</div>
<div class="result-box" id="playgroundValue"></div>
</div>
</div>
<div class="example-card">
<div class="example-title">Logical Operator Cheatsheet</div>
<table>
<thead>
<tr>
<th>Operator</th>
<th>When assignment happens</th>
<th>Equivalent to</th>
<th>Best used for</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>x ||= y</code></td>
<td>When <code>x</code> is falsy</td>
<td><code>x || (x = y)</code></td>
<td>Setting default values when any falsy value should be replaced</td>
</tr>
<tr>
<td><code>x &&= y</code></td>
<td>When <code>x</code> is truthy</td>
<td><code>x && (x = y)</code></td>
<td>Conditionally updating existing properties</td>
</tr>
<tr>
<td><code>x ??= y</code></td>
<td>When <code>x</code> is nullish</td>
<td><code>x ?? (x = y)</code></td>
<td>Setting default values only when values are missing</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Real-World Examples Tab -->
<div id="real-world-tab" class="tab-content">
<h2>Real-World Examples</h2>
<p>Here are some practical examples of how logical assignment operators can simplify your code in real-world scenarios.</p>
<div class="before-after-demo">
<div class="demo-column">
<div class="column-header">1. Default Configuration Settings</div>
<div class="code-wrapper">
<div class="code-header">Before</div>
<div class="code-block">
<pre><span class="code-keyword">function</span> <span class="code-function">initializeApp</span>(<span class="code-variable">config</span> = {}) {
<span class="code-variable">config.theme</span> = <span class="code-variable">config.theme</span> || <span class="code-string">'light'</span>;
<span class="code-variable">config.language</span> = <span class="code-variable">config.language</span> || <span class="code-string">'en'</span>;
<span class="code-variable">config.notifications</span> =
<span class="code-variable">config.notifications</span> ?? <span class="code-keyword">true</span>;
<span class="code-variable">config.volume</span> =
<span class="code-variable">config.volume</span> ?? <span class="code-number">50</span>;
<span class="code-comment">// More configuration setup...</span>
<span class="code-keyword">return</span> <span class="code-variable">config</span>;
}</pre>
</div>
</div>
<div class="code-wrapper">
<div class="code-header">After</div>
<div class="code-block">
<pre><span class="code-keyword">function</span> <span class="code-function">initializeApp</span>(<span class="code-variable">config</span> = {}) {
<span class="code-variable">config.theme</span> <span class="code-highlight">||=</span> <span class="code-string">'light'</span>;
<span class="code-variable">config.language</span> <span class="code-highlight">||=</span> <span class="code-string">'en'</span>;
<span class="code-variable">config.notifications</span> <span class="code-highlight">??=</span> <span class="code-keyword">true</span>;
<span class="code-variable">config.volume</span> <span class="code-highlight">??=</span> <span class="code-number">50</span>;
<span class="code-comment">// More configuration setup...</span>
<span class="code-keyword">return</span> <span class="code-variable">config</span>;
}</pre>
</div>
</div>
</div>
<div class="demo-column">
<div class="column-header">2. Caching and Memoization</div>
<div class="code-wrapper">
<div class="code-header">Before</div>
<div class="code-block">
<pre><span class="code-keyword">const</span> <span class="code-variable">cache</span> = {};
<span class="code-keyword">function</span> <span class="code-function">fetchData</span>(<span class="code-variable">key</span>) {
<span class="code-keyword">if</span> (!<span class="code-variable">cache</span>[<span class="code-variable">key</span>]) {
<span class="code-variable">cache</span>[<span class="code-variable">key</span>] = <span class="code-function">expensiveOperation</span>(<span class="code-variable">key</span>);
}
<span class="code-keyword">return</span> <span class="code-variable">cache</span>[<span class="code-variable">key</span>];
}
<span class="code-comment">// Alternative with nullish coalescing</span>
<span class="code-keyword">function</span> <span class="code-function">fetchData</span>(<span class="code-variable">key</span>) {
<span class="code-variable">cache</span>[<span class="code-variable">key</span>] = <span class="code-variable">cache</span>[<span class="code-variable">key</span>] ?? <span class="code-function">expensiveOperation</span>(<span class="code-variable">key</span>);
<span class="code-keyword">return</span> <span class="code-variable">cache</span>[<span class="code-variable">key</span>];
}</pre>
</div>
</div>
<div class="code-wrapper">
<div class="code-header">After</div>
<div class="code-block">
<pre><span class="code-keyword">const</span> <span class="code-variable">cache</span> = {};
<span class="code-keyword">function</span> <span class="code-function">fetchData</span>(<span class="code-variable">key</span>) {
<span class="code-variable">cache</span>[<span class="code-variable">key</span>] <span class="code-highlight">??=</span> <span class="code-function">expensiveOperation</span>(<span class="code-variable">key</span>);
<span class="code-keyword">return</span> <span class="code-variable">cache</span>[<span class="code-variable">key</span>];
}
<span class="code-comment">// Another common pattern:</span>
<span class="code-keyword">function</span> <span class="code-function">memoize</span>(<span class="code-variable">fn</span>) {
<span class="code-keyword">const</span> <span class="code-variable">cache</span> = {};
<span class="code-keyword">return</span> <span class="code-keyword">function</span>(<span class="code-variable">arg</span>) {
<span class="code-variable">cache</span>[<span class="code-variable">arg</span>] <span class="code-highlight">??=</span> <span class="code-variable">fn</span>(<span class="code-variable">arg</span>);
<span class="code-keyword">return</span> <span class="code-variable">cache</span>[<span class="code-variable">arg</span>];
};
}</pre>
</div>
</div>
</div>
</div>
<div class="before-after-demo">
<div class="demo-column">
<div class="column-header">3. Form Validation</div>
<div class="code-wrapper">
<div class="code-header">Before</div>
<div class="code-block">
<pre><span class="code-keyword">function</span> <span class="code-function">validateForm</span>(<span class="code-variable">formData</span>) {
<span class="code-keyword">const</span> <span class="code-variable">errors</span> = {};
<span class="code-comment">// Check required fields</span>
<span class="code-keyword">if</span> (!<span class="code-variable">formData.name</span>) {
<span class="code-variable">errors.name</span> = <span class="code-string">'Name is required'</span>;
}
<span class="code-keyword">if</span> (!<span class="code-variable">formData.email</span>) {
<span class="code-variable">errors.email</span> = <span class="code-string">'Email is required'</span>;
} <span class="code-keyword">else if</span> (!<span class="code-variable">isValidEmail</span>(<span class="code-variable">formData.email</span>)) {
<span class="code-variable">errors.email</span> = <span class="code-string">'Invalid email format'</span>;
}
<span class="code-comment">// Check age (0 is an invalid age)</span>
<span class="code-keyword">if</span> (!<span class="code-variable">formData.age</span>) {
<span class="code-variable">errors.age</span> = <span class="code-string">'Age is required'</span>;
} <span class="code-keyword">else if</span> (<span class="code-variable">formData.age</span> < <span class="code-number">18</span>) {
<span class="code-variable">errors.age</span> = <span class="code-string">'Must be 18 or older'</span>;
}
<span class="code-keyword">return</span> <span class="code-variable">errors</span>;
}</pre>
</div>
</div>
<div class="code-wrapper">
<div class="code-header">After</div>
<div class="code-block">
<pre><span class="code-keyword">function</span> <span class="code-function">validateForm</span>(<span class="code-variable">formData</span>) {
<span class="code-keyword">const</span> <span class="code-variable">errors</span> = {};
<span class="code-comment">// Check required fields</span>
!<span class="code-variable">formData.name</span> <span class="code-keyword">&&</span> (<span class="code-variable">errors.name</span> <span class="code-highlight">=</span> <span class="code-string">'Name is required'</span>);
<span class="code-comment">// For email, we need to check existence and validity</span>
!<span class="code-variable">formData.email</span>
? <span class="code-variable">errors.email</span> <span class="code-highlight">=</span> <span class="code-string">'Email is required'</span>
: !<span class="code-variable">isValidEmail</span>(<span class="code-variable">formData.email</span>) <span class="code-highlight">&&</span> (
<span class="code-variable">errors.email</span> = <span class="code-string">'Invalid email format'</span>
);
<span class="code-comment">// For age we need to check both existence and minimum</span>
(!<span class="code-variable">formData.age</span> && <span class="code-variable">formData.age</span> !== <span class="code-number">0</span>)
? <span class="code-variable">errors.age</span> <span class="code-highlight">=</span> <span class="code-string">'Age is required'</span>
: <span class="code-variable">formData.age</span> < <span class="code-number">18</span> <span class="code-highlight">&&</span> (
<span class="code-variable">errors.age</span> = <span class="code-string">'Must be 18 or older'</span>
);
<span class="code-keyword">return</span> <span class="code-variable">errors</span>;
}</pre>
</div>
</div>
</div>
<div class="demo-column">
<div class="column-header">4. Feature Flags & A/B Testing</div>
<div class="code-wrapper">
<div class="code-header">Before</div>
<div class="code-block">
<pre><span class="code-keyword">const</span> <span class="code-variable">userConfig</span> = {
<span class="code-variable">experiments</span>: {
<span class="code-variable">newCheckout</span>: <span class="code-keyword">true</span>,
<span class="code-comment">// Other experiment flags...</span>
}
};
<span class="code-keyword">function</span> <span class="code-function">getFeatureFlags</span>(<span class="code-variable">user</span>) {
<span class="code-keyword">const</span> <span class="code-variable">featureFlags</span> = {
<span class="code-variable">darkMode</span>: <span class="code-keyword">false</span>,
<span class="code-variable">betaFeatures</span>: <span class="code-keyword">false</span>,
<span class="code-variable">newCheckout</span>: <span class="code-keyword">false</span>
};
<span class="code-comment">// Apply user-specific overrides</span>
<span class="code-keyword">if</span> (<span class="code-variable">user.preferences</span>) {
<span class="code-variable">featureFlags.darkMode</span> =
<span class="code-variable">user.preferences.darkMode</span> || <span class="code-variable">featureFlags.darkMode</span>;
<span class="code-variable">featureFlags.betaFeatures</span> =
<span class="code-variable">user.preferences.betaFeatures</span> || <span class="code-variable">featureFlags.betaFeatures</span>;
}
<span class="code-comment">// Apply experiment overrides</span>
<span class="code-keyword">if</span> (<span class="code-variable">user.experiments</span>) {
<span class="code-variable">featureFlags.newCheckout</span> =
<span class="code-variable">user.experiments.newCheckout</span> || <span class="code-variable">featureFlags.newCheckout</span>;
}
<span class="code-keyword">return</span> <span class="code-variable">featureFlags</span>;
}</pre>
</div>
</div>
<div class="code-wrapper">
<div class="code-header">After</div>
<div class="code-block">
<pre><span class="code-keyword">const</span> <span class="code-variable">userConfig</span> = {
<span class="code-variable">experiments</span>: {
<span class="code-variable">newCheckout</span>: <span class="code-keyword">true</span>,
<span class="code-comment">// Other experiment flags...</span>
}
};
<span class="code-keyword">function</span> <span class="code-function">getFeatureFlags</span>(<span class="code-variable">user</span>) {
<span class="code-keyword">const</span> <span class="code-variable">featureFlags</span> = {
<span class="code-variable">darkMode</span>: <span class="code-keyword">false</span>,
<span class="code-variable">betaFeatures</span>: <span class="code-keyword">false</span>,
<span class="code-variable">newCheckout</span>: <span class="code-keyword">false</span>
};
<span class="code-comment">// Apply user preferences with ||= </span>
<span class="code-comment">// (we want truthy values to override)</span>
<span class="code-keyword">if</span> (<span class="code-variable">user.preferences</span>) {
<span class="code-variable">featureFlags.darkMode</span> <span class="code-highlight">||=</span> <span class="code-variable">user.preferences.darkMode</span>;
<span class="code-variable">featureFlags.betaFeatures</span> <span class="code-highlight">||=</span> <span class="code-variable">user.preferences.betaFeatures</span>;
}
<span class="code-comment">// Apply experiments</span>
<span class="code-keyword">if</span> (<span class="code-variable">user.experiments</span>) {
<span class="code-variable">featureFlags.newCheckout</span> <span class="code-highlight">||=</span> <span class="code-variable">user.experiments.newCheckout</span>;
}
<span class="code-keyword">return</span> <span class="code-variable">featureFlags</span>;
}</pre>
</div>
</div>
</div>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--code-bg: #1e1e1e;
--code-color: #d4d4d4;
--button-primary: #0d6efd;
--button-success: #198754;
--button-danger: #dc3545;
--button-warning: #ffc107;
--button-text: #fff;
--border-color: #dee2e6;
--card-bg: #ffffff;
--highlight: #9d0fb3;
--success: #28a745;
--danger: #dc3545;
--info: #0dcaf0;
--warning: #ffc107;
--selected-bg: rgba(0, 123, 255, 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--code-bg: #1e1e1e;
--code-color: #d4d4d4;
--button-primary: #0d6efd;
--button-success: #198754;
--button-danger: #dc3545;
--button-warning: #ffc107;
--button-text: #fff;
--border-color: #444;
--card-bg: #1e1e1e;
--highlight: #bb86fc;
--success: #4caf50;
--danger: #f44336;
--info: #03dac6;
--warning: #ffb74d;
--selected-bg: rgba(187, 134, 252, 0.1);
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
margin: 0;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 0 15px;
}
h1, h2, h3, h4 {
color: var(--highlight);
margin-top: 1.5em;
margin-bottom: 0.75em;
}
h1 {
font-size: 2.2rem;
text-align: center;
margin-top: 0.5em;
}
h2 {
font-size: 1.8rem;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.3em;
}
h3 {
font-size: 1.4rem;
}
p {
margin-bottom: 1em;
line-height: 1.6;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 24px;
overflow: hidden;
}
.card-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
font-weight: bold;
font-size: 1.2em;
background-color: rgba(0, 0, 0, 0.03);
}
.card-body {
padding: 20px;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
border-radius: 6px 6px 0 0;
margin-right: 5px;
user-select: none;
}
.tab:hover {
background-color: rgba(128, 128, 128, 0.1);
}
.tab.active {
background-color: var(--button-primary);
color: var(--button-text);
border-color: var(--border-color);
}
.tab-content {
display: none;
animation: fadeIn 0.3s ease;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.code-comparison {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.code-column {
flex: 1;
min-width: 300px;
}
.code-header {
padding: 8px 12px;
background-color: rgba(0, 0, 0, 0.2);
color: var(--button-text);
font-weight: bold;
border-radius: 4px 4px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.old-code .code-header {
background-color: var(--button-danger);
}
.new-code .code-header {
background-color: var(--button-success);
}
.code-block {
background-color: var(--code-bg);
color: var(--code-color);
padding: 15px;
border-radius: 0 0 4px 4px;
overflow-x: auto;
font-family: 'Fira Code', 'Consolas', 'Monaco', 'Andale Mono', monospace;
font-size: 14px;
line-height: 1.5;
tab-size: 4;
}
.code-block pre {
margin: 0;
white-space: pre;
}
.highlight {
color: var(--highlight);
font-weight: bold;
}
.success {
color: var(--success);
}
.danger {
color: var(--danger);
}
.info {
color: var(--info);
}
.warning {
color: var(--warning);
font-weight: bold;
}
button {
background-color: var(--button-primary);
color: var(--button-text);
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-right: 8px;
margin-bottom: 8px;
transition: background-color 0.2s;
}
button:hover {
opacity: 0.9;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background-color: var(--button-primary);
}
.btn-success {
background-color: var(--button-success);
}
.btn-danger {
background-color: var(--button-danger);
}
.btn-warning {
background-color: var(--button-warning);
color: #212529;
}
.output {
background-color: rgba(0, 0, 0, 0.1);
padding: 15px;
border-radius: 4px;
margin: 15px 0;
font-family: 'Fira Code', 'Consolas', 'Monaco', 'Andale Mono', monospace;
white-space: pre-wrap;
overflow-wrap: break-word;
}
.comparison-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.comparison-column {
flex: 1 1 45%;
min-width: 300px;
}
.input-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input, select {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--bg-color);
color: var(--text-color);
font-family: inherit;
box-sizing: border-box;
}
.result-container {
margin-top: 20px;
padding: 15px;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 4px;
border-left: 4px solid var(--highlight);
}
@media (prefers-color-scheme: dark) {
.result-container {
background-color: rgba(255, 255, 255, 0.05);
}
}
.code-comment {
color: #6a9955;
font-style: italic;
}
.code-keyword {
color: #569cd6;
}
.code-operator {
color: #d4d4d4;
}
.code-variable {
color: #9cdcfe;
}
.code-string {
color: #ce9178;
}
.code-number {
color: #b5cea8;
}
.code-function {
color: #dcdcaa;
}
.code-highlight {
background-color: rgba(197, 134, 192, 0.2);
border-radius: 2px;
padding: 2px 0;
}
.example-card {
background-color: rgba(0, 0, 0, 0.03);
border-left: 4px solid var(--highlight);
padding: 15px;
margin: 15px 0;
border-radius: 0 4px 4px 0;
}
@media (prefers-color-scheme: dark) {
.example-card {
background-color: rgba(255, 255, 255, 0.05);
}
}
.example-title {
font-weight: bold;
margin-bottom: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 0.9em;
}
table th, table td {
padding: 10px;
text-align: left;
border: 1px solid var(--border-color);
}
table th {
background-color: rgba(0, 0, 0, 0.05);
font-weight: bold;
}
@media (prefers-color-scheme: dark) {
table th {
background-color: rgba(255, 255, 255, 0.05);
}
}
.result-box {
background-color: var(--code-bg);
color: var(--code-color);
border-radius: 4px;
padding: 10px;
margin-top: 10px;
font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
}
.value {
font-weight: bold;
color: var(--highlight);
}
.falsy {
color: var(--danger);
font-style: italic;
}
.truthy {
color: var(--success);
font-style: italic;
}
.selected {
background-color: var(--selected-bg);
border-radius: 3px;
padding: 0 3px;
}
.note-box {
background-color: rgba(255, 193, 7, 0.1);
border-left: 4px solid var(--warning);
padding: 15px;
margin: 20px 0;
border-radius: 0 4px 4px 0;
}
@media (prefers-color-scheme: dark) {
.note-box {
background-color: rgba(255, 193, 7, 0.15);
}
}
.operators-table td {
text-align: center;
}
.operators-table td:first-child {
text-align: left;
font-weight: bold;
}
.column-header {
font-weight: bold;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid var(--border-color);
}
.interactive-demo {
background-color: rgba(0, 0, 0, 0.03);
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
@media (prefers-color-scheme: dark) {
.interactive-demo {
background-color: rgba(255, 255, 255, 0.05);
}
}
.before-after-demo {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.demo-column {
flex: 1;
min-width: 300px;
}
.code-wrapper {
margin-bottom: 20px;
}
---js
// Tab switching
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => {
t.classList.remove('active');
});
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
const tabId = tab.getAttribute('data-tab') + '-tab';
document.getElementById(tabId).classList.add('active');
});
});
// OR Assignment Demo
document.getElementById('runOrDemo').addEventListener('click', function () {
const output = document.getElementById('orOutput');
output.style.display = 'block';
output.innerHTML = '';
try {
// Before
let user1 = {
name: 'John' };
// ES5 way
user1.preferences = user1.preferences || {};
user1.preferences.theme = user1.preferences.theme || 'light';
user1.stats = user1.stats || {};
user1.stats.visits = user1.stats.visits || 0;
output.innerHTML += "<span class='highlight'>Before Logical OR (||=) Assignment:</span>\n";
output.innerHTML += JSON.stringify(user1, null, 2) + "\n\n";
// After
let user2 = {
name: 'John' };
// ES2021 way
user2.preferences ||= {};
user2.preferences.theme ||= 'light';
user2.stats ||= {};
user2.stats.visits ||= 0;
output.innerHTML += "<span class='highlight'>After Logical OR (||=) Assignment:</span>\n";
output.innerHTML += JSON.stringify(user2, null, 2) + "\n\n";
// Example with falsy values
output.innerHTML += "<span class='highlight'>Example with falsy values:</span>\n";
let config = {
debug: false, // falsy but valid!
volume: 0 // falsy but valid!
};
output.innerHTML += "Original config: " + JSON.stringify(config) + "\n";
// Apply defaults with ||=
config.debug ||= true;
config.volume ||= 100;
output.innerHTML += "After ||= (overwrites falsy values): " + JSON.stringify(config) + "\n";
output.innerHTML += "<span class='warning'>Note: ||= replaces all falsy values, even valid ones like false and 0</span>";
} catch (e) {
output.innerHTML = `<span class="error">Error: ${e.message}</span>`;
}
});
// AND Assignment Demo
document.getElementById('runAndDemo').addEventListener('click', function () {
const output = document.getElementById('andOutput');
output.style.display = 'block';
output.innerHTML = '';
try {
// Example with &&=
let user1 = {
name: 'John',
settings: {
notifications: true,
theme: 'dark' },
isPremium: true };
let user2 = { ...user1 }; // Clone for comparison
output.innerHTML += "<span class='highlight'>Original Object:</span>\n";
output.innerHTML += JSON.stringify(user1, null, 2) + "\n\n";
// Old way
if (user1.settings) {
user1.settings.notifications = false;
}
user1.isPremium = user1.isPremium && false;
output.innerHTML += "<span class='highlight'>After traditional conditional assignment:</span>\n";
output.innerHTML += JSON.stringify(user1, null, 2) + "\n\n";
// ES2021 way
if (user2.settings) {
user2.settings.notifications &&= false;
}
user2.isPremium &&= false;
output.innerHTML += "<span class='highlight'>After &&= operator:</span>\n";
output.innerHTML += JSON.stringify(user2, null, 2) + "\n\n";
// Example with falsy value
let config = {
debug: false,
notifications: true };
output.innerHTML += "<span class='highlight'>Example with falsy value:</span>\n";
output.innerHTML += "Original config: " + JSON.stringify(config) + "\n";
// Only update if truthy
config.debug &&= "Debugging enabled"; // Won't execute because debug is false
config.notifications &&= "Silenced"; // Will execute because notifications is true
output.innerHTML += "After &&= (only updates truthy values): " + JSON.stringify(config) + "\n";
output.innerHTML += "<span class='warning'>Note: &&= only assigns when the left side is truthy</span>";
} catch (e) {
output.innerHTML = `<span class="error">Error: ${e.message}</span>`;
}
});
// Nullish Assignment Demo
document.getElementById('runNullishDemo').addEventListener('click', function () {
const output = document.getElementById('nullishOutput');
output.style.display = 'block';
output.innerHTML = '';
try {
let user = {
name: 'John',
settings: {
theme: 'dark',
notifications: false, // falsy but valid
volume: 0, // falsy but valid
vibration: null // nullish
} };
output.innerHTML += "<span class='highlight'>Original Object:</span>\n";
output.innerHTML += JSON.stringify(user, null, 2) + "\n\n";
// Using ??= (only assigns when nullish)
user.settings.vibration ??= true; // Will be assigned (null is nullish)
user.settings.notifications ??= true; // Won't be assigned (false is not nullish)
user.settings.volume ??= 50; // Won't be assigned (0 is not nullish)
user.settings.colorScheme ??= 'auto'; // Will be assigned (undefined is nullish)
output.innerHTML += "<span class='highlight'>After ??= operator:</span>\n";
output.innerHTML += JSON.stringify(user, null, 2) + "\n\n";
// Compare with ||=
let user2 = {
name: 'John',
settings: {
theme: 'dark',
notifications: false, // falsy
volume: 0, // falsy
vibration: null // nullish
} };
user2.settings.vibration ||= true; // Will be assigned (null is falsy)
user2.settings.notifications ||= true; // Will be assigned (false is falsy)
user2.settings.volume ||= 50; // Will be assigned (0 is falsy)
output.innerHTML += "<span class='highlight'>After ||= operator:</span>\n";
output.innerHTML += JSON.stringify(user2, null, 2) + "\n\n";
output.innerHTML += "<span class='warning'>Comparing ||= and ??=:</span>\n";
output.innerHTML += "โข ||= replaces ANY falsy value (false, 0, '', null, undefined, NaN)\n";
output.innerHTML += "โข ??= only replaces nullish values (null, undefined)\n";
output.innerHTML += "โข Use ??= when you want to preserve valid falsy values like false, 0, or ''";
} catch (e) {
output.innerHTML = `<span class="error">Error: ${e.message}</span>`;
}
});
// Interactive Playground
document.getElementById('runPlayground').addEventListener('click', function () {
const leftValue = document.getElementById('leftValue').value;
const rightValue = document.getElementById('rightValue').value;
const operator = document.querySelector('input[name="operator"]:checked').value;
const result = document.getElementById('playgroundResult');
const codeBlock = document.getElementById('playgroundCode');
const valueBlock = document.getElementById('playgroundValue');
result.style.display = 'block';
try {
// Safely evaluate the input values
const leftEval = eval(leftValue);
const rightEval = eval(rightValue);
// Generate the code to display
let code = `let x = ${leftValue};\nconst y = ${rightValue};\n\n`;
// Determine truthiness class for display
const leftTruthiness = leftEval ? 'truthy' : 'falsy';
const isNullish = leftEval === null || leftEval === undefined;
// Calculate the result value based on the operator type (simulating the logical assignment operators)
let resultValue;
if (operator === '||=') {
code += `// x is <span class="${leftTruthiness}">${leftTruthiness}</span>\n`;
code += `// ${leftTruthiness === 'falsy' ? 'Will' : 'Will NOT'} assign y to x\n\n`;
code += `x ${operator} y; // equivalent to: x || (x = y)\n\n`;
// This simulates ||= behavior without using the actual operator
resultValue = leftEval || rightEval;
} else if (operator === '&&=') {
code += `// x is <span class="${leftTruthiness}">${leftTruthiness}</span>\n`;
code += `// ${leftTruthiness === 'truthy' ? 'Will' : 'Will NOT'} assign y to x\n\n`;
code += `x ${operator} y; // equivalent to: x && (x = y)\n\n`;
// This simulates &&= behavior without using the actual operator
resultValue = leftEval && rightEval;
} else if (operator === '??=') {
code += `// x is ${isNullish ? '<span class="falsy">nullish</span>' : '<span class="truthy">not nullish</span>'}\n`;
code += `// ${isNullish ? 'Will' : 'Will NOT'} assign y to x\n\n`;
code += `x ${operator} y; // equivalent to: x ?? (x = y)\n\n`;
// This simulates ??= behavior without using the actual operator
resultValue = leftEval !== null && leftEval !== undefined ? leftEval : rightEval;
}
codeBlock.innerHTML = code;
// Display the result in a human-readable format
let displayResult = '';
if (resultValue === undefined) {
displayResult = '<span class="falsy">undefined</span>';
} else if (resultValue === null) {
displayResult = '<span class="falsy">null</span>';
} else if (Number.isNaN(resultValue)) {
displayResult = '<span class="falsy">NaN</span>';
} else if (resultValue === '') {
displayResult = '<span class="falsy">Empty string: ""</span>';
} else if (typeof resultValue === 'object') {
displayResult = JSON.stringify(resultValue);
} else {
displayResult = resultValue.toString();
}
valueBlock.innerHTML = `<span class="highlight">Result:</span> x = ${displayResult}`;
} catch (e) {
valueBlock.innerHTML = `<span class="error">Error: ${e.message}</span>`;
}
});
These operators are particularly useful for conditionally updating values in a concise way.
WeakRefs and FinalizationRegistry: Advanced Memory Management#
Advanced, optional. Normally an object stays in memory as long as something refers to it. A WeakRef is a reference that does not keep the object alive, so the garbage collector (the part of JavaScript that frees unused memory) may remove it. You will rarely need this. It is mainly for caches. The timing is not predictable, so do not build logic that depends on when it happens:
const someObject = { name: 'cache entry' };
// Creating a weak reference
const weakRef = new WeakRef(someObject);
// Accessing the target if it still exists. deref() returns undefined once it is collected
const obj = weakRef.deref();
if (obj) {
console.log("Object still in memory"); // printed here, because someObject is still referenced
}
// Setting up cleanup when an object is garbage collected
const registry = new FinalizationRegistry((heldValue) => {
console.log(`Object with value ${heldValue} has been garbage collected`);
});
// Register an object to be tracked
registry.register(someObject, "metadata about the object");
---html
<div class="container">
<h1>WeakRef Behavior Demonstration</h1>
<div class="card">
<h2>Understanding WeakRef</h2>
<p>The <code>WeakRef</code> object, introduced in ES2021 (ES12), lets you hold a weak reference to an object, which doesn't prevent the object from being garbage collected when it's no longer referenced elsewhere.</p>
<div class="info-box">
<strong>Key Point:</strong> Regular references keep objects in memory even if they're not used anymore. <code>WeakRef</code> allows the JavaScript engine to free memory when an object is only referenced by weak references.
</div>
<div class="tabs">
<div class="tab active" data-tab="basics">Basics</div>
<div class="tab" data-tab="demo">Interactive Demo</div>
<div class="tab" data-tab="comparison">WeakRef vs. Regular Ref</div>
<div class="tab" data-tab="use-cases">Use Cases</div>
</div>
<!-- Basics Tab -->
<div id="basics-tab" class="tab-content active">
<h3>WeakRef Basics</h3>
<div class="code-block">
<div class="code-header">Creating and using a WeakRef</div>
<div class="code-content">
<pre>// Create an object
const originalObject = { data: "important information" };
// Create a weak reference to the object
const weakRef = new WeakRef(originalObject);
// Access the referenced object with .deref()
const referencedObject = weakRef.deref();
console.log(referencedObject.data); // "important information"
// If the original object is garbage collected...
// originalObject = null; (imagine this happens)
// Then later:
const maybeObject = weakRef.deref();
// maybeObject might be undefined if the object was collected
if (maybeObject) {
console.log(maybeObject.data);
} else {
console.log("Object has been garbage collected");
}</pre>
</div>
</div>
<div class="warning-box">
<strong>Remember:</strong> You can't predict exactly when garbage collection will happen. It depends on the JavaScript engine, available memory, and other factors. This makes WeakRef behavior non-deterministic.
</div>
<h3>Key Characteristics</h3>
<ul>
<li><strong>Non-deterministic:</strong> You cannot predict when the referenced object will be garbage collected</li>
<li><strong>No Enumeration:</strong> Unlike WeakMap/WeakSet, there's no way to enumerate all referenced objects</li>
<li><strong>Must use .deref():</strong> To access the referenced object, always use the <code>deref()</code> method, which returns the object or <code>undefined</code> if it's been collected</li>
<li><strong>ES2021 (ES12) feature:</strong> Supported in modern browsers and Node.js v14.6.0+</li>
</ul>
</div>
<!-- Interactive Demo Tab -->
<div id="demo-tab" class="tab-content">
<h3>Interactive WeakRef Demo</h3>
<p>This demo simulates the behavior of weak references and garbage collection. Note that in a real environment, garbage collection timing is unpredictable.</p>
<div class="memory-container">
<div class="memory-box">
<div class="memory-title">Memory Visualization</div>
<div class="memory-content" id="memoryVisualization">
<div id="objectDisplay" class="memory-object">Large Object { data: "..." }</div>
<div class="memory-arrow">โ</div>
<div id="regularRefDisplay" class="memory-ref">Regular Reference</div>
<br><br>
<div class="memory-arrow">โ</div>
<div id="weakRefDisplay" class="memory-weakref">Weak Reference</div>
</div>
</div>
<div class="memory-box">
<div class="memory-title">Reference Status</div>
<div class="memory-content" id="referenceStatus">
<div>Regular Reference: <span id="regularRefStatus" style="font-weight: bold; color: var(--success-color);">Active</span></div>
<div>Weak Reference: <span id="weakRefStatus" style="font-weight: bold; color: var(--success-color);">Active (object accessible)</span></div>
<div>Memory Status: <span id="memoryStatus" style="font-weight: bold;">Object retained in memory</span></div>
</div>
</div>
</div>
<div class="action-buttons">
<button id="createObjectBtn" class="btn" disabled>1. Create Object</button>
<button id="createRefsBtn" class="btn" disabled>2. Create References</button>
<button id="removeRegularRefBtn" class="btn" disabled>3. Remove Regular Reference</button>
<button id="triggerGCBtn" class="btn" disabled>4. Trigger Garbage Collection</button>
<button id="checkWeakRefBtn" class="btn" disabled>5. Check Weak Reference</button>
<button id="resetDemoBtn" class="btn">Reset Demo</button>
</div>
<div class="output" id="demoOutput">Demo ready. Click "Create Object" to start.</div>
<div class="timeline">
<h4>Weak Reference Lifecycle</h4>
<div class="timeline-item">
<div class="timeline-number">1</div>
<div class="timeline-content">Create an object in memory that you want to reference weakly</div>
</div>
<div class="timeline-item">
<div class="timeline-number">2</div>
<div class="timeline-content">Create both regular and weak references to the object</div>
</div>
<div class="timeline-item">
<div class="timeline-number">3</div>
<div class="timeline-content">Remove all regular references to the object</div>
</div>
<div class="timeline-item">
<div class="timeline-number">4</div>
<div class="timeline-content">When garbage collection runs, the object becomes eligible for collection because only weak references remain</div>
</div>
<div class="timeline-item">
<div class="timeline-number">5</div>
<div class="timeline-content">After garbage collection, calling <code>weakRef.deref()</code> will return <code>undefined</code></div>
</div>
</div>
</div>
<!-- Comparison Tab -->
<div id="comparison-tab" class="tab-content">
<h3>WeakRef vs. Regular References</h3>
<div class="comparison">
<div class="comparison-column">
<h4>Regular References</h4>
<div class="code-block">
<div class="code-content">
<pre>// Regular reference
let obj = { data: "important" };
let regularRef = obj;
// Even if we clear the original variable
obj = null;
// The object is still in memory
console.log(regularRef.data); // "important"
// The object will remain in memory as long as
// regularRef exists and points to it</pre>
</div>
</div>
<ul>
<li>Keep objects in memory as long as the reference exists</li>
<li>Prevent garbage collection of referenced objects</li>
<li>Always guaranteed to access the object if the reference exists</li>
<li>Can lead to memory leaks if references are forgotten</li>
</ul>
</div>
<div class="comparison-column">
<h4>Weak References</h4>
<div class="code-block">
<div class="code-content">
<pre>// Weak reference
let obj = { data: "important" };
let weakRef = new WeakRef(obj);
// If we clear the original variable
obj = null;
// The object might still be available for a while
let retrieved = weakRef.deref();
console.log(retrieved?.data); // Might be "important"
// But after garbage collection runs
// (sometime later)
retrieved = weakRef.deref();
console.log(retrieved); // Might be undefined</pre>
</div>
</div>
<ul>
<li>Do not prevent garbage collection of referenced objects</li>
<li>Object is collected when no strong references remain</li>
<li>Must check if object still exists using <code>deref()</code></li>
<li>Helps prevent memory leaks for objects that can be recreated</li>
</ul>
</div>
</div>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Regular Reference</th>
<th>WeakRef</th>
</tr>
</thead>
<tbody>
<tr>
<td>Keeps object in memory</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td>Access guarantee</td>
<td>Always available</td>
<td>May become undefined</td>
</tr>
<tr>
<td>Access method</td>
<td>Direct access</td>
<td>Must use <code>.deref()</code></td>
</tr>
<tr>
<td>Usable as cache</td>
<td>Yes, but can cause memory leaks</td>
<td>Yes, memory-efficient but may need to recreate values</td>
</tr>
<tr>
<td>Memory management</td>
<td>Manual (must null references)</td>
<td>Automatic (GC handles it)</td>
</tr>
</tbody>
</table>
</div>
<!-- Use Cases Tab -->
<div id="use-cases-tab" class="tab-content">
<h3>Practical Use Cases for WeakRef</h3>
<div class="warning-box">
<strong>Important:</strong> Due to the non-deterministic nature of WeakRef, they should not be your first choice for most problems. Mozilla Developer Network warns: "Avoid using this feature if possible."
</div>
<h4>1. Memory-Sensitive Caching</h4>
<div class="code-block">
<div class="code-content">
<pre>// Memory-sensitive cache implementation
class MemorySensitiveCache {
constructor() {
this.cache = new Map();
}
set(key, value) {
this.cache.set(key, new WeakRef(value));
return this;
}
get(key) {
const ref = this.cache.get(key);
if (!ref) return undefined;
const value = ref.deref();
if (value === undefined) {
// Reference was garbage collected
this.cache.delete(key);
return undefined;
}
return value;
}
}
// Usage
const cache = new MemorySensitiveCache();
const hugeData = { /* large data object */ };
cache.set('data', hugeData);
// Later:
const retrievedData = cache.get('data');
// If hugeData has no other references, it might be
// garbage collected and retrievedData will be undefined</pre>
</div>
</div>
<h4>2. DOM Element References</h4>
<div class="code-block">
<div class="code-content">
<pre>// Keeping track of DOM elements without preventing them
// from being garbage collected if they're removed from the DOM
function setupElementObserver(element) {
const elementRef = new WeakRef(element);
// Check periodically if the element still exists
const checkInterval = setInterval(() => {
const el = elementRef.deref();
if (!el) {
// Element has been garbage collected (likely removed from DOM)
console.log('Element no longer exists in the DOM');
clearInterval(checkInterval);
return;
}
// Do something with the element if it still exists
console.log('Element still exists:', el.textContent);
}, 1000);
}</pre>
</div>
</div>
<h4>3. Event Listener Association</h4>
<div class="code-block">
<div class="code-content">
<pre>// Associate custom data with DOM elements without
// preventing garbage collection
const elementData = new Map();
function storeDataForElement(element, data) {
elementData.set(new WeakRef(element), data);
}
function getDataForElement(element) {
for (const [weakRef, data] of elementData.entries()) {
const el = weakRef.deref();
if (el === element) {
return data;
} else if (el === undefined) {
// Clean up entries for garbage collected elements
elementData.delete(weakRef);
}
}
return null;
}</pre>
</div>
</div>
<h4>When NOT to Use WeakRef</h4>
<ul>
<li><strong>Critical Data Storage:</strong> Never use for data that must be available</li>
<li><strong>Resource Management:</strong> Not reliable for managing resources that need deterministic cleanup (use finalizers or explicit cleanup instead)</li>
<li><strong>Core Application State:</strong> Avoid using for essential application state</li>
<li><strong>API Contracts:</strong> Don't use in APIs where the existence of objects needs to be guaranteed</li>
</ul>
<div class="success-box">
<p><strong>Best Practice:</strong> Use WeakRef when you want to associate additional data with objects without preventing those objects from being garbage collected when they're no longer needed elsewhere in your program.</p>
</div>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--card-bg: #ffffff;
--border-color: #dee2e6;
--heading-color: #6610f2;
--code-bg: #f5f5f5;
--button-primary: #0d6efd;
--button-text: #ffffff;
--success-color: #198754;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #0dcaf0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--card-bg: #1e1e1e;
--border-color: #444444;
--heading-color: #bb86fc;
--code-bg: #282828;
--button-primary: #0d6efd;
--button-text: #ffffff;
--success-color: #4caf50;
--warning-color: #ffeb3b;
--danger-color: #f44336;
--info-color: #03dac5;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
transition: background-color 0.3s ease, color 0.3s ease;
}
.container {
max-width: 900px;
margin: 0 auto;
}
h1, h2, h3, h4 {
color: var(--heading-color);
}
h1 {
text-align: center;
margin-bottom: 1.5rem;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
.btn {
display: inline-block;
background-color: var(--button-primary);
color: var(--button-text);
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
margin-bottom: 10px;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.9;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
pre, code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 14px;
}
pre {
background-color: var(--code-bg);
padding: 15px;
border-radius: 4px;
overflow-x: auto;
margin: 15px 0;
}
.code-block {
position: relative;
margin: 20px 0;
}
.code-header {
background-color: rgba(0, 0, 0, 0.05);
padding: 8px 15px;
border-radius: 4px 4px 0 0;
font-weight: bold;
border: 1px solid var(--border-color);
border-bottom: none;
}
.code-content {
background-color: var(--code-bg);
padding: 15px;
border-radius: 0 0 4px 4px;
overflow-x: auto;
border: 1px solid var(--border-color);
}
@media (prefers-color-scheme: dark) {
.code-header {
background-color: rgba(255, 255, 255, 0.05);
}
}
.output {
background-color: var(--code-bg);
padding: 15px;
border-radius: 4px;
overflow-x: auto;
min-height: 100px;
margin: 15px 0;
white-space: pre-wrap;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}
.tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
margin-right: 5px;
border-radius: 4px 4px 0 0;
background-color: rgba(0, 0, 0, 0.03);
}
.tab.active {
background-color: var(--button-primary);
color: var(--button-text);
border-color: var(--border-color);
}
@media (prefers-color-scheme: dark) {
.tab {
background-color: rgba(255, 255, 255, 0.03);
}
}
.tab-content {
display: none;
animation: fadeIn 0.3s ease;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.info-box {
background-color: rgba(13, 202, 240, 0.1);
border-left: 4px solid var(--info-color);
padding: 15px;
margin: 20px 0;
border-radius: 0 4px 4px 0;
}
.success-box {
background-color: rgba(25, 135, 84, 0.1);
border-left: 4px solid var(--success-color);
padding: 15px;
margin: 20px 0;
border-radius: 0 4px 4px 0;
}
.warning-box {
background-color: rgba(255, 193, 7, 0.1);
border-left: 4px solid var(--warning-color);
padding: 15px;
margin: 20px 0;
border-radius: 0 4px 4px 0;
}
.danger-box {
background-color: rgba(220, 53, 69, 0.1);
border-left: 4px solid var(--danger-color);
padding: 15px;
margin: 20px 0;
border-radius: 0 4px 4px 0;
}
.comparison {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.comparison-column {
flex: 1;
min-width: 300px;
}
.memory-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.memory-box {
flex: 1;
min-width: 300px;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
.memory-title {
font-weight: bold;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid var(--border-color);
}
.memory-content {
min-height: 150px;
}
.memory-object {
background-color: rgba(13, 110, 253, 0.1);
border: 1px solid var(--button-primary);
border-radius: 4px;
padding: 8px;
margin-bottom: 10px;
display: inline-block;
}
.memory-arrow {
display: inline-block;
margin: 0 10px;
color: var(--text-color);
font-weight: bold;
}
.memory-ref {
background-color: rgba(25, 135, 84, 0.1);
border: 1px solid var(--success-color);
border-radius: 4px;
padding: 8px;
margin-bottom: 10px;
display: inline-block;
}
.memory-weakref {
background-color: rgba(255, 193, 7, 0.1);
border: 1px dashed var(--warning-color);
border-radius: 4px;
padding: 8px;
margin-bottom: 10px;
display: inline-block;
}
.action-buttons {
margin: 20px 0;
}
.timeline {
margin: 30px 0;
}
.timeline-item {
display: flex;
margin-bottom: 15px;
}
.timeline-number {
width: 30px;
height: 30px;
background-color: var(--button-primary);
color: var(--button-text);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
flex-shrink: 0;
}
.timeline-content {
background-color: rgba(0, 0, 0, 0.03);
padding: 15px;
border-radius: 4px;
flex-grow: 1;
}
@media (prefers-color-scheme: dark) {
.timeline-content {
background-color: rgba(255, 255, 255, 0.03);
}
}
/* Animation classes */
.fade-out {
opacity: 0;
transition: opacity 1s ease;
}
.highlight {
animation: highlightPulse 2s ease;
}
@keyframes highlightPulse {
0% { background-color: transparent; }
50% { background-color: rgba(255, 193, 7, 0.3); }
100% { background-color: transparent; }
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th, table td {
border: 1px solid var(--border-color);
padding: 10px;
}
table th {
background-color: rgba(0, 0, 0, 0.03);
font-weight: bold;
}
@media (prefers-color-scheme: dark) {
table th {
background-color: rgba(255, 255, 255, 0.03);
}
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
const tabId = `${tab.dataset.tab}-tab`;
document.getElementById(tabId).classList.add('active');
});
});
// Interactive Demo functionality
let originalObject = null;
let regularRef = null;
let weakRef = null;
const demoOutput = document.getElementById('demoOutput');
function updateLog(message) {
demoOutput.innerHTML += `\n${message}`;
demoOutput.scrollTop = demoOutput.scrollHeight;
}
function clearLog() {
demoOutput.innerHTML = '';
}
function updateMemoryVisualization() {
const objectDisplay = document.getElementById('objectDisplay');
const regularRefDisplay = document.getElementById('regularRefDisplay');
const weakRefDisplay = document.getElementById('weakRefDisplay');
const regularRefStatus = document.getElementById('regularRefStatus');
const weakRefStatus = document.getElementById('weakRefStatus');
const memoryStatus = document.getElementById('memoryStatus');
if (!originalObject && !regularRef) {
// No object created yet or object has been garbage collected
objectDisplay.style.opacity = '0.3';
objectDisplay.textContent = 'Large Object (not in memory)';
regularRefDisplay.style.opacity = '0.3';
weakRefDisplay.style.opacity = '0.3';
regularRefStatus.textContent = 'Inactive';
regularRefStatus.style.color = 'var(--danger-color)';
if (weakRef) {
const derefResult = weakRef.deref();
if (derefResult) {
weakRefStatus.textContent = 'Active (object accessible)';
weakRefStatus.style.color = 'var(--success-color)';
} else {
weakRefStatus.textContent = 'Active, but object collected';
weakRefStatus.style.color = 'var(--warning-color)';
}
} else {
weakRefStatus.textContent = 'Not created';
weakRefStatus.style.color = 'var(--danger-color)';
}
memoryStatus.textContent = 'Object not in memory';
memoryStatus.style.color = 'var(--danger-color)';
} else {
// Object exists
objectDisplay.style.opacity = '1';
objectDisplay.textContent = 'Large Object { data: "..." }';
if (regularRef) {
regularRefDisplay.style.opacity = '1';
regularRefStatus.textContent = 'Active';
regularRefStatus.style.color = 'var(--success-color)';
} else {
regularRefDisplay.style.opacity = '0.3';
regularRefStatus.textContent = 'Inactive';
regularRefStatus.style.color = 'var(--danger-color)';
}
if (weakRef) {
weakRefDisplay.style.opacity = '1';
const derefResult = weakRef.deref();
if (derefResult) {
weakRefStatus.textContent = 'Active (object accessible)';
weakRefStatus.style.color = 'var(--success-color)';
} else {
weakRefStatus.textContent = 'Active, but object collected';
weakRefStatus.style.color = 'var(--warning-color)';
}
} else {
weakRefDisplay.style.opacity = '0.3';
weakRefStatus.textContent = 'Not created';
weakRefStatus.style.color = 'var(--danger-color)';
}
memoryStatus.textContent = 'Object retained in memory';
memoryStatus.style.color = 'var(--success-color)';
}
}
// Reset the demo
function resetDemo() {
originalObject = null;
regularRef = null;
weakRef = null;
document.getElementById('createObjectBtn').disabled = false;
document.getElementById('createRefsBtn').disabled = true;
document.getElementById('removeRegularRefBtn').disabled = true;
document.getElementById('triggerGCBtn').disabled = true;
document.getElementById('checkWeakRefBtn').disabled = true;
clearLog();
updateLog('Demo reset. Click "Create Object" to start.');
updateMemoryVisualization();
}
// Button event listeners
document.getElementById('createObjectBtn').addEventListener('click', () => {
originalObject = {
id: Date.now(),
data: "This is a large object with important data",
nestedData: {
value: "Some nested information",
timestamp: new Date().toISOString() } };
updateLog(`Created new object in memory with id: ${originalObject.id}`);
updateMemoryVisualization();
document.getElementById('createObjectBtn').disabled = true;
document.getElementById('createRefsBtn').disabled = false;
});
document.getElementById('createRefsBtn').addEventListener('click', () => {
regularRef = originalObject;
weakRef = new WeakRef(originalObject);
updateLog('Created regular reference and weak reference to the object');
updateMemoryVisualization();
document.getElementById('createRefsBtn').disabled = true;
document.getElementById('removeRegularRefBtn').disabled = false;
});
document.getElementById('removeRegularRefBtn').addEventListener('click', () => {
updateLog('Removing regular reference to the object...');
regularRef = null;
updateMemoryVisualization();
document.getElementById('removeRegularRefBtn').disabled = true;
document.getElementById('triggerGCBtn').disabled = false;
// If we still have the original reference, show that info
if (originalObject) {
updateLog('Original object variable still exists and prevents garbage collection');
document.getElementById('objectDisplay').classList.add('highlight');
// Remove highlight after animation
setTimeout(() => {
document.getElementById('objectDisplay').classList.remove('highlight');
}, 2000);
// Clear original reference after explanation
setTimeout(() => {
updateLog('Removing original object reference...');
originalObject = null;
updateMemoryVisualization();
}, 3000);
}
});
document.getElementById('triggerGCBtn').addEventListener('click', () => {
updateLog('Simulating garbage collection...');
updateLog('Note: In a real environment, GC timing is unpredictable');
// Simulate garbage collection delay
setTimeout(() => {
// In our simulation, we'll pretend garbage collection has run
// In a real environment, we can't force GC directly from JavaScript
updateLog('Garbage collection complete');
updateMemoryVisualization();
document.getElementById('objectDisplay').classList.add('fade-out');
setTimeout(() => {
document.getElementById('triggerGCBtn').disabled = true;
document.getElementById('checkWeakRefBtn').disabled = false;
}, 1000);
}, 2000);
});
document.getElementById('checkWeakRefBtn').addEventListener('click', () => {
if (!weakRef) {
updateLog('No weak reference exists to check');
return;
}
const obj = weakRef.deref();
if (obj) {
updateLog(`Weak reference still points to object with id: ${obj.id}`);
updateLog('This means the object has not been garbage collected yet');
} else {
updateLog('Weak reference returns undefined');
updateLog('This means the object has been garbage collected');
}
document.getElementById('checkWeakRefBtn').disabled = true;
});
document.getElementById('resetDemoBtn').addEventListener('click', resetDemo);
// Initialize the demo
resetDemo();
While these are advanced features not needed in everyday code, they enable better memory management patterns for complex applications.
Best Practices for Modern JavaScript#
Combining Features for Safer Code#
The real power of these new features comes when combining them:
// Combining optional chaining, nullish coalescing, and logical assignment
const data = { user: { preferences: {} } };
const config = {};
// Before
let oldValue;
if (data && data.user && data.user.preferences) {
oldValue = data.user.preferences.theme || 'default';
}
// After
const value = data?.user?.preferences?.theme ?? 'default';
console.log(oldValue, value); // 'default' 'default'
// With assignment
config.theme ??= data?.user?.preferences?.theme ?? 'default';
console.log(config.theme); // 'default'
---html
<div class="container">
<div class="card">
<h2>Combining Modern Syntax</h2>
<p>
Modern JavaScript has evolved significantly since ES6 (2015), introducing powerful features that work brilliantly together.
This interactive demo showcases how combining these syntax patterns creates cleaner, more expressive code.
</p>
<div class="tabs">
<div class="tab active" data-tab="showcase">Feature Showcase</div>
<div class="tab" data-tab="interactive">Interactive Playground</div>
<div class="tab" data-tab="combined">Combined Pattern Examples</div>
</div>
<!-- Feature Showcase Tab -->
<div id="showcase-tab" class="tab-content active">
<div class="pattern-selector">
<button class="pattern-btn active" data-pattern="destructuring">Destructuring & Spread</button>
<button class="pattern-btn" data-pattern="arrow">Arrow Functions</button>
<button class="pattern-btn" data-pattern="template">Template Literals</button>
<button class="pattern-btn" data-pattern="nullish">Nullish Coalescing</button>
<button class="pattern-btn" data-pattern="optional">Optional Chaining</button>
<button class="pattern-btn" data-pattern="logical">Logical Assignment</button>
<button class="pattern-btn" data-pattern="rest">Rest Parameters</button>
<button class="pattern-btn" data-pattern="dynamic">Dynamic Imports</button>
</div>
<!-- Destructuring & Spread -->
<div class="pattern-content active" id="destructuring-pattern">
<h3>Destructuring Assignment & Spread Syntax</h3>
<p>Extract values from arrays and objects and spread elements with concise syntax.</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// Object extraction</span>
<span class="keyword">const</span> <span class="variable">user</span> = {
<span class="property">name</span>: <span class="string">'John'</span>,
<span class="property">age</span>: <span class="number">30</span>,
<span class="property">address</span>: {
<span class="property">city</span>: <span class="string">'New York'</span>,
<span class="property">country</span>: <span class="string">'USA'</span>
}
};
<span class="keyword">const</span> <span class="variable">name</span> = <span class="variable">user</span>.<span class="property">name</span>;
<span class="keyword">const</span> <span class="variable">age</span> = <span class="variable">user</span>.<span class="property">age</span>;
<span class="keyword">const</span> <span class="variable">country</span> = <span class="variable">user</span>.<span class="property">address</span>.<span class="property">country</span>;
<span class="comment">// Array extraction</span>
<span class="keyword">const</span> <span class="variable">colors</span> = [<span class="string">'red'</span>, <span class="string">'green'</span>, <span class="string">'blue'</span>];
<span class="keyword">const</span> <span class="variable">firstColor</span> = <span class="variable">colors</span>[<span class="number">0</span>];
<span class="keyword">const</span> <span class="variable">secondColor</span> = <span class="variable">colors</span>[<span class="number">1</span>];
<span class="comment">// Copying arrays and objects</span>
<span class="keyword">const</span> <span class="variable">colorsCopy</span> = <span class="variable">colors</span>.<span class="function">slice</span>();
<span class="keyword">const</span> <span class="variable">userCopy</span> = <span class="class">Object</span>.<span class="function">assign</span>({}, <span class="variable">user</span>);</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES6+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Object destructuring</span>
<span class="keyword">const</span> <span class="variable">user</span> = {
<span class="property">name</span>: <span class="string">'John'</span>,
<span class="property">age</span>: <span class="number">30</span>,
<span class="property">address</span>: {
<span class="property">city</span>: <span class="string">'New York'</span>,
<span class="property">country</span>: <span class="string">'USA'</span>
}
};
<span class="highlight"><span class="keyword">const</span> { <span class="property">name</span>, <span class="property">age</span>, <span class="property">address</span>: { <span class="property">country</span> } } = <span class="variable">user</span>;</span>
<span class="comment">// Array destructuring</span>
<span class="keyword">const</span> <span class="variable">colors</span> = [<span class="string">'red'</span>, <span class="string">'green'</span>, <span class="string">'blue'</span>];
<span class="highlight"><span class="keyword">const</span> [<span class="variable">firstColor</span>, <span class="variable">secondColor</span>] = <span class="variable">colors</span>;</span>
<span class="comment">// Spread syntax for copying</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">colorsCopy</span> = [...<span class="variable">colors</span>];</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">userCopy</span> = { ...<span class="variable">user</span> };</span>
<span class="comment">// Combined with rest pattern</span>
<span class="highlight"><span class="keyword">const</span> [<span class="variable">first</span>, ...<span class="variable">rest</span>] = <span class="variable">colors</span>;</span>
<span class="highlight"><span class="keyword">const</span> { <span class="property">name</span>: <span class="variable">userName</span>, ...<span class="variable">userRest</span> } = <span class="variable">user</span>;</span></pre>
</div>
</div>
<div class="interactive-demo">
<h4>Try Destructuring & Spread</h4>
<div class="input-group">
<label for="destructuringObject">Object to Destructure:</label>
<textarea id="destructuringObject" rows="4">{
name: 'Alex',
job: 'Developer',
skills: ['JavaScript', 'React', 'Node'],
contact: { email: 'alex@example.com', phone: '123-456-7890' }
}</textarea>
</div>
<div class="input-group">
<label for="destructuringPattern">Destructuring Pattern:</label>
<input type="text" id="destructuringPattern" value="const { name, job, skills: [primarySkill], contact: { email } } = obj;">
</div>
<button id="runDestructuring" class="btn">Run Destructuring</button>
<div id="destructuringOutput" class="output"></div>
</div>
</div>
<!-- Arrow Functions -->
<div class="pattern-content" id="arrow-pattern">
<h3>Arrow Functions</h3>
<p>Shorter syntax for function expressions with lexical 'this' binding.</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// Function expression</span>
<span class="keyword">const</span> <span class="variable">add</span> = <span class="keyword">function</span>(<span class="variable">a</span>, <span class="variable">b</span>) {
<span class="keyword">return</span> <span class="variable">a</span> + <span class="variable">b</span>;
};
<span class="comment">// Method with 'this' binding issues</span>
<span class="keyword">const</span> <span class="variable">counter</span> = {
<span class="property">count</span>: <span class="number">0</span>,
<span class="property">increment</span>: <span class="keyword">function</span>() {
<span class="keyword">var</span> <span class="variable">self</span> = <span class="keyword">this</span>; <span class="comment">// Store 'this'</span>
<span class="function">setTimeout</span>(<span class="keyword">function</span>() {
<span class="variable">self</span>.<span class="property">count</span>++; <span class="comment">// Using stored 'this'</span>
<span class="function">console</span>.<span class="function">log</span>(<span class="variable">self</span>.<span class="property">count</span>);
}, <span class="number">1000</span>);
}
};
<span class="comment">// Callbacks in array methods</span>
<span class="keyword">const</span> <span class="variable">numbers</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>];
<span class="keyword">const</span> <span class="variable">doubled</span> = <span class="variable">numbers</span>.<span class="function">map</span>(<span class="keyword">function</span>(<span class="variable">num</span>) {
<span class="keyword">return</span> <span class="variable">num</span> * <span class="number">2</span>;
});</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES6+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Arrow function expression</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">add</span> = (<span class="variable">a</span>, <span class="variable">b</span>) => <span class="variable">a</span> + <span class="variable">b</span>;</span>
<span class="comment">// Arrow function with lexical 'this'</span>
<span class="keyword">const</span> <span class="variable">counter</span> = {
<span class="property">count</span>: <span class="number">0</span>,
<span class="property">increment</span>() {
<span class="highlight"><span class="function">setTimeout</span>(() => {
<span class="keyword">this</span>.<span class="property">count</span>++; <span class="comment">// 'this' refers to counter</span>
<span class="function">console</span>.<span class="function">log</span>(<span class="keyword">this</span>.<span class="property">count</span>);
}, <span class="number">1000</span>);</span>
}
};
<span class="comment">// Concise callbacks</span>
<span class="keyword">const</span> <span class="variable">numbers</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>];
<span class="highlight"><span class="keyword">const</span> <span class="variable">doubled</span> = <span class="variable">numbers</span>.<span class="function">map</span>(<span class="variable">num</span> => <span class="variable">num</span> * <span class="number">2</span>);</span>
<span class="comment">// Multi-line arrow function</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">process</span> = (<span class="variable">data</span>) => {
<span class="keyword">const</span> <span class="variable">result</span> = <span class="function">doSomething</span>(<span class="variable">data</span>);
<span class="keyword">return</span> <span class="variable">result</span>;
};</span></pre>
</div>
</div>
<div class="interactive-demo">
<h4>Try Arrow Functions</h4>
<div class="input-group">
<label for="arrowInput">Function to Convert:</label>
<input type="text" id="arrowInput" value="function(x, y) { return x * y; }">
</div>
<button id="convertToArrow" class="btn">Convert to Arrow Function</button>
<div id="arrowOutput" class="output"></div>
</div>
</div>
<!-- Template Literals -->
<div class="pattern-content" id="template-pattern">
<h3>Template Literals</h3>
<p>String interpolation and multi-line strings with embedded expressions.</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// String concatenation</span>
<span class="keyword">const</span> <span class="variable">name</span> = <span class="string">'Alice'</span>;
<span class="keyword">const</span> <span class="variable">age</span> = <span class="number">28</span>;
<span class="keyword">const</span> <span class="variable">greeting</span> = <span class="string">'Hello, my name is '</span> + <span class="variable">name</span> +
<span class="string">' and I am '</span> + <span class="variable">age</span> + <span class="string">' years old.'</span>;
<span class="comment">// Multi-line strings</span>
<span class="keyword">const</span> <span class="variable">multiLine</span> = <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>;
<span class="comment">// HTML template</span>
<span class="keyword">function</span> <span class="function">createUserCard</span>(<span class="variable">user</span>) {
<span class="keyword">return</span> <span class="string">'<div class="user-card">\n'</span> +
<span class="string">' <h2>'</span> + <span class="variable">user</span>.<span class="property">name</span> + <span class="string">'</h2>\n'</span> +
<span class="string">' <p>Email: '</span> + <span class="variable">user</span>.<span class="property">email</span> + <span class="string">'</p>\n'</span> +
<span class="string">' <p>Age: '</span> + <span class="variable">user</span>.<span class="property">age</span> + <span class="string">'</p>\n'</span> +
<span class="string">'</div>'</span>;
}</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES6+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Template literals with interpolation</span>
<span class="keyword">const</span> <span class="variable">name</span> = <span class="string">'Alice'</span>;
<span class="keyword">const</span> <span class="variable">age</span> = <span class="number">28</span>;
<span class="highlight"><span class="keyword">const</span> <span class="variable">greeting</span> = <span class="string">`Hello, my name is ${name} and I am ${age} years old.`</span>;</span>
<span class="comment">// Multi-line template literals</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">multiLine</span> = <span class="string">`This is line one.
This is line two.
This is line three.`</span>;</span>
<span class="comment">// HTML templates with expressions</span>
<span class="highlight"><span class="keyword">function</span> <span class="function">createUserCard</span>(<span class="variable">user</span>) {
<span class="keyword">return</span> <span class="string">`<div class="user-card">
<h2>${user.name}</h2>
<p>Email: ${user.email}</p>
<p>Age: ${user.age}</p>
<p>Status: ${user.active ? 'Active' : 'Inactive'}</p>
</div>`</span>;
}</span>
<span class="comment">// Tagged templates</span>
<span class="highlight"><span class="keyword">function</span> <span class="function">highlight</span>(<span class="variable">strings</span>, ...<span class="variable">values</span>) {
<span class="keyword">return</span> <span class="variable">strings</span>.<span class="function">reduce</span>((<span class="variable">result</span>, <span class="variable">str</span>, <span class="variable">i</span>) =>
<span class="variable">result</span> + <span class="variable">str</span> +
(<span class="variable">values</span>[<span class="variable">i</span>] ? <span class="string">`<mark>${values[i]}</mark>`</span> : <span class="string">''</span>),
<span class="string">''</span>
);
}
<span class="keyword">const</span> <span class="variable">highlighted</span> = <span class="function">highlight</span><span class="string">`The important word is ${keyword}.`</span>;</span></pre>
</div>
</div>
<div class="interactive-demo">
<h4>Try Template Literals</h4>
<div class="input-group">
<label for="templateInputs">Variables to Include:</label>
<textarea id="templateInputs" rows="3">const name = 'David';
const product = 'JavaScript course';
const price = 49.99;</textarea>
</div>
<div class="input-group">
<label for="templateString">Template String:</label>
<textarea id="templateString" rows="3">Hello ${name},
Thank you for purchasing the ${product} for $${price.toFixed(2)}.</textarea>
</div>
<button id="runTemplate" class="btn">Run Template</button>
<div id="templateOutput" class="output"></div>
</div>
</div>
<!-- Nullish Coalescing -->
<div class="pattern-content" id="nullish-pattern">
<h3>Nullish Coalescing Operator (??)</h3>
<p>Returns the right-hand operand when the left is null or undefined (not just falsy).</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// Setting default values</span>
<span class="keyword">function</span> <span class="function">createUser</span>(<span class="variable">options</span>) {
<span class="variable">options</span> = <span class="variable">options</span> || {};
<span class="comment">// Problem: 0, '', and false are valid values</span>
<span class="comment">// but will be replaced with defaults</span>
<span class="keyword">const</span> <span class="variable">username</span> = <span class="variable">options</span>.<span class="property">username</span> || <span class="string">'guest'</span>;
<span class="keyword">const</span> <span class="variable">level</span> = <span class="variable">options</span>.<span class="property">level</span> || <span class="number">1</span>;
<span class="keyword">const</span> <span class="variable">notifications</span> =
<span class="variable">options</span>.<span class="property">notifications</span> === <span class="keyword">undefined</span> ?
<span class="keyword">true</span> : <span class="variable">options</span>.<span class="property">notifications</span>;
<span class="keyword">return</span> {
<span class="property">username</span>: <span class="variable">username</span>,
<span class="property">level</span>: <span class="variable">level</span>,
<span class="property">notifications</span>: <span class="variable">notifications</span>
};
}</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES11+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Using nullish coalescing</span>
<span class="keyword">function</span> <span class="function">createUser</span>(<span class="variable">options</span> = {}) {
<span class="comment">// Only assigns default when value is null/undefined</span>
<span class="comment">// Preserves '', 0, false as valid values</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">username</span> = <span class="variable">options</span>.<span class="property">username</span> ?? <span class="string">'guest'</span>;</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">level</span> = <span class="variable">options</span>.<span class="property">level</span> ?? <span class="number">1</span>;</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">notifications</span> = <span class="variable">options</span>.<span class="property">notifications</span> ?? <span class="keyword">true</span>;</span>
<span class="keyword">return</span> {
<span class="property">username</span>, <span class="comment">// Object property shorthand</span>
<span class="property">level</span>,
<span class="property">notifications</span>
};
}</span>
<span class="comment">// Combined with optional chaining</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">userCity</span> = <span class="variable">user</span>?.<span class="property">address</span>?.<span class="property">city</span> ?? <span class="string">'Unknown'</span>;</span>
<span class="comment">// Nullish assignment (ES12)</span>
<span class="highlight"><span class="variable">config</span>.<span class="property">timeout</span> ??= <span class="number">3000</span>; <span class="comment">// Set only if null/undefined</span></span></pre>
</div>
</div>
<div class="interactive-demo">
<h4>Try Nullish Coalescing vs Logical OR</h4>
<div class="input-group">
<label for="nullishLeft">Value to Test:</label>
<select id="nullishLeft">
<option value="undefined">undefined</option>
<option value="null">null</option>
<option value="''">Empty string ('')</option>
<option value="0">0</option>
<option value="false">false</option>
<option value="NaN">NaN</option>
<option value="'hello'">'hello'</option>
<option value="42">42</option>
</select>
</div>
<div class="input-group">
<label for="nullishRight">Default Value:</label>
<input type="text" id="nullishRight" value="'default value'">
</div>
<button id="runNullish" class="btn">Compare OR vs Nullish</button>
<div id="nullishOutput" class="output"></div>
</div>
</div>
<!-- Optional Chaining -->
<div class="pattern-content" id="optional-pattern">
<h3>Optional Chaining Operator (?.)</h3>
<p>Access properties of nested objects without error if a reference is nullish.</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// Accessing nested properties</span>
<span class="keyword">function</span> <span class="function">getUserCity</span>(<span class="variable">user</span>) {
<span class="keyword">if</span> (<span class="variable">user</span> &&
<span class="variable">user</span>.<span class="property">address</span> &&
<span class="variable">user</span>.<span class="property">address</span>.<span class="property">city</span>) {
<span class="keyword">return</span> <span class="variable">user</span>.<span class="property">address</span>.<span class="property">city</span>;
}
<span class="keyword">return</span> <span class="string">'Unknown'</span>;
}
<span class="comment">// Calling methods safely</span>
<span class="keyword">function</span> <span class="function">processData</span>(<span class="variable">data</span>) {
<span class="keyword">if</span> (<span class="variable">data</span> &&
<span class="keyword">typeof</span> <span class="variable">data</span>.<span class="property">process</span> === <span class="string">'function'</span>) {
<span class="keyword">return</span> <span class="variable">data</span>.<span class="function">process</span>();
}
<span class="keyword">return</span> <span class="keyword">null</span>;
}
<span class="comment">// Accessing array items</span>
<span class="keyword">const</span> <span class="variable">firstItem</span> = <span class="variable">items</span> && <span class="variable">items</span>.<span class="property">length</span> > <span class="number">0</span> ?
<span class="variable">items</span>[<span class="number">0</span>] : <span class="keyword">undefined</span>;</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES11+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Using optional chaining for properties</span>
<span class="keyword">function</span> <span class="function">getUserCity</span>(<span class="variable">user</span>) {
<span class="highlight"><span class="keyword">return</span> <span class="variable">user</span>?.<span class="property">address</span>?.<span class="property">city</span> ?? <span class="string">'Unknown'</span>;</span>
}
<span class="comment">// Optional method calls</span>
<span class="keyword">function</span> <span class="function">processData</span>(<span class="variable">data</span>) {
<span class="highlight"><span class="keyword">return</span> <span class="variable">data</span>?.<span class="function">process</span>() ?? <span class="keyword">null</span>;</span>
}
<span class="comment">// Optional element access</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">firstItem</span> = <span class="variable">items</span>?.[<span class="number">0</span>];</span>
<span class="comment">// Combining with destructuring</span>
<span class="highlight"><span class="keyword">const</span> { <span class="property">name</span>, <span class="property">address</span>: { <span class="property">city</span> } = {} } = <span class="variable">user</span> ?? {};</span>
<span class="comment">// Nested optional chains</span>
<span class="highlight"><span class="keyword">const</span> <span class="variable">zipCode</span> = <span class="variable">user</span>?.<span class="property">getAddress</span>?.()?.<span class="property">zipCode</span>;</span></pre>
</div>
</div>
<div class="interactive-demo">
<h4>Try Optional Chaining</h4>
<div class="input-group">
<label for="optionalObject">Object with Nested Properties:</label>
<textarea id="optionalObject" rows="4">const user = {
name: 'Emily',
// Uncomment to test with address
// address: { city: 'Boston', zip: '02108' }
};</textarea>
</div>
<div class="input-group">
<label for="optionalExpression">Access Expression:</label>
<input type="text" id="optionalExpression" value="user?.address?.city ?? 'No city available'">
</div>
<button id="runOptional" class="btn">Run Expression</button>
<div id="optionalOutput" class="output"></div>
</div>
</div>
<!-- Logical Assignment -->
<div class="pattern-content" id="logical-pattern">
<h3>Logical Assignment Operators (||=, &&=, ??=)</h3>
<p>Combine logical operations with assignment in a concise syntax (ES12).</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// Setting default values (OR)</span>
<span class="keyword">function</span> <span class="function">initConfig</span>(<span class="variable">config</span>) {
<span class="keyword">if</span> (!<span class="variable">config</span>.<span class="property">theme</span>) {
<span class="variable">config</span>.<span class="property">theme</span> = <span class="string">'default'</span>;
}
<span class="keyword">if</span> (!<span class="variable">config</span>.<span class="property">timeout</span>) {
<span class="variable">config</span>.<span class="property">timeout</span> = <span class="number">3000</span>;
}
<span class="comment">// Alternative syntax</span>
<span class="variable">config</span>.<span class="property">theme</span> = <span class="variable">config</span>.<span class="property">theme</span> || <span class="string">'default'</span>;
<span class="variable">config</span>.<span class="property">timeout</span> = <span class="variable">config</span>.<span class="property">timeout</span> || <span class="number">3000</span>;
<span class="keyword">return</span> <span class="variable">config</span>;
}
<span class="comment">// Conditional assignment (AND)</span>
<span class="keyword">if</span> (<span class="variable">user</span>.<span class="property">isAdmin</span>) {
<span class="variable">user</span>.<span class="property">canEdit</span> = <span class="keyword">true</span>;
}
<span class="comment">// Default for nullish values</span>
<span class="keyword">if</span> (<span class="variable">options</span>.<span class="property">debug</span> === <span class="keyword">null</span> ||
<span class="variable">options</span>.<span class="property">debug</span> === <span class="keyword">undefined</span>) {
<span class="variable">options</span>.<span class="property">debug</span> = <span class="keyword">false</span>;
}</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES12+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Logical OR assignment (||=)</span>
<span class="keyword">function</span> <span class="function">initConfig</span>(<span class="variable">config</span>) {
<span class="highlight"><span class="variable">config</span>.<span class="property">theme</span> ||= <span class="string">'default'</span>;</span>
<span class="highlight"><span class="variable">config</span>.<span class="property">timeout</span> ||= <span class="number">3000</span>;</span>
<span class="keyword">return</span> <span class="variable">config</span>;
}
<span class="comment">// Logical AND assignment (&&=)</span>
<span class="highlight"><span class="variable">user</span>.<span class="property">isAdmin</span> &&= <span class="variable">user</span>.<span class="property">validatePermissions</span>();</span>
<span class="highlight"><span class="variable">user</span>.<span class="property">canEdit</span> &&= <span class="variable">user</span>.<span class="property">isAdmin</span>;</span>
<span class="comment">// Nullish coalescing assignment (??=)</span>
<span class="comment">// Only assigns when value is null/undefined</span>
<span class="highlight"><span class="variable">options</span>.<span class="property">debug</span> ??= <span class="keyword">false</span>;</span>
<span class="highlight"><span class="variable">options</span>.<span class="property">timeout</span> ??= <span class="number">3000</span>;</span>
<span class="comment">// Practical example - initializing with defaults</span>
<span class="highlight"><span class="keyword">function</span> <span class="function">createApi</span>(<span class="variable">options</span> = {}) {
<span class="variable">options</span>.<span class="property">baseUrl</span> ??= <span class="string">'https://api.example.com'</span>;
<span class="variable">options</span>.<span class="property">timeout</span> ??= <span class="number">5000</span>;
<span class="variable">options</span>.<span class="property">retries</span> ||= <span class="number">3</span>;
<span class="variable">options</span>.<span class="property">debug</span> &&= <span class="variable">process</span>.<span class="property">env</span>.<span class="property">NODE_ENV</span> !== <span class="string">'production'</span>;
<span class="keyword">return</span> <span class="variable">options</span>;
}</span></pre>
</div>
</div>
<div class="interactive-demo">
<h4>Try Logical Assignment</h4>
<div class="input-group">
<label for="logicalObject">Object to Modify:</label>
<textarea id="logicalObject" rows="3">const config = {
// Modify these values to see different behaviors
theme: '', timeout: null, debug: false, cache: true,
};</textarea>
</div>
<div class="input-group">
<label for="logicalOperator">Logical Operator:</label>
<select id="logicalOperator">
<option value="||=">||= (OR assignment)</option>
<option value="&&=">&&= (AND assignment)</option>
<option value="??="??= (Nullish assignment)</option>
</select>
</div>
<div class="input-group">
<label for="logicalProperty">Property to Update:</label>
<select id="logicalProperty">
<option value="theme">theme</option>
<option value="timeout">timeout</option>
<option value="debug">debug</option>
<option value="cache">cache</option>
</select>
</div>
<div class="input-group">
<label for="logicalValue">Value to Assign:</label>
<input type="text" id="logicalValue" value="'default'">
</div>
<button id="runLogical" class="btn">Run Assignment</button>
<div id="logicalOutput" class="output"></div>
</div>
</div>
<!-- Rest Parameters -->
<div class="pattern-content" id="rest-pattern">
<h3>Rest Parameters and Spread for Functions</h3>
<p>Collect remaining arguments into an array and spread arrays into argument lists.</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// Variadic functions (variable arguments)</span>
<span class="keyword">function</span> <span class="function">sum</span>() {
<span class="keyword">var</span> <span class="variable">total</span> = <span class="number">0</span>;
<span class="keyword">for</span> (<span class="keyword">var</span> <span class="variable">i</span> = <span class="number">0</span>; <span class="variable">i</span> < <span class="variable">arguments</span>.<span class="property">length</span>; <span class="variable">i</span>++) {
<span class="variable">total</span> += <span class="variable">arguments</span>[<span class="variable">i</span>];
}
<span class="keyword">return</span> <span class="variable">total</span>;
}
<span class="comment">// Converting arguments to array</span>
<span class="keyword">function</span> <span class="function">logArgs</span>() {
<span class="keyword">var</span> <span class="variable">args</span> = <span class="class">Array</span>.<span class="property">prototype</span>.<span class="function">slice</span>.<span class="function">call</span>(<span class="variable">arguments</span>);
<span class="variable">args</span>.<span class="function">forEach</span>(<span class="keyword">function</span>(<span class="variable">arg</span>) {
<span class="function">console</span>.<span class="function">log</span>(<span class="variable">arg</span>);
});
}
<span class="comment">// Function with fixed and variable parts</span>
<span class="keyword">function</span> <span class="function">createUser</span>(<span class="variable">name</span>, <span class="variable">email</span>) {
<span class="keyword">var</span> <span class="variable">roles</span> = <span class="class">Array</span>.<span class="property">prototype</span>.<span class="function">slice</span>.<span class="function">call</span>(<span class="variable">arguments</span>, <span class="number">2</span>);
<span class="keyword">return</span> {
<span class="property">name</span>: <span class="variable">name</span>,
<span class="property">email</span>: <span class="variable">email</span>,
<span class="property">roles</span>: <span class="variable">roles</span>
};
}</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES6+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Rest parameters</span>
<span class="highlight"><span class="keyword">function</span> <span class="function">sum</span>(...<span class="variable">numbers</span>) {
<span class="keyword">return</span> <span class="variable">numbers</span>.<span class="function">reduce</span>((<span class="variable">total</span>, <span class="variable">n</span>) => <span class="variable">total</span> + <span class="variable">n</span>, <span class="number">0</span>);
}</span>
<span class="comment">// Rest collects remaining arguments into an array</span>
<span class="highlight"><span class="keyword">function</span> <span class="function">logArgs</span>(...<span class="variable">args</span>) {
<span class="variable">args</span>.<span class="function">forEach</span>(<span class="variable">arg</span> => <span class="function">console</span>.<span class="function">log</span>(<span class="variable">arg</span>));
}</span>
<span class="comment">// Mix of normal parameters and rest parameters</span>
<span class="highlight"><span class="keyword">function</span> <span class="function">createUser</span>(<span class="variable">name</span>, <span class="variable">email</span>, ...<span class="variable">roles</span>) {
<span class="keyword">return</span> {
<span class="property">name</span>,
<span class="property">email</span>,
<span class="property">roles</span>
};
}</span>
<span class="comment">// Spread syntax for function calls</span>
<span class="keyword">const</span> <span class="variable">numbers</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];
<span class="highlight"><span class="keyword">const</span> <span class="variable">max</span> = <span class="class">Math</span>.<span class="function">max</span>(...<span class="variable">numbers</span>);</span>
<span class="comment">// Combining arrays with spread</span>
<span class="highlight"><span class="keyword">function</span> <span class="function">mergeArrays</span>(<span class="variable">arr1</span>, <span class="variable">arr2</span>) {
<span class="keyword">return</span> [...<span class="variable">arr1</span>, ...<span class="variable">arr2</span>];
}</span></pre>
</div>
</div>
<div class="interactive-demo">
<h4>Try Rest Parameters</h4>
<div class="input-group">
<label for="restParams">Function Parameter Setup:</label>
<input type="text" id="restParams" value="function logUserInfo(name, email, ...details)">
</div>
<div class="input-group">
<label for="restArgs">Function Call Arguments:</label>
<input type="text" id="restArgs" value="'Sarah', 'sarah@example.com', 'admin', 28, true">
</div>
<button id="runRest" class="btn">Run Function</button>
<div id="restOutput" class="output"></div>
</div>
</div>
<!-- Dynamic Imports -->
<div class="pattern-content" id="dynamic-pattern">
<h3>Dynamic Imports</h3>
<p>Import modules on-demand with the dynamic <code>import()</code> function (ES11).</p>
<div class="code-comparison">
<div class="code-column">
<div class="code-header">Traditional Approach</div>
<pre class="code-block syntax-highlight"><span class="comment">// Static imports - always loaded</span>
<span class="keyword">import</span> <span class="variable">Chart</span> <span class="keyword">from</span> <span class="string">'./chart.js'</span>;
<span class="keyword">import</span> { <span class="variable">formatDate</span> } <span class="keyword">from</span> <span class="string">'./utils.js'</span>;
<span class="comment">// All imported modules are loaded before
// any code executes, even if not used</span>
<span class="keyword">function</span> <span class="function">initApp</span>() {
<span class="comment">// Chart module is loaded even if
// the user never opens the charts page</span>
<span class="function">setupPage</span>();
}
<span class="keyword">function</span> <span class="function">showChart</span>() {
<span class="comment">// Uses the already loaded module</span>
<span class="keyword">const</span> <span class="variable">chart</span> = <span class="keyword">new</span> <span class="variable">Chart</span>();
<span class="variable">chart</span>.<span class="function">render</span>();
}</pre>
</div>
<div class="code-column">
<div class="code-header">Modern Approach (ES11+)</div>
<pre class="code-block syntax-highlight"><span class="comment">// Basic modules imported statically</span>
<span class="keyword">import</span> { <span class="variable">formatDate</span> } <span class="keyword">from</span> <span class="string">'./utils.js'</span>;
<span class="comment">// Heavy modules loaded dynamically</span>
<span class="highlight"><span class="keyword">async</span> <span class="keyword">function</span> <span class="function">showChart</span>() {
<span class="comment">// Import only when needed - reduces initial load</span>
<span class="keyword">const</span> { <span class="keyword">default</span>: <span class="variable">Chart</span> } = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">'./chart.js'</span>);
<span class="keyword">const</span> <span class="variable">chart</span> = <span class="keyword">new</span> <span class="variable">Chart</span>();
<span class="variable">chart</span>.<span class="function">render</span>();
}</span>
<span class="comment">// Conditional imports</span>
<span class="highlight"><span class="keyword">async</span> <span class="keyword">function</span> <span class="function">loadLanguage</span>(<span class="variable">language</span>) {
<span class="keyword">try</span> {
<span class="keyword">const</span> <span class="variable">translations</span> = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">`./i18n/${language}.js`</span>);
<span class="keyword">return</span> <span class="variable">translations</span>.<span class="keyword">default</span>;
} <span class="keyword">catch</span> (<span class="variable">error</span>) {
<span class="function">console</span>.<span class="function">warn</span>(<span class="string">`Could not load ${language}, using English`</span>);
<span class="keyword">return</span> <span class="keyword">import</span>(<span class="string">'./i18n/en.js'</span>)
.<span class="function">then</span>(<span class="variable">m</span> => <span class="variable">m</span>.<span class="keyword">default</span>);
}
}</span></pre>
</div>
</div>
<div class="interactive-demo">
<div class="info-box">
<strong>Note:</strong> Dynamic imports require a real module environment to demonstrate. This example explains the concept but can't execute real dynamic imports in this demo environment.
</div>
<h4>Dynamic Import Example</h4>
<div class="code-block">
<pre class="syntax-highlight"><span class="comment">// feature-based code splitting example</span>
<span class="keyword">const</span> <span class="variable">featureButtons</span> = {
<span class="property">charts</span>: <span class="function">document</span>.<span class="function">getElementById</span>(<span class="string">'load-charts'</span>),
<span class="property">datePicker</span>: <span class="function">document</span>.<span class="function">getElementById</span>(<span class="string">'load-date-picker'</span>),
<span class="property">dataTable</span>: <span class="function">document</span>.<span class="function">getElementById</span>(<span class="string">'load-data-table'</span>)
};
<span class="comment">// Load features on demand when the user clicks</span>
<span class="variable">featureButtons</span>.<span class="property">charts</span>.<span class="function">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">async</span> () => {
<span class="keyword">try</span> {
<span class="variable">featureButtons</span>.<span class="property">charts</span>.<span class="property">disabled</span> = <span class="keyword">true</span>;
<span class="variable">featureButtons</span>.<span class="property">charts</span>.<span class="property">textContent</span> = <span class="string">'Loading charts...'</span>;
<span class="comment">// Dynamic import - loads only when needed</span>
<span class="keyword">const</span> { <span class="function">renderCharts</span> } = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">'./features/charts.js'</span>);
<span class="comment">// Initialize the dynamically loaded module</span>
<span class="function">renderCharts</span>(<span class="string">'#chart-container'</span>);
<span class="variable">featureButtons</span>.<span class="property">charts</span>.<span class="property">textContent</span> = <span class="string">'Charts loaded!'</span>;
} <span class="keyword">catch</span> (<span class="variable">error</span>) {
<span class="function">console</span>.<span class="function">error</span>(<span class="string">'Failed to load charts:'</span>, <span class="variable">error</span>);
<span class="variable">featureButtons</span>.<span class="property">charts</span>.<span class="property">textContent</span> = <span class="string">'Error loading charts'</span>;
}
});</span></pre>
</div>
</div>
</div>
</div>
<!-- Interactive Playground Tab -->
<div id="interactive-tab" class="tab-content">
<h3>Interactive Code Playground</h3>
<p>Experiment with combining multiple modern JavaScript features in one place.</p>
<div class="interactive-demo">
<div class="input-group">
<label for="playgroundCode">JavaScript Code:</label>
<textarea id="playgroundCode" rows="15">// Example combining multiple modern JS features
const fetchUserData = async (userId) => {
try {
// Simulating API call
const user = {
id: userId,
name: 'Alex Johnson',
email: 'alex@example.com',
preferences: null,
// Uncomment to test: address: { city: 'Seattle', country: 'USA' }
};
// Optional chaining with nullish coalescing
const city = user?.address?.city ?? 'Unknown location';
// Object with shorthand properties and computed property
const key = 'lastAccessed';
const userData = {
...user,
city,
[key]: new Date().toISOString()
};
// Logical assignment
userData.preferences ??= { theme: 'light', notifications: true };
// Destructuring with rest pattern
const { id, name, ...details } = userData;
// Template literal with expressions
console.log(`User ${name} (ID: ${id}) lives in ${city}`);
return { id, name, details };
} catch (error) {
console.error(`Error fetching user: ${error.message}`);
return null;
}
};
// Call the function and log the result
fetchUserData(123).then(result => console.log(result));</textarea>
</div>
<button id="runPlayground" class="btn">Run Code</button>
<div id="playgroundOutput" class="output"></div>
</div>
<div class="info-box">
<strong>Tip:</strong> Try modifying the example to use more modern JavaScript features. Uncomment the address property, add new properties, or try different combinations of features.
</div>
</div>
<!-- Combined Pattern Examples Tab -->
<div id="combined-tab" class="tab-content">
<h3>Practical Combined Pattern Examples</h3>
<p>These examples demonstrate how combining multiple modern JavaScript features creates clean, expressive code.</p>
<div class="combined-example">
<div class="combined-title">1. Data Processing Pipeline</div>
<pre class="code-block syntax-highlight"><span class="comment">// Combining arrow functions, destructuring, spread, and modern array methods</span>
<span class="keyword">const</span> <span class="function">processUserData</span> = <span class="keyword">async</span> (<span class="variable">usersData</span> = []) => {
<span class="comment">// Filter, map, and extract in one pipeline</span>
<span class="keyword">const</span> <span class="variable">processedUsers</span> = <span class="variable">usersData</span>
.<span class="function">filter</span>(<span class="variable">user</span> => <span class="variable">user</span>?.<span class="property">active</span> ?? <span class="keyword">false</span>)
.<span class="function">map</span>(<span class="variable">user</span> => {
<span class="keyword">const</span> { <span class="property">id</span>, <span class="property">name</span>, <span class="property">email</span>, <span class="property">metadata</span> = {}, <span class="property">profile</span> = {} } = <span class="variable">user</span>;
<span class="keyword">const</span> { <span class="property">city</span> } = <span class="variable">profile</span>.<span class="property">address</span> ?? {};
<span class="keyword">return</span> {
<span class="property">id</span>,
<span class="property">name</span>,
<span class="property">email</span>,
<span class="property">displayName</span>: <span class="variable">metadata</span>.<span class="property">displayName</span> ?? <span class="variable">name</span>,
<span class="property">location</span>: <span class="variable">city</span> ?? <span class="string">'Unknown'</span>
};
});
<span class="comment">// Group by location using reduce</span>
<span class="keyword">const</span> <span class="variable">usersByLocation</span> = <span class="variable">processedUsers</span>.<span class="function">reduce</span>((<span class="variable">acc</span>, <span class="variable">user</span>) => {
<span class="variable">acc</span>[<span class="variable">user</span>.<span class="property">location</span>] ??= [];
<span class="variable">acc</span>[<span class="variable">user</span>.<span class="property">location</span>].<span class="function">push</span>(<span class="variable">user</span>);
<span class="keyword">return</span> <span class="variable">acc</span>;
}, {});
<span class="keyword">return</span> <span class="variable">usersByLocation</span>;
};</pre>
</div>
<div class="combined-example">
<div class="combined-title">2. Smart API Client</div>
<pre class="code-block syntax-highlight"><span class="comment">// Combining optional chaining, nullish coalescing, logical assignment, and async/await</span>
<span class="keyword">class</span> <span class="class">ApiClient</span> {
<span class="function">constructor</span>(<span class="variable">options</span> = {}) {
<span class="comment">// Set defaults with nullish coalescing assignment</span>
<span class="keyword">this</span>.<span class="property">baseUrl</span> = <span class="variable">options</span>.<span class="property">baseUrl</span> ?? <span class="string">'https://api.example.com'</span>;
<span class="keyword">this</span>.<span class="property">timeout</span> = <span class="variable">options</span>.<span class="property">timeout</span> ?? <span class="number">5000</span>;
<span class="keyword">this</span>.<span class="property">headers</span> = {
<span class="string">'Content-Type'</span>: <span class="string">'application/json'</span>,
...<span class="variable">options</span>.<span class="property">headers</span> ?? {}
};
<span class="comment">// Initialize cache</span>
<span class="keyword">this</span>.<span class="property">cache</span> = <span class="keyword">new</span> <span class="class">Map</span>();
<span class="keyword">this</span>.<span class="property">cacheEnabled</span> = <span class="variable">options</span>.<span class="property">cache</span> ?? <span class="keyword">true</span>;
}
<span class="keyword">async</span> <span class="function">get</span>(<span class="variable">endpoint</span>, <span class="variable">params</span> = {}) {
<span class="keyword">const</span> <span class="variable">url</span> = <span class="keyword">new</span> <span class="class">URL</span>(<span class="variable">endpoint</span>, <span class="keyword">this</span>.<span class="property">baseUrl</span>);
<span class="comment">// Add query parameters</span>
<span class="class">Object</span>.<span class="function">entries</span>(<span class="variable">params</span>).<span class="function">forEach</span>(([<span class="variable">key</span>, <span class="variable">value</span>]) => {
<span class="variable">url</span>.<span class="property">searchParams</span>.<span class="function">append</span>(<span class="variable">key</span>, <span class="variable">value</span>);
});
<span class="keyword">const</span> <span class="variable">cacheKey</span> = <span class="variable">url</span>.<span class="function">toString</span>();
<span class="comment">// Check cache if enabled</span>
<span class="keyword">if</span> (<span class="keyword">this</span>.<span class="property">cacheEnabled</span>) {
<span class="keyword">const</span> <span class="variable">cachedData</span> = <span class="keyword">this</span>.<span class="property">cache</span>.<span class="function">get</span>(<span class="variable">cacheKey</span>);
<span class="keyword">if</span> (<span class="variable">cachedData</span>) {
<span class="function">console</span>.<span class="function">log</span>(<span class="string">`Using cached data for ${url}`</span>);
<span class="keyword">return</span> <span class="variable">cachedData</span>;
}
}
<span class="keyword">try</span> {
<span class="keyword">const</span> <span class="variable">response</span> = <span class="keyword">await</span> <span class="function">fetch</span>(<span class="variable">url</span>, {
<span class="property">method</span>: <span class="string">'GET'</span>,
<span class="property">headers</span>: <span class="keyword">this</span>.<span class="property">headers</span>,
<span class="property">signal</span>: <span class="class">AbortSignal</span>.<span class="function">timeout</span>(<span class="keyword">this</span>.<span class="property">timeout</span>)
});
<span class="keyword">if</span> (!<span class="variable">response</span>.<span class="property">ok</span>) {
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="class">Error</span>(<span class="string">`API error: ${response.status}`</span>);
}
<span class="keyword">const</span> <span class="variable">data</span> = <span class="keyword">await</span> <span class="variable">response</span>.<span class="function">json</span>();
<span class="comment">// Cache the result if enabled</span>
<span class="keyword">this</span>.<span class="property">cacheEnabled</span> &&= <span class="keyword">this</span>.<span class="property">cache</span>.<span class="function">set</span>(<span class="variable">cacheKey</span>, <span class="variable">data</span>);
<span class="keyword">return</span> <span class="variable">data</span>;
} <span class="keyword">catch</span> (<span class="variable">error</span>) {
<span class="function">console</span>.<span class="function">error</span>(<span class="string">`Request failed: ${error.message}`</span>);
<span class="keyword">throw</span> <span class="variable">error</span>;
}
}
}</pre>
</div>
<div class="combined-example">
<div class="combined-title">3. Dynamic Configuration Management</div>
<pre class="code-block syntax-highlight"><span class="comment">// Combining destructuring, nullish coalescing, dynamic imports, and template literals</span>
<span class="keyword">const</span> <span class="function">loadConfig</span> = <span class="keyword">async</span> (<span class="variable">options</span> = {}) => {
<span class="keyword">const</span> {
<span class="property">environment</span> = <span class="string">'development'</span>,
<span class="property">configPath</span> = <span class="string">'./config'</span>,
<span class="property">overrides</span> = {}
} = <span class="variable">options</span>;
<span class="comment">// Load base config</span>
<span class="keyword">let</span> <span class="variable">config</span>;
<span class="keyword">try</span> {
<span class="keyword">const</span> <span class="variable">baseConfigModule</span> = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">`${configPath}/base.js`</span>);
<span class="variable">config</span> = { ...<span class="variable">baseConfigModule</span>.<span class="keyword">default</span> };
<span class="function">console</span>.<span class="function">log</span>(<span class="string">`Base config loaded from ${configPath}/base.js`</span>);
} <span class="keyword">catch</span> (<span class="variable">error</span>) {
<span class="function">console</span>.<span class="function">warn</span>(<span class="string">`Failed to load base config: ${error.message}`</span>);
<span class="variable">config</span> = {};
}
<span class="comment">// Load environment-specific config</span>
<span class="keyword">try</span> {
<span class="keyword">const</span> <span class="variable">envConfigModule</span> = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">`${configPath}/${environment}.js`</span>);
<span class="variable">config</span> = {
...<span class="variable">config</span>,
...<span class="variable">envConfigModule</span>.<span class="keyword">default</span>
};
<span class="function">console</span>.<span class="function">log</span>(<span class="string">`Environment config loaded for ${environment}`</span>);
} <span class="keyword">catch</span> (<span class="variable">error</span>) {
<span class="function">console</span>.<span class="function">warn</span>(<span class="string">`No specific config for ${environment}: ${error.message}`</span>);
}
<span class="comment">// Apply manual overrides</span>
<span class="variable">config</span> = { ...<span class="variable">config</span>, ...<span class="variable">overrides</span> };
<span class="comment">// Ensure required properties with nullish coalescing</span>
<span class="variable">config</span>.<span class="property">apiUrl</span> ??= <span class="string">'https://api.example.com'</span>;
<span class="variable">config</span>.<span class="property">timeout</span> ??= <span class="number">5000</span>;
<span class="variable">config</span>.<span class="property">retries</span> ??= <span class="number">3</span>;
<span class="comment">// Add computed properties</span>
<span class="variable">config</span>.<span class="property">isProduction</span> = <span class="variable">environment</span> === <span class="string">'production'</span>;
<span class="variable">config</span>.<span class="property">isDevelopment</span> = <span class="variable">environment</span> === <span class="string">'development'</span>;
<span class="comment">// Only enable debug in non-production</span>
<span class="variable">config</span>.<span class="property">debug</span> &&= !<span class="variable">config</span>.<span class="property">isProduction</span>;
<span class="keyword">return</span> <span class="variable">config</span>;
};</pre>
</div>
<div class="grid">
<div class="feature-card">
<div class="feature-badge">ES6</div>
<h3>Project Manager Task Assignment</h3>
<pre class="code-block syntax-highlight"><span class="keyword">const</span> <span class="function">assignTasks</span> = (<span class="variable">tasks</span>, <span class="variable">team</span> = []) => {
<span class="keyword">if</span> (!<span class="variable">team</span>.<span class="property">length</span>) <span class="keyword">return</span> [];
<span class="keyword">const</span> [<span class="variable">lead</span>, ...<span class="variable">members</span>] = <span class="variable">team</span>;
<span class="keyword">const</span> <span class="variable">criticalTasks</span> = <span class="variable">tasks</span>
.<span class="function">filter</span>(<span class="variable">t</span> => <span class="variable">t</span>.<span class="property">priority</span> === <span class="string">'high'</span>);
<span class="keyword">const</span> <span class="variable">regularTasks</span> = <span class="variable">tasks</span>
.<span class="function">filter</span>(<span class="variable">t</span> => <span class="variable">t</span>.<span class="property">priority</span> !== <span class="string">'high'</span>);
<span class="keyword">return</span> [
{
<span class="property">assignee</span>: <span class="variable">lead</span>.<span class="property">name</span>,
<span class="property">tasks</span>: <span class="variable">criticalTasks</span>
},
...<span class="variable">members</span>.<span class="function">map</span>((<span class="variable">member</span>, <span class="variable">i</span>) => ({
<span class="property">assignee</span>: <span class="variable">member</span>.<span class="property">name</span>,
<span class="property">tasks</span>: <span class="function">splitArray</span>(<span class="variable">regularTasks</span>, <span class="variable">members</span>.<span class="property">length</span>)[<span class="variable">i</span>] || []
}))
];
};</pre>
</div>
<div class="feature-card">
<div class="feature-badge">ES11/ES12</div>
<h3>Smart Form Validation</h3>
<pre class="code-block syntax-highlight"><span class="keyword">const</span> <span class="function">validateForm</span> = (<span class="variable">formData</span> = {}) => {
<span class="keyword">const</span> <span class="variable">errors</span> = {};
<span class="comment">// Required fields</span>
[<span class="string">'name'</span>, <span class="string">'email'</span>, <span class="string">'password'</span>].<span class="function">forEach</span>(<span class="variable">field</span> => {
<span class="keyword">if</span> (!<span class="variable">formData</span>[<span class="variable">field</span>]?.trim?.()) {
<span class="variable">errors</span>[<span class="variable">field</span>] = <span class="string">`${field} is required`</span>;
}
});
<span class="comment">// Email validation</span>
<span class="keyword">const</span> <span class="variable">emailRegex</span> = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
<span class="keyword">if</span> (<span class="variable">formData</span>.<span class="property">email</span> &&
!<span class="variable">emailRegex</span>.<span class="function">test</span>(<span class="variable">formData</span>.<span class="property">email</span>)) {
<span class="variable">errors</span>.<span class="property">email</span> ??= <span class="string">'Invalid email format'</span>;
}
<span class="comment">// Password checks</span>
<span class="keyword">if</span> (<span class="variable">formData</span>.<span class="property">password</span>) {
<span class="variable">formData</span>.<span class="property">password</span>.<span class="property">length</span> < <span class="number">8</span> &&=
<span class="variable">errors</span>.<span class="property">password</span> = <span class="string">'Password too short'</span>;
}
<span class="keyword">return</span> {
<span class="property">valid</span>: <span class="class">Object</span>.<span class="function">keys</span>(<span class="variable">errors</span>).<span class="property">length</span> === <span class="number">0</span>,
<span class="property">errors</span>
};
};</pre>
</div>
</div>
</div>
</div>
<div class="card">
<h2>Feature Overview Table</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Introduced</th>
<th>Description</th>
<th>Common Uses</th>
</tr>
</thead>
<tbody>
<tr>
<td>Arrow Functions</td>
<td>ES6 (2015)</td>
<td>Shorter function syntax with lexical <code>this</code></td>
<td>Callbacks, short methods, functional programming</td>
</tr>
<tr>
<td>Destructuring</td>
<td>ES6 (2015)</td>
<td>Extract values from arrays and objects</td>
<td>Parameter handling, data processing, React hooks</td>
</tr>
<tr>
<td>Template Literals</td>
<td>ES6 (2015)</td>
<td>String interpolation and multi-line strings</td>
<td>String formatting, HTML templates, SQL queries</td>
</tr>
<tr>
<td>Spread/Rest Operators</td>
<td>ES6/ES9 (2015/2018)</td>
<td>Spread arrays/objects or collect values</td>
<td>Clone objects, variadic functions, parameter handling</td>
</tr>
<tr>
<td>Optional Chaining</td>
<td>ES11 (2020)</td>
<td>Access nested properties safely with <code>?.</code></td>
<td>API responses, nested objects, avoiding null errors</td>
</tr>
<tr>
<td>Nullish Coalescing</td>
<td>ES11 (2020)</td>
<td>Default values for null/undefined with <code>??</code></td>
<td>Default parameters, configuration, form handling</td>
</tr>
<tr>
<td>Dynamic Imports</td>
<td>ES11 (2020)</td>
<td>Import modules on demand with <code>import()</code></td>
<td>Code splitting, conditional loading, performance optimization</td>
</tr>
<tr>
<td>Logical Assignment</td>
<td>ES12 (2021)</td>
<td>Combined logical operators and assignment (<code>||=</code>, <code>&&=</code>, <code>??=</code>)</td>
<td>Default values, conditionally updating properties</td>
</tr>
</tbody>
</table>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--header-bg: #6610f2;
--header-text: #ffffff;
--card-bg: #ffffff;
--border-color: #dee2e6;
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--success-color: #198754;
--danger-color: #dc3545;
--warning-color: #ffc107;
--info-color: #0dcaf0;
--code-bg: #f5f5f5;
--code-text: #212529;
--selector-bg: #e9ecef;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--header-bg: #bb86fc;
--header-text: #000000;
--card-bg: #1e1e1e;
--border-color: #444444;
--primary-color: #3d8bfd;
--secondary-color: #adb5bd;
--success-color: #42ba96;
--danger-color: #ff6b78;
--warning-color: #ffb700;
--info-color: #15e0ec;
--code-bg: #2d2d2d;
--code-text: #e0e0e0;
--selector-bg: #2d2d2d;
}
}
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;
margin: 0;
padding: 0;
transition: background-color 0.3s, color 0.3s;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--header-bg);
color: var(--header-text);
padding: 20px 0;
margin-bottom: 30px;
text-align: center;
}
h1, h2, h3, h4 {
margin-top: 1.5rem;
margin-bottom: 1rem;
}
h1 {
font-size: 2.5rem;
}
h2 {
font-size: 2rem;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.5rem;
}
h3 {
font-size: 1.5rem;
}
p {
margin-bottom: 1rem;
}
pre {
background-color: var(--code-bg);
color: var(--code-text);
padding: 1rem;
border-radius: 5px;
overflow-x: auto;
font-family: 'Fira Code', Consolas, Monaco, 'Andale Mono', monospace;
font-size: 0.9rem;
margin: 1rem 0;
}
code {
font-family: 'Fira Code', Consolas, Monaco, 'Andale Mono', monospace;
background-color: var(--code-bg);
color: var(--code-text);
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-size: 0.9em;
}
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 30px;
}
.btn {
display: inline-block;
font-weight: 400;
color: #ffffff;
text-align: center;
vertical-align: middle;
cursor: pointer;
background-color: var(--primary-color);
border: 1px solid transparent;
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: 0.25rem;
transition: background-color 0.15s ease-in-out;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.btn:hover {
opacity: 0.9;
}
.btn-success {
background-color: var(--success-color);
}
.btn-danger {
background-color: var(--danger-color);
}
.btn-warning {
background-color: var(--warning-color);
color: #212529;
}
.btn-info {
background-color: var(--info-color);
color: #212529;
}
.output {
background-color: var(--code-bg);
border-radius: 5px;
padding: 1rem;
margin: 1rem 0;
min-height: 50px;
white-space: pre-wrap;
font-family: 'Fira Code', Consolas, Monaco, 'Andale Mono', monospace;
}
.pattern-selector {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 1.5rem;
}
.pattern-btn {
background-color: var(--selector-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 8px 15px;
cursor: pointer;
font-size: 0.9rem;
color: var(--text-color);
transition: all 0.2s;
}
.pattern-btn:hover {
background-color: var(--primary-color);
color: white;
}
.pattern-btn.active {
background-color: var(--primary-color);
color: white;
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
}
.pattern-content {
display: none;
}
.pattern-content.active {
display: block;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.tabs {
display: flex;
flex-wrap: wrap;
margin-bottom: 20px;
border-bottom: 1px solid var(--border-color);
}
.tab {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
border-radius: 5px 5px 0 0;
margin-right: 5px;
}
.tab.active {
background-color: var(--primary-color);
color: white;
border-color: var(--border-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.code-comparison {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.code-column {
flex: 1;
min-width: 300px;
}
.code-header {
background-color: #6c757d;
color: white;
padding: 8px 15px;
border-radius: 5px 5px 0 0;
font-weight: bold;
}
.code-block {
background-color: var(--code-bg);
color: var(--code-text);
border-radius: 0 0 5px 5px;
padding: 15px;
overflow-x: auto;
margin-top: 0;
}
.interactive-demo {
margin: 20px 0;
padding: 20px;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 8px;
}
@media (prefers-color-scheme: dark) {
.interactive-demo {
background-color: rgba(255, 255, 255, 0.05);
}
}
.input-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input, select, textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--bg-color);
color: var(--text-color);
font-family: inherit;
box-sizing: border-box;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
.feature-card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
border-top: 4px solid var(--primary-color);
}
.feature-badge {
display: inline-block;
background-color: var(--primary-color);
color: white;
border-radius: 20px;
padding: 3px 10px;
font-size: 0.75rem;
margin-bottom: 10px;
}
.syntax-highlight .keyword { color: #569cd6; }
.syntax-highlight .operator { color: #d4d4d4; }
.syntax-highlight .string { color: #ce9178; }
.syntax-highlight .number { color: #b5cea8; }
.syntax-highlight .comment { color: #6a9955; font-style: italic; }
.syntax-highlight .function { color: #dcdcaa; }
.syntax-highlight .class { color: #4ec9b0; }
.syntax-highlight .property { color: #9cdcfe; }
.syntax-highlight .variable { color: #9cdcfe; }
.syntax-highlight .highlight { background-color: rgba(197, 134, 192, 0.2); padding: 2px 0; }
.result-box {
background-color: rgba(0, 0, 0, 0.05);
padding: 15px;
border-radius: 5px;
margin-top: 15px;
}
@media (prefers-color-scheme: dark) {
.result-box {
background-color: rgba(255, 255, 255, 0.05);
}
}
.info-box {
background-color: rgba(13, 202, 240, 0.1);
border-left: 4px solid var(--info-color);
padding: 15px;
margin: 20px 0;
border-radius: 0 4px 4px 0;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 0.9em;
}
table th,
table td {
padding: 10px;
text-align: left;
border: 1px solid var(--border-color);
}
table th {
background-color: rgba(0, 0, 0, 0.05);
font-weight: bold;
}
@media (prefers-color-scheme: dark) {
table th {
background-color: rgba(255, 255, 255, 0.05);
}
}
.combined-example {
margin: 30px 0;
padding: 20px;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 8px;
border-left: 4px solid var(--primary-color);
}
@media (prefers-color-scheme: dark) {
.combined-example {
background-color: rgba(255, 255, 255, 0.05);
}
}
.combined-title {
font-weight: bold;
margin-bottom: 10px;
color: var(--primary-color);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.code-comparison {
flex-direction: column;
}
.pattern-selector {
flex-direction: column;
}
.pattern-btn {
width: 100%;
}
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
const tabId = `${tab.dataset.tab}-tab`;
document.getElementById(tabId).classList.add('active');
});
});
// Pattern selector functionality
document.querySelectorAll('.pattern-btn').forEach(button => {
button.addEventListener('click', () => {
document.querySelectorAll('.pattern-btn').forEach(b => b.classList.remove('active'));
button.classList.add('active');
document.querySelectorAll('.pattern-content').forEach(content => {
content.classList.remove('active');
});
const patternId = `${button.dataset.pattern}-pattern`;
document.getElementById(patternId).classList.add('active');
});
});
// Destructuring demo
document.getElementById('runDestructuring').addEventListener('click', function () {
const outputDiv = document.getElementById('destructuringOutput');
outputDiv.innerHTML = '';
try {
const objText = document.getElementById('destructuringObject').value;
const pattern = document.getElementById('destructuringPattern').value;
// Create a safe evaluation environment
const obj = eval(`(${objText})`);
// Extract the variable names from the pattern (simple regex)
const varPattern = /const\s+\{\s*([^}]+)\s*\}\s*=\s*obj;/;
const match = pattern.match(varPattern);
if (!match) {
throw new Error('Pattern must be in the form: const { ... } = obj;');
}
// Extract variable names including nested patterns
const varNames = match[1].split(',').map(v => v.trim().split(':')[0].trim());
// Evaluate the destructuring
eval(pattern);
// Display the results
outputDiv.innerHTML = `<strong>Result of Destructuring:</strong>\n`;
// Get all extracted variables in this scope
varNames.forEach(name => {
try {
// For nested destructuring, get only the top-level name
const topName = name.split('.')[0];
if (topName.includes('{') || topName.includes('[')) return; // Skip complex patterns
const value = eval(topName);
outputDiv.innerHTML += `${topName} = ${JSON.stringify(value)}\n`;
} catch (e) {
outputDiv.innerHTML += `${name}: Could not evaluate\n`;
}
});
} catch (error) {
outputDiv.innerHTML = `Error: ${error.message}`;
}
});
// Arrow function conversion
document.getElementById('convertToArrow').addEventListener('click', function () {
const outputDiv = document.getElementById('arrowOutput');
const input = document.getElementById('arrowInput').value;
try {
// Simple regex to convert function to arrow function
// This is a basic conversion for demo purposes
let arrowFunc = input.replace(/function\s*\((.*?)\)\s*\{([\s\S]*?)return\s+(.*?);[\s\S]*?\}/, (match, params, body, returnVal) => {
// Clean up whitespace
params = params.trim();
returnVal = returnVal.trim();
// For simple one-liners, use concise syntax
if (body.trim() === '') {
return `(${params}) => ${returnVal}`;
}
// For single return statements with no other code
if (body.trim() === '') {
return `(${params}) => ${returnVal}`;
}
// Otherwise use block syntax
return `(${params}) => {\n${body} return ${returnVal};\n}`;
});
outputDiv.innerHTML = `<strong>Arrow Function:</strong>\n${arrowFunc}`;
// Add comparison
outputDiv.innerHTML += `\n\n<strong>Differences:</strong>\n`;
outputDiv.innerHTML += `โข Shorter syntax\n`;
outputDiv.innerHTML += `โข Lexical 'this' binding\n`;
outputDiv.innerHTML += `โข Implicit return for single expressions\n`;
} catch (error) {
outputDiv.innerHTML = `Error: ${error.message}`;
}
});
// Template literals demo
document.getElementById('runTemplate').addEventListener('click', function () {
const outputDiv = document.getElementById('templateOutput');
const inputs = document.getElementById('templateInputs').value;
const template = document.getElementById('templateString').value;
try {
// Evaluate the variables first
eval(inputs);
// Then evaluate the template string by converting it to a real template literal
const result = eval('`' + template + '`');
outputDiv.innerHTML = `<strong>Evaluated Template:</strong>\n${result}`;
// Also show how it would look with traditional concatenation
const traditional = template.
replace(/\${([^}]*)}/g, "' + ($1) + '").
replace(/`/g, "'").
replace(/\n/g, "\\n' +\n'");
outputDiv.innerHTML += `\n\n<strong>Equivalent Traditional Concatenation:</strong>\n'${traditional}'`;
} catch (error) {
outputDiv.innerHTML = `Error: ${error.message}`;
}
});
// Nullish coalescing demo
document.getElementById('runNullish').addEventListener('click', function () {
const outputDiv = document.getElementById('nullishOutput');
const leftValue = document.getElementById('nullishLeft').value;
const rightValue = document.getElementById('nullishRight').value;
try {
// Set up test values
let left;
if (leftValue === 'undefined') {
left = undefined;
} else if (leftValue === 'null') {
left = null;
} else {
left = eval(leftValue);
}
const right = eval(rightValue);
// Compare || vs ??
const orResult = left || right;
const nullishResult = left ?? right;
// Show results
outputDiv.innerHTML = `<strong>Left value:</strong> ${leftValue} (${typeof left})\n`;
outputDiv.innerHTML += `<strong>Right value:</strong> ${rightValue}\n\n`;
outputDiv.innerHTML += `<strong>Using OR (||):</strong>\n`;
outputDiv.innerHTML += `${leftValue} || ${rightValue} = ${JSON.stringify(orResult)}\n\n`;
outputDiv.innerHTML += `<strong>Using Nullish Coalescing (??):</strong>\n`;
outputDiv.innerHTML += `${leftValue} ?? ${rightValue} = ${JSON.stringify(nullishResult)}\n\n`;
// Explain the difference
if (orResult !== nullishResult) {
outputDiv.innerHTML += `<strong>Why the difference?</strong>\n`;
outputDiv.innerHTML += `โข || returns the right side when left is ANY falsy value (false, 0, \"\", null, undefined, NaN)\n`;
outputDiv.innerHTML += `โข ?? returns the right side ONLY when left is null or undefined\n`;
}
} catch (error) {
outputDiv.innerHTML = `Error: ${error.message}`;
}
});
// Optional chaining demo
document.getElementById('runOptional').addEventListener('click', function () {
const outputDiv = document.getElementById('optionalOutput');
const objText = document.getElementById('optionalObject').value;
const expression = document.getElementById('optionalExpression').value;
try {
// Evaluate the object
eval(objText);
// Evaluate the expression
const result = eval(expression);
// Show results
outputDiv.innerHTML = `<strong>Expression:</strong> ${expression}\n`;
outputDiv.innerHTML += `<strong>Result:</strong> ${JSON.stringify(result)}\n\n`;
// Show traditional equivalent
let traditional = expression.replace(/(\w+)\?\./g, '$1 && $1.');
if (traditional.includes('??')) {
traditional = `(${traditional.replace(' ?? ', ') !== undefined && (${traditional.split(' ?? ')[0]}) !== null ? (${traditional.split(' ?? ')[0]}) : (')})`;
}
outputDiv.innerHTML += `<strong>Traditional equivalent:</strong>\n${traditional}`;
} catch (error) {
outputDiv.innerHTML = `Error: ${error.message}`;
}
});
// Logical assignment demo
document.getElementById('runLogical').addEventListener('click', function () {
const outputDiv = document.getElementById('logicalOutput');
const objText = document.getElementById('logicalObject').value;
const operator = document.getElementById('logicalOperator').value;
const property = document.getElementById('logicalProperty').value;
const value = document.getElementById('logicalValue').value;
try {
// Evaluate the object first
eval(objText);
// Show original values
outputDiv.innerHTML = `<strong>Before assignment:</strong>\n`;
outputDiv.innerHTML += `config = ${JSON.stringify(config)}\n\n`;
// Explanation
outputDiv.innerHTML += `<strong>Assignment operation:</strong>\n`;
outputDiv.innerHTML += `config.${property} ${operator} ${value}\n\n`;
// Perform operation
eval(`config.${property} ${operator} ${value}`);
// Show result
outputDiv.innerHTML += `<strong>After assignment:</strong>\n`;
outputDiv.innerHTML += `config = ${JSON.stringify(config)}\n\n`;
// Explain the operation
outputDiv.innerHTML += `<strong>How it works:</strong>\n`;
if (operator === '||=') {
outputDiv.innerHTML += `โข Logical OR assignment (||=): Only assigns right side when left is falsy\n`;
outputDiv.innerHTML += `โข Equivalent to: if (!config.${property}) config.${property} = ${value}\n`;
} else if (operator === '&&=') {
outputDiv.innerHTML += `โข Logical AND assignment (&&=): Only assigns right side when left is truthy\n`;
outputDiv.innerHTML += `โข Equivalent to: if (config.${property}) config.${property} = ${value}\n`;
} else if (operator === '??=') {
outputDiv.innerHTML += `โข Nullish coalescing assignment (??=): Only assigns when left is null/undefined\n`;
outputDiv.innerHTML += `โข Equivalent to: if (config.${property} === null || config.${property} === undefined) config.${property} = ${value}\n`;
}
} catch (error) {
outputDiv.innerHTML = `Error: ${error.message}`;
}
});
// Rest parameters demo
document.getElementById('runRest').addEventListener('click', function () {
const outputDiv = document.getElementById('restOutput');
const paramsText = document.getElementById('restParams').value;
const argsText = document.getElementById('restArgs').value;
try {
// Extract function name, params, and details
const funcMatch = paramsText.match(/function\s+(\w+)\s*\((.*?)\)/);
if (!funcMatch) {
throw new Error('Invalid function syntax');
}
const funcName = funcMatch[1];
const params = funcMatch[2].split(',').map(p => p.trim());
// Find the rest parameter
const restParam = params.find(p => p.startsWith('...'));
const restName = restParam ? restParam.substring(3) : null;
// Create the function
const funcBody = `
// Get the arguments
const normalArgs = Array.prototype.slice.call(arguments, 0, ${params.length - (restName ? 1 : 0)});
const restArgs = Array.prototype.slice.call(arguments, ${params.length - (restName ? 1 : 0)});
// Log info
console.log('Function ${funcName} called with:');
${params.map((p, i) => {
if (p.startsWith('...')) {
return `console.log(' ${p}: ', restArgs);`;
} else {
return `console.log(' ${p}: ', normalArgs[${i}]);`;
}
}).join('\n')}
return { normalArgs, restArgs };
`;
// Create and evaluate the function
const func = new Function(funcBody);
Object.defineProperty(func, 'name', { value: funcName });
// Parse arguments and call the function
const args = argsText.split(',').map(arg => eval(arg.trim()));
const result = func.apply(null, args);
// Output the result
outputDiv.innerHTML = `<strong>Function Signature:</strong> ${paramsText}\n`;
outputDiv.innerHTML += `<strong>Called with:</strong> ${argsText}\n\n`;
params.forEach((param, i) => {
if (param.startsWith('...')) {
outputDiv.innerHTML += `<strong>${param}:</strong> ${JSON.stringify(result.restArgs)}\n`;
} else {
outputDiv.innerHTML += `<strong>${param}:</strong> ${JSON.stringify(result.normalArgs[i])}\n`;
}
});
// Compare to old way
outputDiv.innerHTML += `\n<strong>Traditional equivalent (pre-ES6):</strong>\n`;
const oldFuncParams = params.filter(p => !p.startsWith('...')).join(', ');
outputDiv.innerHTML += `function ${funcName}(${oldFuncParams}) {\n`;
outputDiv.innerHTML += ` // Collect remaining arguments\n`;
outputDiv.innerHTML += ` var ${restName} = Array.prototype.slice.call(arguments, ${params.length - 1});\n`;
outputDiv.innerHTML += ` // Function body...\n`;
outputDiv.innerHTML += `}`;
} catch (error) {
outputDiv.innerHTML = `Error: ${error.message}`;
}
});
// Interactive playground
document.getElementById('runPlayground').addEventListener('click', function () {
const outputDiv = document.getElementById('playgroundOutput');
const code = document.getElementById('playgroundCode').value;
try {
// Create a sandbox for safe evaluation
const sandbox = {};
const sandboxProxy = new Proxy(sandbox, {
has: () => true,
get: (target, key) => {
if (key === 'console') {
// Redirect console to our output
return {
log: (...args) => {
const output = args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2);
} else {
return String(arg);
}
}).join(' ');
outputDiv.textContent += output + '\n';
},
error: (...args) => {
const output = args.map(arg => String(arg)).join(' ');
outputDiv.textContent += `Error: ${output}\n`;
} };
}
// Control which globals are available
const globals = {
setTimeout, clearTimeout,
console, Object, Array, String, Number,
Date, Math, Promise, Set, Map,
JSON, RegExp, Error };
if (key in globals) {
return globals[key];
}
return undefined;
} });
// Clear previous output
outputDiv.textContent = '// Output:\n';
// Execute the code in a function to simulate module scope
const asyncWrapper = `
(async function() {
try {
${code}
} catch (error) {
console.error(error);
}
})();
`;
Function('sandbox', `with (sandbox) { ${asyncWrapper} }`)(sandboxProxy);
} catch (error) {
outputDiv.textContent = `Error: ${error.message}`;
}
});
// Initialize by triggering the first run of each demo
document.getElementById('runDestructuring').click();
document.getElementById('convertToArrow').click();
document.getElementById('runTemplate').click();
document.getElementById('runNullish').click();
document.getElementById('runOptional').click();
document.getElementById('runLogical').click();
document.getElementById('runRest').click();
Progressive Enhancement Strategies#
When using these modern features, consider browser compatibility:
// Feature detection
const hasOptionalChaining = () => {
try {
eval('({})?.prop');
return true;
} catch (e) {
return false;
}
};
// Conditional code
if (hasOptionalChaining()) {
// Use optional chaining
} else {
// Use fallback approach
}
---html
<header>
<div class="container">
<h1>Modern JavaScript Feature Compatibility</h1>
<p>Explore browser support for JavaScript features from ES6 (2015) to ES13 (2022) and learn how to detect feature support</p>
</div>
</header>
<div class="container">
<div class="tabs">
<div class="tab active" data-tab="compatibility">Compatibility Table</div>
<div class="tab" data-tab="detect">Feature Detection</div>
<div class="tab" data-tab="polyfills">Polyfills & Fallbacks</div>
</div>
<!-- Compatibility Table Tab -->
<div id="compatibility-tab" class="tab-content active">
<h2>Browser Compatibility Table</h2>
<p>This table shows which modern JavaScript features are supported in major browsers. Data is based on current browser versions as of 2023.</p>
<div class="info-box">
<strong>Note:</strong> Always check for the most up-to-date compatibility information as browsers are frequently updated. For detailed information, visit <a href="https://caniuse.com/" target="_blank">Can I Use</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">MDN Web Docs</a>.
</div>
<table id="compatibility-table">
<thead>
<tr>
<th>Feature</th>
<th>Chrome</th>
<th>Firefox</th>
<th>Safari</th>
<th>Edge</th>
<th>IE 11</th>
<th>Released</th>
</tr>
</thead>
<tbody>
<!-- ES6 (ES2015) -->
<tr>
<td>Arrow Functions <span class="badge badge-es6">ES6</span></td>
<td><span class="supported">45+</span></td>
<td><span class="supported">22+</span></td>
<td><span class="supported">10+</span></td>
<td><span class="supported">12+</span></td>
<td><span class="not-supported">No</span></td>
<td>2015</td>
</tr>
<tr>
<td>Classes <span class="badge badge-es6">ES6</span></td>
<td><span class="supported">49+</span></td>
<td><span class="supported">45+</span></td>
<td><span class="supported">9+</span></td>
<td><span class="supported">13+</span></td>
<td><span class="not-supported">No</span></td>
<td>2015</td>
</tr>
<tr>
<td>Let/Const <span class="badge badge-es6">ES6</span></td>
<td><span class="supported">49+</span></td>
<td><span class="supported">44+</span></td>
<td><span class="supported">10+</span></td>
<td><span class="supported">12+</span></td>
<td><span class="partial">Partial</span></td>
<td>2015</td>
</tr>
<tr>
<td>Template Literals <span class="badge badge-es6">ES6</span></td>
<td><span class="supported">41+</span></td>
<td><span class="supported">34+</span></td>
<td><span class="supported">9+</span></td>
<td><span class="supported">12+</span></td>
<td><span class="not-supported">No</span></td>
<td>2015</td>
</tr>
<tr>
<td>Promises <span class="badge badge-es6">ES6</span></td>
<td><span class="supported">32+</span></td>
<td><span class="supported">29+</span></td>
<td><span class="supported">8+</span></td>
<td><span class="supported">12+</span></td>
<td><span class="not-supported">No</span></td>
<td>2015</td>
</tr>
<tr>
<td>Destructuring <span class="badge badge-es6">ES6</span></td>
<td><span class="supported">49+</span></td>
<td><span class="supported">41+</span></td>
<td><span class="supported">8+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="not-supported">No</span></td>
<td>2015</td>
</tr>
<tr>
<td>Default Parameters <span class="badge badge-es6">ES6</span></td>
<td><span class="supported">49+</span></td>
<td><span class="supported">15+</span></td>
<td><span class="supported">10+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="not-supported">No</span></td>
<td>2015</td>
</tr>
<!-- ES7 (ES2016) -->
<tr>
<td>Array.prototype.includes <span class="badge badge-es7">ES7</span></td>
<td><span class="supported">47+</span></td>
<td><span class="supported">43+</span></td>
<td><span class="supported">10+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="not-supported">No</span></td>
<td>2016</td>
</tr>
<tr>
<td>Exponentiation Operator (**) <span class="badge badge-es7">ES7</span></td>
<td><span class="supported">52+</span></td>
<td><span class="supported">52+</span></td>
<td><span class="supported">10.1+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="not-supported">No</span></td>
<td>2016</td>
</tr>
<!-- ES8 (ES2017) -->
<tr>
<td>Async/Await <span class="badge badge-es8">ES8</span></td>
<td><span class="supported">55+</span></td>
<td><span class="supported">52+</span></td>
<td><span class="supported">11+</span></td>
<td><span class="supported">15+</span></td>
<td><span class="not-supported">No</span></td>
<td>2017</td>
</tr>
<tr>
<td>Object.values/entries <span class="badge badge-es8">ES8</span></td>
<td><span class="supported">54+</span></td>
<td><span class="supported">47+</span></td>
<td><span class="supported">10.1+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="not-supported">No</span></td>
<td>2017</td>
</tr>
<tr>
<td>String padding <span class="badge badge-es8">ES8</span></td>
<td><span class="supported">57+</span></td>
<td><span class="supported">48+</span></td>
<td><span class="supported">10+</span></td>
<td><span class="supported">15+</span></td>
<td><span class="not-supported">No</span></td>
<td>2017</td>
</tr>
<!-- ES9 (ES2018) -->
<tr>
<td>Rest/Spread Properties <span class="badge badge-es9">ES9</span></td>
<td><span class="supported">60+</span></td>
<td><span class="supported">55+</span></td>
<td><span class="supported">11.1+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2018</td>
</tr>
<tr>
<td>Promise.prototype.finally <span class="badge badge-es9">ES9</span></td>
<td><span class="supported">63+</span></td>
<td><span class="supported">58+</span></td>
<td><span class="supported">11.1+</span></td>
<td><span class="supported">18+</span></td>
<td><span class="not-supported">No</span></td>
<td>2018</td>
</tr>
<tr>
<td>Async Iteration <span class="badge badge-es9">ES9</span></td>
<td><span class="supported">63+</span></td>
<td><span class="supported">57+</span></td>
<td><span class="supported">12+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2018</td>
</tr>
<!-- ES10 (ES2019) -->
<tr>
<td>Array.prototype.flat <span class="badge badge-es10">ES10</span></td>
<td><span class="supported">69+</span></td>
<td><span class="supported">62+</span></td>
<td><span class="supported">12+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2019</td>
</tr>
<tr>
<td>Object.fromEntries <span class="badge badge-es10">ES10</span></td>
<td><span class="supported">73+</span></td>
<td><span class="supported">63+</span></td>
<td><span class="supported">12.1+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2019</td>
</tr>
<tr>
<td>Optional Catch Binding <span class="badge badge-es10">ES10</span></td>
<td><span class="supported">66+</span></td>
<td><span class="supported">58+</span></td>
<td><span class="supported">11.1+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2019</td>
</tr>
<!-- ES11 (ES2020) -->
<tr>
<td>Optional Chaining (?.) <span class="badge badge-es11">ES11</span></td>
<td><span class="supported">80+</span></td>
<td><span class="supported">74+</span></td>
<td><span class="supported">13.1+</span></td>
<td><span class="supported">80+</span></td>
<td><span class="not-supported">No</span></td>
<td>2020</td>
</tr>
<tr>
<td>Nullish Coalescing (??) <span class="badge badge-es11">ES11</span></td>
<td><span class="supported">80+</span></td>
<td><span class="supported">72+</span></td>
<td><span class="supported">13.1+</span></td>
<td><span class="supported">80+</span></td>
<td><span class="not-supported">No</span></td>
<td>2020</td>
</tr>
<tr>
<td>Dynamic Import <span class="badge badge-es11">ES11</span></td>
<td><span class="supported">63+</span></td>
<td><span class="supported">67+</span></td>
<td><span class="supported">11.1+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2020</td>
</tr>
<tr>
<td>BigInt <span class="badge badge-es11">ES11</span></td>
<td><span class="supported">67+</span></td>
<td><span class="supported">68+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2020</td>
</tr>
<tr>
<td>Promise.allSettled <span class="badge badge-es11">ES11</span></td>
<td><span class="supported">76+</span></td>
<td><span class="supported">71+</span></td>
<td><span class="supported">13+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2020</td>
</tr>
<tr>
<td>globalThis <span class="badge badge-es11">ES11</span></td>
<td><span class="supported">71+</span></td>
<td><span class="supported">65+</span></td>
<td><span class="supported">12.1+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2020</td>
</tr>
<!-- ES12 (ES2021) -->
<tr>
<td>String.prototype.replaceAll <span class="badge badge-es12">ES12</span></td>
<td><span class="supported">85+</span></td>
<td><span class="supported">77+</span></td>
<td><span class="supported">13.1+</span></td>
<td><span class="supported">85+</span></td>
<td><span class="not-supported">No</span></td>
<td>2021</td>
</tr>
<tr>
<td>Promise.any <span class="badge badge-es12">ES12</span></td>
<td><span class="supported">85+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="supported">85+</span></td>
<td><span class="not-supported">No</span></td>
<td>2021</td>
</tr>
<tr>
<td>Logical Assignment (&&=, ||=, ??=) <span class="badge badge-es12">ES12</span></td>
<td><span class="supported">85+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="supported">14+</span></td>
<td><span class="supported">85+</span></td>
<td><span class="not-supported">No</span></td>
<td>2021</td>
</tr>
<tr>
<td>Numeric Separators <span class="badge badge-es12">ES12</span></td>
<td><span class="supported">75+</span></td>
<td><span class="supported">70+</span></td>
<td><span class="supported">13+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2021</td>
</tr>
<tr>
<td>WeakRefs <span class="badge badge-es12">ES12</span></td>
<td><span class="supported">84+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="supported">14.1+</span></td>
<td><span class="supported">84+</span></td>
<td><span class="not-supported">No</span></td>
<td>2021</td>
</tr>
<!-- ES13 (ES2022) -->
<tr>
<td>Class Fields (Public, Private) <span class="badge badge-es13">ES13</span></td>
<td><span class="supported">74+</span></td>
<td><span class="supported">90+</span></td>
<td><span class="supported">14.1+</span></td>
<td><span class="supported">79+</span></td>
<td><span class="not-supported">No</span></td>
<td>2022</td>
</tr>
<tr>
<td>Top-level await <span class="badge badge-es13">ES13</span></td>
<td><span class="supported">89+</span></td>
<td><span class="supported">89+</span></td>
<td><span class="supported">15+</span></td>
<td><span class="supported">89+</span></td>
<td><span class="not-supported">No</span></td>
<td>2022</td>
</tr>
<tr>
<td>at() Method for Indexing <span class="badge badge-es13">ES13</span></td>
<td><span class="supported">92+</span></td>
<td><span class="supported">90+</span></td>
<td><span class="supported">15.4+</span></td>
<td><span class="supported">92+</span></td>
<td><span class="not-supported">No</span></td>
<td>2022</td>
</tr>
<tr>
<td>Error Cause <span class="badge badge-es13">ES13</span></td>
<td><span class="supported">93+</span></td>
<td><span class="supported">91+</span></td>
<td><span class="supported">15+</span></td>
<td><span class="supported">93+</span></td>
<td><span class="not-supported">No</span></td>
<td>2022</td>
</tr>
<tr>
<td>Object.hasOwn <span class="badge badge-es13">ES13</span></td>
<td><span class="supported">93+</span></td>
<td><span class="supported">92+</span></td>
<td><span class="supported">15.4+</span></td>
<td><span class="supported">93+</span></td>
<td><span class="not-supported">No</span></td>
<td>2022</td>
</tr>
</tbody>
</table>
<div class="browser-info">
<h3>Your Browser Information</h3>
<div id="browser-details">Detecting your browser...</div>
</div>
</div>
<!-- Feature Detection Tab -->
<div id="detect-tab" class="tab-content">
<h2>Feature Detection</h2>
<p>Testing for feature support is more reliable than browser detection. Run these tests to check which modern JavaScript features your browser supports.</p>
<div class="demo-box">
<h3>Test Your Browser</h3>
<p>Click the button below to run feature detection tests in your browser:</p>
<button id="run-detection">Run Feature Detection</button>
<div class="result" id="detection-result">
<p>Results will appear here...</p>
</div>
</div>
<h3>How Feature Detection Works</h3>
<p>Feature detection works by checking if certain objects, methods, or syntax features are available in the current JavaScript environment. Here are some ways to detect key features:</p>
<div class="detection-section">
<h4>Optional Chaining Detection</h4>
<pre><code>function detectOptionalChaining() {
try {
// Try to use optional chaining syntax
const test = { a: { b: 1 } };
const value = test?.a?.b;
return true;
} catch (error) {
return false;
}
}</code></pre>
</div>
<div class="detection-section">
<h4>Nullish Coalescing Detection</h4>
<pre><code>function detectNullishCoalescing() {
try {
// Try to use nullish coalescing operator
const test = null;
const value = test ?? 'default';
return value === 'default';
} catch (error) {
return false;
}
}</code></pre>
</div>
<div class="detection-section">
<h4>Dynamic Import Detection</h4>
<pre><code>function detectDynamicImport() {
return typeof import === 'function';
}</code></pre>
</div>
<div class="detection-section">
<h4>Async/Await Detection</h4>
<pre><code>function detectAsyncAwait() {
try {
// Try to parse async/await syntax
eval('async function test() { await Promise.resolve(); }');
return true;
} catch (error) {
return false;
}
}</code></pre>
</div>
<div class="detection-section">
<h4>Promise.any Detection</h4>
<pre><code>function detectPromiseAny() {
return typeof Promise.any === 'function';
}</code></pre>
</div>
<div class="info-box">
<p><strong>Best Practices:</strong> Always use feature detection instead of browser detection. Browser detection can be unreliable due to user agent spoofing and doesn't accurately reflect feature support in the specific browser version.</p>
</div>
</div>
<!-- Polyfills & Fallbacks Tab -->
<div id="polyfills-tab" class="tab-content">
<h2>Polyfills & Fallbacks</h2>
<p>When a feature isn't supported in a browser, you can use polyfills (code that implements missing functionality) or fallbacks (alternative code paths).</p>
<div class="demo-box">
<h3>Interactive Polyfill Demo</h3>
<div class="controls">
<label for="feature-select">Select a feature to test:</label>
<select id="feature-select">
<option value="array-includes">Array.prototype.includes</option>
<option value="object-entries">Object.entries</option>
<option value="promise-finally">Promise.prototype.finally</option>
<option value="string-padstart">String.prototype.padStart</option>
<option value="string-replaceall">String.prototype.replaceAll</option>
<option value="array-flat">Array.prototype.flat</option>
<option value="array-at">Array.prototype.at</option>
</select>
<button id="test-feature">Test Feature Support</button>
<button id="apply-polyfill">Apply Polyfill</button>
<button id="test-with-polyfill">Test With Polyfill</button>
</div>
<div class="message" id="feature-message"></div>
<div class="polyfill-code" id="polyfill-code"></div>
<div class="result" id="polyfill-result">
<p>Select a feature and test its support</p>
</div>
</div>
<h3>Common Polyfill Strategies</h3>
<div class="detection-section">
<h4>Feature Detection with Polyfill</h4>
<pre><code>// Example for Array.prototype.includes
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
var len = o.length >>> 0;
if (len === 0) return false;
var n = fromIndex | 0;
var k = Math.max(n >= 0 ? n : len + n, 0);
while (k < len) {
if (o[k] === searchElement) return true;
k++;
}
return false;
};
}</code></pre>
</div>
<div class="detection-section">
<h4>Using Conditional Code (Fallbacks)</h4>
<pre><code>// Example using optional chaining with fallback
function getUserCity(user) {
// Modern browsers with optional chaining
if (typeof user?.address?.city === 'string') {
return user.address.city;
}
// Fallback for older browsers
return user && user.address && user.address.city ? user.address.city : 'Unknown';
}</code></pre>
</div>
<div class="detection-section">
<h4>Using Transpilers (Babel)</h4>
<pre><code>// Original code with modern syntax
const getUserData = async (id) => {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
const { name, email, ...details } = data;
return { name, email, details };
};
// Transpiled code (simplified example)
var getUserData = function getUserData(id) {
return regeneratorRuntime.async(function getUserData$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return regeneratorRuntime.awrap(fetch("/api/users/".concat(id)));
case 2:
response = _context.sent;
_context.next = 5;
return regeneratorRuntime.awrap(response.json());
case 5:
// And so on...
}
}
});
};</code></pre>
</div>
<div class="info-box">
<p><strong>Polyfill Resources:</strong></p>
<ul>
<li><a href="https://github.com/zloirock/core-js" target="_blank">core-js</a> - Comprehensive polyfills for ECMAScript up to 2022</li>
<li><a href="https://polyfill.io" target="_blank">polyfill.io</a> - Service which serves polyfills based on the user's browser</li>
<li><a href="https://babeljs.io" target="_blank">Babel</a> - JavaScript compiler to use next generation JavaScript today</li>
</ul>
</div>
</div>
</div>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--border-color: #dee2e6;
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--success-color: #198754;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #0dcaf0;
--light-color: #f8f9fa;
--dark-color: #212529;
--muted-color: #6c757d;
--table-stripe: rgba(0, 0, 0, 0.02);
--shadow-color: rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--border-color: #444444;
--primary-color: #3d8bfd;
--secondary-color: #adb5bd;
--success-color: #42ba96;
--warning-color: #ffb700;
--danger-color: #ff6b78;
--info-color: #15e0ec;
--light-color: #2d2d2d;
--dark-color: #e0e0e0;
--muted-color: #adb5bd;
--table-stripe: rgba(255, 255, 255, 0.03);
--shadow-color: rgba(0, 0, 0, 0.3);
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
margin: 0;
padding: 0;
transition: background-color 0.3s, color 0.3s;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 15px;
}
header {
margin-bottom: 2rem;
text-align: center;
border-bottom: 1px solid var(--border-color);
padding-bottom: 1rem;
}
h1, h2, h3, h4 {
color: var(--primary-color);
margin-top: 1.5rem;
margin-bottom: 1rem;
}
h1 {
font-size: 2.5rem;
}
h2 {
font-size: 2rem;
}
h3 {
font-size: 1.5rem;
}
table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
background-color: var(--bg-color);
box-shadow: 0 0 10px var(--shadow-color);
overflow: hidden;
border-radius: 8px;
}
table th, table td {
padding: 12px 15px;
border: 1px solid var(--border-color);
text-align: left;
}
table th {
background-color: var(--primary-color);
color: white;
text-transform: uppercase;
letter-spacing: 0.03em;
font-weight: bold;
}
table tr:nth-child(even) {
background-color: var(--table-stripe);
}
@media (prefers-color-scheme: dark) {
table th {
background-color: var(--primary-color);
}
}
.supported {
background-color: var(--success-color);
color: white;
text-align: center;
padding: 2px 6px;
border-radius: 4px;
}
.partial {
background-color: var(--warning-color);
color: black;
text-align: center;
padding: 2px 6px;
border-radius: 4px;
}
.not-supported {
background-color: var(--danger-color);
color: white;
text-align: center;
padding: 2px 6px;
border-radius: 4px;
}
.na {
background-color: var(--secondary-color);
color: white;
text-align: center;
padding: 2px 6px;
border-radius: 4px;
}
code {
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
background-color: var(--light-color);
color: var(--dark-color);
padding: 2px 4px;
border-radius: 4px;
}
pre {
background-color: var(--light-color);
color: var(--dark-color);
padding: 15px;
border-radius: 8px;
overflow-x: auto;
margin: 1rem 0;
border: 1px solid var(--border-color);
}
.demo-box {
background-color: var(--light-color);
border: 1px solid var(--border-color);
padding: 20px;
border-radius: 8px;
margin: 1.5rem 0;
}
.result {
background-color: var(--bg-color);
border: 1px solid var(--border-color);
padding: 15px;
border-radius: 8px;
margin-top: 15px;
min-height: 100px;
}
.tabs {
display: flex;
flex-wrap: wrap;
border-bottom: 1px solid var(--border-color);
margin-bottom: 1rem;
}
.tab {
padding: 10px 20px;
cursor: pointer;
margin-right: 5px;
border-radius: 8px 8px 0 0;
border: 1px solid var(--border-color);
border-bottom: none;
background-color: var(--light-color);
color: var(--dark-color);
transition: background-color 0.3s;
}
.tab.active {
background-color: var(--primary-color);
color: white;
font-weight: bold;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Styles for the demo section */
button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
margin-bottom: 10px;
transition: background-color 0.3s;
}
button:hover {
opacity: 0.9;
}
button:disabled {
background-color: var(--secondary-color);
cursor: not-allowed;
}
.feature-row {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid var(--border-color);
}
.feature-name {
flex: 1;
font-weight: bold;
}
.feature-support {
width: 120px;
text-align: center;
}
.browser-info {
margin-top: 20px;
border: 1px solid var(--border-color);
padding: 15px;
border-radius: 8px;
background-color: var(--light-color);
}
.browser-logo {
height: 24px;
margin-right: 10px;
vertical-align: middle;
}
.emoji {
font-size: 1.2em;
margin-right: 5px;
}
.badge {
display: inline-block;
padding: 3px 8px;
border-radius: 20px;
font-size: 0.8em;
margin-left: 8px;
}
.badge-es6 {
background-color: #007bff;
color: white;
}
.badge-es7 {
background-color: #17a2b8;
color: white;
}
.badge-es8 {
background-color: #28a745;
color: white;
}
.badge-es9 {
background-color: #6f42c1;
color: white;
}
.badge-es10 {
background-color: #fd7e14;
color: white;
}
.badge-es11 {
background-color: #e83e8c;
color: white;
}
.badge-es12 {
background-color: #20c997;
color: white;
}
.badge-es13 {
background-color: #6610f2;
color: white;
}
.feature-description {
font-size: 0.9em;
color: var(--secondary-color);
margin-top: 4px;
}
.info-box {
background-color: rgba(13, 202, 240, 0.15);
color: var(--text-color);
padding: 15px 20px;
border-radius: 8px;
margin: 1.5rem 0;
border-left: 5px solid var(--primary-color);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
line-height: 1.6;
font-size: 1.05em;
}
@media (prefers-color-scheme: dark) {
.info-box {
background-color: rgba(13, 202, 240, 0.1);
color: var(--text-color);
border-left: 5px solid var(--info-color);
}
}
.info-box strong {
color: var(--text-color);
font-weight: 600;
}
.info-box a {
color: var(--text-color);
text-decoration: underline;
font-weight: 500;
}
.info-box a:hover, .info-box a:focus {
text-decoration: none;
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
.info-box a:focus-visible {
outline: 2px solid var(--info-color);
outline-offset: 3px;
}
/* Detection status indicators */
.status-circle {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 8px;
}
.status-supported {
background-color: var(--success-color);
}
.status-not-supported {
background-color: var(--danger-color);
}
.controls {
margin: 20px 0;
}
.message {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
}
.message-success {
background-color: rgba(25, 135, 84, 0.1);
border: 1px solid var(--success-color);
color: var(--success-color);
}
.message-error {
background-color: rgba(220, 53, 69, 0.1);
border: 1px solid var(--danger-color);
color: var(--danger-color);
}
.polyfill-code {
margin-top: 10px;
max-height: 200px;
overflow-y: auto;
}
.detection-section {
margin-bottom: 30px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
table {
display: block;
overflow-x: auto;
white-space: nowrap;
}
.tab {
padding: 8px 12px;
font-size: 0.9em;
}
.feature-row {
flex-direction: column;
align-items: flex-start;
}
.feature-support {
width: 100%;
text-align: left;
margin-top: 8px;
}
}
---js
// Tab switching functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
const tabId = `${tab.dataset.tab}-tab`;
document.getElementById(tabId).classList.add('active');
});
});
// Detect and display browser information
function detectBrowser() {
const userAgent = navigator.userAgent;
let browserName = "Unknown";
let browserVersion = "Unknown";
let browserLogo = "";
// Chrome
if (userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Edg") === -1) {
browserName = "Chrome";
browserLogo = "๐"; // Chrome logo emoji
const chromeVersion = userAgent.match(/Chrome\/(\d+)/);
if (chromeVersion) {
browserVersion = chromeVersion[1];
}
}
// Firefox
else if (userAgent.indexOf("Firefox") > -1) {
browserName = "Firefox";
browserLogo = "๐ฆ"; // Firefox logo emoji
const firefoxVersion = userAgent.match(/Firefox\/(\d+)/);
if (firefoxVersion) {
browserVersion = firefoxVersion[1];
}
}
// Safari
else if (userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") === -1) {
browserName = "Safari";
browserLogo = "๐งญ"; // Safari logo emoji
const safariVersion = userAgent.match(/Version\/(\d+)/);
if (safariVersion) {
browserVersion = safariVersion[1];
}
}
// Edge
else if (userAgent.indexOf("Edg") > -1) {
browserName = "Edge";
browserLogo = "๐ต"; // Edge logo emoji
const edgeVersion = userAgent.match(/Edg\/(\d+)/);
if (edgeVersion) {
browserVersion = edgeVersion[1];
}
}
// Internet Explorer
else if (userAgent.indexOf("Trident") > -1) {
browserName = "Internet Explorer";
browserLogo = "๐ท"; // IE logo emoji
const ieVersion = userAgent.match(/rv:(\d+)/);
if (ieVersion) {
browserVersion = ieVersion[1];
} else {
browserVersion = "11"; // Default to IE11 if rv: tag not found
}
}
// Highlight corresponding browser column in the table
const tableHeaders = document.querySelectorAll("#compatibility-table th");
let browserColumnIndex = 0;
for (let i = 0; i < tableHeaders.length; i++) {
if (tableHeaders[i].textContent.indexOf(browserName) > -1) {
browserColumnIndex = i;
break;
}
}
if (browserColumnIndex > 0) {
document.querySelectorAll("#compatibility-table tr").forEach(row => {
const cells = row.querySelectorAll("td, th");
if (cells.length > browserColumnIndex) {
cells[browserColumnIndex].style.backgroundColor = "rgba(13, 110, 253, 0.1)";
cells[browserColumnIndex].style.fontWeight = "bold";
}
});
}
// Display browser information
document.getElementById("browser-details").innerHTML = `
<p><span class="emoji">${browserLogo}</span> <strong>${browserName} ${browserVersion}</strong></p>
<p>User Agent: <code>${userAgent}</code></p>
`;
return { name: browserName, version: browserVersion };
}
// Run browser detection on page load
window.addEventListener('load', detectBrowser);
// Feature detection functions
function detectFeatures() {
const result = document.getElementById('detection-result');
result.innerHTML = '<h4>Testing features...</h4>';
setTimeout(() => {
const features = [
{ name: 'Arrow Functions', test: detectArrowFunctions, es: 'ES6' },
{ name: 'Classes', test: detectClasses, es: 'ES6' },
{ name: 'Let/Const', test: detectLetConst, es: 'ES6' },
{ name: 'Template Literals', test: detectTemplateLiterals, es: 'ES6' },
{ name: 'Destructuring', test: detectDestructuring, es: 'ES6' },
{ name: 'Default Parameters', test: detectDefaultParams, es: 'ES6' },
{ name: 'Promises', test: detectPromises, es: 'ES6' },
{ name: 'Array.prototype.includes', test: detectArrayIncludes, es: 'ES7' },
{ name: 'Exponentiation Operator', test: detectExponentiation, es: 'ES7' },
{ name: 'Async/Await', test: detectAsyncAwait, es: 'ES8' },
{ name: 'Object.entries/values', test: detectObjectEntriesValues, es: 'ES8' },
{ name: 'String Padding', test: detectStringPadding, es: 'ES8' },
{ name: 'Rest/Spread Properties', test: detectRestSpreadProperties, es: 'ES9' },
{ name: 'Promise.prototype.finally', test: detectPromiseFinally, es: 'ES9' },
{ name: 'Array.prototype.flat', test: detectArrayFlat, es: 'ES10' },
{ name: 'Object.fromEntries', test: detectObjectFromEntries, es: 'ES10' },
{ name: 'Optional Catch Binding', test: detectOptionalCatch, es: 'ES10' },
{ name: 'Optional Chaining', test: detectOptionalChaining, es: 'ES11' },
{ name: 'Nullish Coalescing', test: detectNullishCoalescing, es: 'ES11' },
{ name: 'Dynamic Import', test: detectDynamicImport, es: 'ES11' },
{ name: 'BigInt', test: detectBigInt, es: 'ES11' },
{ name: 'globalThis', test: detectGlobalThis, es: 'ES11' },
{ name: 'Promise.allSettled', test: detectPromiseAllSettled, es: 'ES11' },
{ name: 'String.prototype.replaceAll', test: detectStringReplaceAll, es: 'ES12' },
{ name: 'Promise.any', test: detectPromiseAny, es: 'ES12' },
{ name: 'Logical Assignment', test: detectLogicalAssignment, es: 'ES12' },
{ name: 'Numeric Separators', test: detectNumericSeparators, es: 'ES12' },
{ name: 'Private Class Fields', test: detectPrivateClassFields, es: 'ES13' },
{ name: 'Array.prototype.at', test: detectArrayAt, es: 'ES13' }];
// Run all tests
const results = features.map(feature => {
try {
const isSupported = feature.test();
return {
name: feature.name,
supported: isSupported,
es: feature.es };
} catch (e) {
return {
name: feature.name,
supported: false,
es: feature.es,
error: e.message };
}
});
// Group results by ES version
const groupedResults = {};
results.forEach(result => {
if (!groupedResults[result.es]) {
groupedResults[result.es] = [];
}
groupedResults[result.es].push(result);
});
// Display results
let html = '';
Object.keys(groupedResults).forEach(es => {
html += `<h4>${es} Features</h4>`;
groupedResults[es].forEach(result => {
const statusClass = result.supported ? 'status-supported' : 'status-not-supported';
html += `
<div class="feature-row">
<div class="feature-name">
<span class="status-circle ${statusClass}"></span>
${result.name}
</div>
<div class="feature-support">
${result.supported ?
'<span class="supported">Supported</span>' :
'<span class="not-supported">Not Supported</span>'}
</div>
</div>
`;
});
});
result.innerHTML = html;
// Count supported features
const supportedCount = results.filter(r => r.supported).length;
const totalFeatures = results.length;
const supportPercentage = Math.round(supportedCount / totalFeatures * 100);
result.innerHTML += `
<div style="margin-top: 20px; text-align: center;">
<h4>Your browser supports ${supportedCount} out of ${totalFeatures} features (${supportPercentage}%)</h4>
</div>
`;
}, 500); // Small delay to show the "testing" message
}
// Individual feature detection functions
function detectArrowFunctions() {
try {
eval('const test = () => true; return test();');
return true;
} catch (e) {
return false;
}
}
function detectClasses() {
try {
eval('class TestClass {}; new TestClass();');
return true;
} catch (e) {
return false;
}
}
function detectLetConst() {
try {
eval('let x = 1; const y = 2;');
return true;
} catch (e) {
return false;
}
}
function detectTemplateLiterals() {
try {
eval('const name = "World"; const greeting = `Hello ${name}`;');
return true;
} catch (e) {
return false;
}
}
function detectDestructuring() {
try {
eval('const obj = {a: 1, b: 2}; const {a, b} = obj;');
return true;
} catch (e) {
return false;
}
}
function detectDefaultParams() {
try {
eval('function test(a = 1) { return a; }; test();');
return true;
} catch (e) {
return false;
}
}
function detectPromises() {
return typeof Promise === 'function';
}
function detectArrayIncludes() {
return Array.prototype.includes !== undefined;
}
function detectExponentiation() {
try {
eval('const result = 2 ** 3;');
return true;
} catch (e) {
return false;
}
}
function detectAsyncAwait() {
try {
eval('async function test() { await Promise.resolve(); }');
return true;
} catch (e) {
return false;
}
}
function detectObjectEntriesValues() {
return typeof Object.entries === 'function' && typeof Object.values === 'function';
}
function detectStringPadding() {
return typeof String.prototype.padStart === 'function' &&
typeof String.prototype.padEnd === 'function';
}
function detectRestSpreadProperties() {
try {
eval('const obj = { a: 1, b: 2 }; const { a, ...rest } = obj; const newObj = { ...obj };');
return true;
} catch (e) {
return false;
}
}
function detectPromiseFinally() {
return typeof Promise.prototype.finally === 'function';
}
function detectArrayFlat() {
return typeof Array.prototype.flat === 'function';
}
function detectObjectFromEntries() {
return typeof Object.fromEntries === 'function';
}
function detectOptionalCatch() {
try {
eval('try { throw new Error(); } catch { }');
return true;
} catch (e) {
return false;
}
}
function detectOptionalChaining() {
try {
eval('const obj = { a: { b: 1 } }; const value = obj?.a?.b;');
return true;
} catch (e) {
return false;
}
}
function detectNullishCoalescing() {
try {
eval('const value = null ?? "default";');
return true;
} catch (e) {
return false;
}
}
function detectDynamicImport() {
try {
eval("typeof import('') === 'function'");
return true;
} catch (e) {
return false;
}
}
function detectBigInt() {
try {
eval('const value = 9007199254740991n;');
return true;
} catch (e) {
return false;
}
}
function detectGlobalThis() {
return typeof globalThis !== 'undefined';
}
function detectPromiseAllSettled() {
return typeof Promise.allSettled === 'function';
}
function detectStringReplaceAll() {
return typeof String.prototype.replaceAll === 'function';
}
function detectPromiseAny() {
return typeof Promise.any === 'function';
}
function detectLogicalAssignment() {
try {
eval('let x = 0; x ||= 1;');
return true;
} catch (e) {
return false;
}
}
function detectNumericSeparators() {
try {
eval('const value = 1_000_000;');
return true;
} catch (e) {
return false;
}
}
function detectPrivateClassFields() {
try {
eval('class Test { #private = 1; }');
return true;
} catch (e) {
return false;
}
}
function detectArrayAt() {
return typeof Array.prototype.at === 'function';
}
// Run feature detection tests when button is clicked
document.getElementById('run-detection').addEventListener('click', detectFeatures);
// Polyfill demo functionality
const polyfills = {
'array-includes': {
test: function () {
return Array.prototype.includes !== undefined;
},
code: `
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
var len = o.length >>> 0;
if (len === 0) return false;
var n = fromIndex | 0;
var k = Math.max(n >= 0 ? n : len + n, 0);
while (k < len) {
if (o[k] === searchElement) return true;
k++;
}
return false;
};
}`,
example: `
// Test array
const fruits = ['apple', 'banana', 'orange'];
// Testing includes() functionality
const hasApple = fruits.includes('apple');
const hasGrape = fruits.includes('grape');
return \`fruits.includes('apple'): \${hasApple}
fruits.includes('grape'): \${hasGrape}\`;` },
'object-entries': {
test: function () {
return typeof Object.entries === 'function';
},
code: `
if (!Object.entries) {
Object.entries = function(obj) {
if (obj == null) {
throw new TypeError('Object.entries called on non-object');
}
var ownProps = Object.keys(obj);
var i = ownProps.length;
var resArray = new Array(i);
while (i--) {
resArray[i] = [ownProps[i], obj[ownProps[i]]];
}
return resArray;
};
}`,
example: `
// Test object
const person = { name: 'John', age: 30, city: 'New York' };
// Using Object.entries()
const entries = Object.entries(person);
return \`Object.entries() result: \${JSON.stringify(entries)}\`;` },
'promise-finally': {
test: function () {
return typeof Promise.prototype.finally === 'function';
},
code: `
if (!Promise.prototype.finally) {
Promise.prototype.finally = function(onFinally) {
return this.then(
value => Promise.resolve(onFinally()).then(() => value),
reason => Promise.resolve(onFinally()).then(() => { throw reason; })
);
};
}`,
example: `
// Create a promise
const loadData = () => new Promise((resolve, reject) => {
// Simulate success
setTimeout(() => resolve('Data loaded successfully'), 500);
});
// Test finally() functionality
let status = 'Loading...';
return loadData()
.then(data => {
status = data;
return 'Promise resolved with: ' + data;
})
.finally(() => {
return 'Status after finally: ' + status;
});` },
'string-padstart': {
test: function () {
return typeof String.prototype.padStart === 'function';
},
code: `
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength, padString) {
targetLength = targetLength >> 0;
padString = String(typeof padString !== 'undefined' ? padString : ' ');
if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return padString.slice(0, targetLength) + String(this);
}
};
}`,
example: `
// Test strings
const num = '5';
const id = '123';
// Using padStart()
const paddedNum = num.padStart(2, '0');
const paddedId = id.padStart(10, 'X');
return \`num.padStart(2, '0'): \${paddedNum}
id.padStart(10, 'X'): \${paddedId}\`;` },
'string-replaceall': {
test: function () {
return typeof String.prototype.replaceAll === 'function';
},
code: `
if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function(search, replacement) {
if (typeof search === 'string') {
search = new RegExp(search.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&'), 'g');
}
return this.replace(search, replacement);
};
}`,
example: `
// Test string
const text = 'The quick brown fox jumps over the lazy dog. The fox is quick.';
// Using replaceAll()
const replaced = text.replaceAll('fox', 'cat');
return \`Original: \${text}
Replaced: \${replaced}\`;` },
'array-flat': {
test: function () {
return typeof Array.prototype.flat === 'function';
},
code: `
if (!Array.prototype.flat) {
Array.prototype.flat = function(depth) {
var flattened = [];
(function flat(array, depth) {
for (var i = 0; i < array.length; i++) {
if (Array.isArray(array[i]) && depth > 0) {
flat(array[i], depth - 1);
} else {
flattened.push(array[i]);
}
}
})(this, depth === undefined ? 1 : depth);
return flattened;
};
}`,
example: `
// Test array
const nested = [1, 2, [3, 4, [5, 6]]];
// Using flat()
const flattened1 = nested.flat(); // Depth 1
const flattened2 = nested.flat(2); // Depth 2
return \`Original: \${JSON.stringify(nested)}
flat(): \${JSON.stringify(flattened1)}
flat(2): \${JSON.stringify(flattened2)}\`;` },
'array-at': {
test: function () {
return typeof Array.prototype.at === 'function';
},
code: `
if (!Array.prototype.at) {
Array.prototype.at = function(n) {
// ToInteger() abstract op
n = Math.trunc(n) || 0;
// Allow negative indexing from the end
if (n < 0) n += this.length;
// OOB access is guaranteed to return undefined
if (n < 0 || n >= this.length) return undefined;
// Otherwise, this is just normal property access
return this[n];
};
}`,
example: `
// Test array
const colors = ['red', 'green', 'blue', 'yellow', 'purple'];
// Using at() for positive and negative indexing
const first = colors.at(0);
const last = colors.at(-1);
const secondLast = colors.at(-2);
return \`colors.at(0): \${first}
colors.at(-1): \${last}
colors.at(-2): \${secondLast}\`;` } };
// Feature testing functionality for polyfill demos
document.getElementById('test-feature').addEventListener('click', function () {
const featureId = document.getElementById('feature-select').value;
const feature = polyfills[featureId];
const message = document.getElementById('feature-message');
const result = document.getElementById('polyfill-result');
const code = document.getElementById('polyfill-code');
// Test if feature is supported
const isSupported = feature.test();
if (isSupported) {
message.className = 'message message-success';
message.innerHTML = `<strong>โ Supported!</strong> This feature is natively supported in your browser.`;
} else {
message.className = 'message message-error';
message.innerHTML = `<strong>โ Not Supported</strong> This feature is not natively supported in your browser. You can apply the polyfill below.`;
}
// Display polyfill code
code.innerHTML = `<pre><code>${feature.code}</code></pre>`;
// Run example without polyfill
try {
const evalResult = new Function(feature.example)();
if (evalResult instanceof Promise) {
result.innerHTML = '<p>Testing...</p>';
evalResult.then(res => {
result.innerHTML = `<p>Result (Native Implementation):</p><pre>${res}</pre>`;
}).catch(err => {
result.innerHTML = `<p>Error (Native Implementation):</p><pre>${err.message}</pre>`;
});
} else {
result.innerHTML = `<p>Result (Native Implementation):</p><pre>${evalResult}</pre>`;
}
} catch (err) {
result.innerHTML = `<p>Error (Native Implementation):</p><pre>${err.message}</pre>`;
}
});
// Apply polyfill
document.getElementById('apply-polyfill').addEventListener('click', function () {
const featureId = document.getElementById('feature-select').value;
const feature = polyfills[featureId];
const message = document.getElementById('feature-message');
// Apply polyfill
try {
eval(feature.code);
message.className = 'message message-success';
message.innerHTML = '<strong>โ Polyfill Applied!</strong> The polyfill has been applied.';
} catch (err) {
message.className = 'message message-error';
message.innerHTML = `<strong>โ Error</strong> Failed to apply polyfill: ${err.message}`;
}
});
// Test feature with polyfill
document.getElementById('test-with-polyfill').addEventListener('click', function () {
const featureId = document.getElementById('feature-select').value;
const feature = polyfills[featureId];
const result = document.getElementById('polyfill-result');
// Apply polyfill first
try {
eval(feature.code);
// Run example with polyfill
try {
const evalResult = new Function(feature.example)();
if (evalResult instanceof Promise) {
result.innerHTML = '<p>Testing...</p>';
evalResult.then(res => {
result.innerHTML = `<p>Result (With Polyfill):</p><pre>${res}</pre>`;
}).catch(err => {
result.innerHTML = `<p>Error (With Polyfill):</p><pre>${err.message}</pre>`;
});
} else {
result.innerHTML = `<p>Result (With Polyfill):</p><pre>${evalResult}</pre>`;
}
} catch (err) {
result.innerHTML = `<p>Error (With Polyfill):</p><pre>${err.message}</pre>`;
}
} catch (err) {
result.innerHTML = `<p>Error applying polyfill: ${err.message}</p>`;
}
});
Looking Forward: What's Next for JavaScript?#
JavaScript keeps evolving through proposals reviewed by the TC39 committee. Three that people ask about are pattern matching for more expressive conditionals, decorators for metaprogramming, and a pipeline operator for chaining functions.
As of October 2026, according to the TC39 proposals repository, pattern matching is at Stage 1, the pipeline operator is at Stage 2, and decorators are at Stage 2.7. None of them is finished, so none is in the language yet. Proposals move between stages, and some are changed or withdrawn, so check the TC39 proposals list before relying on any of them.
Conclusion: The Maturing JavaScript Ecosystem#
ES11 and ES12 added ?. and ?? for safer reads and defaults, Promise.allSettled(), exact big numbers with BigInt, and small conveniences like replaceAll(), numeric separators and logical assignment. Each one removes a specific piece of defensive or clumsy code.
The pattern to remember is value?.deep?.path ?? fallback: read safely, then default only when the value is truly missing.
---html
<header>
<h1>Modern JavaScript Features Demo</h1>
<p>Interactive showcase of ES11 (ES2020) and ES12 (ES2021) features</p>
</header>
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" data-tab="interactive">Interactive Demo</button>
<button class="tab-button" data-tab="features">Feature Reference</button>
</div>
<div class="tab-content">
<div class="tab-panel active" id="interactive">
<div class="demo-section">
<h2>API Data Explorer</h2>
<p>This demo combines multiple ES11/ES12 features to fetch and display data from an API with elegant error handling.</p>
<div>
<label for="user-id">Enter a user ID (1-10) or leave empty for a list of users:</label>
<input type="number" id="user-id" min="1" max="10" placeholder="Enter user ID (optional)">
<button id="fetch-btn" class="btn">Fetch Data</button>
<button id="reset-btn" class="btn secondary">Reset</button>
</div>
<div id="status-container" class="hidden status-message"></div>
<div id="result" class="result">Results will appear here</div>
</div>
<div class="demo-section">
<h2>Financial Calculator</h2>
<p>Demonstrates BigInt and logical assignment operators for financial calculations with large numbers.</p>
<div>
<label for="amount">Enter a large amount (no commas):</label>
<input type="text" id="amount" placeholder="e.g. 9007199254740992" value="9007199254740992">
<label for="operation">Select operation:</label>
<select id="operation">
<option value="compound">Compound 5%</option>
<option value="add">Add Fixed Amount</option>
<option value="double">Double</option>
<option value="halve">Halve</option>
</select>
<label for="iterations">Number of iterations:</label>
<input type="number" id="iterations" min="1" max="100" value="10">
<button id="calculate-btn" class="btn">Calculate</button>
</div>
<div id="finance-result" class="result">Financial calculation results will appear here</div>
</div>
<div class="demo-section">
<h2>Form Data Processor</h2>
<p>Uses nullish coalescing, optional chaining, and logical assignment to handle form data.</p>
<div>
<form id="data-form">
<label for="username">Username:</label>
<input type="text" id="username" placeholder="Enter username">
<label for="email">Email:</label>
<input type="email" id="email" placeholder="Enter email">
<label for="preferences">Preferences (JSON):</label>
<textarea id="preferences" rows="5" placeholder='{"theme": "auto", "notifications": false}'></textarea>
<button type="submit" class="btn">Process Form</button>
</form>
</div>
<div id="form-result" class="result">Form processing results will appear here</div>
</div>
</div>
<div class="tab-panel" id="features">
<h2>ES11 (ES2020) Features</h2>
<div class="card">
<span class="badge es11">ES11</span>
<h3>Optional Chaining (?.)</h3>
<p>Safely access nested object properties without error if a property doesn't exist.</p>
<pre><code>// Old way
if (user && user.profile && user.profile.name) {
console.log(user.profile.name);
}
// With optional chaining
console.log(user?.profile?.name);
</code><button class="copy-btn">Copy</button></pre>
</div>
<div class="card">
<span class="badge es11">ES11</span>
<h3>Nullish Coalescing (??)</h3>
<p>Provides default values only when a value is null or undefined, but preserves 0, '', and false.</p>
<pre><code>// Old way with logical OR
const count = data.count || 10; // 0 becomes 10!
// With nullish coalescing
const count = data.count ?? 10; // 0 stays 0!
</code><button class="copy-btn">Copy</button></pre>
</div>
<div class="card">
<span class="badge es11">ES11</span>
<h3>Promise.allSettled()</h3>
<p>Wait for all promises to settle (resolve or reject) without failing fast like Promise.all().</p>
<pre><code>const promises = [
fetch('/api/users'),
fetch('/api/products'),
fetch('/missing-url')
];
Promise.allSettled(promises)
.then(results => {
// All results available, even if some failed
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});
</code><button class="copy-btn">Copy</button></pre>
</div>
<div class="card">
<span class="badge es11">ES11</span>
<h3>BigInt</h3>
<p>Handle integers larger than 2^53 - 1 with the new BigInt type.</p>
<pre><code>// Creating BigInts
const bigNumber = 9007199254740991n; // Using 'n' suffix
const alsoHuge = BigInt("9007199254740991");
// Operations
const result = bigNumber + 1n;
console.log(result); // 9007199254740992n
// Mixing requires explicit conversion
const mixed = BigInt(100) + 50n;
</code><button class="copy-btn">Copy</button></pre>
</div>
<div class="card">
<span class="badge es11">ES11</span>
<h3>Dynamic Import</h3>
<p>Load modules on-demand when needed instead of upfront.</p>
<pre><code>// Load a module only when needed
button.addEventListener('click', async () => {
try {
const { formatDate } = await import('./date-utils.js');
console.log(formatDate(new Date()));
} catch (error) {
console.error('Failed to load module');
}
});
</code><button class="copy-btn">Copy</button></pre>
</div>
<h2>ES12 (ES2021) Features</h2>
<div class="card">
<span class="badge es12">ES12</span>
<h3>String.prototype.replaceAll()</h3>
<p>Replace all occurrences of a string without using a global regex.</p>
<pre><code>// Old way with global regex
const text = 'hello world, hello universe';
text.replace(/hello/g, 'hi'); // 'hi world, hi universe'
// With replaceAll
text.replaceAll('hello', 'hi'); // 'hi world, hi universe'
</code><button class="copy-btn">Copy</button></pre>
</div>
<div class="card">
<span class="badge es12">ES12</span>
<h3>Promise.any()</h3>
<p>Returns the first promise that resolves (or rejects if all promises reject).</p>
<pre><code>const promises = [
fetch('/api/endpoint-1').then(res => res.json()),
fetch('/api/endpoint-2').then(res => res.json()),
fetch('/api/endpoint-3').then(res => res.json())
];
// Get the first successful response
Promise.any(promises)
.then(firstResult => {
console.log('First successful result:', firstResult);
})
.catch(error => {
console.log('All promises failed:', error);
});
</code><button class="copy-btn">Copy</button></pre>
</div>
<div class="card">
<span class="badge es12">ES12</span>
<h3>Logical Assignment Operators</h3>
<p>Combine logical operations with assignment in a single step.</p>
<pre><code>// Logical OR assignment (||=)
user.name ||= 'Anonymous'; // Only assigns if falsy
// Logical AND assignment (&&=)
user.visits &&= user.visits + 1; // Only increments if visits exists
// Nullish coalescing assignment (??=)
config.timeout ??= 3000; // Only assigns if null/undefined
</code><button class="copy-btn">Copy</button></pre>
</div>
<div class="card">
<span class="badge es12">ES12</span>
<h3>Numeric Separator</h3>
<p>Make large numbers more readable by adding underscores as visual separators.</p>
<pre><code>// Hard to read
const billion = 1000000000;
const bytes = 0xFFFFFFFFFFFFFFFFF;
// With numeric separators
const billion = 1_000_000_000;
const bytes = 0xFFFF_FFFF_FFFF_FFFF;
// Works with BigInt too
const trillion = 1_000_000_000_000n;
</code><button class="copy-btn">Copy</button></pre>
</div>
</div>
</div>
</div>
<footer>
<p>Created with modern JavaScript features (ES11/ES2020 & ES12/ES2021)</p>
</footer>
---css
:root {
--bg-color: #f8f9fa;
--text-color: #212529;
--card-bg: #ffffff;
--card-border: #dee2e6;
--primary-color: #6366f1;
--secondary-color: #8b5cf6;
--success-color: #10b981;
--error-color: #ef4444;
--code-bg: #f1f5f9;
--code-color: #334155;
--highlight: #fcd34d;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #111827;
--text-color: #e5e7eb;
--card-bg: #1f2937;
--card-border: #374151;
--primary-color: #818cf8;
--secondary-color: #a78bfa;
--success-color: #34d399;
--error-color: #f87171;
--code-bg: #1e293b;
--code-color: #cbd5e1;
--highlight: #ca8a04;
}
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 2rem;
max-width: 900px;
margin: 0 auto;
transition: background-color 0.3s, color 0.3s;
}
header {
text-align: center;
margin-bottom: 2rem;
}
h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
h2 {
font-size: 1.8rem;
margin: 1.5rem 0 1rem;
color: var(--primary-color);
}
h3 {
font-size: 1.3rem;
margin: 1rem 0 0.5rem;
color: var(--secondary-color);
}
p {
margin-bottom: 1rem;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.card {
background: var(--card-bg);
border-radius: 0.5rem;
padding: 1.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border: 1px solid var(--card-border);
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
code {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
background-color: var(--code-bg);
color: var(--code-color);
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-size: 0.9em;
}
pre {
background-color: var(--code-bg);
color: var(--code-color);
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin: 1rem 0;
position: relative;
}
pre code {
background-color: transparent;
padding: 0;
}
.copy-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: var(--primary-color);
color: white;
border: none;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
}
pre:hover .copy-btn {
opacity: 1;
}
.badge {
display: inline-block;
background-color: var(--primary-color);
color: white;
font-size: 0.8rem;
padding: 0.25rem 0.5rem;
border-radius: 1rem;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.badge.es11 {
background-color: var(--primary-color);
}
.badge.es12 {
background-color: var(--secondary-color);
}
.demo-section {
margin: 2rem 0;
padding: 1.5rem;
background: var(--card-bg);
border-radius: 0.5rem;
border: 1px solid var(--card-border);
}
.btn {
background-color: var(--primary-color);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
margin: 0.5rem 0.5rem 0.5rem 0;
}
.btn:hover {
background-color: var(--secondary-color);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn.secondary {
background-color: transparent;
border: 1px solid var(--primary-color);
color: var(--primary-color);
}
.btn.secondary:hover {
background-color: rgba(99, 102, 241, 0.1);
}
input, select, textarea {
width: 100%;
padding: 0.75rem;
border-radius: 0.25rem;
border: 1px solid var(--card-border);
background-color: var(--card-bg);
color: var(--text-color);
font-size: 1rem;
margin-bottom: 1rem;
}
.result {
background-color: var(--code-bg);
padding: 1rem;
border-radius: 0.5rem;
margin-top: 1rem;
min-height: 100px;
white-space: pre-wrap;
}
.highlight {
background-color: var(--highlight);
color: black;
padding: 0.1rem 0.2rem;
border-radius: 0.25rem;
}
footer {
margin-top: 3rem;
text-align: center;
font-size: 0.9rem;
color: var(--text-color);
opacity: 0.8;
}
.status-message {
padding: 0.75rem;
border-radius: 0.25rem;
margin: 1rem 0;
}
.status-message.success {
background-color: rgba(16, 185, 129, 0.2);
border: 1px solid var(--success-color);
color: var(--success-color);
}
.status-message.error {
background-color: rgba(239, 68, 68, 0.2);
border: 1px solid var(--error-color);
color: var(--error-color);
}
.hidden {
display: none;
}
.tab-container {
margin-bottom: 1rem;
}
.tab-buttons {
display: flex;
margin-bottom: -1px;
}
.tab-button {
padding: 0.75rem 1rem;
background-color: transparent;
border: 1px solid transparent;
border-bottom: none;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
cursor: pointer;
font-weight: 500;
color: var(--text-color);
opacity: 0.8;
}
.tab-button.active {
background-color: var(--card-bg);
border-color: var(--card-border);
color: var(--primary-color);
opacity: 1;
}
.tab-content {
background-color: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 0 0.25rem 0.25rem 0.25rem;
padding: 1.5rem;
}
.tab-panel {
display: none;
}
.tab-panel.active {
display: block;
}
@media (max-width: 768px) {
body {
padding: 1rem;
}
.container {
grid-template-columns: 1fr;
}
}
---js
// Tab switching functionality
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
// Remove active class from all buttons and panels
document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.remove('active'));
// Add active class to clicked button and corresponding panel
button.classList.add('active');
document.getElementById(button.dataset.tab).classList.add('active');
});
});
// Copy button functionality
document.querySelectorAll('.copy-btn').forEach(button => {
button.addEventListener('click', () => {
const code = button.parentElement.querySelector('code').textContent;
navigator.clipboard.writeText(code);
// Provide feedback
const originalText = button.textContent;
button.textContent = 'Copied!';
button.style.opacity = 1;
setTimeout(() => {
button.textContent = originalText;
button.style.opacity = '';
}, 1500);
});
});
// ES11/ES12 Feature Demo: API Data Explorer
const fetchBtn = document.getElementById('fetch-btn');
const resetBtn = document.getElementById('reset-btn');
const userIdInput = document.getElementById('user-id');
const resultContainer = document.getElementById('result');
const statusContainer = document.getElementById('status-container');
fetchBtn.addEventListener('click', async () => {
try {
// Reset UI
resultContainer.textContent = 'Fetching data...';
statusContainer.classList.add('hidden');
fetchBtn.disabled = true;
const userId = userIdInput.value.trim();
const endpoint = userId ?
`https://dummyjson.com/users/${userId}` :
'https://dummyjson.com/users';
// ES11: Optional Chaining & Promise handling
const response = await fetch(endpoint);
// ES11: Nullish Coalescing for default values
const contentType = response.headers.get('content-type') ?? 'unknown';
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// Process and display the data
let output = '';
if (data && data.users && Array.isArray(data.users)) {
// DummyJSON returns data in a different format with a users array
const users = data.users;
// ES12: Logical Assignment
let userCount = 0;
userCount ||= users.length;
output = `Found ${userCount} users:\n\n`;
// ES12: Numeric Separators
const totalUsersInSystem = 1_000_000;
// Process user list
users.forEach(user => {
// ES11: Optional Chaining
const city = user?.address?.city;
const companyName = user?.company?.name;
// ES11: Nullish Coalescing
const website = user.domain ?? 'No website';
output += `- ${user.firstName} ${user.lastName} (${user.email})\n`;
output += ` City: ${city}\n`;
output += ` Company: ${companyName}\n`;
output += ` Website: ${website}\n\n`;
});
// Show success status
statusContainer.textContent = `Successfully fetched ${data.length} users from a total of ${totalUsersInSystem.toLocaleString()} in system`;
statusContainer.classList.remove('hidden', 'error');
statusContainer.classList.add('success');
} else {
// Single user data
// ES11: Optional Chaining
const addressStr = data?.address ?
`${data.address.address}, ${data.address.city}, ${data.address.state} ${data.address.postalCode}` :
'Address not available';
// ES11: Nullish Coalescing
const phone = data.phone ?? 'No phone provided';
const website = data.domain ?? 'No website';
output = `User Details:\n\n`;
output += `Name: ${data.firstName} ${data.lastName}\n`;
output += `Username: ${data.username}\n`;
output += `Email: ${data.email}\n`;
output += `Phone: ${phone}\n`;
output += `Website: ${website}\n`;
output += `Address: ${addressStr}\n`;
if (data?.company) {
output += `\nCompany:\n`;
output += `Name: ${data.company.name}\n`;
// ES12: String.replaceAll()
const department = data.company.department?.replaceAll('the', 'THE') ?? '';
output += `Department: "${department}"\n`;
}
// Show success status
statusContainer.textContent = `Successfully fetched user #${data.id}`;
statusContainer.classList.remove('hidden', 'error');
statusContainer.classList.add('success');
}
resultContainer.textContent = output;
} catch (error) {
// Error handling
resultContainer.textContent = 'An error occurred';
statusContainer.textContent = error.message;
statusContainer.classList.remove('hidden', 'success');
statusContainer.classList.add('error');
console.error('Fetch error:', error);
} finally {
fetchBtn.disabled = false;
}
});
resetBtn.addEventListener('click', () => {
userIdInput.value = '';
resultContainer.textContent = 'Results will appear here';
statusContainer.classList.add('hidden');
});
// ES11/ES12 Feature Demo: Financial Calculator
const calculateBtn = document.getElementById('calculate-btn');
const amountInput = document.getElementById('amount');
const operationSelect = document.getElementById('operation');
const iterationsInput = document.getElementById('iterations');
const financeResult = document.getElementById('finance-result');
calculateBtn.addEventListener('click', () => {
try {
let amount = amountInput.value.trim();
const operation = operationSelect.value;
const iterations = parseInt(iterationsInput.value) || 1;
// Validate amount
if (!amount || isNaN(Number(amount))) {
throw new Error('Please enter a valid number');
}
// ES11: BigInt for handling large numbers
let bigAmount = BigInt(amount);
let results = [`Starting amount: ${formatBigInt(bigAmount)}`];
// Perform operations based on selection
for (let i = 1; i <= iterations; i++) {
switch (operation) {
case 'compound':
// Add 5% using BigInt math
// ES12: Numeric separators
const interestRate = BigInt(5_00); // 5.00%
const interest = bigAmount * interestRate / BigInt(10000);
// ES12: Logical assignment operators
bigAmount += interest;
break;
case 'add':
// ES12: Numeric separators
const addAmount = BigInt(1_000_000);
bigAmount += addAmount;
break;
case 'double':
bigAmount *= BigInt(2);
break;
case 'halve':
bigAmount /= BigInt(2);
break;}
results.push(`Iteration ${i}: ${formatBigInt(bigAmount)}`);
}
// Display results
financeResult.textContent = results.join('\n');
// ES12: Numeric separators & BigInt
const maxSafeNumber = 9_007_199_254_740_991;
if (bigAmount > BigInt(maxSafeNumber)) {
financeResult.textContent += `\n\nNote: This amount exceeds JavaScript's Number.MAX_SAFE_INTEGER (${maxSafeNumber.toLocaleString()}), which is why BigInt is needed.`;
}
} catch (error) {
financeResult.textContent = `Error: ${error.message}`;
console.error('Calculation error:', error);
}
});
// Helper function to format BigInt with commas
function formatBigInt(bigint) {
return bigint.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
// ES11/ES12 Feature Demo: Form Data Processor
const dataForm = document.getElementById('data-form');
const formResult = document.getElementById('form-result');
dataForm.addEventListener('submit', event => {
event.preventDefault();
try {
const username = document.getElementById('username').value.trim();
const email = document.getElementById('email').value.trim();
let preferencesRaw = document.getElementById('preferences').value.trim();
// ES11: Nullish Coalescing
const defaultUsername = username ?? 'guest_user';
// Try to parse preferences if provided
let preferences = {};
if (preferencesRaw) {
try {
preferences = JSON.parse(preferencesRaw);
} catch (e) {
throw new Error('Invalid JSON in preferences field');
}
}
// ES11: Optional Chaining
const theme = preferences?.theme;
// ES12: Logical Assignment Operators
preferences.theme ??= 'light';
preferences.notifications ||= false;
preferences.language ??= 'en';
if (preferences?.advanced) {
preferences.advanced.debug &&= false; // Turn off debug
}
// ES12: String.replaceAll()
let formattedEmail = email;
if (email.includes('@gmail.com')) {
formattedEmail = email.replaceAll('@gmail.com', '@gmail-custom.com');
}
// Process the data
const result = {
processedUsername: defaultUsername,
processedEmail: formattedEmail,
processedPreferences: preferences,
timestamp: new Date().toISOString() };
// Display results
formResult.textContent = JSON.stringify(result, null, 2);
// Highlight ES11/ES12 features used
const explanation = `\n\nES11/ES12 Features Used:
- Nullish Coalescing (??) for username defaulting
- Optional Chaining (?.) for accessing nested preferences
- Logical Assignment (??=, ||=, &&=) for preference defaults
- String.replaceAll() for email formatting`;
formResult.textContent += explanation;
} catch (error) {
formResult.textContent = `Error: ${error.message}`;
console.error('Form processing error:', error);
}
});
What modern JavaScript features do you find most useful in your projects? Let us know in the comments below!
Comments require JavaScript.