JavaScript ES2019

Array methods and object improvements

🎯 What is ES2019?

ES2019 introduced useful array methods like flat() and flatMap(), Object.fromEntries(), and optional catch binding. These features made data manipulation and error handling more convenient.


// ES2019 introduced Array.flat()
const nested = [1, [2, 3], [4, [5, 6]]];
const flattened = nested.flat(2);
console.log(flattened); // [1, 2, 3, 4, 5, 6]

// And Object.fromEntries()
const entries = [['name', 'John'], ['age', 30]];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'John', age: 30 }
                                    

Output:

[1, 2, 3, 4, 5, 6]
{ name: 'John', age: 30 }

ES2019 Features

πŸ“

Array.flat()

Flatten nested arrays

arr.flat(depth)
// [1, [2, 3]].flat() β†’ [1, 2, 3]
πŸ—ΊοΈ

Array.flatMap()

Map and flatten in one step

arr.flatMap(fn)
// [1, 2].flatMap(x => [x, x*2])
πŸ”„

Object.fromEntries()

Create object from key-value pairs

Object.fromEntries(entries)
// [['a', 1]] β†’ {a: 1}
🎯

Optional Catch

Catch without error parameter

try {
  // code
} catch {
  // no error parameter needed
}

πŸ”Ή Array.flat()

Flatten nested arrays to specified depth:

// Basic flattening
const arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]

// Multiple levels
const arr2 = [1, [2, [3, [4]]]];
console.log(arr2.flat());    // [1, 2, [3, [4]]] - only 1 level by default
console.log(arr2.flat(2));   // [1, 2, 3, [4]] - 2 levels
console.log(arr2.flat(3));   // [1, 2, 3, 4] - 3 levels

// Flatten all levels with Infinity
const deepNested = [1, [2, [3, [4, [5]]]]];
console.log(deepNested.flat(Infinity)); // [1, 2, 3, 4, 5]

// Real-world example: Flattening grouped data
const studentGrades = [
    ["Math", [85, 92, 78]],
    ["Science", [90, 88, 95]],
    ["English", [82, 89, 91]]
];

// Get all grades in a single array
const allGrades = studentGrades.map(([subject, grades]) => grades).flat();
console.log("All grades:", allGrades);

// Calculate average
const average = allGrades.reduce((sum, grade) => sum + grade, 0) / allGrades.length;
console.log("Average grade:", average.toFixed(1));

// Remove empty slots
const sparseArray = [1, , 3, , 5]; // Array with empty slots
console.log("Original:", sparseArray);
console.log("Flattened:", sparseArray.flat()); // Removes empty slots

Output:

[1, 2, 3, 4]
[1, 2, [3, [4]]]
[1, 2, 3, [4]]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
All grades: [85, 92, 78, 90, 88, 95, 82, 89, 91]
Average grade: 87.8
Original: [1, empty, 3, empty, 5]
Flattened: [1, 3, 5]

πŸ”Ή Array.flatMap()

Map and flatten in a single operation:

// Basic flatMap - equivalent to map().flat()
const numbers = [1, 2, 3];

// Using map + flat
const doubled1 = numbers.map(x => [x, x * 2]).flat();
console.log("Map + flat:", doubled1);

// Using flatMap (more efficient)
const doubled2 = numbers.flatMap(x => [x, x * 2]);
console.log("FlatMap:", doubled2);

// Real-world example: Processing sentences
const sentences = [
    "Hello world",
    "JavaScript is awesome",
    "ES2019 rocks"
];

// Get all words in a single array
const allWords = sentences.flatMap(sentence => sentence.split(' '));
console.log("All words:", allWords);

// Count word lengths
const wordLengths = allWords.flatMap(word => [word.length]);
console.log("Word lengths:", wordLengths);

// Example: Processing user data
const users = [
    { name: "Alice", hobbies: ["reading", "swimming"] },
    { name: "Bob", hobbies: ["gaming", "cooking", "hiking"] },
    { name: "Charlie", hobbies: ["music"] }
];

// Get all hobbies from all users
const allHobbies = users.flatMap(user => user.hobbies);
console.log("All hobbies:", allHobbies);

// Get unique hobbies
const uniqueHobbies = [...new Set(allHobbies)];
console.log("Unique hobbies:", uniqueHobbies);

// Example: Filtering and flattening
const data = [
    { category: "fruits", items: ["apple", "banana"] },
    { category: "vegetables", items: ["carrot", "broccoli"] },
    { category: "dairy", items: [] } // empty array
];

// Get all items, filtering out empty categories
const allItems = data.flatMap(category => 
    category.items.length > 0 ? category.items : []
);
console.log("All items:", allItems);

Output:

Map + flat: [1, 2, 2, 4, 3, 6]
FlatMap: [1, 2, 2, 4, 3, 6]
All words: ["Hello", "world", "JavaScript", "is", "awesome", "ES2019", "rocks"]
Word lengths: [5, 5, 10, 2, 7, 6, 5]
All hobbies: ["reading", "swimming", "gaming", "cooking", "hiking", "music"]
Unique hobbies: ["reading", "swimming", "gaming", "cooking", "hiking", "music"]
All items: ["apple", "banana", "carrot", "broccoli"]

πŸ”Ή Object.fromEntries()

Create objects from key-value pair arrays:

// Basic usage
const entries = [
    ['name', 'John'],
    ['age', 30],
    ['city', 'New York']
];

const person = Object.fromEntries(entries);
console.log(person); // { name: 'John', age: 30, city: 'New York' }

// Reverse of Object.entries()
const originalObj = { a: 1, b: 2, c: 3 };
const objEntries = Object.entries(originalObj);
console.log("Entries:", objEntries);

const recreatedObj = Object.fromEntries(objEntries);
console.log("Recreated:", recreatedObj);

