// 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.