Skip to content
Journey to Code

JavaScript Evolution: From Scripting Language to Modern Development Platform

JavaScript's Evolution Continues

The new features of ES13 & ES14

· By · Updated · 7 min read

Cover reading "JavaScript's Evolution Continues" with a private class field snippet and an edition rail marking ES2022 and ES2023

Two Problems: Open Classes and Surprise Mutation#

Two things go wrong in everyday code. A class has a value that nobody outside should touch, and somebody touches it anyway. And we call sort() on a list, then find the original list rearranged.

In this article, we are going to fix both with ES13 (June 2022) and ES14 (June 2023). A class can now keep a value truly private. Array methods like toSorted() give us a sorted copy instead of rearranging the array we started with.

What's here: private and static class fields, await at the top of a module, at(), Error causes, and four array methods that return copies (toSorted(), toReversed(), toSpliced(), with()) plus findLast(). A few small extras (/d match indices, hashbang comments and symbols as WeakMap keys) are marked optional. A class is a template for making objects; if that is new, the first example below shows one.

ES13 (ECMAScript 2022): Making Classes More Powerful#

Class Fields: Public, Private, and Static#

One of the most substantial improvements in ES13 is the enhanced class system. JavaScript's class syntax, introduced in ES6, has been gradually enhanced over the years, but ES13 brings it closer to the object-oriented model familiar to developers from other languages.

Before ES13, class fields had to be declared inside constructors, which often made classes verbose and less readable:

class User {
  constructor() {
    this.name = "Alice";  // Public field
    this._email = "alice@example.com";  // Convention for "private" (not actually private)
  }
}

With ES13, we can now declare fields directly in the class body, making code more concise and intuitive without having to define them inside of the constructor:

class User {
  name = "Alice";  // Public field
  #email = "alice@example.com";  // Truly private field
}

The # prefix creates genuinely private fields that cannot be accessed from outside the class. This is a major improvement over the underscore convention, which was merely a signal to other developers and didn't prevent outside access. Writing user.#email anywhere outside the class is a SyntaxError, so the mistake is caught before the code even runs.

ES13 also introduces static class fields and methods that belong to the class itself rather than to instances:

class User {
  name = "Alice";
  #email = "alice@example.com";

  static userCount = 0;

  constructor() {
    User.userCount++;
  }

  static hasPrivateEmail(obj) {
    return #email in obj;  // Check if private field exists
  }
}

console.log(User.userCount);  // 0
const user = new User();
console.log(User.userCount);  // 1
console.log(User.hasPrivateEmail(user));  // true

The in operator now works with private fields. Writing #email in obj checks whether obj was built by this class and therefore has that private field, without risking an error. By contrast, reading obj.#email on an object that does not have the field throws a TypeError.

ES13 Private Class Fields Explorer
---html
<div class="container">
    <h1>ES13 Private Class Fields Explorer</h1>
    
    <div class="card">
      <h2>BankAccount Class Demo</h2>
      <p>This demo shows how ES13's private fields and methods work in a practical BankAccount class.</p>
      
      <div>
        <button id="createAccountBtn">Create Account</button>
        <button id="depositBtn">Deposit $50</button>
        <button id="withdrawBtn">Withdraw $30</button>
        <button id="checkBalanceBtn">Check Balance</button>
        <button id="tryAccessPrivateBtn">Try Access Private Field</button>
        <button id="hasPrivateFieldBtn">Check Private Field</button>
      </div>
      
      <div id="accountResults" class="result"></div>
      
      <pre id="codeDisplay">class BankAccount {
  // Public field
  accountHolder = "";
  
  // Private fields with # syntax (ES13 feature)
  #balance = 0;
  #accountNumber;
  
  // Static field (ES13 feature)
  static bankName = "Modern JS Bank";
  
  constructor(holder, initialDeposit = 0) {
    this.accountHolder = holder;
    this.#balance = initialDeposit;
    this.#accountNumber = this.#generateAccountNumber();
  }
  
  // Public method
  deposit(amount) {
    if (amount <= 0) {
      throw new Error("Deposit amount must be positive");
    }
    this.#balance += amount;
    return this.#balance;
  }
  
  // Public method
  withdraw(amount) {
    if (amount <= 0) {
      throw new Error("Withdrawal amount must be positive");
    }
    
    if (amount > this.#balance) {
      throw new Error("Insufficient funds", { cause: "Balance too low" });
    }
    
    this.#balance -= amount;
    return this.#balance;
  }
  
  // Public method to access private data
  getBalance() {
    return this.#balance;
  }
  
  getAccountDetails() {
    return {
      holder: this.accountHolder,
      number: this.#accountNumber,
      balance: this.#balance
    };
  }
  
  // Private method (ES13 feature)
  #generateAccountNumber() {
    return Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
  }
  
  // Static method to check if an object has a private field (ES13 feature)
  static hasPrivateBalance(obj) {
    return #balance in obj;
  }
}</pre>
    </div>
  </div>
