The Top 10 Changes in ECMAScript 2024

ECMAScript 2024 (also known as ES2024) introduced some powerful new capabilities to help developers build robust, performant applications. Here are 10 of the most notable changes along with example usage and use cases.

1. Top-level await

Top-level await allows developers to use the await keyword outside of an async function. This simplifies asynchronous logic by allowing promises to be awaited at the top level in modules.

For example:

// lib.js
const data = await fetch('/data.json');
export function processData(data) {
  // ...
}
// app.js
import { processData } from './lib.js';

async function main() {
  processData(await data);
}

Previously you would need to wrap the entire module in an async function to use await. Top-level await reduces nesting and improves readability.

2. Class fields

Class fields allow defining fields directly in a class without needing to define getter/setter properties. For example:

class Person {
  // Class field 
  name = '';

  constructor(name) {
    this.name = name;
  }
}

This simplifies class definition and avoids repetitive boilerplate. Fields can also be public, private, or static.

3. Static blocks

Static blocks provide a way to define shared initialization logic for a class:

class DataStore {
  static cache = {};

  static {
    // initialization code here
  }
}

This avoids duplicated initialization code and allows separating initialization from constructor logic.

4. Private fields and methods

Private class fields and methods allow encapsulating internals and preventing external code from directly accessing or modifying private state.

class Counter {
  #count = 0;

  increment() {
    this.#count++; 
  }

  get count() {
    return this.#count;
  }
}

Private methods also enable refactoring by decoupling internals from the public class interface.

5. Match indices in regular expressions

The /d flag in regular expressions captures substring match indices, useful for extracting matches by index.

/hello(\d)/d.exec('hello world') 
// returns ["hello1", "1"]

This avoids needing a second call to map or replace matches.

6. BigInt in expressions

BigInt can now be freely used in all expressions without needing to.toString() calls.

let two = 2n;
let four = two + two;

This improves ergonomics for working with very large integers.

7. Channel Operator (#)

The channel operator (#) extracts captures from tagged template literals, avoiding repetition.

let name = 'Bob';
let output = html`Hello ${name}!`; 
// "Hello Bob!"

let name = 'Jane';
output = html`Hello #name!`;
// "Hello Jane!" 

This simplifies building templates with dynamic property names.

8. GlobalThis

GlobalThis provides a standard reference to the global object, avoiding browser inconsistencies with referring to window.

console.log(GlobalThis);

This is useful for polyfilling and cross-realm sharing without relying on the outdated this keyword.

9. Import assertions

Import assertions allow validating imports prior to execution for type checking and throw early errors.

import type { User } from './users.js';

This enables catching errors earlier in large codebases and improving type safety.

10. WeakRefs and FinalizationRegistry

WeakRef allows creating references to objects that don't inhibit garbage collection. FinalizationRegistry provides callbacks on object finalization.

const wr = new WeakRef(obj);

const fr = new FinalizationRegistry(unregisterCallback);
fr.register(obj, 'id');

These enable new memory management strategies like caching and cleanup on object destruction.

In summary, ES2024 includes powerful new capabilities for asynchrony, classes, modules, and memory management. Developers building complex apps will appreciate improvements in these core areas.