JavaScript 2023 (ES14)
Array methods, immutable operations, and enhanced functionality
đ¯ What's New in JavaScript 2023?
JavaScript 2023 (ES14) introduced powerful array methods like toSorted(), toReversed(), and with(). These methods provide immutable alternatives to existing operations, making code safer and more predictable.
// New in 2023: Immutable array operations
const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.toSorted(); // Original array unchanged!
console.log(numbers); // [3, 1, 4, 1, 5]
console.log(sorted); // [1, 1, 3, 4, 5]
Key Features of ES14
toSorted()
Sort arrays without mutation
const arr = [3, 1, 4];
const sorted = arr.toSorted();
// arr is unchanged!
toReversed()
Reverse arrays immutably
const arr = [1, 2, 3];
const reversed = arr.toReversed();
// [3, 2, 1]
with()
Change array elements immutably
const arr = [1, 2, 3];
const updated = arr.with(1, 'X');
// [1, 'X', 3]
findLast()
Find elements from the end
const arr = [1, 2, 3, 2, 1];
const last = arr.findLast(x => x === 2);
// Returns 2 (last occurrence)
đš Immutable Array Methods
New methods that don't modify the original array:
const originalArray = [5, 2, 8, 1, 9];
// toSorted() - immutable sort
const sorted = originalArray.toSorted();
console.log('Original:', originalArray); // [5, 2, 8, 1, 9]
console.log('Sorted:', sorted); // [1, 2, 5, 8, 9]
// toReversed() - immutable reverse
const reversed = originalArray.toReversed();
console.log('Reversed:', reversed); // [9, 1, 8, 2, 5]
// toSpliced() - immutable splice
const spliced = originalArray.toSpliced(2, 1, 'X', 'Y');
console.log('Spliced:', spliced); // [5, 2, 'X', 'Y', 1, 9]
// with() - immutable element replacement
const withReplacement = originalArray.with(0, 'FIRST');
console.log('With replacement:', withReplacement); // ['FIRST', 2, 8, 1, 9]
// Chain operations safely
const result = originalArray
.toSorted()
.toReversed()
.with(0, 'MAX');
console.log('Chained result:', result); // ['MAX', 8, 5, 2, 1]
console.log('Original still intact:', originalArray); // [5, 2, 8, 1, 9]
Output:
Original: [5, 2, 8, 1, 9] Sorted: [1, 2, 5, 8, 9] Reversed: [9, 1, 8, 2, 5] Spliced: [5, 2, 'X', 'Y', 1, 9] With replacement: ['FIRST', 2, 8, 1, 9] Chained result: ['MAX', 8, 5, 2, 1] Original still intact: [5, 2, 8, 1, 9]
đš findLast() and findLastIndex()
Find elements starting from the end of the array:
const users = [
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
{ id: 3, name: 'Charlie', active: true },
{ id: 4, name: 'David', active: true },
{ id: 5, name: 'Eve', active: false }
];
// findLast() - find last matching element
const lastActiveUser = users.findLast(user => user.active);
console.log('Last active user:', lastActiveUser.name); // 'David'
// findLastIndex() - find index of last matching element
const lastActiveIndex = users.findLastIndex(user => user.active);
console.log('Last active user index:', lastActiveIndex); // 3
// Practical example: finding the most recent log entry
const logs = [
{ timestamp: '2023-01-01', level: 'info', message: 'App started' },
{ timestamp: '2023-01-02', level: 'error', message: 'Database error' },
{ timestamp: '2023-01-03', level: 'info', message: 'User login' },
{ timestamp: '2023-01-04', level: 'error', message: 'API timeout' },
{ timestamp: '2023-01-05', level: 'info', message: 'Backup completed' }
];
const lastError = logs.findLast(log => log.level === 'error');
console.log('Most recent error:', lastError.message); // 'API timeout'
// Compare with regular find() (finds first occurrence)
const firstError = logs.find(log => log.level === 'error');
console.log('First error:', firstError.message); // 'Database error'
Output:
Last active user: David Last active user index: 3 Most recent error: API timeout First error: Database error
đš Practical Examples
Real-world use cases for ES14 features:
// Shopping cart management with immutable operations
class ShoppingCart {
constructor() {
this.items = [];
}
// Add item without mutating original array
addItem(item) {
this.items = [...this.items, item];
return this;
}
// Remove item by index immutably
removeItem(index) {
this.items = this.items.toSpliced(index, 1);
return this;
}
// Update quantity immutably
updateQuantity(index, quantity) {
const updatedItem = { ...this.items[index], quantity };
this.items = this.items.with(index, updatedItem);
return this;
}
// Get sorted items by price (without affecting original order)
getSortedByPrice() {
return this.items.toSorted((a, b) => a.price - b.price);
}
// Get most expensive item
getMostExpensive() {
return this.items.findLast(item =>
item.price === Math.max(...this.items.map(i => i.price))
);
}
}
// Usage
const cart = new ShoppingCart();
cart.addItem({ name: 'Laptop', price: 999, quantity: 1 })
.addItem({ name: 'Mouse', price: 25, quantity: 2 })
.addItem({ name: 'Keyboard', price: 75, quantity: 1 });
console.log('Original order:', cart.items.map(i => i.name));
console.log('Sorted by price:', cart.getSortedByPrice().map(i => i.name));
console.log('Most expensive:', cart.getMostExpensive().name);
// Update quantity of first item
cart.updateQuantity(0, 2);
console.log('Updated laptop quantity:', cart.items[0].quantity);
Output:
Original order: ['Laptop', 'Mouse', 'Keyboard'] Sorted by price: ['Mouse', 'Keyboard', 'Laptop'] Most expensive: Laptop Updated laptop quantity: 2
đš Performance and Best Practices
When to use immutable vs mutable methods:
Use Immutable Methods When:
- State Management: Working with React, Redux, or other state libraries
- Functional Programming: Building pure functions
- Data Safety: Need to preserve original data
- Debugging: Want to track changes over time
Use Mutable Methods When:
- Performance Critical: Large arrays where copying is expensive
- Memory Constrained: Limited memory environments
- Simple Scripts: One-off operations where immutability isn't needed
// Performance comparison example
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
console.time('Mutable sort');
largeArray.sort((a, b) => b - a); // Modifies original
console.timeEnd('Mutable sort');
console.time('Immutable sort');
const sortedCopy = largeArray.toSorted((a, b) => a - b); // Creates new array
console.timeEnd('Immutable sort');
// For small arrays, the difference is negligible
// For large arrays, consider your memory and performance needs