---css
:root {
      --bg-color: #ffffff;
      --text-color: #333333;
      --card-bg: #f5f5f5;
      --button-bg: #4285f4;
      --button-text: white;
      --code-bg: #f0f0f0;
      --highlight: #ff5722;
      --success: #4caf50;
      --error: #f44336;
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #121212;
        --text-color: #eeeeee;
        --card-bg: #1e1e1e;
        --button-bg: #4285f4;
        --button-text: white;
        --code-bg: #2d2d2d;
        --highlight: #ff9800;
        --success: #81c784;
        --error: #e57373;
      }
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      margin: 0;
      padding: 20px;
      line-height: 1.6;
    }
    
    .container {
      max-width: 800px;
      margin: 0 auto;
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      margin-bottom: 20px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    
    h1, h2 {
      margin-top: 0;
      color: var(--highlight);
    }
    
    button {
      background-color: var(--button-bg);
      color: var(--button-text);
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      cursor: pointer;
      margin: 5px;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    code {
      font-family: 'Courier New', Courier, monospace;
      background-color: var(--code-bg);
      padding: 2px 5px;
      border-radius: 3px;
    }
    
    pre {
      background-color: var(--code-bg);
      padding: 15px;
      border-radius: 5px;
      overflow-x: auto;
      white-space: pre-wrap;
    }
    
    .result {
      margin-top: 10px;
      padding: 10px;
      border-radius: 4px;
    }
    
    .success {
      border-left: 4px solid var(--success);
    }
    
    .error {
      border-left: 4px solid var(--error);
    }
---js
class BankAccount {
      // Public field
      accountHolder = "";
      
      // Private fields with # syntax (ES13 feature)
      #balance = 0;
      #accountNumber;
      
      // Static field (ES13 feature)
      static bankName = "Modern JS Bank";
      
      constructor(holder, initialDeposit = 0) {
        this.accountHolder = holder;
        this.#balance = initialDeposit;
        this.#accountNumber = this.#generateAccountNumber();
      }
      
      // Public method
      deposit(amount) {
        if (amount <= 0) {
          throw new Error("Deposit amount must be positive");
        }
        this.#balance += amount;
        return this.#balance;
      }
      
      // Public method
      withdraw(amount) {
        if (amount <= 0) {
          throw new Error("Withdrawal amount must be positive");
        }
        
        if (amount > this.#balance) {
          throw new Error("Insufficient funds", { cause: "Balance too low" });
        }
        
        this.#balance -= amount;
        return this.#balance;
      }
      
      // Public method to access private data
      getBalance() {
        return this.#balance;
      }
      
      getAccountDetails() {
        return {
          holder: this.accountHolder,
          number: this.#accountNumber,
          balance: this.#balance
        };
      }
      
      // Private method (ES13 feature)
      #generateAccountNumber() {
        return Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
      }
      
      // Static method to check if an object has a private field (ES13 feature)
      static hasPrivateBalance(obj) {
        return #balance in obj;
      }
    }
    
    let account = null;
    const resultsDiv = document.getElementById('accountResults');
    
    document.getElementById('createAccountBtn').addEventListener('click', () => {
      try {
        account = new BankAccount("John Doe", 100);
        updateResult(`Account created for ${account.accountHolder} at ${BankAccount.bankName}`, true);
      } catch (error) {
        updateResult(`Error: ${error.message}`, false);
      }
    });
    
    document.getElementById('depositBtn').addEventListener('click', () => {
      if (!account) {
        updateResult("Please create an account first", false);
        return;
      }
      
      try {
        const newBalance = account.deposit(50);
        updateResult(`Deposited $50. New balance: $${newBalance}`, true);
      } catch (error) {
        updateResult(`Error: ${error.message}`, false);
      }
    });
    
    document.getElementById('withdrawBtn').addEventListener('click', () => {
      if (!account) {
        updateResult("Please create an account first", false);
        return;
      }
      
      try {
        const newBalance = account.withdraw(30);
        updateResult(`Withdrew $30. New balance: $${newBalance}`, true);
      } catch (error) {
        updateResult(`Error: ${error.message}${error.cause ? ` (Cause: ${error.cause})` : ''}`, false);
      }
    });
    
    document.getElementById('checkBalanceBtn').addEventListener('click', () => {
      if (!account) {
        updateResult("Please create an account first", false);
        return;
      }
      
      try {
        const details = account.getAccountDetails();
        updateResult(`Account #${details.number} | Holder: ${details.holder} | Balance: $${details.balance}`, true);
      } catch (error) {
        updateResult(`Error: ${error.message}`, false);
      }
    });
    
    document.getElementById('tryAccessPrivateBtn').addEventListener('click', () => {
      if (!account) {
        updateResult("Please create an account first", false);
        return;
      }
      
      try {
        // This will cause a SyntaxError, but we'll handle it specially for the demo
        updateResult("Trying to access account.#balance directly will cause a SyntaxError", false);
        updateResult("This demonstrates that private fields are truly private in ES13", true);
      } catch (error) {
        updateResult(`Error: ${error.message}`, false);
      }
    });
    
    document.getElementById('hasPrivateFieldBtn').addEventListener('click', () => {
      if (!account) {
        updateResult("Please create an account first", false);
        return;
      }
      
      try {
        const hasField = BankAccount.hasPrivateBalance(account);
        updateResult(`Does account have #balance field? ${hasField}`, true);
        
        const fakeAccount = { accountHolder: "Fake" };
        const fakeHasField = BankAccount.hasPrivateBalance(fakeAccount);
        updateResult(`Does fake account have #balance field? ${fakeHasField}`, true);
      } catch (error) {
        updateResult(`Error: ${error.message}`, false);
      }
    });
    
    function updateResult(message, isSuccess) {
      const resultElement = document.createElement('div');
      resultElement.textContent = message;
      resultElement.className = `result ${isSuccess ? 'success' : 'error'}`;
      
      resultsDiv.prepend(resultElement);
      
      // Keep only the last 5 results
      while (resultsDiv.children.length > 5) {
        resultsDiv.removeChild(resultsDiv.lastChild);
      }
    }

Top-Level Await#

Before ES13, the await keyword could only be used inside an async function. This often led to unnecessary wrapper functions just to handle Promise resolution:

// Before ES13
const getData = async () => {
  const response = await fetch('https://api.example.com/data');
  return response.json();
};

getData().then(data => {
  console.log(data);
});

ES13 introduces top-level await, which means you can use await directly at the top level of a module without wrapping it in an async function. A module is a file loaded with import/export syntax: a <script type="module"> in the browser, or a .mjs file (or "type": "module" in package.json) in Node. In an ordinary script or a CommonJS file, top-level await is a SyntaxError.

// With ES13, in a module
const value = await Promise.resolve(42);
console.log(value); // 42

const response = await fetch('https://api.example.com/data'); // example URL
const data = await response.json();
console.log(data);

This feature is particularly useful for dynamic imports, dependency fallbacks, and resource initialization:

// Dynamically import a module
let module;
try {
  module = await import('./primary-module.js');
} catch {
  // Fallback if primary module fails to load
  module = await import('./fallback-module.js');
}
Top-Level Await Explorer
---html
<div class="container">
    <h1>Top-Level Await Explorer</h1>
    <p>ES13 (ECMAScript 2022) introduced top-level await, allowing you to use the <code>await</code> keyword outside of async functions at the module level. This feature simplifies initialization code and makes asynchronous operations more straightforward.</p>
    
    <div class="card">
      <h2>Evolution of Async JavaScript</h2>
      
      <div class="timeline">
        <div class="timeline-item">
          <div class="timeline-marker">1</div>
          <div class="timeline-content">
            <h3 class="timeline-title">Pre-ES6: Callback Hell</h3>
            <p>Before Promises, asynchronous code relied on nested callbacks, leading to "callback hell" — deeply nested, hard-to-read code.</p>
            <div class="code-block">
              <pre><code>// Module initialization with callbacks
function initializeModule(callback) {
  getConfig(function(err, config) {
    if (err) {
      callback(err);
      return;
    }
    
    connectDatabase(config.dbUrl, function(err, db) {
      if (err) {
        callback(err);
        return;
      }
      
      loadData(db, function(err, data) {
        if (err) {
          callback(err);
          return;
        }
        
        // Now we're ready
        callback(null, { db, data });
      });
    });
  });
}

// Usage
initializeModule(function(err, result) {
  if (err) {
    console.error('Failed to initialize:', err);
    return;
  }
  
  console.log('Module initialized!');
  // Do something with result.db and result.data
});</code></pre>
            </div>
          </div>
        </div>
        
        <div class="timeline-item">
          <div class="timeline-marker">2</div>
          <div class="timeline-content">
            <h3 class="timeline-title">ES6 (2015): Promises</h3>
            <p>Promises improved the situation by allowing chaining instead of nesting, but still required complex constructs for module initialization.</p>
            <div class="code-block">
              <pre><code>// Module initialization with Promises
function initializeModule() {
  return getConfig()
    .then(config => {
      return connectDatabase(config.dbUrl);
    })
    .then(db => {
      return loadData(db).then(data => {
        return { db, data };
      });
    });
}

// Usage as IIFE for module-level initialization
let moduleData;
(function() {
  initializeModule()
    .then(result => {
      moduleData = result;
      console.log('Module initialized!');
    })
    .catch(err => {
      console.error('Failed to initialize:', err);
    });
})();</code></pre>
            </div>
          </div>
        </div>
        
        <div class="timeline-item">
          <div class="timeline-marker">3</div>
          <div class="timeline-content">
            <h3 class="timeline-title">ES8 (2017): Async/Await</h3>
            <p>Async/await made Promise-based code read more like synchronous code, but still required wrapping module-level code in async functions or IIFEs.</p>
            <div class="code-block">
              <pre><code>// Module initialization with async/await
async function initializeModule() {
  const config = await getConfig();
  const db = await connectDatabase(config.dbUrl);
  const data = await loadData(db);
  return { db, data };
}

// Usage at module level still required an IIFE
let moduleData;
(async function() {
  try {
    moduleData = await initializeModule();
    console.log('Module initialized!');
  } catch (err) {
    console.error('Failed to initialize:', err);
  }
})();</code></pre>
            </div>
          </div>
        </div>
        
        <div class="timeline-item">
          <div class="timeline-marker">4</div>
          <div class="timeline-content">
            <h3 class="timeline-title">ES13 (2022): Top-Level Await</h3>
            <p>Top-level await allows using await directly at the module level without wrapping in an async function, simplifying initialization code.</p>
            <div class="code-block">
              <pre><code>// Module initialization with top-level await
// This code is directly at the module level
try {
  const config = await getConfig();
  const db = await connectDatabase(config.dbUrl);
  const data = await loadData(db);
  
  export const moduleData = { db, data };
  console.log('Module initialized!');
} catch (err) {
  console.error('Failed to initialize:', err);
  // Could provide meaningful fallbacks
  export const moduleData = null;
}</code></pre>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <div class="card">
      <h2>Top-Level Await in Action</h2>
      
      <div class="tabs">
        <div class="tab active" data-tab="use-cases">Use Cases</div>
        <div class="tab" data-tab="module-graph">Module Dependency</div>
      </div>
      
      <div id="use-cases-tab" class="tab-content active">
        <h3>Key Use Cases for Top-Level Await</h3>
        
        <div class="flex-container">
          <div class="flex-item">
            <h4><span class="step-marker">1</span>Dynamic Dependency Pathing</h4>
            <div class="code-block">
              <pre><code>// Dynamically load the appropriate implementation
// based on feature detection or configuration
let translations;
try {
  translations = await import(
    './i18n/' + detectLanguage() + '.js'
  );
} catch (err) {
  // Fallback if language pack doesn't exist
  translations = await import('./i18n/en.js');
}

export { translations };</code></pre>
            </div>
          </div>
          
          <div class="flex-item">
            <h4><span class="step-marker">2</span>Resource Initialization</h4>
            <div class="code-block">
              <pre><code>// Initialize database connection once at module load
// Will block imports of this module until DB is ready
const dbConfig = await fetchDatabaseConfig();
const connection = await createConnection(dbConfig);

// Connection is ready for any imports of this module
export function query(sql) {
  return connection.query(sql);
}

export function close() {
  return connection.close();
}</code></pre>
            </div>
          </div>
          
          <div class="flex-item">
            <h4><span class="step-marker">3</span>Dependency Fallbacks</h4>
            <div class="code-block">
              <pre><code>// Try to load the preferred implementation
let dataService;
try {
  dataService = await import('./fast-data-service.js');
} catch (err) {
  console.warn('Fast data service unavailable, using fallback');
  dataService = await import('./fallback-data-service.js');
}

export const getData = dataService.getData;</code></pre>
            </div>
          </div>
          
          <div class="flex-item">
            <h4><span class="step-marker">4</span>Conditional Exports</h4>
            <div class="code-block">
              <pre><code>// Load different features based on permissions
const userPermissions = await fetchUserPermissions();

// Only export authorized features
if (userPermissions.includes('admin')) {
  export async function adminOperation() {
    // Admin-only operation
  }
}

// Always export basic operations
export async function basicOperation() {
  // Available to all users
}</code></pre>
            </div>
          </div>
        </div>
        
        <h3>Interactive Demo</h3>
        <p>This demonstration simulates how top-level await affects module loading in a web application.</p>
        
        <button id="runTopLevelAwait">Run Simulation</button>
        <div class="output" id="awaitOutput"></div>
      </div>
      
      <div id="module-graph-tab" class="tab-content">
        <h3>Module Graph and Load Order</h3>
        <p>Top-level await affects how modules are loaded. When a module uses top-level await, modules that import it must wait for the async operations to complete.</p>
        
        <div class="diagram">
          <h4>Module Dependency Graph</h4>
          
          <div class="module-box">
            <div class="module-title">main.js</div>
            <pre><code>// imports both fast-module and slow-module
import { fastData } from './fast-module.js';
import { slowData } from './slow-module.js';

console.log('Main module running');
console.log('Fast data:', fastData);
console.log('Slow data:', slowData);</code></pre>
          </div>
          
          <div class="flex-container">
            <div class="flex-item">
              <div class="module-box">
                <div class="module-title">fast-module.js</div>
                <pre><code>// This module loads instantly
console.log('Fast module initializing');

export const fastData = {
  value: 'Quick to load'
};

console.log('Fast module ready');</code></pre>
              </div>
            </div>
            
            <div class="flex-item">
              <div class="module-box">
                <div class="module-title">slow-module.js</div>
                <pre><code>// This module uses top-level await
console.log('Slow module initializing');

// Simulate async data fetching
const response = await fetch(
  'https://api.example.com/data'
);
const data = await response.json();

export const slowData = {
  value: data.value
};

console.log('Slow module ready');</code></pre>
              </div>
            </div>
          </div>
          
          <h4>Load Order Without Top-Level Await</h4>
          <div class="code-block">
            <pre><code>// Traditional module loading (pre-ES13)
// All modules execute immediately
1. "Fast module initializing"
2. "Fast module ready"
3. "Slow module initializing"
4. "Slow module ready"
5. "Main module running"
6. "Fast data: {value: 'Quick to load'}"
7. "Slow data: {value: 'API data'}"</code></pre>
          </div>
          
          <h4>Load Order With Top-Level Await</h4>
          <div class="code-block">
            <pre><code>// With top-level await in slow-module.js
// Execution order changes
1. "Fast module initializing"
2. "Fast module ready"
3. "Slow module initializing"
   // Wait for async operations...
   // (execution pauses here)
4. "Slow module ready"
5. "Main module running"
6. "Fast data: {value: 'Quick to load'}"
7. "Slow data: {value: 'API data'}"</code></pre>
          </div>
        </div>
        
        <button id="runModuleDemo">Run Module Load Simulation</button>
        <div class="output" id="moduleOutput"></div>
      </div>
    </div>
    
    <div class="card">
      <h2>Best Practices and Considerations</h2>
      
      <table>
        <thead>
          <tr>
            <th>Consideration</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Module Load Time</td>
            <td>Top-level await can delay module execution, potentially affecting application startup time.</td>
          </tr>
          <tr>
            <td>Dependency Bottlenecks</td>
            <td>A slow top-level await in one module can block all dependent modules from initializing.</td>
          </tr>
          <tr>
            <td>Error Handling</td>
            <td>Always include proper try/catch blocks around top-level awaits to handle potential failures gracefully.</td>
          </tr>
          <tr>
            <td>Browser Support</td>
            <td>Consider browser compatibility; older browsers may require transpiling or bundling tools.</td>
          </tr>
          <tr>
            <td>Performance Optimization</td>
            <td>Consider lazy loading or prioritizing critical modules to avoid blocking the entire application.</td>
          </tr>
          <tr>
            <td>DevTools Support</td>
            <td>Modern browser DevTools show top-level await operations in performance profiles and breakpoint debugging.</td>
          </tr>
        </tbody>
      </table>
    </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: #198754;
      --yellow: #ffc107;
      --red: #dc3545;
      --blue: #0d6efd;
      --gray: #6c757d;
      --light-gray: #e9ecef;
      --dark-gray: #343a40;
    }
    
    @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;
        --light-gray: #424242;
        --dark-gray: #212121;
      }
    }
    
    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: rgba(0, 0, 0, 0.03);
      border-radius: 8px;
      margin-bottom: 30px;
      padding: 20px;
    }
    
    @media (prefers-color-scheme: dark) {
      .card {
        background-color: rgba(255, 255, 255, 0.05);
      }
    }
    
    .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;
      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;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    .output {
      background-color: var(--output-bg);
      padding: 15px;
      border-radius: 5px;
      margin-top: 15px;
      min-height: 120px;
      max-height: 300px;
      overflow-y: auto;
      border: 1px solid var(--border-color);
    }
    
    .timeline {
      display: flex;
      flex-direction: column;
      margin: 30px 0;
      position: relative;
    }
    
    .timeline::before {
      content: '';
      position: absolute;
      top: 0;
      bottom: 0;
      left: 20px;
      width: 4px;
      background-color: var(--gray);
    }
    
    .timeline-item {
      position: relative;
      margin-left: 45px;
      padding-bottom: 25px;
    }
    
    .timeline-marker {
      position: absolute;
      left: -35px;
      width: 24px;
      height: 24px;
      border-radius: 50%;
      background-color: var(--button-bg);
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: bold;
      z-index: 1;
    }
    
    .timeline-content {
      padding: 15px;
      background-color: var(--light-gray);
      border-radius: 5px;
    }
    
    @media (prefers-color-scheme: dark) {
      .timeline-content {
        background-color: var(--dark-gray);
      }
    }
    
    .timeline-title {
      margin-top: 0;
      color: var(--highlight);
    }
    
    .highlight {
      color: var(--highlight);
      font-weight: bold;
    }
    
    .info {
      color: var(--blue);
    }
    
    .success {
      color: var(--green);
    }
    
    .warning {
      color: var(--yellow);
    }
    
    .error {
      color: var(--red);
    }
    
    .step-marker {
      display: inline-block;
      width: 24px;
      height: 24px;
      border-radius: 50%;
      background-color: var(--button-bg);
      color: white;
      text-align: center;
      line-height: 24px;
      font-weight: bold;
      margin-right: 10px;
    }
    
    table {
      width: 100%;
      border-collapse: collapse;
      margin-bottom: 20px;
    }
    
    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);
      }
    }
    
    .loading {
      display: inline-block;
      width: 16px;
      height: 16px;
      border: 3px solid rgba(0, 0, 0, 0.1);
      border-radius: 50%;
      border-top-color: var(--highlight);
      animation: spin 1s ease-in-out infinite;
      margin-right: 8px;
    }
    
    @keyframes spin {
      to { transform: rotate(360deg); }
    }
    
    .diagram {
      width: 100%;
      padding: 20px 0;
    }
    
    .module-box {
      border: 2px solid var(--border-color);
      border-radius: 5px;
      padding: 15px;
      margin-bottom: 15px;
      position: relative;
    }
    
    .module-title {
      position: absolute;
      top: -10px;
      left: 10px;
      background-color: var(--bg-color);
      padding: 0 5px;
      font-weight: bold;
    }
    
    .flex-container {
      display: flex;
      gap: 20px;
      flex-wrap: wrap;
    }
    
    .flex-item {
      flex: 1;
      min-width: 300px;
    }
---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');
  });
});

// Logger function to output to 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 = '';
    } };

}

// Top-level await demo
document.getElementById('runTopLevelAwait').addEventListener('click', function () {
  const output = document.getElementById('awaitOutput');
  const logger = createLogger(output);
  logger.clear();

  logger.log('<b>Simulating application initialization with top-level await...</b>');

  // Simulate a fetch function
  function fakeFetch(url, delay = 1000, success = true) {
    logger.log(`<span class="info"><span class="loading"></span>Fetching ${url}...</span>`);

    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (success) {
          logger.log(`<span class="success">Fetched ${url} successfully</span>`);
          resolve({
            json: () => Promise.resolve({
              data: `Data from ${url}`,
              timestamp: new Date().toISOString() }) });


        } else {
          logger.log(`<span class="error">Failed to fetch ${url}</span>`);
          reject(new Error(`Failed to fetch ${url}`));
        }
      }, delay);
    });
  }

  // Simulate ES13 module with top-level await
  async function simulateModuleWithTopLevelAwait() {
    logger.log('<b>CONFIG MODULE</b>: Starting initialization...', 'highlight');

    try {
      // This would be top-level await in an actual module
      const response = await fakeFetch('/api/config', 1500);
      const config = await response.json();

      logger.log('<b>CONFIG MODULE</b>: Configuration loaded:', 'highlight');
      logger.log(`API Key: ****${Math.random().toString(36).substring(5)}`);
      logger.log(`Environment: production`);
      logger.log(`Loaded at: ${config.timestamp}`);

      return config;
    } catch (error) {
      logger.log(`<b>CONFIG MODULE</b>: Failed to initialize: ${error.message}`, 'error');

      // Fallback configuration
      logger.log('<b>CONFIG MODULE</b>: Using fallback configuration', 'warning');
      return {
        data: 'Fallback configuration',
        timestamp: new Date().toISOString() };

    }
  }

  // Simulate database module that depends on config
  async function simulateDatabaseModule(config) {
    logger.log('<b>DATABASE MODULE</b>: Starting initialization...', 'highlight');

    try {
      // This would be top-level await in an actual module
      const response = await fakeFetch('/api/database/connect', 2000);
      const dbConnection = await response.json();

      logger.log('<b>DATABASE MODULE</b>: Database connected:', 'highlight');
      logger.log(`Connection ID: conn_${Math.random().toString(36).substring(5)}`);
      logger.log(`Connected at: ${dbConnection.timestamp}`);
      logger.log(`Using config from: ${config.timestamp}`);

      return {
        query: async sql => {
          logger.log(`<span class="info">Executing query: ${sql}</span>`);
          return ['result1', 'result2'];
        },
        disconnect: () => {
          logger.log('<span class="warning">Database disconnected</span>');
        } };

    } catch (error) {
      logger.log(`<b>DATABASE MODULE</b>: Failed to connect: ${error.message}`, 'error');
      throw error; // Re-throw to propagate the error
    }
  }

  // Main application simulation
  async function simulateMainApplication() {
    logger.log('<b>MAIN APPLICATION</b>: Starting...', 'highlight');

    try {
      // In ES13, these would be import statements that wait for the modules with top-level await
      logger.log('<b>MAIN APPLICATION</b>: Loading config module...', 'highlight');
      const config = await simulateModuleWithTopLevelAwait();

      logger.log('<b>MAIN APPLICATION</b>: Loading database module...', 'highlight');
      const db = await simulateDatabaseModule(config);

      logger.log('<b>MAIN APPLICATION</b>: All modules loaded, running application', 'success');

      // Use the initialized modules
      logger.log('<b>MAIN APPLICATION</b>: Executing database query', 'highlight');
      const results = await db.query('SELECT * FROM users LIMIT 10');

      logger.log('<b>MAIN APPLICATION</b>: Query complete, application ready!', 'success');
      logger.log('<b>MAIN APPLICATION</b>: With top-level await, all this initialization happens before any dependent code runs.', 'highlight');
    } catch (error) {
      logger.log(`<b>MAIN APPLICATION</b>: Failed to initialize: ${error.message}`, 'error');
    }
  }

  // Run the simulation
  simulateMainApplication();
});

