JavaScript continues to evolve with ES2024 (ES15) bringing exciting new features that enhance developer productivity and code readability. As a full-stack developer, staying updated with the latest JavaScript features is crucial for writing modern, efficient code. Let's explore the most important ES2024 features with practical examples.
1. Array.prototype.toReversed()
A new method that returns a new array with elements in reverse order, without mutating the original array:
const numbers = [1, 2, 3, 4, 5];
// ❌ Old way (mutates original array)
const reversed1 = numbers.reverse();
console.log(numbers); // [5, 4, 3, 2, 1] - original is mutated!
// ✅ ES2024 way (non-mutating)
const numbers2 = [1, 2, 3, 4, 5];
const reversed2 = numbers2.toReversed();
console.log(numbers2); // [1, 2, 3, 4, 5] - original unchanged
console.log(reversed2); // [5, 4, 3, 2, 1] - new array
// Practical example: Reversing user comments for display
const comments = [
{ id: 1, text: "First comment", timestamp: "2024-01-01" },
{ id: 2, text: "Second comment", timestamp: "2024-01-02" },
{ id: 3, text: "Latest comment", timestamp: "2024-01-03" }
];
const latestFirst = comments.toReversed();
// Original comments array remains unchanged
2. Array.prototype.toSorted()
Returns a new sorted array without modifying the original:
const fruits = ['banana', 'apple', 'cherry', 'date'];
// ✅ ES2024 non-mutating sort
const sortedFruits = fruits.toSorted();
console.log(fruits); // ['banana', 'apple', 'cherry', 'date'] - unchanged
console.log(sortedFruits); // ['apple', 'banana', 'cherry', 'date']
// With custom comparator
const products = [
{ name: 'Laptop', price: 999 },
{ name: 'Phone', price: 599 },
{ name: 'Tablet', price: 299 }
];
const sortedByPrice = products.toSorted((a, b) => a.price - b.price);
// Original products array remains unchanged
// Practical use case: Sorting search results
function sortSearchResults(results, sortBy) {
switch (sortBy) {
case 'price-low':
return results.toSorted((a, b) => a.price - b.price);
case 'price-high':
return results.toSorted((a, b) => b.price - a.price);
case 'name':
return results.toSorted((a, b) => a.name.localeCompare(b.name));
default:
return results;
}
}
3. Array.prototype.toSpliced()
Non-mutating version of splice that returns a new array:
const items = ['a', 'b', 'c', 'd', 'e'];
// ✅ ES2024 non-mutating splice
const newItems = items.toSpliced(2, 1, 'X', 'Y');
console.log(items); // ['a', 'b', 'c', 'd', 'e'] - unchanged
console.log(newItems); // ['a', 'b', 'X', 'Y', 'd', 'e']
// Practical example: Managing a todo list
const todos = [
{ id: 1, text: 'Buy groceries', completed: false },
{ id: 2, text: 'Walk the dog', completed: true },
{ id: 3, text: 'Write blog post', completed: false }
];
// Insert new todo at specific position
const updatedTodos = todos.toSpliced(1, 0, {
id: 4,
text: 'Call dentist',
completed: false
});
// Remove completed todos
const activeTodos = todos.toSpliced(1, 1); // Remove second item
4. Array.prototype.with()
Returns a new array with a single element replaced:
const colors = ['red', 'green', 'blue'];
// ✅ ES2024 non-mutating element replacement
const newColors = colors.with(1, 'yellow');
console.log(colors); // ['red', 'green', 'blue'] - unchanged
console.log(newColors); // ['red', 'yellow', 'blue']
// Practical example: Updating user preferences
const userPreferences = ['dark-mode', 'notifications-on', 'auto-save'];
function updatePreference(preferences, index, newValue) {
return preferences.with(index, newValue);
}
const updatedPrefs = updatePreference(userPreferences, 0, 'light-mode');
// Original preferences unchanged, new array returned
5. Object.groupBy()
Groups array elements by a specified key:
const users = [
{ name: 'Alice', department: 'Engineering', age: 28 },
{ name: 'Bob', department: 'Marketing', age: 32 },
{ name: 'Charlie', department: 'Engineering', age: 25 },
{ name: 'Diana', department: 'Sales', age: 30 }
];
// ✅ ES2024 Object.groupBy
const byDepartment = Object.groupBy(users, user => user.department);
console.log(byDepartment);
/*
{
Engineering: [
{ name: 'Alice', department: 'Engineering', age: 28 },
{ name: 'Charlie', department: 'Engineering', age: 25 }
],
Marketing: [
{ name: 'Bob', department: 'Marketing', age: 32 }
],
Sales: [
{ name: 'Diana', department: 'Sales', age: 30 }
]
}
*/
// Group by age ranges
const byAgeRange = Object.groupBy(users, user => {
if (user.age < 30) return 'Under 30';
if (user.age < 35) return '30-35';
return 'Over 35';
});
// Practical use case: Analytics dashboard
const salesData = [
{ product: 'Laptop', category: 'Electronics', revenue: 1200, quarter: 'Q1' },
{ product: 'Book', category: 'Education', revenue: 25, quarter: 'Q1' },
{ product: 'Phone', category: 'Electronics', revenue: 800, quarter: 'Q2' }
];
const revenueByCategory = Object.groupBy(salesData, item => item.category);
6. Map.groupBy()
Similar to Object.groupBy but returns a Map:
const products = [
{ name: 'Laptop', price: 999, inStock: true },
{ name: 'Phone', price: 599, inStock: false },
{ name: 'Tablet', price: 299, inStock: true },
{ name: 'Watch', price: 199, inStock: false }
];
// ✅ ES2024 Map.groupBy
const stockStatus = Map.groupBy(products, product => product.inStock);
console.log(stockStatus.get(true)); // In-stock products
console.log(stockStatus.get(false)); // Out-of-stock products
// Benefits of Map over Object:
// - Preserves insertion order
// - Can use any type as key
// - Better performance for frequent additions/deletions
const priceRanges = Map.groupBy(products, product => {
if (product.price < 300) return 'Budget';
if (product.price < 700) return 'Mid-range';
return 'Premium';
});
7. Promise.withResolvers()
A cleaner way to create promises with external resolve/reject functions:
// ❌ Old way
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// ✅ ES2024 way
const { promise, resolve, reject } = Promise.withResolvers();
// Practical example: Creating a timeout utility
function createTimeout(ms) {
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => resolve(), ms);
return promise;
}
// Usage
await createTimeout(1000); // Wait 1 second
// Advanced example: Event-based promise
class EventEmitter {
constructor() {
this.listeners = new Map();
}
once(event) {
const { promise, resolve } = Promise.withResolvers();
const listener = (data) => {
this.off(event, listener);
resolve(data);
};
this.on(event, listener);
return promise;
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
off(event, callback) {
const callbacks = this.listeners.get(event);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index > -1) callbacks.splice(index, 1);
}
}
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
}
}
8. Atomics.waitAsync()
Non-blocking version of Atomics.wait for SharedArrayBuffer:
// For advanced use cases with Web Workers and SharedArrayBuffer
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);
// ✅ ES2024 non-blocking wait
const result = Atomics.waitAsync(sharedArray, 0, 0, 1000);
if (result.async) {
result.value.then(outcome => {
console.log('Wait completed:', outcome); // 'ok', 'not-equal', or 'timed-out'
});
} else {
console.log('Wait completed immediately:', result.value);
}
// Practical use case: Coordinating Web Workers
// Main thread
const worker = new Worker('worker.js');
const coordination = new SharedArrayBuffer(4);
const coordinationArray = new Int32Array(coordination);
worker.postMessage({ coordination });
// Wait for worker to signal completion
const waitResult = Atomics.waitAsync(coordinationArray, 0, 0);
if (waitResult.async) {
waitResult.value.then(() => {
console.log('Worker completed its task');
});
}
9. String.prototype.isWellFormed() and toWellFormed()
Methods to check and fix Unicode string encoding:
// Checking if string is well-formed
const validString = 'Hello, World! 👋';
const invalidString = 'Hello\uD800World'; // Lone surrogate
console.log(validString.isWellFormed()); // true
console.log(invalidString.isWellFormed()); // false
// Converting to well-formed string
console.log(invalidString.toWellFormed()); // 'Hello�World' (replacement character)
// Practical use case: Sanitizing user input
function sanitizeUserInput(input) {
if (!input.isWellFormed()) {
return input.toWellFormed();
}
return input;
}
// Useful for handling text from various sources
const userTexts = [
'Normal text',
'Emoji text 🎉',
'Invalid\uD800text',
'More\uDC00invalid'
];
const sanitized = userTexts.map(text => sanitizeUserInput(text));
console.log(sanitized);
10. RegExp v Flag
Enhanced Unicode support in regular expressions:
// ✅ ES2024 RegExp v flag for better Unicode support
const regex = /[\p{Script=Latin}&&\p{Letter}]/v;
console.log(regex.test('a')); // true
console.log(regex.test('α')); // false (Greek, not Latin)
// Set operations in character classes
const emojiRegex = /[\p{Emoji}--\p{Emoji_Modifier}]/v;
console.log(emojiRegex.test('👋')); // true
console.log(emojiRegex.test('🏽')); // false (modifier)
// Practical example: Input validation
function validateUsername(username) {
// Allow Latin letters, numbers, and specific symbols, but not emojis
const validPattern = /^[\p{Script=Latin}\p{Number}_-]+$/v;
return validPattern.test(username);
}
console.log(validateUsername('john_doe123')); // true
console.log(validateUsername('john👋')); // false
// Email validation with better Unicode support
const emailRegex = /^[\p{Letter}\p{Number}._-]+@[\p{Letter}\p{Number}.-]+\.[\p{Letter}]{2,}$/v;
Browser Support and Usage Tips
While ES2024 features are cutting-edge, here are some tips for using them:
Feature Detection
// Check for feature support before using
function supportsToReversed() {
return Array.prototype.toReversed !== undefined;
}
// Polyfill or fallback
function safeToReversed(array) {
if (supportsToReversed()) {
return array.toReversed();
}
return [...array].reverse(); // Fallback
}
// Use with Babel for broader support
// npm install @babel/preset-env
// Configure to target specific browsers
Migration Strategy
- Start with non-critical features: Begin using new array methods in utility functions
- Use TypeScript: Get better IDE support and type checking
- Configure Babel: Ensure compatibility with older browsers
- Test thoroughly: Verify behavior across different environments
- Update gradually: Replace old patterns incrementally
Conclusion
ES2024 brings valuable improvements to JavaScript, focusing on array immutability, better grouping capabilities, and enhanced Unicode support. These features promote functional programming patterns and make code more predictable and maintainable.
As these features gain broader browser support, they'll become essential tools in every JavaScript developer's toolkit. Start experimenting with them in your projects today, and you'll be ahead of the curve when they become mainstream.