Engineering

Engineering

Come for answers, stay for best practices. All we're missing is you.

 View Only

Mastering Modern JavaScript: ECMAScript 2024-2025 Features and Best Practices

By Krunal Vachheta posted Fri November 28, 2025 08:47 AM

  
Introduction:
JavaScript continues to evolve rapidly as one of the world's most widely used programming languages. This document explores the latest features introduced in ECMAScript 2024 (ES15) and ECMAScript 2025 (ES16), along with emerging trends in the JavaScript ecosystem. These advancements aim to make JavaScript more powerful, expressive, and developer-friendly while maintaining backward compatibility.

ECMAScript 2024 (ES15) - Officially Approved Features

1. Array Grouping Methods

The `Object.groupBy()` and `Map.groupBy()` methods provide a native way to group array elements.
// Grouping objects by a property
const products = [
  { name: 'Laptop', category: 'Electronics', price: 999 },
  { name: 'Phone', category: 'Electronics', price: 699 },
  { name: 'Desk', category: 'Furniture', price: 299 },
  { name: 'Chair', category: 'Furniture', price: 199 }
];

// Using Object.groupBy
const groupedByCategory = Object.groupBy(products, product => product.category);
console.log(groupedByCategory);
// {
//   Electronics: [{ name: 'Laptop', ... }, { name: 'Phone', ... }],
//   Furniture: [{ name: 'Desk', ... }, { name: 'Chair', ... }]
// }

// Using Map.groupBy for non-string keys
const groupedByPriceRange = Map.groupBy(products, product => {
  if (product.price < 300) return 'budget';
  if (product.price < 700) return 'mid-range';
  return 'premium';
});



2. Promise.withResolvers()

This utility function simplifies promise creation by returning an object with the promise and its resolver functions.

// Traditional approach
let resolveFunc, rejectFunc;
const promise = new Promise((resolve, reject) => {
  resolveFunc = resolve;
  rejectFunc = reject;
});

// New approach with Promise.withResolvers()
const { promise, resolve, reject } = Promise.withResolvers();

// Practical usage example
function createAsyncQueue() {
  const queue = [];
  
  return {
    enqueue(task) {
      const { promise, resolve, reject } = Promise.withResolvers();
      queue.push({ task, resolve, reject });
      return promise;
    },
    async processNext() {
      if (queue.length === 0) return;
      const { task, resolve, reject } = queue.shift();
      try {
        const result = await task();
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }
  };
}


3. ArrayBuffer Transfer and Resizing
New methods for efficiently transferring and resizing ArrayBuffers.
// Transferring ArrayBuffer ownership
const buffer1 = new ArrayBuffer(8);
const view1 = new Uint8Array(buffer1);
view1[0] = 42;

// Transfer ownership to a new buffer
const buffer2 = buffer1.transfer();
console.log(buffer1.byteLength); // 0 (detached)
console.log(buffer2.byteLength); // 8

// Resizable ArrayBuffer
const resizableBuffer = new ArrayBuffer(8, { maxByteLength: 16 });
console.log(resizableBuffer.byteLength); // 8
console.log(resizableBuffer.maxByteLength); // 16

resizableBuffer.resize(12);
console.log(resizableBuffer.byteLength); // 12


5. Well-Formed Unicode Strings
New methods to ensure strings are well-formed Unicode.
// Check if string is well-formed
const validString = "Hello 👋";
const malformedString = "Hello \uD800"; // Lone surrogate

console.log(validString.isWellFormed()); // true
console.log(malformedString.isWellFormed()); // false

// Convert to well-formed string
console.log(malformedString.toWellFormed()); // "Hello �" (replacement character)

// Practical usage
function safeStringOperation(str) {
  if (!str.isWellFormed()) {
    str = str.toWellFormed();
  }
  return str.toUpperCase();
}


## ECMAScript 2023 (ES14) - Recently Standardised Features

1. Array Methods: `toSorted()`, `toReversed()`, `toSpliced()`, and `with()`

Non-destructive array methods that return new arrays without modifying the original.
const numbers = [3, 1, 4, 1, 5];

// toSorted - returns sorted copy
const sorted = numbers.toSorted();
console.log(numbers); // [3, 1, 4, 1, 5] (unchanged)
console.log(sorted);  // [1, 1, 3, 4, 5]

// toReversed - returns reversed copy
const reversed = numbers.toReversed();
console.log(reversed); // [5, 1, 4, 1, 3]

// toSpliced - returns spliced copy
const spliced = numbers.toSpliced(1, 2, 7, 8);
console.log(spliced); // [3, 7, 8, 1, 5]

// with - returns copy with element replaced
const colors = ["red", "green", "blue"];
const newColors = colors.with(1, "yellow");
console.log(newColors); // ["red", "yellow", "blue"]


2. Array `findLast()` and `findLastIndex()`

Find elements from the end of an array.
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];