// Module loading simulation
document.getElementById('runModuleDemo').addEventListener('click', function () {
  const output = document.getElementById('moduleOutput');
  const logger = createLogger(output);
  logger.clear();

  logger.log('<b>Simulating module loading with top-level await...</b>');

  // Simulate modules loading
  function simulateModuleLoading() {
    logger.log('<b>Phase 1: Load and parse modules</b>', 'highlight');
    logger.log('Browser discovers import statements and begins loading modules');

    setTimeout(() => {
      logger.log('fast-module.js file loaded', 'success');
      logger.log('slow-module.js file loaded', 'success');

      logger.log('<b>Phase 2: Module evaluation</b>', 'highlight');
      logger.log('Modules are evaluated in dependency order');

      setTimeout(() => {
        logger.log('"Fast module initializing"', 'info');
        logger.log('"Fast module ready"', 'success');
        logger.log('"Slow module initializing"', 'info');

        logger.log('<i>Top-level await encountered in slow-module.js</i>', 'highlight');
        logger.log('<i>API request in progress...</i>');

        // Simulate the await delay
        setTimeout(() => {
          logger.log('<i>API request completed</i>', 'success');
          logger.log('"Slow module ready"', 'success');

          logger.log('<b>Phase 3: Main module execution</b>', 'highlight');
          logger.log('Now that all dependencies are ready, main.js can execute');

          setTimeout(() => {
            logger.log('"Main module running"', 'info');
            logger.log('"Fast data: {value: \'Quick to load\'}"', 'info');
            logger.log('"Slow data: {value: \'API data\'}"', 'info');

            logger.log('<b>Key takeaway:</b> Without top-level await, main.js would start executing immediately after parsing, potentially before slow-module.js is fully initialized.', 'highlight');
          }, 500);
        }, 2000);
      }, 1000);
    }, 1000);
  }

  simulateModuleLoading();
});

The at() Method#

ES13 introduces the .at() method for arrays, strings, and typed arrays. It takes an index and, unlike square brackets, accepts negative numbers that count back from the end:

const array = [10, 20, 30, 40, 50];

// Traditional way to get the last element
const lastElement = array[array.length - 1];  // 50

// With ES13 at() method
const lastWithAt = array.at(-1);    // 50
const secondToLast = array.at(-2);  // 40
console.log('abc'.at(-1));          // 'c'

This makes code more readable when accessing elements from the end of a collection.

at() Method Explorer
---html
<div class="container">
    <h1>ES13 at() Method Explorer</h1>
    <p>The <code>at()</code> method introduced in ES13 allows for more intuitive element access in arrays, strings, and typed arrays, especially with negative indices.</p>
    
    <div class="tab-container">
      <div class="tabs">
        <button class="tab active" data-tab="array-tab">Arrays</button>
        <button class="tab" data-tab="string-tab">Strings</button>
      </div>
      
      <div class="tab-content active" id="array-tab">
        <div class="card demo-section">
          <h2>Array.prototype.at()</h2>
          <p>Explore how the <code>at()</code> method works with arrays, using both positive and negative indices.</p>
          
          <div class="collection-container" id="array-demo">
            <div class="array-display" id="array-items"></div>
            
            <div class="input-container">
              <label for="array-index">Enter an index:</label>
              <input type="number" id="array-index" value="0">
              <button id="get-array-item">Get Item</button>
            </div>
            
            <div class="result" id="array-result">
              Select an index to see the result
            </div>
            
            <div class="comparison">
              <div class="comparison-column">
                <div class="comparison-title">Traditional Way</div>
                <div class="code-display" id="array-traditional-code"></div>
              </div>
              <div class="comparison-column">
                <div class="comparison-title">Using at() Method</div>
                <div class="code-display" id="array-at-code"></div>
              </div>
            </div>
          </div>
        </div>
      </div>
      
      <div class="tab-content" id="string-tab">
        <div class="card demo-section">
          <h2>String.prototype.at()</h2>
          <p>Explore how the <code>at()</code> method works with strings, using both positive and negative indices.</p>
          
          <div class="collection-container" id="string-demo">
            <div class="string-display" id="string-items"></div>
            
            <div class="input-container">
              <label for="string-index">Enter an index:</label>
              <input type="number" id="string-index" value="0">
              <button id="get-string-item">Get Character</button>
            </div>
            
            <div class="result" id="string-result">
              Select an index to see the result
            </div>
            
            <div class="comparison">
              <div class="comparison-column">
                <div class="comparison-title">Traditional Way</div>
                <div class="code-display" id="string-traditional-code"></div>
              </div>
              <div class="comparison-column">
                <div class="comparison-title">Using at() Method</div>
                <div class="code-display" id="string-at-code"></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <div class="card">
      <h2>Why use at()?</h2>
      <p>The <code>at()</code> method provides a clean, intuitive way to access elements from the end of arrays and strings without complex calculations:</p>
      
      <ul>
        <li><strong>Improved readability:</strong> <code>array.at(-1)</code> is more readable than <code>array[array.length - 1]</code></li>
        <li><strong>Less error-prone:</strong> No need to calculate indices manually</li>
        <li><strong>Consistent API:</strong> Works the same way on arrays, strings, and typed arrays</li>
      </ul>
      
      <p>Before ES13, accessing elements from the end required either using <code>length - n</code> or methods like <code>slice(-n)[0]</code>, which were less intuitive and potentially less performant.</p>
    </div>
  </div>
---css
:root {
      --bg-color: #ffffff;
      --text-color: #333333;
      --card-bg: #f5f5f5;
      --button-bg: #4285f4;
      --button-text: white;
      --code-bg: #f0f0f0;
      --border: #e0e0e0;
      --highlight: #673ab7;
      --index-color: #ff5722;
      --negative-color: #2196f3;
      --item-bg: rgba(0, 0, 0, 0.05);
      --selected: #ffeb3b;
      --selected-bg: rgba(255, 235, 59, 0.2);
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #121212;
        --text-color: #eeeeee;
        --card-bg: #1e1e1e;
        --button-bg: #4285f4;
        --button-text: white;
        --code-bg: #2d2d2d;
        --border: #333333;
        --highlight: #bb86fc;
        --index-color: #ff9800;
        --negative-color: #64b5f6;
        --item-bg: rgba(255, 255, 255, 0.1);
        --selected: #ffd600;
        --selected-bg: rgba(255, 214, 0, 0.2);
      }
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      margin: 0;
      padding: 20px;
      line-height: 1.6;
    }
    
    .container {
      max-width: 800px;
      margin: 0 auto;
    }
    
    h1, h2 {
      margin-top: 0;
      color: var(--highlight);
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      margin-bottom: 20px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    
    .demo-section {
      margin-bottom: 30px;
    }
    
    .collection-container {
      margin: 20px 0;
      position: relative;
    }
    
    .array-display, .string-display {
      display: flex;
      flex-wrap: wrap;
      gap: 5px;
      margin-bottom: 10px;
    }
    
    .item-container {
      position: relative;
      text-align: center;
    }
    
    .array-item, .string-item {
      display: inline-block;
      padding: 10px;
      min-width: 40px;
      text-align: center;
      background-color: var(--item-bg);
      border-radius: 4px;
      margin: 2px;
      border: 1px solid var(--border);
      position: relative;
      transition: background-color 0.3s;
    }
    
    .selected {
      background-color: var(--selected-bg);
      border-color: var(--selected);
    }
    
    .index-marker {
      position: absolute;
      top: -25px;
      left: 50%;
      transform: translateX(-50%);
      font-size: 12px;
      color: var(--text-color);
    }
    
    .positive-index {
      color: var(--index-color);
    }
    
    .negative-index {
      color: var(--negative-color);
    }
    
    .input-container {
      margin: 15px 0;
      display: flex;
      gap: 10px;
      align-items: center;
    }
    
    input {
      padding: 8px;
      border: 1px solid var(--border);
      border-radius: 4px;
      width: 60px;
      background-color: var(--bg-color);
      color: var(--text-color);
    }
    
    button {
      background-color: var(--button-bg);
      color: var(--button-text);
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      cursor: pointer;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    .result {
      margin-top: 15px;
      padding: 10px;
      background-color: var(--code-bg);
      border-radius: 4px;
    }
    
    .comparison {
      display: flex;
      gap: 20px;
      margin-top: 15px;
    }
    
    .comparison-column {
      flex: 1;
    }
    
    .comparison-title {
      font-weight: bold;
      margin-bottom: 10px;
      color: var(--highlight);
    }
    
    code {
      font-family: 'Courier New', Courier, monospace;
      background-color: var(--code-bg);
      padding: 2px 5px;
      border-radius: 3px;
    }
    
    .code-display {
      background-color: var(--code-bg);
      padding: 10px;
      border-radius: 4px;
      margin-top: 10px;
      font-family: 'Courier New', Courier, monospace;
    }
    
    .tab-container {
      margin-bottom: 20px;
    }
    
    .tabs {
      display: flex;
      border-bottom: 1px solid var(--border);
    }
    
    .tab {
      padding: 8px 16px;
      cursor: pointer;
      border: none;
      background: none;
      color: var(--text-color);
      opacity: 0.7;
    }
    
    .tab.active {
      opacity: 1;
      border-bottom: 2px solid var(--highlight);
    }
    
    .tab-content {
      display: none;
    }
    
    .tab-content.active {
      display: block;
      padding-top: 15px;
    }
---js
// Sample data
const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
const sampleString = 'Hello, World!';

// Tab functionality
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
  tab.addEventListener('click', () => {
    const tabId = tab.getAttribute('data-tab');

    // Update active tab
    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
    tab.classList.add('active');

    // Update active content
    document.querySelectorAll('.tab-content').forEach(content => {
      content.classList.remove('active');
    });
    document.getElementById(tabId).classList.add('active');
  });
});

// Array Demo
function setupArrayDemo() {
  const arrayItemsContainer = document.getElementById('array-items');

  // Clear container
  arrayItemsContainer.innerHTML = '';

  // Create display with index markers
  fruits.forEach((fruit, index) => {
    const itemContainer = document.createElement('div');
    itemContainer.className = 'item-container';

    // Positive index marker
    const posIndex = document.createElement('div');
    posIndex.className = 'index-marker positive-index';
    posIndex.textContent = index;
    itemContainer.appendChild(posIndex);

    // Negative index marker
    const negIndex = document.createElement('div');
    negIndex.className = 'index-marker negative-index';
    negIndex.style.top = '20px';
    negIndex.textContent = index - fruits.length;
    itemContainer.appendChild(negIndex);

    // Actual item
    const item = document.createElement('div');
    item.className = 'array-item';
    item.textContent = fruit;
    item.dataset.index = index;
    itemContainer.appendChild(item);

    arrayItemsContainer.appendChild(itemContainer);
  });

  // Handle get item button
  document.getElementById('get-array-item').addEventListener('click', () => {
    const indexInput = document.getElementById('array-index');
    const index = parseInt(indexInput.value);

    // Reset all item highlights
    document.querySelectorAll('.array-item').forEach(item => {
      item.classList.remove('selected');
    });

    let resultText, traditionalCode, atCode;

    // Traditional method
    if (index >= 0) {
      const item = fruits[index];
      traditionalCode = `const fruits = ${JSON.stringify(fruits)};\nconst item = fruits[${index}];\n// Result: ${item !== undefined ? JSON.stringify(item) : 'undefined'}`;
    } else {
      const negIndex = index;
      const adjustedIndex = fruits.length + negIndex;
      const item = adjustedIndex >= 0 ? fruits[adjustedIndex] : undefined;
      traditionalCode = `const fruits = ${JSON.stringify(fruits)};\nconst item = fruits[fruits.length + (${negIndex})];\n// Result: ${item !== undefined ? JSON.stringify(item) : 'undefined'}`;
    }

    // at() method
    const itemWithAt = fruits.at(index);
    atCode = `const fruits = ${JSON.stringify(fruits)};\nconst item = fruits.at(${index});\n// Result: ${itemWithAt !== undefined ? JSON.stringify(itemWithAt) : 'undefined'}`;

    // Display results
    resultText = itemWithAt !== undefined ?
    `fruits.at(${index}) = "${itemWithAt}"` :
    `fruits.at(${index}) = undefined (out of bounds)`;

    document.getElementById('array-result').textContent = resultText;
    document.getElementById('array-traditional-code').textContent = traditionalCode;
    document.getElementById('array-at-code').textContent = atCode;

    // Highlight the selected item
    if (index >= 0 && index < fruits.length) {
      const selectedItem = document.querySelector(`.array-item[data-index="${index}"]`);
      if (selectedItem) selectedItem.classList.add('selected');
    } else if (index < 0 && index >= -fruits.length) {
      const adjustedIndex = fruits.length + index;
      const selectedItem = document.querySelector(`.array-item[data-index="${adjustedIndex}"]`);
      if (selectedItem) selectedItem.classList.add('selected');
    }
  });
}

// String Demo
function setupStringDemo() {
  const stringItemsContainer = document.getElementById('string-items');

  // Clear container
  stringItemsContainer.innerHTML = '';

  // Create display with index markers
  [...sampleString].forEach((char, index) => {
    const itemContainer = document.createElement('div');
    itemContainer.className = 'item-container';

    // Positive index marker
    const posIndex = document.createElement('div');
    posIndex.className = 'index-marker positive-index';
    posIndex.textContent = index;
    itemContainer.appendChild(posIndex);

    // Negative index marker
    const negIndex = document.createElement('div');
    negIndex.className = 'index-marker negative-index';
    negIndex.style.top = '20px';
    negIndex.textContent = index - sampleString.length;
    itemContainer.appendChild(negIndex);

    // Actual item
    const item = document.createElement('div');
    item.className = 'string-item';
    item.textContent = char;
    item.dataset.index = index;
    itemContainer.appendChild(item);

    stringItemsContainer.appendChild(itemContainer);
  });

  // Handle get character button
  document.getElementById('get-string-item').addEventListener('click', () => {
    const indexInput = document.getElementById('string-index');
    const index = parseInt(indexInput.value);

    // Reset all item highlights
    document.querySelectorAll('.string-item').forEach(item => {
      item.classList.remove('selected');
    });

    let resultText, traditionalCode, atCode;

    // Traditional method
    if (index >= 0) {
      const char = sampleString[index];
      traditionalCode = `const str = "${sampleString}";\nconst char = str[${index}];\n// Result: ${char !== undefined ? JSON.stringify(char) : 'undefined'}`;
    } else {
      const negIndex = index;
      const adjustedIndex = sampleString.length + negIndex;
      const char = adjustedIndex >= 0 ? sampleString[adjustedIndex] : undefined;
      traditionalCode = `const str = "${sampleString}";\nconst char = str[str.length + (${negIndex})];\n// Result: ${char !== undefined ? JSON.stringify(char) : 'undefined'}`;
    }

    // at() method
    const charWithAt = sampleString.at(index);
    atCode = `const str = "${sampleString}";\nconst char = str.at(${index});\n// Result: ${charWithAt !== undefined ? JSON.stringify(charWithAt) : 'undefined'}`;

    // Display results
    resultText = charWithAt !== undefined ?
    `str.at(${index}) = "${charWithAt}"` :
    `str.at(${index}) = undefined (out of bounds)`;

    document.getElementById('string-result').textContent = resultText;
    document.getElementById('string-traditional-code').textContent = traditionalCode;
    document.getElementById('string-at-code').textContent = atCode;

    // Highlight the selected item
    if (index >= 0 && index < sampleString.length) {
      const selectedItem = document.querySelector(`.string-item[data-index="${index}"]`);
      if (selectedItem) selectedItem.classList.add('selected');
    } else if (index < 0 && index >= -sampleString.length) {
      const adjustedIndex = sampleString.length + index;
      const selectedItem = document.querySelector(`.string-item[data-index="${adjustedIndex}"]`);
      if (selectedItem) selectedItem.classList.add('selected');
    }
  });
}

