The Top 10 Changes in ECMAScript 2024

ECMAScript 2024 brings some exciting new features to help developers. Here are 10 of the most notable changes along with examples and use cases.

1. Top-level await

Top-level await allows awaiting promises at the top level of modules. This simplifies asynchronous logic in modules.

// index.js

let data = await fetch('/data.json');

console.log(data);

Previously we had to wrap top-level code in an async IIFE. Top-level await makes code cleaner.

2. Class fields and private methods

Class fields let us declare fields directly in classes without getter/setter syntax. Private fields and methods use a # syntax.

class Customer {
  #name

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

  #getInfo() {
    return `Name is ${this.#name}`; 
  }

  greet() {
    console.log(this.#getInfo());
  }
}

This is a more straightforward way to define classes compared to ES6 syntax.

3. Pattern matching with matches

The matches method provides cleaner control flow than if/else chains by pattern matching input values.

switch(checkType(value)) {
  case {kind: "number", number: 42}:
    return "The Answer";

  case {kind: "string"}: 
    return value;
}

It improves readability of complex conditional logic.

4. Import assertions

Import assertions allow validating imported values, like ensuring an import is a function.

import { parse } from 'path/to/parser.js';

assert.function(parse);

This provides self-documenting code and catches errors early.

5. Weak references

Weak references allow holding onto objects without preventing garbage collection.

const wr = new WeakRef(node);

node = null;
// GC can collect node

console.log(wr.deref()); 

Useful for caches, proxies, and other scenarios where strong references aren't needed.

6. Private methods and private fields

Class fields with public and private accessors and methods. Private methods start with # syntax.

class Point {
  #x = 0;

  constructor(x) {
    this.#x = x; 
  }

  #timesTwo() {
    return this.#x * 2;
  }

  publicTwice() {
    return this.#timesTwo();
  }
}

Encapsulates details and avoids inheritance issues.

7. Top-level await inside modules

// math.js

export const e = await loadNumericConstant('./e.json');

Modules can now have asynchronous top-level code, avoiding workers or IIFEs.

8. WeakMap and WeakSet

Collections that allow holding onto objects without preventing garbage collection.

const cache = new WeakMap();

function getKey(obj) {
  return cache.get(obj) || newName(obj);
}

Useful for caching, identifiers and other non-owning references.

9. Legacy class fields support

Class fields can be initialized using getter/setter syntax:

class Point {
  get x() { return 0; }
  set x(v) { this.#x = v; }

  #x;
}

Supports gradual migration from ES5 style classes.

10. Export * as namespace syntax

export * as math from './math.js';

Concise way to re-export all exports under a single name.