// Transform object values
const prices = { apple: 1.20, banana: 0.80, orange: 1.50 };

// Convert to entries, transform, then back to object
const discountedPrices = Object.fromEntries(
    Object.entries(prices).map(([fruit, price]) => [fruit, price * 0.9])
);
console.log("Discounted prices:", discountedPrices);

// Filter object properties
const user = {
    id: 1,
    name: "Alice",
    email: "[email protected]",
    password: "secret123",
    role: "admin"
};

// Create public profile (remove sensitive data)
const publicProfile = Object.fromEntries(
    Object.entries(user).filter(([key, value]) => key !== 'password')
);
console.log("Public profile:", publicProfile);

// Convert Map to Object
const userPreferences = new Map([
    ['theme', 'dark'],
    ['language', 'en'],
    ['notifications', true]
]);

const preferencesObj = Object.fromEntries(userPreferences);
console.log("Preferences object:", preferencesObj);

// URL search params to object
const urlParams = new URLSearchParams('?name=John&age=30&city=Boston');
const paramsObj = Object.fromEntries(urlParams);
console.log("URL params as object:", paramsObj);

Output:

{ name: 'John', age: 30, city: 'New York' }
Entries: [['a', 1], ['b', 2], ['c', 3]]
Recreated: { a: 1, b: 2, c: 3 }
Discounted prices: { apple: 1.08, banana: 0.72, orange: 1.35 }
Public profile: { id: 1, name: 'Alice', email: '[email protected]', role: 'admin' }
Preferences object: { theme: 'dark', language: 'en', notifications: true }
URL params as object: { name: 'John', age: '30', city: 'Boston' }

πŸ”Ή Optional Catch Binding

Use try-catch without specifying an error parameter:

// Old way - error parameter was required
try {
    JSON.parse("invalid json");
} catch (error) {
    console.log("Parsing failed"); // We don't actually use 'error'
}

// New way - error parameter is optional
try {
    JSON.parse("invalid json");
} catch {
    console.log("Parsing failed - no error parameter needed");
}

// Practical example: Feature detection
function hasLocalStorage() {
    try {
        localStorage.setItem('test', 'test');
        localStorage.removeItem('test');
        return true;
    } catch {
        // Don't need to know the specific error
        return false;
    }
}

console.log("Has localStorage:", hasLocalStorage());

// Example: Safe JSON parsing
function safeJsonParse(jsonString) {
    try {
        return JSON.parse(jsonString);
    } catch {
        // Return null if parsing fails, don't care about the error details
        return null;
    }
}

console.log(safeJsonParse('{"name": "John"}')); // { name: "John" }
console.log(safeJsonParse('invalid json'));     // null

// Example: Safe number conversion
function safeParseInt(value) {
    try {
        const num = parseInt(value);
        if (isNaN(num)) throw new Error();
        return num;
    } catch {
        return 0; // Default value
    }
}

console.log(safeParseInt("123"));   // 123
console.log(safeParseInt("abc"));   // 0
console.log(safeParseInt(""));      // 0

// When you DO need the error
try {
    throw new Error("Something specific went wrong");
} catch (error) {
    console.log("Error details:", error.message);
}

// When you DON'T need the error
try {
    someRiskyOperation();
} catch {
    console.log("Operation failed, using fallback");
    useFallbackMethod();
}

function someRiskyOperation() {
    throw new Error("Simulated failure");
}

function useFallbackMethod() {
    console.log("Fallback method executed");
}

Output:

Parsing failed
Parsing failed - no error parameter needed
Has localStorage: true
{ name: "John" }
null
123
0
0
Error details: Something specific went wrong
Operation failed, using fallback
Fallback method executed

πŸ”Ή String.trimStart() and String.trimEnd()

More specific string trimming methods:

const text = "   Hello World   ";

// Old method (still works)
console.log("trim():", `"${text.trim()}"`);

// New methods in ES2019
console.log("trimStart():", `"${text.trimStart()}"`);
console.log("trimEnd():", `"${text.trimEnd()}"`);

// Aliases (for compatibility)
console.log("trimLeft():", `"${text.trimLeft()}"`);   // Same as trimStart()
console.log("trimRight():", `"${text.trimRight()}"`); // Same as trimEnd()

// Practical example: Processing user input
const userInputs = [
    "  [email protected]  ",
    "[email protected]   ",
    "   [email protected]",
    "[email protected]"
];

console.log("Cleaned emails:");
userInputs.forEach(email => {
    const cleaned = email.trimStart().trimEnd(); // or just trim()
    console.log(`"${email}" β†’ "${cleaned}"`);
});

// Example: Formatting code indentation
const codeLines = [
    "function example() {",
    "    console.log('Hello');",
    "    return true;",
    "}"
];

// Remove leading spaces but keep structure
const minIndent = Math.min(
    ...codeLines
        .filter(line => line.trimStart().length > 0)
        .map(line => line.length - line.trimStart().length)
);

const normalizedCode = codeLines.map(line => 
    line.length > minIndent ? line.slice(minIndent) : line
);

console.log("Normalized code:");
normalizedCode.forEach(line => console.log(`"${line}"`));

Output:

trim(): "Hello World"
trimStart(): "Hello World "
trimEnd(): " Hello World"
trimLeft(): "Hello World "
trimRight(): " Hello World"
Cleaned emails:
" [email protected] " β†’ "[email protected]"
"[email protected] " β†’ "[email protected]"
" [email protected]" β†’ "[email protected]"
"[email protected]" β†’ "[email protected]"
Normalized code:
"function example() {"
" console.log('Hello');"
" return true;"
"}"

🧠 Test Your Knowledge

What does [1, [2, 3], [4, 5]].flat() return?