// Initialize demos
setupArrayDemo();
setupStringDemo();

Regular Expression Match Indices#

Optional. ES13 adds a /d flag to regular expressions. A match then carries the start and end position of the whole match and of each capture group (the parts in parentheses):

const regex = /(a)(b)/d;
const result = regex.exec('ab');
console.log(result.indices);  // [[0, 2], [0, 1], [1, 2]]
// Overall match: [0, 2]
// First group (a): [0, 1]
// Second group (b): [1, 2]

This feature is particularly useful for syntax highlighting, code analysis tools, and other applications where the position of matches is important.

Error Cause#

Errors can now record what caused them. Pass { cause: ... } as the second argument to new Error() and read it back from error.cause, so a low-level failure is not lost when you throw a friendlier one:

try {
  // Some operation that might fail
  throw new Error('Failed to fetch data', { cause: 'Network disconnected' });
} catch (error) {
  console.log(error.message);  // 'Failed to fetch data'
  console.log(error.cause);    // 'Network disconnected'
}

This creates a more informative error chain, making debugging and error handling more effective.

ES14 (ECMAScript 2023): Working Smarter with Arrays#

Array Methods That Don't Mutate#

ES14 introduces a set of array methods that create new arrays instead of modifying the original. This functional programming approach helps avoid unwanted side effects and makes code more predictable. The new copying methods are toSorted(), toReversed(), toSpliced() and with(), and ES14 also adds findLast() and findLastIndex() for searching from the end. The first demo below covers the search methods; the copying methods follow it.

findLast() & findLastIndex() Explorer
---html
<div class="container">
    <h1>ES14 findLast() & findLastIndex() Explorer</h1>
    <p>This demo showcases the new <code>findLast()</code> and <code>findLastIndex()</code> methods introduced in ES14 (ECMAScript 2023), which search arrays from the end instead of the beginning.</p>
    
    <div class="card">
      <h2>Interactive Demo</h2>
      <p>Set your search criteria and see how <code>find()</code> and <code>findLast()</code> return different results when multiple matches exist in an array.</p>
      
      <div class="data-container">
        <h3>Sample Data</h3>
        <div class="array-display" id="sample-array"></div>
        
        <div class="criteria-controls">
          <div>
            <label for="property-select">Search by:</label>
            <select id="property-select">
              <option value="type">Product Type</option>
              <option value="price">Price</option>
              <option value="inStock">Stock Status</option>
            </select>
            
            <span id="criteria-controls-container"></span>
          </div>
          
          <div style="margin-top: 15px;">
            <button id="run-find">Run find()</button>
            <button id="run-findlast">Run findLast()</button>
            <button id="run-both">Run Both</button>
            <button id="reset">Reset</button>
          </div>
        </div>
        
        <div class="results-container">
          <div class="result-card find-result">
            <div class="result-title find">
              <span class="method-indicator find-indicator"></span>
              find() Result
            </div>
            <div class="result-value" id="find-result-value">No search performed yet</div>
            <div class="index-result" id="find-index-result"></div>
          </div>
          
          <div class="result-card findlast-result">
            <div class="result-title findlast">
              <span class="method-indicator findlast-indicator"></span>
              findLast() Result
            </div>
            <div class="result-value" id="findlast-result-value">No search performed yet</div>
            <div class="index-result" id="findlast-index-result"></div>
          </div>
        </div>
        
        <div class="code-display" id="code-display">
          // Run a search to see the code
        </div>
      </div>
    </div>
    
    <div class="card">
      <h2>Practical Applications</h2>
      
      <div class="practical-container">
        <div class="practical-title">Finding the Most Recent Data</div>
        <div class="practical-description">
          <p>In applications that deal with time-series data, <code>findLast()</code> is perfect for finding the most recent entry that matches a certain criteria without having to sort the array first.</p>
          <p>Examples include:</p>
          <ul>
            <li>Finding the most recent log entry with a specific error code</li>
            <li>Getting the latest transaction of a certain type</li>
            <li>Identifying the most recent user activity</li>
          </ul>
        </div>
        
        <div class="practical-title">Efficient DOM Traversal</div>
        <div class="practical-description">
          <p>When working with DOM elements, <code>findLast()</code> can help traverse large collections of elements in reverse order without converting NodeLists to arrays and using reverse().</p>
        </div>
        
        <div class="practical-title">Parsing and Text Processing</div>
        <div class="practical-description">
          <p>When processing text or parsing files, <code>findLast()</code> makes it easier to locate the last occurrence of patterns, such as the last comment in a code file or the last mention of a keyword in a document.</p>
        </div>
      </div>
      
      <table class="comparison-table">
        <thead>
          <tr>
            <th>Feature</th>
            <th>find() & findIndex()</th>
            <th>findLast() & findLastIndex()</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Search Direction</td>
            <td class="feature-find">First to last (index 0 → length-1)</td>
            <td class="feature-findlast">Last to first (length-1 → 0)</td>
          </tr>
          <tr>
            <td>Return Value</td>
            <td class="feature-find">First matching element/index</td>
            <td class="feature-findlast">Last matching element/index</td>
          </tr>
          <tr>
            <td>Performance</td>
            <td class="feature-find">More efficient for finding early matches</td>
            <td class="feature-findlast">More efficient for finding late matches</td>
          </tr>
          <tr>
            <td>Alternative Before ES14</td>
            <td class="feature-find">Native method</td>
            <td class="feature-findlast">Array.reverse().find() (inefficient)</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
---css
:root {
      --bg-color: #ffffff;
      --text-color: #333333;
      --card-bg: #f5f5f5;
      --button-bg: #4285f4;
      --button-text: white;
      --code-bg: #f0f0f0;
      --border: #e0e0e0;
      --highlight: #009688;
      --find-color: #ff5722;
      --findlast-color: #2196f3;
      --item-bg: rgba(0, 0, 0, 0.05);
      --find-bg: rgba(255, 87, 34, 0.15);
      --findlast-bg: rgba(33, 150, 243, 0.15);
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #121212;
        --text-color: #eeeeee;
        --card-bg: #1e1e1e;
        --button-bg: #4285f4;
        --button-text: white;
        --code-bg: #2d2d2d;
        --border: #333333;
        --highlight: #4db6ac;
        --find-color: #ff9800;
        --findlast-color: #64b5f6;
        --item-bg: rgba(255, 255, 255, 0.1);
        --find-bg: rgba(255, 152, 0, 0.15);
        --findlast-bg: rgba(100, 181, 246, 0.15);
      }
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      margin: 0;
      padding: 20px;
      line-height: 1.6;
    }
    
    .container {
      max-width: 900px;
      margin: 0 auto;
    }
    
    h1, h2, h3 {
      margin-top: 0;
      color: var(--highlight);
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      margin-bottom: 20px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    
    button {
      background-color: var(--button-bg);
      color: var(--button-text);
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      cursor: pointer;
      margin: 5px;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    .data-container {
      margin: 20px 0;
    }
    
    .array-display {
      display: flex;
      flex-wrap: wrap;
      gap: 5px;
      margin-bottom: 20px;
    }
    
    .array-item {
      display: inline-block;
      padding: 10px;
      min-width: 40px;
      text-align: center;
      background-color: var(--item-bg);
      border-radius: 4px;
      margin: 2px;
      border: 1px solid var(--border);
      position: relative;
      transition: background-color 0.3s, transform 0.2s;
    }
    
    .array-item::after {
      content: attr(data-index);
      position: absolute;
      bottom: -20px;
      left: 50%;
      transform: translateX(-50%);
      font-size: 12px;
      color: var(--text-color);
      opacity: 0.7;
    }
    
    .find-match {
      background-color: var(--find-bg);
      border-color: var(--find-color);
      transform: translateY(-3px);
    }
    
    .findlast-match {
      background-color: var(--findlast-bg);
      border-color: var(--findlast-color);
      transform: translateY(-3px);
    }
    
    .criteria-controls {
      margin: 20px 0;
      padding: 15px;
      border: 1px solid var(--border);
      border-radius: 8px;
      background-color: rgba(0, 0, 0, 0.03);
    }
    
    .criteria-controls label {
      margin-right: 10px;
    }
    
    input, select {
      padding: 8px;
      border: 1px solid var(--border);
      border-radius: 4px;
      background-color: var(--bg-color);
      color: var(--text-color);
      margin-right: 15px;
    }
    
    .results-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
      margin-top: 20px;
    }
    
    .result-card {
      flex: 1;
      min-width: 250px;
      padding: 15px;
      border-radius: 8px;
      border: 1px solid var(--border);
    }
    
    .find-result {
      border-left: 4px solid var(--find-color);
    }
    
    .findlast-result {
      border-left: 4px solid var(--findlast-color);
    }
    
    .result-title {
      font-weight: bold;
      margin-bottom: 10px;
      display: flex;
      align-items: center;
    }
    
    .result-title.find {
      color: var(--find-color);
    }
    
    .result-title.findlast {
      color: var(--findlast-color);
    }
    
    .method-indicator {
      display: inline-block;
      width: 12px;
      height: 12px;
      border-radius: 50%;
      margin-right: 8px;
    }
    
    .find-indicator {
      background-color: var(--find-color);
    }
    
    .findlast-indicator {
      background-color: var(--findlast-color);
    }
    
    .result-value {
      font-size: 16px;
      margin-bottom: 5px;
    }
    
    .index-result {
      font-size: 14px;
      color: var(--text-color);
      opacity: 0.8;
    }
    
    code {
      font-family: 'Courier New', Courier, monospace;
      background-color: var(--code-bg);
      padding: 2px 5px;
      border-radius: 3px;
    }
    
    .code-display {
      font-family: 'Courier New', Courier, monospace;
      background-color: var(--code-bg);
      padding: 15px;
      border-radius: 5px;
      overflow-x: auto;
      margin-top: 15px;
    }
    
    .practical-container {
      margin-top: 30px;
    }
    
    .practical-title {
      font-weight: bold;
      margin-bottom: 10px;
    }
    
    .practical-description {
      margin-bottom: 15px;
    }
    
    .comparison-table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 20px;
    }
    
    .comparison-table th,
    .comparison-table td {
      padding: 10px;
      text-align: left;
      border-bottom: 1px solid var(--border);
    }
    
    .comparison-table th {
      background-color: rgba(0, 0, 0, 0.05);
    }
    
    .feature-find {
      color: var(--find-color);
    }
    
    .feature-findlast {
      color: var(--findlast-color);
    }
---js
// Sample data
const products = [
{ id: 1, name: "Basic Laptop", type: "laptop", price: 699, inStock: true },
{ id: 2, name: "Deluxe Headphones", type: "audio", price: 149, inStock: true },
{ id: 3, name: "Wireless Mouse", type: "accessory", price: 29, inStock: false },
{ id: 4, name: "Gaming Laptop", type: "laptop", price: 1299, inStock: true },
{ id: 5, name: "Bluetooth Speaker", type: "audio", price: 89, inStock: true },
{ id: 6, name: "External SSD", type: "storage", price: 129, inStock: true },
{ id: 7, name: "Budget Laptop", type: "laptop", price: 499, inStock: false },
{ id: 8, name: "USB Hub", type: "accessory", price: 25, inStock: true },
{ id: 9, name: "Premium Headphones", type: "audio", price: 249, inStock: false },
{ id: 10, name: "Portable SSD", type: "storage", price: 89, inStock: true }];


// Initialize the display
function initializeDemo() {
  // Populate the sample array display
  const sampleArrayEl = document.getElementById('sample-array');
  sampleArrayEl.innerHTML = '';

  products.forEach((product, index) => {
    const itemEl = document.createElement('div');
    itemEl.className = 'array-item';
    itemEl.dataset.index = index;
    itemEl.dataset.id = product.id;

    itemEl.innerHTML = `
          <strong>${product.name}</strong><br>
          Type: ${product.type}<br>
          Price: $${product.price}<br>
          ${product.inStock ? '✅ In Stock' : '❌ Out of Stock'}
        `;

    sampleArrayEl.appendChild(itemEl);
  });

  // Set up property change handler
  const propertySelect = document.getElementById('property-select');
  propertySelect.addEventListener('change', updateCriteriaControls);

  // Initial criteria controls setup
  updateCriteriaControls();

  // Set up button handlers
  document.getElementById('run-find').addEventListener('click', () => runSearch('find'));
  document.getElementById('run-findlast').addEventListener('click', () => runSearch('findlast'));
  document.getElementById('run-both').addEventListener('click', () => runSearch('both'));
  document.getElementById('reset').addEventListener('click', resetSearch);
}

// Update the criteria controls based on selected property
function updateCriteriaControls() {
  const property = document.getElementById('property-select').value;
  const containerEl = document.getElementById('criteria-controls-container');

  let controlsHTML = '';

  switch (property) {
    case 'type':
      const types = [...new Set(products.map(p => p.type))];
      controlsHTML = `
            <label for="type-select">Type:</label>
            <select id="type-select">
              ${types.map(type => `<option value="${type}">${type}</option>`).join('')}
            </select>
          `;
      break;

    case 'price':
      controlsHTML = `
            <label for="price-operator">Condition:</label>
            <select id="price-operator">
              <option value="greater">Greater than</option>
              <option value="less">Less than</option>
              <option value="equal">Equal to</option>
            </select>
            
            <label for="price-value">Price:</label>
            <input type="number" id="price-value" value="100" min="0">
          `;
      break;

    case 'inStock':
      controlsHTML = `
            <label for="stock-status">Status:</label>
            <select id="stock-status">
              <option value="true">In Stock</option>
              <option value="false">Out of Stock</option>
            </select>
          `;
      break;}


  containerEl.innerHTML = controlsHTML;
}