// Find last element matching condition
const lastEven = numbers.findLast(n => n % 2 === 0);
console.log(lastEven); // 2

// Find last index matching condition
const lastEvenIndex = numbers.findLastIndex(n => n % 2 === 0);
console.log(lastEvenIndex); // 7

// Practical example
const logs = [
  { level: 'info', message: 'App started' },
  { level: 'error', message: 'Connection failed' },
  { level: 'info', message: 'Retrying...' },
  { level: 'error', message: 'Timeout' }
];

const lastError = logs.findLast(log => log.level === 'error');
console.log(lastError); // { level: 'error', message: 'Timeout' }


3. Hashbang Grammar

Support for hashbang/shebang comments in JavaScript files.
#!/usr/bin/env node
// This is now valid JavaScript syntax

console.log('This script can be executed directly');


4. Symbols as WeakMap Keys

WeakMaps can now use Symbol values as keys.
const weakMap = new WeakMap();
const symbolKey = Symbol('myKey');

// Symbols can now be used as WeakMap keys
weakMap.set(symbolKey, { data: 'value' });
console.log(weakMap.get(symbolKey)); // { data: 'value' }

// Practical usage for private data
const privateData = new WeakMap();

class User {
  constructor(name) {
    const key = Symbol('userData');
    privateData.set(key, { name, createdAt: Date.now() });
    this._key = key;
  }
  
  getName() {
    return privateData.get(this._key).name;
  }
}



## JavaScript Ecosystem Trends (2024-2025)

1. Modern Build Tools and Bundlers
The JavaScript tooling landscape has matured significantly.

Vite - Lightning-fast development server with optimised builds:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    target: 'esnext',
    minify: 'esbuild',
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash-es', 'date-fns']
        }
      }
    }
  },
  server: {
    hmr: true,
    port: 3000
  }
});


esbuild - Extremely fast JavaScript bundler:
// build.js
import * as esbuild from 'esbuild';

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  minify: true,
  sourcemap: true,
  target: ['es2020'],
  outfile: 'dist/bundle.js',
});


2. Runtime Performance Improvements

Node.js continues to improve with V8 optimizations:
// Using native fetch in Node.js (available since v18)
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Native test runner (available since v18)
import { test, describe } from 'node:test';
import assert from 'node:assert';

describe('Math operations', () => {
  test('addition works', () => {
    assert.strictEqual(2 + 2, 4);
  });
});


Bun - High-performance JavaScript runtime:
// Fast file operations with Bun
const file = Bun.file('data.json');
const data = await file.json();

// Built-in SQLite support
import { Database } from 'bun:sqlite';
const db = new Database('mydb.sqlite');
const query = db.query('SELECT * FROM users WHERE id = ?');
const user = query.get(1);


3. TypeScript Adoption

TypeScript has become the standard for large-scale JavaScript applications:
/ Modern TypeScript patterns (2024-2025)

// Using satisfies operator for type safety
const config = {
  endpoint: 'https://api.example.com',
  timeout: 5000,
  retries: 3
} satisfies Record<string, string | number>;

