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;"
"}"