// Run the search with the selected method(s)
function runSearch(method) {
  resetHighlights();

  const property = document.getElementById('property-select').value;
  let predicateFunction, predicateString;

  // Build the predicate function based on the selected property and criteria
  switch (property) {
    case 'type':
      const typeValue = document.getElementById('type-select').value;
      predicateFunction = product => product.type === typeValue;
      predicateString = `product => product.type === "${typeValue}"`;
      break;

    case 'price':
      const operator = document.getElementById('price-operator').value;
      const price = parseFloat(document.getElementById('price-value').value);

      switch (operator) {
        case 'greater':
          predicateFunction = product => product.price > price;
          predicateString = `product => product.price > ${price}`;
          break;
        case 'less':
          predicateFunction = product => product.price < price;
          predicateString = `product => product.price < ${price}`;
          break;
        case 'equal':
          predicateFunction = product => product.price === price;
          predicateString = `product => product.price === ${price}`;
          break;}

      break;

    case 'inStock':
      const inStock = document.getElementById('stock-status').value === 'true';
      predicateFunction = product => product.inStock === inStock;
      predicateString = `product => product.inStock === ${inStock}`;
      break;}


  // Run the appropriate search method(s)
  if (method === 'find' || method === 'both') {
    const foundProduct = products.find(predicateFunction);
    const foundIndex = products.findIndex(predicateFunction);

    updateFindResult(foundProduct, foundIndex);

    if (foundProduct) {
      highlightItem(foundIndex, 'find');
    }
  }

  if (method === 'findlast' || method === 'both') {
    const foundLastProduct = products.findLast(predicateFunction);
    const foundLastIndex = products.findLastIndex(predicateFunction);

    updateFindLastResult(foundLastProduct, foundLastIndex);

    if (foundLastProduct) {
      highlightItem(foundLastIndex, 'findlast');
    }
  }

  // Update the code display
  updateCodeDisplay(predicateString, method);
}

// Update the find result display
function updateFindResult(product, index) {
  const valueEl = document.getElementById('find-result-value');
  const indexEl = document.getElementById('find-index-result');

  if (product) {
    valueEl.innerHTML = `<strong>${product.name}</strong> (${product.type}, $${product.price}, ${product.inStock ? 'In Stock' : 'Out of Stock'})`;
    indexEl.textContent = `Found at index: ${index}`;
  } else {
    valueEl.textContent = 'No matching item found';
    indexEl.textContent = '';
  }
}

// Update the findLast result display
function updateFindLastResult(product, index) {
  const valueEl = document.getElementById('findlast-result-value');
  const indexEl = document.getElementById('findlast-index-result');

  if (product) {
    valueEl.innerHTML = `<strong>${product.name}</strong> (${product.type}, $${product.price}, ${product.inStock ? 'In Stock' : 'Out of Stock'})`;
    indexEl.textContent = `Found at index: ${index}`;
  } else {
    valueEl.textContent = 'No matching item found';
    indexEl.textContent = '';
  }
}

// Highlight a matching item in the array display
function highlightItem(index, method) {
  const itemEl = document.querySelector(`.array-item[data-index="${index}"]`);
  if (itemEl) {
    itemEl.classList.add(method === 'find' ? 'find-match' : 'findlast-match');
  }
}

// Reset all item highlights
function resetHighlights() {
  document.querySelectorAll('.array-item').forEach(item => {
    item.classList.remove('find-match', 'findlast-match');
  });
}

// Update the code display
function updateCodeDisplay(predicateString, method) {
  const codeEl = document.getElementById('code-display');

  let code = `const products = [\n  // Array of product objects\n];\n\n`;

  if (method === 'find' || method === 'both') {
    code += `// find() searches from the beginning (index 0)\n`;
    code += `const foundProduct = products.find(${predicateString});\n`;
    code += `const foundIndex = products.findIndex(${predicateString});\n\n`;
  }

  if (method === 'findlast' || method === 'both') {
    code += `// findLast() searches from the end (index length-1)\n`;
    code += `const lastProduct = products.findLast(${predicateString});\n`;
    code += `const lastIndex = products.findLastIndex(${predicateString});\n`;
  }

  codeEl.textContent = code;
}

// Reset the search results
function resetSearch() {
  resetHighlights();

  document.getElementById('find-result-value').textContent = 'No search performed yet';
  document.getElementById('find-index-result').textContent = '';

  document.getElementById('findlast-result-value').textContent = 'No search performed yet';
  document.getElementById('findlast-index-result').textContent = '';

  document.getElementById('code-display').textContent = '// Run a search to see the code';
}

// Initialize the demo when the page loads
window.addEventListener('DOMContentLoaded', initializeDemo);

toSorted()#

First, the problem. sort() rearranges the array it is called on and returns that same array, so the original is lost:

const scores = [3, 1, 2];
const sortedScores = scores.sort();

console.log(scores);                 // [1, 2, 3], the original was changed
console.log(sortedScores === scores); // true, it is the very same array

The toSorted() method takes the same arguments as sort() but returns a new array and leaves the original alone:

const numbers = [3, 1, 4, 1, 5, 9];
const sorted = numbers.toSorted();

console.log(sorted);      // [1, 1, 3, 4, 5, 9]
console.log(numbers);     // [3, 1, 4, 1, 5, 9] - original unchanged

// With custom sorting
const descending = numbers.toSorted((a, b) => b - a);
console.log(descending);  // [9, 5, 4, 3, 1, 1]

Coding challenge: Sort without changing the original

Run the starter, then change it so the original scores array stays [3, 1, 2] while sorted is [1, 2, 3].

Expected result: the console prints sorted: [1, 2, 3] and scores: [3, 1, 2]. As written, the starter prints scores: [1, 2, 3], because sort() changed it.

Keep the original intact
---html
<p>Open the Console panel to see the result.</p>
---js
const scores = [3, 1, 2];

// TODO: replace sort() with the method that returns a new array
const sorted = scores.sort();

console.log('sorted:', sorted);
console.log('scores:', scores);
After const b = a.toSorted();, which array is rearranged?

toReversed()#

Similarly, toReversed() is the non-mutating version of reverse(). It returns a new array with the elements in reverse order.

const fruits = ['apple', 'banana', 'cherry'];
const reversed = fruits.toReversed();

console.log(reversed);    // ['cherry', 'banana', 'apple']
console.log(fruits);      // ['apple', 'banana', 'cherry'] - original unchanged

toSpliced()#

The toSpliced() method is the non-mutating version of splice(). It returns a new array with elements added, removed or replaced, and leaves the original alone. The arguments are the start index, how many to remove, then the items to insert.

const colors = ['red', 'green', 'blue'];
const newColors = colors.toSpliced(1, 1, 'yellow');

console.log(newColors);   // ['red', 'yellow', 'blue']
console.log(colors);      // ['red', 'green', 'blue'] - original unchanged

with()#

The with() method returns a new array with one element replaced at a given index, which replaces writing array[1] = 'x' on the original.

const planets = ['Mercury', 'Venus', 'Earth', 'Mars'];
const corrected = planets.with(1, 'Venus (not habitable)');

console.log(corrected);   // ['Mercury', 'Venus (not habitable)', 'Earth', 'Mars']
console.log(planets);     // ['Mercury', 'Venus', 'Earth', 'Mars'] - original unchanged
ES14 Array Methods Explorer
---html
<div class="container">
    <h1>ES14 Array Methods Explorer</h1>
    <p>ES14 (ECMAScript 2023) introduces new non-mutating array methods. This demo compares them with their mutating counterparts.</p>
    
    <div class="card method-section">
      <h2>toSorted() vs sort()</h2>
      <div class="method-explanation">
        <p><code>toSorted()</code> creates a new sorted array without modifying the original, whereas <code>sort()</code> sorts the array in place.</p>
      </div>
      
      <div class="controls">
        <button id="resetSort">Reset Arrays</button>
        <button id="runSort">Run sort()</button>
        <button id="runToSorted">Run toSorted()</button>
        <div class="input-control">
          <label>Sort direction:</label>
          <select id="sortDirection">
            <option value="asc">Ascending</option>
            <option value="desc">Descending</option>
          </select>
        </div>
      </div>
      
      <div class="arrays-container">
        <div class="array-display original">
          <h3>Original Array</h3>
          <div id="sortOriginal" class="array-items"></div>
        </div>
        <div class="array-display modified">
          <h3>Result Array</h3>
          <div id="sortResult" class="array-items"></div>
        </div>
      </div>
      
      <div class="info">
        <code id="sortCode"></code>
      </div>
    </div>
    
    <div class="card method-section">
      <h2>toReversed() vs reverse()</h2>
      <div class="method-explanation">
        <p><code>toReversed()</code> creates a new reversed array without modifying the original, whereas <code>reverse()</code> reverses the array in place.</p>
      </div>
      
      <div class="controls">
        <button id="resetReverse">Reset Arrays</button>
        <button id="runReverse">Run reverse()</button>
        <button id="runToReversed">Run toReversed()</button>
      </div>
      
      <div class="arrays-container">
        <div class="array-display original">
          <h3>Original Array</h3>
          <div id="reverseOriginal" class="array-items"></div>
        </div>
        <div class="array-display modified">
          <h3>Result Array</h3>
          <div id="reverseResult" class="array-items"></div>
        </div>
      </div>
      
      <div class="info">
        <code id="reverseCode"></code>
      </div>
    </div>
    
    <div class="card method-section">
      <h2>with() - Replace by Index</h2>
      <div class="method-explanation">
        <p><code>with()</code> returns a new array with an element replaced at a specified index, without modifying the original array.</p>
      </div>
      
      <div class="controls">
        <button id="resetWith">Reset Arrays</button>
        <button id="runWith">Run with()</button>
        <div class="input-control">
          <label>Index:</label>
          <input type="number" id="withIndex" min="0" max="4" value="2" style="width: 60px;">
          <label>New Value:</label>
          <input type="text" id="withValue" value="NEW" style="width: 100px;">
        </div>
      </div>
      
      <div class="arrays-container">
        <div class="array-display original">
          <h3>Original Array</h3>
          <div id="withOriginal" class="array-items"></div>
        </div>
        <div class="array-display modified">
          <h3>Result Array</h3>
          <div id="withResult" class="array-items"></div>
        </div>
      </div>
      
      <div class="info">
        <code id="withCode"></code>
      </div>
    </div>
    
    <div class="card method-section">
      <h2>toSpliced() vs splice()</h2>
      <div class="method-explanation">
        <p><code>toSpliced()</code> creates a new array with elements added or removed, whereas <code>splice()</code> modifies the original array.</p>
      </div>
      
      <div class="controls">
        <button id="resetSplice">Reset Arrays</button>
        <button id="runSplice">Run splice()</button>
        <button id="runToSpliced">Run toSpliced()</button>
        <div class="input-control">
          <label>Start:</label>
          <input type="number" id="spliceStart" min="0" max="4" value="1" style="width: 60px;">
          <label>Delete Count:</label>
          <input type="number" id="spliceDeleteCount" min="0" max="3" value="1" style="width: 60px;">
          <label>Items to Add:</label>
          <input type="text" id="spliceItems" value="NEW" style="width: 100px;">
        </div>
      </div>
      
      <div class="arrays-container">
        <div class="array-display original">
          <h3>Original Array</h3>
          <div id="spliceOriginal" class="array-items"></div>
        </div>
        <div class="array-display modified">
          <h3>Result Array</h3>
          <div id="spliceResult" class="array-items"></div>
        </div>
      </div>
      
      <div class="info">
        <code id="spliceCode"></code>
      </div>
    </div>
    
    <div class="card method-section">
      <h2>findLast() & findLastIndex()</h2>
      <div class="method-explanation">
        <p><code>findLast()</code> and <code>findLastIndex()</code> search from the end of the array, contrasting with <code>find()</code> and <code>findIndex()</code>.</p>
      </div>
      
      <div class="controls">
        <button id="resetFind">Reset Array</button>
        <button id="runFind">Run find()</button>
        <button id="runFindLast">Run findLast()</button>
        <div class="input-control">
          <label>Find value greater than:</label>
          <input type="number" id="findValue" min="0" max="100" value="50" style="width: 60px;">
        </div>
      </div>
      
      <div class="arrays-container">
        <div class="array-display original">
          <h3>Test Array</h3>
          <div id="findOriginal" class="array-items"></div>
        </div>
        <div class="array-display modified">
          <h3>Results</h3>
          <div id="findResult" class="array-items"></div>
        </div>
      </div>
      
      <div class="info">
        <code id="findCode"></code>
      </div>
    </div>
  </div>
---css
:root {
      --bg-color: #ffffff;
      --text-color: #333333;
      --card-bg: #f5f5f5;
      --button-bg: #4285f4;
      --button-text: white;
      --highlight: #673ab7;
      --original: #2196f3;
      --modified: #4caf50;
      --border: #e0e0e0;
      --code-bg: #f0f0f0;
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #121212;
        --text-color: #eeeeee;
        --card-bg: #1e1e1e;
        --button-bg: #4285f4;
        --button-text: white;
        --highlight: #bb86fc;
        --original: #64b5f6;
        --modified: #81c784;
        --border: #333333;
        --code-bg: #2d2d2d;
      }
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      margin: 0;
      padding: 20px;
      line-height: 1.6;
    }
    
    .container {
      max-width: 900px;
      margin: 0 auto;
    }
    
    h1, h2, h3 {
      margin-top: 0;
    }
    
    h1 {
      color: var(--highlight);
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      padding: 20px;
      margin-bottom: 20px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    
    .method-section {
      margin-bottom: 30px;
    }
    
    .arrays-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
      margin: 15px 0;
    }
    
    .array-display {
      flex: 1;
      min-width: 250px;
      border: 1px solid var(--border);
      border-radius: 8px;
      padding: 15px;
    }
    
    .array-display h3 {
      margin-top: 0;
      font-size: 16px;
    }
    
    .original {
      color: var(--original);
    }
    
    .modified {
      color: var(--modified);
    }
    
    .array-items {
      display: flex;
      flex-wrap: wrap;
      gap: 5px;
    }
    
    .array-item {
      display: inline-block;
      padding: 5px 10px;
      background-color: rgba(255, 255, 255, 0.05);
      border-radius: 4px;
      margin: 2px;
      border: 1px solid var(--border);
    }
    
    button {
      background-color: var(--button-bg);
      color: var(--button-text);
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      cursor: pointer;
      margin: 5px;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    .controls {
      margin-bottom: 15px;
    }
    
    code {
      font-family: 'Courier New', Courier, monospace;
      background-color: var(--code-bg);
      padding: 2px 5px;
      border-radius: 3px;
    }
    
    .input-control {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-top: 10px;
    }
    
    input {
      padding: 8px;
      border: 1px solid var(--border);
      border-radius: 4px;
      background-color: var(--bg-color);
      color: var(--text-color);
    }
    
    .info {
      font-size: 14px;
      margin-top: 10px;
      opacity: 0.8;
    }
    
    .method-explanation {
      margin-bottom: 15px;
    }
  <
---js
// Initial data
const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
const numbers = [34, 12, 78, 23, 56];
const values = [10, 60, 30, 70, 20, 60, 40, 90, 50];

// Sort demo
let sortArray = [...fruits];
let sortResult = [];

function updateSortDisplay() {
  const originalEl = document.getElementById('sortOriginal');
  const resultEl = document.getElementById('sortResult');

  originalEl.innerHTML = '';
  resultEl.innerHTML = '';

  sortArray.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    originalEl.appendChild(el);
  });

  sortResult.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    resultEl.appendChild(el);
  });
}

document.getElementById('resetSort').addEventListener('click', () => {
  sortArray = [...fruits];
  sortResult = [];
  updateSortDisplay();
  document.getElementById('sortCode').textContent = '';
});