// Template literal types
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `/api/${string}`;
type APIRoute = `${HTTPMethod} ${Endpoint}`;

const route: APIRoute = 'GET /api/users';

// Const type parameters (TypeScript 5.0+)
function makeArray<const T>(items: T[]): readonly T[] {
  return items;
}

const arr = makeArray([1, 2, 3]); // Type: readonly [1, 2, 3]



4. Modern Testing Frameworks

Testing has become more streamlined with modern tools:

// Vitest - Fast unit testing
import { describe, it, expect } from 'vitest';

describe('User Service', () => {
  it('should create a user', async () => {
    const user = await createUser({ name: 'John' });
    expect(user.name).toBe('John');
    expect(user.id).toBeDefined();
  });
});

// Playwright - Modern E2E testing
import { test, expect } from '@playwright/test';

test('user can login', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.fill('[name="email"]', 'user@example.com');
  await page.fill('[name="password"]', 'password123');
  await page.click('button[type="submit"]');
  await expect(page).toHaveURL('https://example.com/dashboard');
});


## Best Practices for Modern JavaScript (2024-2025)

1. Use Native Features When Available
// Prefer native methods over libraries
// Good: Using native groupBy
const grouped = Object.groupBy(items, item => item.category);

// Good: Using native Promise.withResolvers
const { promise, resolve } = Promise.withResolvers();

// Good: Using native fetch
const data = await fetch(url).then(r => r.json());


2. Leverage TypeScript for Type Safety
// Define clear interfaces
interface User {
  id: number;
  name: string;
  email: string;
}

// Use type guards
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    'email' in obj
  );
}



3. Optimize for Performance

// Use appropriate data structures
const userMap = new Map(); // O(1) lookup
const uniqueIds = new Set(); // O(1) membership test

// Avoid unnecessary array copies
// Bad: Multiple array operations
const result = arr.filter(x => x > 0).map(x => x * 2).slice(0, 10);

// Good: Single pass when possible
const result = arr.reduce((acc, x) => {
  if (x > 0 && acc.length < 10) {
    acc.push(x * 2);
  }
  return acc;
}, []);


4. Handle Errors Properly
// Use try-catch for async operations
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error; // Re-throw or handle appropriately
  }
}

// Use optional chaining and nullish coalescing
const userName = user?.profile?.name ?? 'Anonymous';


5. Write Maintainable Code
// Use descriptive names
// Bad
const d = new Date();
const u = users.filter(u => u.a);

// Good
const currentDate = new Date();
const activeUsers = users.filter(user => user.isActive);

// Keep functions small and focused
function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

function validateUser(user) {
  return (
    user.name &&
    user.name.length > 0 &&
    validateEmail(user.email)
  );
}


## Browser and Runtime Support

### Feature Support Matrix (2024-2025)

| Feature | Chrome | Firefox | Safari | Node.js | Deno | Bun |
|---------|--------|---------|--------|---------|------|-----|
| Array Grouping | 117+ | 119+ | 17+ | 21+ | 1.37+ | 1.0+ |
| Promise.withResolvers | 119+ | 121+ | 17.4+ | 22+ | 1.38+ | 1.0+ |
| Array toSorted/toReversed | 110+ | 115+ | 16+ | 20+ | 1.31+ | 0.6+ |
| findLast/findLastIndex | 97+ | 104+ | 15.4+ | 18+ | 1.25+ | 0.1+ |


## Conclusion

JavaScript in 2024-2025 offers a robust set of officially approved features that enhance developer productivity and code quality. The ecosystem has matured with excellent tooling, runtime performance improvements, and widespread TypeScript adoption.

Key takeaways:
- **Use native features** when available instead of external libraries
- **Adopt TypeScript** for better type safety and developer experience
- **Leverage modern build tools** like Vite and esbuild for faster development
- **Follow best practices** for maintainable and performant code
- **Stay updated** with browser and runtime support for new features

By embracing these officially supported features and following modern best practices, developers can build more robust, efficient, and maintainable JavaScript applications.



0 comments
22 views

Permalink