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

🧠 Test Your Knowledge

What's the main advantage of toSorted() over sort()?