document.getElementById('runSort').addEventListener('click', () => {
  const direction = document.getElementById('sortDirection').value;
  let code = '';

  if (direction === 'desc') {
    sortArray.sort((a, b) => b.localeCompare(a));
    code = 'array.sort((a, b) => b.localeCompare(a));';
  } else {
    sortArray.sort();
    code = 'array.sort();';
  }

  sortResult = [...sortArray];
  updateSortDisplay();
  document.getElementById('sortCode').textContent = code;
});

document.getElementById('runToSorted').addEventListener('click', () => {
  const direction = document.getElementById('sortDirection').value;
  let code = '';

  if (direction === 'desc') {
    sortResult = sortArray.toSorted((a, b) => b.localeCompare(a));
    code = 'const result = array.toSorted((a, b) => b.localeCompare(a));';
  } else {
    sortResult = sortArray.toSorted();
    code = 'const result = array.toSorted();';
  }

  updateSortDisplay();
  document.getElementById('sortCode').textContent = code;
});

// Reverse demo
let reverseArray = [...fruits];
let reverseResult = [];

function updateReverseDisplay() {
  const originalEl = document.getElementById('reverseOriginal');
  const resultEl = document.getElementById('reverseResult');

  originalEl.innerHTML = '';
  resultEl.innerHTML = '';

  reverseArray.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    originalEl.appendChild(el);
  });

  reverseResult.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    resultEl.appendChild(el);
  });
}

document.getElementById('resetReverse').addEventListener('click', () => {
  reverseArray = [...fruits];
  reverseResult = [];
  updateReverseDisplay();
  document.getElementById('reverseCode').textContent = '';
});

document.getElementById('runReverse').addEventListener('click', () => {
  reverseArray.reverse();
  reverseResult = [...reverseArray];
  updateReverseDisplay();
  document.getElementById('reverseCode').textContent = 'array.reverse();';
});

document.getElementById('runToReversed').addEventListener('click', () => {
  reverseResult = reverseArray.toReversed();
  updateReverseDisplay();
  document.getElementById('reverseCode').textContent = 'const result = array.toReversed();';
});

// With demo
let withArray = [...fruits];
let withResult = [];

function updateWithDisplay() {
  const originalEl = document.getElementById('withOriginal');
  const resultEl = document.getElementById('withResult');

  originalEl.innerHTML = '';
  resultEl.innerHTML = '';

  withArray.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    originalEl.appendChild(el);
  });

  withResult.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    resultEl.appendChild(el);
  });
}

document.getElementById('resetWith').addEventListener('click', () => {
  withArray = [...fruits];
  withResult = [];
  updateWithDisplay();
  document.getElementById('withCode').textContent = '';
});

document.getElementById('runWith').addEventListener('click', () => {
  const index = parseInt(document.getElementById('withIndex').value);
  const value = document.getElementById('withValue').value;

  withResult = withArray.with(index, value);
  updateWithDisplay();
  document.getElementById('withCode').textContent = `const result = array.with(${index}, "${value}");`;
});

// Splice demo
let spliceArray = [...fruits];
let spliceResult = [];

function updateSpliceDisplay() {
  const originalEl = document.getElementById('spliceOriginal');
  const resultEl = document.getElementById('spliceResult');

  originalEl.innerHTML = '';
  resultEl.innerHTML = '';

  spliceArray.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    originalEl.appendChild(el);
  });

  spliceResult.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    resultEl.appendChild(el);
  });
}

document.getElementById('resetSplice').addEventListener('click', () => {
  spliceArray = [...fruits];
  spliceResult = [];
  updateSpliceDisplay();
  document.getElementById('spliceCode').textContent = '';
});

document.getElementById('runSplice').addEventListener('click', () => {
  const start = parseInt(document.getElementById('spliceStart').value);
  const deleteCount = parseInt(document.getElementById('spliceDeleteCount').value);
  const items = document.getElementById('spliceItems').value;

  spliceArray.splice(start, deleteCount, items);
  spliceResult = [...spliceArray];
  updateSpliceDisplay();
  document.getElementById('spliceCode').textContent = `array.splice(${start}, ${deleteCount}, "${items}");`;
});

document.getElementById('runToSpliced').addEventListener('click', () => {
  const start = parseInt(document.getElementById('spliceStart').value);
  const deleteCount = parseInt(document.getElementById('spliceDeleteCount').value);
  const items = document.getElementById('spliceItems').value;

  spliceResult = spliceArray.toSpliced(start, deleteCount, items);
  updateSpliceDisplay();
  document.getElementById('spliceCode').textContent = `const result = array.toSpliced(${start}, ${deleteCount}, "${items}");`;
});

// Find demo
let findArray = [...values];

function updateFindDisplay() {
  const originalEl = document.getElementById('findOriginal');
  const resultEl = document.getElementById('findResult');

  originalEl.innerHTML = '';
  resultEl.innerHTML = '';

  findArray.forEach(item => {
    const el = document.createElement('span');
    el.className = 'array-item';
    el.textContent = item;
    originalEl.appendChild(el);
  });
}

function updateFindResults(findValue, findIndex, findLastValue, findLastIndex) {
  const resultEl = document.getElementById('findResult');
  resultEl.innerHTML = '';

  const addResult = (label, value) => {
    const resultContainer = document.createElement('div');
    resultContainer.style.marginBottom = '10px';
    resultContainer.innerHTML = `<strong>${label}:</strong> ${value !== undefined ? value : 'Not found'}`;
    resultEl.appendChild(resultContainer);
  };

  if (findValue !== undefined || findIndex !== undefined) {
    addResult('find()', findValue);
    addResult('findIndex()', findIndex !== -1 ? findIndex : 'Not found');
  }

  if (findLastValue !== undefined || findLastIndex !== undefined) {
    addResult('findLast()', findLastValue);
    addResult('findLastIndex()', findLastIndex !== -1 ? findLastIndex : 'Not found');
  }
}

document.getElementById('resetFind').addEventListener('click', () => {
  findArray = [...values];
  updateFindDisplay();
  document.getElementById('findResult').innerHTML = '';
  document.getElementById('findCode').textContent = '';
});

document.getElementById('runFind').addEventListener('click', () => {
  const threshold = parseInt(document.getElementById('findValue').value);

  const foundValue = findArray.find(num => num > threshold);
  const foundIndex = findArray.findIndex(num => num > threshold);

  updateFindResults(foundValue, foundIndex);
  document.getElementById('findCode').textContent = `array.find(num => num > ${threshold});\narray.findIndex(num => num > ${threshold});`;
});

document.getElementById('runFindLast').addEventListener('click', () => {
  const threshold = parseInt(document.getElementById('findValue').value);

  const foundLastValue = findArray.findLast(num => num > threshold);
  const foundLastIndex = findArray.findLastIndex(num => num > threshold);

  updateFindResults(undefined, undefined, foundLastValue, foundLastIndex);
  document.getElementById('findCode').textContent = `array.findLast(num => num > ${threshold});\narray.findLastIndex(num => num > ${threshold});`;
});

// Initialize all displays
updateSortDisplay();
updateReverseDisplay();
updateWithDisplay();
updateSpliceDisplay();
updateFindDisplay();

Finding Elements from the End#

ES14 adds findLast() and findLastIndex() methods to arrays. They work like find() and findIndex() but search from the end, so they return the last element that satisfies the condition.

const numbers = [5, 12, 8, 130, 44, 4, 12];

// Find last number greater than 10
const lastBig = numbers.findLast(num => num > 10);
console.log(lastBig);  // 12 (the last 12 in the array)

// Find its index
const lastBigIndex = numbers.findLastIndex(num => num > 10);
console.log(lastBigIndex);  // 6

These methods are particularly useful when you need to find the most recent occurrence of something in an array.

Hashbang Grammar#

Optional. ES14 formally allows a hashbang line (also called a shebang), #! followed by a program path, as the very first line of a JavaScript file. On Unix-like systems it tells the operating system which program should run the file, so the script can be started directly from the command line. Only the first line of a file can be a hashbang.

#!/usr/bin/env node

console.log('Hello, world!');

Symbol as WeakMap Keys#

Optional. A WeakMap is a map whose keys do not stop the garbage collector (the part of JavaScript that frees unused memory) from removing them. Previously only objects could be keys. ES14 lets symbols made with Symbol() be keys too. A symbol from the shared registry, made with Symbol.for(), is rejected with a TypeError.

const weakMap = new WeakMap();
const key = Symbol('unique identifier');

weakMap.set(key, { data: 'Important information' });
console.log(weakMap.get(key));  // { data: 'Important information' }

You will rarely need this outside library code.

The Impact on Modern JavaScript Development#

These new features in ES13 and ES14 collectively represent JavaScript's continuing evolution toward a more mature, developer-friendly language. Several key themes emerge:

More Functional Programming Support#

The non-mutating array methods in ES14 and other immutable patterns show a clear shift toward functional programming paradigms, making it easier to write code that avoids side effects. This approach is increasingly favored in modern JavaScript applications for its predictability and testability.

Better Object-Oriented Programming#

The improvements to classes in ES13, particularly private fields and static members, bring JavaScript's object-oriented capabilities more in line with other class-based languages. This makes it easier for developers coming from languages like Java or C# to work effectively in JavaScript.

Enhanced Ergonomics#

Features like at(), top-level await, and findLast() simply make JavaScript more pleasant to work with by reducing boilerplate and making common operations more intuitive.

Improved Error Handling#

The cause property for errors improves debugging and error reporting, especially in complex applications where understanding the chain of failures matters.

From Theory to Practice: Modern JavaScript in Action#

To truly appreciate the power of ES13 and ES14's new features, let's move beyond individual examples and see how they work together in a real-world application. The following Task Manager demo combines multiple modern JavaScript features into a cohesive, practical system that showcases their combined benefits. By implementing private class fields, non-mutating array methods, error causes, and more within a single application, we can better understand how these features complement each other and create more maintainable, readable code. This interactive demo not only illustrates the syntax of these new features but demonstrates their practical advantages in an everyday programming context. Whether you're managing data, handling user interactions, or organizing application logic, you'll see how ES13 and ES14 provide elegant solutions to common development challenges.

ES13 & ES14 Features Demo - Task Manager
---html
<div class="container">
    <div class="header">
      <h1>Modern JavaScript Task Manager</h1>
      <p>Showcasing ES13 and ES14 features in a practical application</p>
    </div>
    
    <div class="tab-container">
      <div class="tabs">
        <button class="tab active" data-tab="app-tab">Application</button>
        <button class="tab" data-tab="features-tab">Features Explained</button>
      </div>
      
      <div class="tab-content active" id="app-tab">
        <div class="app-container">
          <div class="tasks-panel">
            <div class="card">
              <div class="card-header">
                <h2 class="card-title">Add New Task</h2>
              </div>
              
              <form id="task-form">
                <div class="input-group">
                  <label for="task-title">Title</label>
                  <input type="text" id="task-title" required placeholder="Enter task title">
                </div>
                
                <div class="input-group">
                  <label for="task-description">Description</label>
                  <textarea id="task-description" rows="3" placeholder="Enter task description"></textarea>
                </div>
                
                <div class="input-group">
                  <label for="task-priority">Priority</label>
                  <select id="task-priority">
                    <option value="low">Low</option>
                    <option value="medium">Medium</option>
                    <option value="high">High</option>
                  </select>
                </div>
                
                <div class="input-group">
                  <label for="task-due-date">Due Date</label>
                  <input type="date" id="task-due-date">
                </div>
                
                <div class="checkbox-container">
                  <input type="checkbox" id="task-completed">
                  <label for="task-completed">Mark as completed</label>
                </div>
                
                <button type="submit" class="btn-primary">Add Task</button>
              </form>
            </div>
            
            <div class="card">
              <div class="card-header">
                <h2 class="card-title">Task Statistics</h2>
              </div>
              <div id="task-stats">
                <p>Total Tasks: <span id="total-count">0</span></p>
                <p>Completed: <span id="completed-count">0</span></p>
                <p>Pending: <span id="pending-count">0</span></p>
                <p>High Priority: <span id="high-priority-count">0</span></p>
              </div>
            </div>
          </div>
          
          <div class="task-list-panel">
            <div class="card">
              <div class="card-header">
                <h2 class="card-title">Task List</h2>
              </div>
              
              <div id="alert-container"></div>
              
              <div class="filters">
                <div class="filter-group">
                  <label for="status-filter">Status</label>
                  <select id="status-filter">
                    <option value="all">All</option>
                    <option value="completed">Completed</option>
                    <option value="pending">Pending</option>
                  </select>
                </div>
                
                <div class="filter-group">
                  <label for="priority-filter">Priority</label>
                  <select id="priority-filter">
                    <option value="all">All</option>
                    <option value="high">High</option>
                    <option value="medium">Medium</option>
                    <option value="low">Low</option>
                  </select>
                </div>
              </div>
              
              <div class="sort-controls">
                <label for="sort-by">Sort By:</label>
                <select id="sort-by">
                  <option value="dueDate">Due Date</option>
                  <option value="priority">Priority</option>
                  <option value="title">Title</option>
                </select>
                
                <button id="sort-btn" class="btn-secondary btn-sm">Sort Ascending</button>
                <button id="reverse-btn" class="btn-secondary btn-sm">Reverse Order</button>
              </div>
              
              <ul id="task-list" class="task-list"></ul>
            </div>
          </div>
        </div>
      </div>
      
      <div class="tab-content" id="features-tab">
        <div class="card">
          <h2>ES13 and ES14 Features Used in This Demo</h2>
          <p>This task manager application showcases several new JavaScript features from ECMAScript 2022 (ES13) and ECMAScript 2023 (ES14).</p>
          
          <div class="features-list">
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es13">ES13</span>
                Private Class Fields
              </h3>
              <div class="feature-description">
                Private class fields allow encapsulation of internal properties and methods using the # prefix.
              </div>
              <div class="code-sample">
                class TaskManager {
                  // Private fields (ES13)
                  #tasks = [];
                  #nextId = 1;
                  
                  addTask(task) {
                    task.id = this.#generateId();
                    this.#tasks.push(task);
                    return task;
                  }
                  
                  #generateId() {
                    return this.#nextId++;
                  }
                }
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es13">ES13</span>
                Top-level await
              </h3>
              <div class="feature-description">
                Allows using the await keyword outside of async functions at the module level.
              </div>
              <div class="code-sample">
                // This would be at the top level of a module
                try {
                  // No need to wrap in an async function
                  const response = await fetch('/api/tasks');
                  const tasks = await response.json();
                  
                  // Code here only runs after the fetch completes
                  taskManager.initializeTasks(tasks);
                } catch (error) {
                  console.error('Failed to load tasks:', error);
                }
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es13">ES13</span>
                Error Cause
              </h3>
              <div class="feature-description">
                The cause property on Error objects helps create more informative error chains.
              </div>
              <div class="code-sample">
                async function loadTasks() {
                  try {
                    const response = await fetch('/api/tasks');
                    
                    if (!response.ok) {
                      throw new Error('Failed to load tasks', { 
                        cause: {
                          status: response.status,
                          statusText: response.statusText
                        }
                      });
                    }
                    
                    return await response.json();
                  } catch (error) {
                    // The original error information is preserved
                    console.error('Error:', error.message);
                    console.error('Cause:', error.cause);
                    throw error;
                  }
                }
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es13">ES13</span>
                Static Class Blocks
              </h3>
              <div class="feature-description">
                Static initialization blocks for classes enable complex class-level initialization.
              </div>
              <div class="code-sample">
                class TaskManager {
                  static sortFunctions = {};
                  
                  // Static initialization block (ES13)
                  static {
                    this.sortFunctions.priority = (a, b) => {
                      const priorities = {high: 3, medium: 2, low: 1};
                      return priorities[a.priority] - priorities[b.priority];
                    };
                    
                    this.sortFunctions.dueDate = (a, b) => {
                      return new Date(a.dueDate) - new Date(b.dueDate);
                    };
                    
                    this.sortFunctions.title = (a, b) => {
                      return a.title.localeCompare(b.title);
                    };
                  }
                }
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es13">ES13</span>
                at() Method
              </h3>
              <div class="feature-description">
                Provides a cleaner way to access elements in arrays, especially with negative indices.
              </div>
              <div class="code-sample">
                const tasks = [
                  { title: "Task 1", priority: "high" },
                  { title: "Task 2", priority: "medium" },
                  { title: "Task 3", priority: "low" }
                ];
                
                // Get the last task in the array
                const lastTask = tasks.at(-1); // { title: "Task 3", priority: "low" }
                
                // Without at() method
                const oldWay = tasks[tasks.length - 1];
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es14">ES14</span>
                toSorted() Method
              </h3>
              <div class="feature-description">
                Creates a new sorted array without modifying the original array.
              </div>
              <div class="code-sample">
                const tasks = [
                  { title: "Learn ES14", priority: "high" },
                  { title: "Take a break", priority: "low" },
                  { title: "Review code", priority: "medium" }
                ];
                
                // Sort by priority without modifying original array
                const sortedTasks = tasks.toSorted((a, b) => {
                  const priorities = {high: 3, medium: 2, low: 1};
                  return priorities[b.priority] - priorities[a.priority];
                });
                
                // Original array remains unchanged
                console.log(tasks[0].title); // "Learn ES14"
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es14">ES14</span>
                toReversed() Method
              </h3>
              <div class="feature-description">
                Creates a new reversed array without modifying the original array.
              </div>
              <div class="code-sample">
                const tasks = [
                  { title: "First task" },
                  { title: "Second task" },
                  { title: "Third task" }
                ];
                
                // Create reversed array without modifying original
                const reversedTasks = tasks.toReversed();
                
                console.log(reversedTasks[0].title); // "Third task"
                console.log(tasks[0].title); // "First task" (unchanged)
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es14">ES14</span>
                with() Method
              </h3>
              <div class="feature-description">
                Creates a new array with one element replaced at a specific index.
              </div>
              <div class="code-sample">
                const tasks = [
                  { title: "Task A", completed: false },
                  { title: "Task B", completed: false },
                  { title: "Task C", completed: false }
                ];
                
                // Replace element at index 1 without modifying original
                const updatedTasks = tasks.with(1, { 
                  title: "Updated Task B", 
                  completed: true 
                });
                
                console.log(updatedTasks[1].title); // "Updated Task B"
                console.log(tasks[1].title); // "Task B" (unchanged)
              </div>
            </div>
            
            <div class="feature-card">
              <h3>
                <span class="feature-tag feature-es14">ES14</span>
                findLast() & findLastIndex()
              </h3>
              <div class="feature-description">
                Search an array from the end instead of the beginning.
              </div>
              <div class="code-sample">
                const tasks = [
                  { title: "Task 1", completed: false },
                  { title: "Task 2", completed: true },
                  { title: "Task 3", completed: false },
                  { title: "Task 4", completed: true }
                ];
                
                // Find the last completed task
                const lastCompleted = tasks.findLast(
                  task => task.completed
                );
                console.log(lastCompleted.title); // "Task 4"
                
                // Find the index of the last completed task
                const lastCompletedIndex = tasks.findLastIndex(
                  task => task.completed
                );
                console.log(lastCompletedIndex); // 3
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  
  <div id="loading-overlay" class="loading-overlay hidden">
    <div class="loading-spinner"></div>
  </div>
---css
:root {
      --bg-color: #f8f9fa;
      --text-color: #212529;
      --card-bg: #ffffff;
      --border-color: #dee2e6;
      --primary-color: #6610f2;
      --secondary-color: #6c757d;
      --success-color: #198754;
      --danger-color: #dc3545;
      --warning-color: #ffc107;
      --info-color: #0dcaf0;
      --input-bg: #ffffff;
      --header-bg: #f1f3f5;
      --shadow: rgba(0, 0, 0, 0.1);
      --hover-bg: #f1f3f5;
      --code-bg: #f5f5f5;
      --code-color: #e83e8c;
    }
    
    @media (prefers-color-scheme: dark) {
      :root {
        --bg-color: #121212;
        --text-color: #e9ecef;
        --card-bg: #1e1e1e;
        --border-color: #495057;
        --primary-color: #a370f7;
        --secondary-color: #adb5bd;
        --success-color: #28a745;
        --danger-color: #dc3545;
        --warning-color: #ffc107;
        --info-color: #0dcaf0;
        --input-bg: #2d2d2d;
        --header-bg: #2d2d2d;
        --shadow: rgba(0, 0, 0, 0.3);
        --hover-bg: #343a40;
        --code-bg: #2d2d2d;
        --code-color: #f08c8c;
      }
    }
    
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: var(--bg-color);
      color: var(--text-color);
      line-height: 1.6;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    
    .container {
      max-width: 1200px;
      margin: 0 auto;
    }
    
    .header {
      margin-bottom: 30px;
      text-align: center;
    }
    
    h1, h2, h3 {
      margin-bottom: 15px;
      color: var(--primary-color);
    }
    
    .app-container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
    }
    
    @media (max-width: 768px) {
      .app-container {
        grid-template-columns: 1fr;
      }
    }
    
    .card {
      background-color: var(--card-bg);
      border-radius: 8px;
      box-shadow: 0 2px 10px var(--shadow);
      padding: 20px;
      margin-bottom: 20px;
    }
    
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding-bottom: 10px;
      margin-bottom: 15px;
      border-bottom: 1px solid var(--border-color);
    }
    
    .card-title {
      font-size: 1.2rem;
      margin: 0;
    }
    
    .input-group {
      margin-bottom: 15px;
    }
    
    .input-group label {
      display: block;
      margin-bottom: 5px;
    }
    
    input, select, textarea {
      width: 100%;
      padding: 10px;
      border: 1px solid var(--border-color);
      border-radius: 4px;
      background-color: var(--input-bg);
      color: var(--text-color);
      font-family: inherit;
      font-size: 1rem;
    }
    
    button {
      padding: 8px 16px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 1rem;
      transition: opacity 0.2s;
    }
    
    button:hover {
      opacity: 0.9;
    }
    
    .btn-primary {
      background-color: var(--primary-color);
      color: white;
    }
    
    .btn-secondary {
      background-color: var(--secondary-color);
      color: white;
    }
    
    .btn-danger {
      background-color: var(--danger-color);
      color: white;
    }
    
    .btn-sm {
      padding: 4px 8px;
      font-size: 0.875rem;
    }
    
    .btn-group {
      display: flex;
      gap: 10px;
    }
    
    .task-list {
      list-style: none;
      padding: 0;
    }
    
    .task-item {
      padding: 12px;
      border: 1px solid var(--border-color);
      border-radius: 4px;
      margin-bottom: 10px;
      background-color: var(--card-bg);
      transition: transform 0.2s;
    }
    
    .task-item:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 6px var(--shadow);
    }
    
    .task-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 8px;
    }
    
    .task-title {
      font-weight: bold;
      font-size: 1.1rem;
    }
    
    .task-footer {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 8px;
      font-size: 0.875rem;
      color: var(--secondary-color);
    }
    
    .task-actions {
      display: flex;
      gap: 8px;
    }
    
    .badge {
      display: inline-block;
      padding: 3px 8px;
      border-radius: 10px;
      font-size: 0.75rem;
      font-weight: bold;
    }
    
    .badge-high {
      background-color: var(--danger-color);
      color: white;
    }
    
    .badge-medium {
      background-color: var(--warning-color);
      color: black;
    }
    
    .badge-low {
      background-color: var(--info-color);
      color: black;
    }
    
    .badge-completed {
      background-color: var(--success-color);
      color: white;
    }
    
    .badge-pending {
      background-color: var(--secondary-color);
      color: white;
    }
    
    .checkbox-container {
      display: flex;
      align-items: center;
      gap: 10px;
    }
    
    .checkbox-container input[type="checkbox"] {
      width: auto;
    }
    
    .filters {
      display: flex;
      gap: 10px;
      margin-bottom: 15px;
      flex-wrap: wrap;
    }
    
    .filter-group {
      flex: 1;
      min-width: 120px;
    }
    
    .sort-controls {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 15px;
    }
    
    .loading-overlay {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.5);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 1000;
    }
    
    .loading-spinner {
      border: 4px solid rgba(255, 255, 255, 0.3);
      border-radius: 50%;
      border-top: 4px solid white;
      width: 40px;
      height: 40px;
      animation: spin 1s linear infinite;
    }
    
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
    
    .alert {
      padding: 12px;
      border-radius: 4px;
      margin-bottom: 15px;
    }
    
    .alert-danger {
      background-color: rgba(220, 53, 69, 0.1);
      border: 1px solid var(--danger-color);
      color: var(--danger-color);
    }
    
    .alert-success {
      background-color: rgba(25, 135, 84, 0.1);
      border: 1px solid var(--success-color);
      color: var(--success-color);
    }
    
    .alert-warning {
      background-color: rgba(255, 193, 7, 0.1);
      border: 1px solid var(--warning-color);
      color: var(--warning-color);
    }
    
    .hidden {
      display: none;
    }
    
    pre, code {
      font-family: 'Courier New', Courier, monospace;
      background-color: var(--code-bg);
      color: var(--code-color);
    }
    
    .code-block {
      margin: 15px 0;
      padding: 15px;
      border-radius: 4px;
      background-color: var(--code-bg);
      overflow: auto;
    }
    
    .feature-tag {
      display: inline-block;
      padding: 2px 6px;
      border-radius: 4px;
      margin-right: 5px;
      font-size: 0.75rem;
      font-weight: bold;
      background-color: var(--primary-color);
      color: white;
    }
    
    .feature-es13 {
      background-color: var(--primary-color);
    }
    
    .feature-es14 {
      background-color: var(--success-color);
    }
    
    .tab-container {
      margin-bottom: 20px;
    }
    
    .tabs {
      display: flex;
      border-bottom: 1px solid var(--border-color);
    }
    
    .tab {
      padding: 8px 16px;
      cursor: pointer;
      border: none;
      background: none;
      color: var(--text-color);
      opacity: 0.7;
    }
    
    .tab.active {
      opacity: 1;
      border-bottom: 2px solid var(--primary-color);
      font-weight: bold;
    }
    
    .tab-content {
      display: none;
      padding: 20px 0;
    }
    
    .tab-content.active {
      display: block;
    }
    
    .features-list {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 15px;
      margin-top: 20px;
    }
    
    .feature-card {
      border: 1px solid var(--border-color);
      border-radius: 4px;
      padding: 15px;
      background-color: var(--card-bg);
    }
    
    .feature-card h3 {
      margin-top: 0;
      margin-bottom: 10px;
      display: flex;
      align-items: center;
      gap: 8px;
    }
    
    .feature-description {
      margin-bottom: 10px;
    }
    
    .code-sample {
      font-size: 0.9rem;
      padding: 10px;
      background-color: var(--code-bg);
      border-radius: 4px;
      margin-top: 10px;
      overflow-x: auto;
    }
---js
// Class for managing the application
    class TaskManager {
      // Private fields (ES13 feature)
      #tasks = [];
      #nextId = 1;
      #eventListeners = {
        taskAdded: [],
        taskUpdated: [],
        taskDeleted: [],
        tasksFiltered: [],
        tasksSorted: []
      };
      
      // Static class fields (ES13 feature)
      static PRIORITIES = ['low', 'medium', 'high'];
      static sortFunctions = {};
      
      // Static initialization block (ES13 feature)
      static {
        // Define sort functions for different properties
        this.sortFunctions.priority = (a, b) => {
          const priorities = {high: 3, medium: 2, low: 1};
          return priorities[a.priority] - priorities[b.priority];
        };
        
        this.sortFunctions.dueDate = (a, b) => {
          if (!a.dueDate) return 1;
          if (!b.dueDate) return -1;
          return new Date(a.dueDate) - new Date(b.dueDate);
        };
        
        this.sortFunctions.title = (a, b) => {
          return a.title.localeCompare(b.title);
        };
      }
      
      constructor() {
        // Initialize with some sample tasks
        this.#initializeSampleTasks();
      }
      
      // Private method (ES13 feature)
      #generateId() {
        return this.#nextId++;
      }
      
      // Private method to initialize sample tasks
      #initializeSampleTasks() {
        const today = new Date();
        const tomorrow = new Date(today);
        tomorrow.setDate(tomorrow.getDate() + 1);
        
        const nextWeek = new Date(today);
        nextWeek.setDate(nextWeek.getDate() + 7);
        
        const sampleTasks = [
          {
            title: "Complete ES13/ES14 features demo",
            description: "Create a full demo showcasing ES13 and ES14 features",
            priority: "high",
            dueDate: today.toISOString().split('T')[0],
            completed: false
          },
          {
            title: "Learn about top-level await",
            description: "Research and implement examples of top-level await in modules",
            priority: "medium",
            dueDate: tomorrow.toISOString().split('T')[0],
            completed: true
          },
          {
            title: "Experiment with private class fields",
            description: "Create classes using the new private field syntax with # prefix",
            priority: "medium",
            dueDate: tomorrow.toISOString().split('T')[0],
            completed: false
          },
          {
            title: "Implement error cause pattern",
            description: "Use the new error cause property for better error handling",
            priority: "low",
            dueDate: nextWeek.toISOString().split('T')[0],
            completed: false
          }
        ];
        
        sampleTasks.forEach(task => this.addTask(task));
      }
      
      // Add a new task
      addTask(task) {
        const newTask = {
          ...task,
          id: this.#generateId(),
          createdAt: new Date().toISOString()
        };
        
        this.#tasks.push(newTask);
        this.#notifyListeners('taskAdded', newTask);
        return newTask;
      }
      
      // Update an existing task
      updateTask(taskId, updates) {
        const taskIndex = this.#tasks.findIndex(t => t.id === taskId);
        
        if (taskIndex === -1) {
          throw new Error(`Task with ID ${taskId} not found`, { 
            cause: 'TASK_NOT_FOUND' 
          });
        }
        
        // Create updated task using with() method (ES14 feature)
        const updatedTask = { ...this.#tasks[taskIndex], ...updates };
        
        // Update tasks array using with() method (ES14 feature)
        this.#tasks = this.#tasks.with(taskIndex, updatedTask);
        
        this.#notifyListeners('taskUpdated', updatedTask);
        return updatedTask;
      }
      
      // Delete a task
      deleteTask(taskId) {
        const taskIndex = this.#tasks.findIndex(t => t.id === taskId);
        
        if (taskIndex === -1) {
          throw new Error(`Task with ID ${taskId} not found`, { 
            cause: 'TASK_NOT_FOUND' 
          });
        }
        
        const deletedTask = this.#tasks[taskIndex];
        
        // Remove the task using toSpliced (ES14 feature)
        this.#tasks = this.#tasks.toSpliced(taskIndex, 1);
        
        this.#notifyListeners('taskDeleted', deletedTask);
        return deletedTask;
      }
      
      // Get all tasks
      getAllTasks() {
        return [...this.#tasks];
      }
      
      // Filter tasks by various criteria
      filterTasks(filters = {}) {
        let filteredTasks = [...this.#tasks];
        
        if (filters.status === 'completed') {
          filteredTasks = filteredTasks.filter(task => task.completed);
        } else if (filters.status === 'pending') {
          filteredTasks = filteredTasks.filter(task => !task.completed);
        }
        
        if (filters.priority && filters.priority !== 'all') {
          filteredTasks = filteredTasks.filter(task => task.priority === filters.priority);
        }
        
        if (filters.search) {
          const searchLower = filters.search.toLowerCase();
          filteredTasks = filteredTasks.filter(task => 
            task.title.toLowerCase().includes(searchLower) || 
            (task.description && task.description.toLowerCase().includes(searchLower))
          );
        }
        
        this.#notifyListeners('tasksFiltered', filteredTasks);
        return filteredTasks;
      }
      
      // Sort tasks by a specific property
      sortTasks(property, direction = 'asc') {
        if (!TaskManager.sortFunctions[property]) {
          throw new Error(`Invalid sort property: ${property}`, {
            cause: 'INVALID_SORT_PROPERTY'
          });
        }
        
        const sortFn = TaskManager.sortFunctions[property];
        
        // Use toSorted (ES14 feature) to create a new sorted array
        let sortedTasks = this.#tasks.toSorted((a, b) => {
          return direction === 'asc' ? sortFn(a, b) : sortFn(b, a);
        });
        
        this.#notifyListeners('tasksSorted', sortedTasks);
        return sortedTasks;
      }
      
      // Get statistics about tasks
      getTaskStats() {
        const totalCount = this.#tasks.length;
        const completedCount = this.#tasks.filter(task => task.completed).length;
        const pendingCount = totalCount - completedCount;
        
        const highPriorityCount = this.#tasks.filter(task => task.priority === 'high').length;
        const mediumPriorityCount = this.#tasks.filter(task => task.priority === 'medium').length;
        const lowPriorityCount = this.#tasks.filter(task => task.priority === 'low').length;
        
        // Find the last due date using findLast (ES14 feature)
        const lastDueTask = this.#tasks.findLast(task => task.dueDate);
        const nextDueDate = lastDueTask ? lastDueTask.dueDate : null;
        
        return {
          totalCount,
          completedCount,
          pendingCount,
          highPriorityCount,
          mediumPriorityCount,
          lowPriorityCount,
          nextDueDate
        };
      }
      
      // Find a task by ID
      getTaskById(taskId) {
        return this.#tasks.find(task => task.id === taskId);
      }
      
      // Event handlers
      on(event, callback) {
        if (this.#eventListeners[event]) {
          this.#eventListeners[event].push(callback);
        }
        return this; // For chaining
      }
      
      // Notify listeners of events
      #notifyListeners(event, data) {
        if (this.#eventListeners[event]) {
          this.#eventListeners[event].forEach(callback => callback(data));
        }
      }
    }
    
    // Class for UI handling
    class TaskUI {
      // Private fields (ES13 feature)
      #taskManager;
      #currentFilters = { status: 'all', priority: 'all', search: '' };
      #currentSort = { property: 'dueDate', direction: 'asc' };
      
      constructor(taskManager) {
        this.#taskManager = taskManager;
        this.#initializeEventListeners();
        this.#renderTaskList();
        this.#updateTaskStats();
        
        // Listen for task manager events
        taskManager
          .on('taskAdded', () => this.#handleTaskChange())
          .on('taskUpdated', () => this.#handleTaskChange())
          .on('taskDeleted', () => this.#handleTaskChange());
      }
      
      // Initialize all event listeners
      #initializeEventListeners() {
        // Task form submission
        const taskForm = document.getElementById('task-form');
        taskForm.addEventListener('submit', (e) => this.#handleTaskFormSubmit(e));
        
        // Filters
        document.getElementById('status-filter').addEventListener('change', () => this.#applyFilters());
        document.getElementById('priority-filter').addEventListener('change', () => this.#applyFilters());
        
        // Sorting
        document.getElementById('sort-by').addEventListener('change', () => this.#applySorting());
        document.getElementById('sort-btn').addEventListener('click', () => this.#toggleSortDirection());
        document.getElementById('reverse-btn').addEventListener('click', () => this.#reverseTaskList());
        
        // Tab switching
        document.querySelectorAll('.tab').forEach(tab => {
          tab.addEventListener('click', () => {
            const tabId = tab.getAttribute('data-tab');
            this.#switchTab(tabId);
          });
        });
      }
      
      // Handle tab switching
      #switchTab(tabId) {
        // Update active tab
        document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
        document.querySelector(`.tab[data-tab="${tabId}"]`).classList.add('active');
        
        // Update active content
        document.querySelectorAll('.tab-content').forEach(content => {
          content.classList.remove('active');
        });
        document.getElementById(tabId).classList.add('active');
      }
      
      // Handle task form submission
      #handleTaskFormSubmit(e) {
        e.preventDefault();
        
        const taskTitle = document.getElementById('task-title').value.trim();
        const taskDescription = document.getElementById('task-description').value.trim();
        const taskPriority = document.getElementById('task-priority').value;
        const taskDueDate = document.getElementById('task-due-date').value;
        const taskCompleted = document.getElementById('task-completed').checked;
        
        if (!taskTitle) {
          this.#showAlert('Task title is required', 'danger');
          return;
        }
        
        try {
          this.#showLoading();
          
          // Simulate API delay
          setTimeout(() => {
            const newTask = this.#taskManager.addTask({
              title: taskTitle,
              description: taskDescription,
              priority: taskPriority,
              dueDate: taskDueDate,
              completed: taskCompleted
            });
            
            this.#hideLoading();
            this.#showAlert(`Task "${newTask.title}" added successfully`, 'success');
            this.#resetTaskForm();
          }, 500);
        } catch (error) {
          this.#hideLoading();
          this.#showAlert(`Error adding task: ${error.message}`, 'danger');
          console.error('Error cause:', error.cause);
        }
      }
      
      // Reset the task form
      #resetTaskForm() {
        document.getElementById('task-form').reset();
      }
      
      // Handle any task change (add, update, delete)
      #handleTaskChange() {
        this.#renderTaskList();
        this.#updateTaskStats();
      }
      
      // Render the task list based on current filters and sorting
      #renderTaskList() {
        const taskListElement = document.getElementById('task-list');
        taskListElement.innerHTML = '';
        
        let tasks = this.#taskManager.filterTasks(this.#currentFilters);
        tasks = this.#taskManager.sortTasks(
          this.#currentSort.property, 
          this.#currentSort.direction
        );
        
        if (tasks.length === 0) {
          taskListElement.innerHTML = '<li class="task-item">No tasks found</li>';
          return;
        }
        
        tasks.forEach(task => {
          const taskElement = document.createElement('li');
          taskElement.className = 'task-item';
          taskElement.dataset.id = task.id;
          
          taskElement.innerHTML = `
            <div class="task-header">
              <div class="task-title">${task.title}</div>
              <div>
                <span class="badge badge-${task.priority}">${task.priority}</span>
                <span class="badge badge-${task.completed ? 'completed' : 'pending'}">${task.completed ? 'Completed' : 'Pending'}</span>
              </div>
            </div>
            
            <div class="task-description">${task.description || 'No description'}</div>
            
            <div class="task-footer">
              <div>
                ${task.dueDate ? `Due: ${this.#formatDate(task.dueDate)}` : 'No due date'}
              </div>
              
              <div class="task-actions">
                <button class="btn-primary btn-sm toggle-status-btn">${task.completed ? 'Mark Pending' : 'Mark Complete'}</button>
                <button class="btn-danger btn-sm delete-btn">Delete</button>
              </div>
            </div>
          `;
          
          // Add event listeners to buttons
          taskElement.querySelector('.toggle-status-btn').addEventListener('click', () => {
            this.#toggleTaskStatus(task.id);
          });
          
          taskElement.querySelector('.delete-btn').addEventListener('click', () => {
            this.#deleteTask(task.id);
          });
          
          taskListElement.appendChild(taskElement);
        });
      }
      
      // Toggle task completed status
      #toggleTaskStatus(taskId) {
        try {
          this.#showLoading();
          
          const task = this.#taskManager.getTaskById(taskId);
          
          // Simulate API delay
          setTimeout(() => {
            this.#taskManager.updateTask(taskId, {
              completed: !task.completed
            });
            
            this.#hideLoading();
            this.#showAlert(`Task status updated`, 'success');
          }, 300);
        } catch (error) {
          this.#hideLoading();
          this.#showAlert(`Error updating task: ${error.message}`, 'danger');
          console.error('Error cause:', error.cause);
        }
      }
      
      // Delete a task
      #deleteTask(taskId) {
        try {
          if (!confirm('Are you sure you want to delete this task?')) {
            return;
          }
          
          this.#showLoading();
          
          // Simulate API delay
          setTimeout(() => {
            const task = this.#taskManager.deleteTask(taskId);
            
            this.#hideLoading();
            this.#showAlert(`Task "${task.title}" deleted`, 'success');
          }, 300);
        } catch (error) {
          this.#hideLoading();
          this.#showAlert(`Error deleting task: ${error.message}`, 'danger');
          console.error('Error cause:', error.cause);
        }
      }
      
      // Apply filters to the task list
      #applyFilters() {
        const statusFilter = document.getElementById('status-filter').value;
        const priorityFilter = document.getElementById('priority-filter').value;
        
        this.#currentFilters = {
          ...this.#currentFilters,
          status: statusFilter,
          priority: priorityFilter
        };
        
        this.#renderTaskList();
      }
      
      // Apply sorting to the task list
      #applySorting() {
        const sortProperty = document.getElementById('sort-by').value;
        
        this.#currentSort = {
          ...this.#currentSort,
          property: sortProperty
        };
        
        this.#renderTaskList();
        this.#updateSortButtonText();
      }
      
      // Toggle sort direction
      #toggleSortDirection() {
        this.#currentSort.direction = this.#currentSort.direction === 'asc' ? 'desc' : 'asc';
        this.#renderTaskList();
        this.#updateSortButtonText();
      }
      
      // Reverse the task list
      #reverseTaskList() {
        const taskListElement = document.getElementById('task-list');
        const taskItems = Array.from(taskListElement.children);
        
        // Use toReversed (ES14 feature) to create a new reversed array
        const reversedItems = taskItems.toReversed();
        
        taskListElement.innerHTML = '';
        reversedItems.forEach(item => taskListElement.appendChild(item));
        
        // Toggle the direction
        this.#currentSort.direction = this.#currentSort.direction === 'asc' ? 'desc' : 'asc';
        this.#updateSortButtonText();
      }
      
      // Update the sort button text
      #updateSortButtonText() {
        const sortBtn = document.getElementById('sort-btn');
        sortBtn.textContent = `Sort ${this.#currentSort.direction === 'asc' ? 'Ascending' : 'Descending'}`;
      }
      
      // Update task statistics
      #updateTaskStats() {
        const stats = this.#taskManager.getTaskStats();
        
        document.getElementById('total-count').textContent = stats.totalCount;
        document.getElementById('completed-count').textContent = stats.completedCount;
        document.getElementById('pending-count').textContent = stats.pendingCount;
        document.getElementById('high-priority-count').textContent = stats.highPriorityCount;
      }
      
      // Helper to format date
      #formatDate(dateString) {
        const date = new Date(dateString);
        return date.toLocaleDateString();
      }
      
      // Show loading overlay
      #showLoading() {
        document.getElementById('loading-overlay').classList.remove('hidden');
      }
      
      // Hide loading overlay
      #hideLoading() {
        document.getElementById('loading-overlay').classList.add('hidden');
      }
      
      // Show alert message
      #showAlert(message, type) {
        const alertContainer = document.getElementById('alert-container');
        
        const alertElement = document.createElement('div');
        alertElement.className = `alert alert-${type}`;
        alertElement.textContent = message;
        
        alertContainer.appendChild(alertElement);
        
        // Remove alert after 5 seconds
        setTimeout(() => {
          alertElement.remove();
        }, 5000);
      }
    }
    
    // Initialize the application
    try {
      // Simulate loading data from an API with top-level await (in a real app)
      // (We're wrapping this in a function since we can't use top-level await in a script tag)
      const loadApp = async () => {
        try {
          document.getElementById('loading-overlay').classList.remove('hidden');
          
          // Simulate API delay
          await new Promise(resolve => setTimeout(resolve, 1000));
          
          const taskManager = new TaskManager();
          const taskUI = new TaskUI(taskManager);
          
          document.getElementById('loading-overlay').classList.add('hidden');
        } catch (error) {
          document.getElementById('loading-overlay').classList.add('hidden');
          
          const alertContainer = document.getElementById('alert-container');
          alertContainer.innerHTML = `
            <div class="alert alert-danger">
              Failed to initialize application: ${error.message}
              ${error.cause ? `<div>(Cause: ${error.cause})</div>` : ''}
            </div>
          `;
          console.error('Error initializing app:', error);
        }
      };
      
      loadApp();
    } catch (error) {
      console.error('Application initialization error:', error);
    }

Adoption Considerations#

The snippets in this post ran in Node 22. These features are old enough that current browsers and maintained Node releases have them, but we did not re-check exact version numbers in October 2026. Look up each one on MDN's compatibility table before you ship to an older environment.

For production applications:

  1. Check the compatibility table for the browsers and Node version you target

  2. Polyfill a missing built-in such as toSorted(), and transpile only syntax such as #private fields

  3. Adopt gradually: use a new feature in non-critical code first

Conclusion: The JavaScript Evolution Continues#

ES13 and ES14 gave us private class fields, a way to wait at the top of a module, and array methods that return copies. Each one fixes a specific habit: reaching into a class, or sorting the original list by accident.

The next article moves to ES2024. It starts with a job we used to write a reduce for: grouping a list.

Comments require JavaScript.