JavaScript Evolution: A Tour of the Game-Changing Features in ES10 (ECMAScript 2019)
Abdulmoiz Ahmer
Software Consultant (Fullstack | Mern stack | Mevn stack | ReactJS | NextJS | PreactJS | VueJS | NuxtJS | ExpressJS | Fastify | Nestjs | MongoDB | PostgreSql | Firebase )
In the ever-evolving landscape of JavaScript, staying up-to-date with the latest language features is crucial for developers striving to write efficient and modern code. With the release of ECMAScript 2019, commonly known as ES10, JavaScript took another leap forward, introducing an array of powerful features and improvements. In this comprehensive guide, we will explore the key advancements brought by ES10, delving into each feature's functionality and showcasing how they can enhance your JavaScript development experience. Whether you're a seasoned developer or just starting your journey with JavaScript, join us as we uncover the transformative capabilities of ES10, unlocking new possibilities and expanding your repertoire of coding techniques. Let's dive into the world of ECMAScript 2019 and harness its full potential!
Table of content:
Optional Catch Binding:
It allows you to omit the parameter in the catch block, enabling you to catch an exception without explicitly assigning it to a variable. This feature provides a cleaner and more concise syntax when you're not interested in the exception object itself.
Here's an example that demonstrates the usage of Optional Catch Binding:
try {
? // Code that may throw an exception
? throw new Error('Something went wrong!');
} catch {
? // Optional catch binding, no parameter specified
? console.log('Caught an exception without binding it to a variable');
}
In this code snippet, we have a try block that intentionally throws an Error object. In the corresponding catch block, we omit the parameter, indicating that we don't need to access the exception object. Instead, we directly log a message to the console.
Prior to ES10, you would need to specify a parameter in the catch block, even if you didn't use it:
try {
? // Code that may throw an exception
? throw new Error('Something went wrong!');
} catch (error) {
? // Explicitly binding the exception object to a variable (error)
? console.log('Caught an exception:', error);
}
In this earlier code snippet, we catch the exception and assign it to the error variable, but if we don't use error within the catch block, it becomes unnecessary and adds extra noise to the code.
With Optional Catch Binding, you can skip the parameter declaration altogether, making the code cleaner and more readable, especially in situations where you don't need to interact with the exception object.
Array.prototype.flat() and Array.prototype.flatMap():
Array.prototype.flat() and Array.prototype.flatMap() are both methods introduced in ECMAScript 2019 (ES10) that operate on arrays. They provide convenient ways to transform and manipulate array elements.
The flat() method creates a new array that is a flattened version of the original array, merging all subarrays into a single level array.
Syntax:
let newArray = array.flat(depth);
Example:
const arr = [1, 2, [3, 4, [5, 6]]];
const flattened = arr.flat();
console.log(flattened);
// Output: [1, 2, 3, 4, [5, 6]]
In the above example, the arr array contains nested arrays. By calling flat() without specifying the depth, it flattens the array to a depth of 1, merging the subarrays into the main array.
The flatMap() method combines mapping and flattening operations in a single step. It applies a mapping function to each element of an array and then flattens the result into a new array.
Syntax:
let newArray = array.flatMap(callback);
Example:
const arr = [1, 2, 3, 4];
const mappedAndFlattened = arr.flatMap(x => [x, x * 2]);
console.log(mappedAndFlattened);
// Output: [1, 2, 2, 4, 3, 6, 4, 8]
In the above example, the arr array contains numbers. By calling flatMap() with a callback function, each element is mapped to an array with the original element and its doubled value. The resulting mapped arrays are then flattened into a single-level array.
Object.fromEntries():
Object.fromEntries() allows you to convert an iterable object, such as an array, into an object. It takes an iterable of key-value pairs and returns a new object with properties corresponding to those key-value pairs.
Here's an example code snippet to illustrate its usage:
const entries = [
? ['name', 'John'],
? ['age', 30],
? ['city', 'New York']
];
const obj = Object.fromEntries(entries);
console.log(obj);
In this example, we have an array called entries containing several key-value pairs. We then use Object.fromEntries(entries) to convert this array into an object.
The resulting object will have properties corresponding to the key-value pairs in the entries array:
{
? name: 'John',
? age: 30,
? city: 'New York'
}
It's important to note that the entries array must be an iterable containing nested arrays, where each nested array represents a key-value pair.
If any entry in the entries array has a duplicate key, the last occurrence of the key-value pair will be used in the resulting object. This behavior is similar to how duplicate keys are handled in object literals.
const entries = [
? ['name', 'John'],
? ['age', 30],
? ['name', 'Jane']
];
const obj = Object.fromEntries(entries);
console.log(obj);
In this case, the resulting object will have the last occurrence of the duplicate key 'name':
{
? name: 'Jane',
? age: 30
}
The Object.fromEntries() method is particularly useful when you need to convert an array of key-value pairs into an object quickly and concisely.
String.prototype.trimStart() and String.prototype.trimEnd():
The trimStart() and trimEnd() methods are part of the ECMAScript 2019 (ES10) specification and are used to remove whitespace characters from the beginning and end of a string, respectively. Let's explain each method with code snippets:
The trimStart() method removes leading whitespace characters from a string. Here's an example:
const str = ' Hello, World! ';
const trimmedStr = str.trimStart();
console.log(trimmedStr); // Output: 'Hello, World! '
In the code snippet above, the original string str contains leading whitespace characters at the beginning and end. The trimStart() method is then called on str, resulting in the removal of leading whitespace characters. The resulting string, trimmedStr, is logged to the console.
The trimEnd() method removes trailing whitespace characters from a string. Here's an example:
const str = ' Hello, World! ';
const trimmedStr = str.trimEnd();
console.log(trimmedStr); // Output: ' Hello, World!'
In this example, the original string str contains leading whitespace characters at the beginning and end. The trimEnd() method is called on str, resulting in the removal of trailing whitespace characters. The resulting string, trimmedStr, is logged to the console.
It's worth noting that these methods are also available with the aliases trimLeft() and trimRight(), respectively. The behavior is identical; the only difference lies in the naming convention.
Keep in mind that the trimStart() and trimEnd() methods are part of the ECMAScript 2019 specification, so they might not be supported in older JavaScript environments.
Symbol.prototype.description:
In ECMAScript 10 (ES10), the Symbol.prototype.description property was introduced to provide a human-readable description of a symbol. Before ES10, symbols were opaque and didn't have a built-in way to retrieve descriptive information. This property allows you to associate a description with a symbol, making it easier to work with symbols in code.
Here's an example code snippet to demonstrate the usage of Symbol.prototype.description:
// Create a symbol with a description
const mySymbol = Symbol('My Symbol Description');
// Retrieve the description using SSymbol.prototype.description
const description = mySymbol.description;
console.log(description); // Output: My Symbol Description
In the example above, we create a new symbol mySymbol with the description "My Symbol Description" by passing it as an argument to the Symbol() constructor. Then, we retrieve the description of the symbol using the Symbol.prototype.description property. Finally, we log the description to the console, which will output "My Symbol Description".
It's important to note that the description property is only available on the prototype of the Symbol object, hence Symbol.prototype.description. This means that you can access it on any symbol instance created with Symbol(), as shown in the example.
If a symbol doesn't have a description associated with it, accessing Symbol.prototype.description will return undefined. Here's an example to illustrate this:
const mySymbol = Symbol();
console.log(mySymbol.description); // Output: undefined
In the example above, we create a symbol mySymbol without providing a description. Therefore, when we try to access the description property, it will return undefined.
领英推荐
Function.prototype.toString():
The Function.prototype.toString() method in ECMAScript 2019 (ES10) returns a string representing the source code of the function. It retrieves the original function declaration, including any comments or whitespace, as a string.
Here's an example to demonstrate its usage:
function greet(name) {
? return `Hello, ${name}!`;
}
console.log(greet.toString());
Output:
function greet(name) {
? return `Hello, ${name}!`;
}
In the above code, the toString() method is called on the greet function. It returns the source code of the function as a string, including the function declaration and the function body.
Keep in mind that toString() only works for functions defined using the function declaration syntax or function expressions. It doesn't work for built-in functions or functions defined using arrow function syntax. Let's see an example:
const add = (a, b) => a + b;
console.log(add.toString());
Output:
() => a + b
In this example, toString() returns the source code of the arrow function as a string, but it doesn't include the function name or the function body because arrow functions have a more concise syntax.
BigInt:
In ECMAScript 10 (also known as ES10 or ES2019), the BigInt data type was introduced to provide a way to represent and perform arithmetic operations on integers of arbitrary precision. Before ES10, JavaScript had a limitation on the maximum safe integer value, which was 2^53 - 1. BigInt allows you to work with integers beyond this limit.
To create a BigInt value, you can append the n suffix to an integer literal or use the BigInt() constructor function. Here's an example:
const bigNumber1 = 1234567890123456789012345678901234567890n;
const bigNumber2 = BigInt("9876543210987654321098765432109876543210");
In the above code snippet, bigNumber1 and bigNumber2 are BigInt values created using the n suffix and the BigInt() constructor, respectively.
You can perform various arithmetic operations with BigInt values, such as addition, subtraction, multiplication, division, and modulo, using regular arithmetic operators. However, when working with BigInts, you need to use the corresponding BigInt versions of the operators, which are +, -, *, /, and %. Here's an example:
const sum = bigNumber1 + bigNumber2;
const difference = bigNumber1 - bigNumber2;
const product = bigNumber1 * bigNumber2;
const quotient = bigNumber1 / bigNumber2;
const remainder = bigNumber1 % bigNumber2;
In the above code snippet, sum, difference, product, quotient, and the remainder represent the results of performing addition, subtraction, multiplication, division, and modulo operations on the BigInt values.
It's important to note that you cannot directly mix BigInt values with regular number values in arithmetic operations. You need to explicitly convert regular numbers to BigInts using the BigInt() constructor. Here's an example:
const regularNumber = 42;
const bigSum = BigInt(regularNumber) + bigNumber1;
In the above code snippet, the regular number regularNumber is converted to a BigInt using BigInt(regularNumber) before performing the addition with bigNumber1.
BigInts also support comparison operations like >, <, >=, <=, ==, and != to compare values. Here's an example:
const isGreater = bigNumber1 > bigNumber2;
const isEqual = bigNumber1 === bigNumber2;
In the above code snippet, isGreater represents whether bigNumber1 is greater than bigNumber2, and isEqual represents whether the two BigInt values are equal.
BigInts can be used in conjunction with other JavaScript features and libraries, but it's worth noting that not all JavaScript operations and methods support BigInts. Therefore, you need to ensure compatibility and handle any potential limitations when working with BigInts in your code.
Optional Chaining Operator (?.):
The Optional Chaining Operator (?.) is a feature introduced in ECMAScript 2020 (ES2020) that allows you to safely access the properties and methods of an object without worrying about potential null or undefined values. It provides a concise way to handle situations where intermediate properties or methods in a chain of property accesses might be missing.
Let's go through some code snippets to understand how the Optional Chaining Operator works.
const user = {
? name: 'John',
? address: {
? ? city: 'New York',
? ? street: '123 ABC Street'
? }
};
const cityName = user.address?.city;
console.log(cityName);? // Output: "New York"
const zipCode = user.address?.zipCode;
console.log(zipCode);? // Output: undefined
In the above example, the Optional Chaining Operator (?.) is used to safely access the city property of the address object. If address is null or undefined, the expression evaluates to undefined without throwing an error.
const user = {
? name: 'John',
? greet: function() {
? ? console.log(`Hello, ${this.name}!`);
? }
};
user.greet?.();? // Output: "Hello, John!"
const guest = {
? name: 'Guest'
};
guest.greet?.();? // No output (no error thrown)
Here, the Optional Chaining Operator is used to invoke the greet method on the user and guest objects. If the method exists, it will be executed, otherwise, no error will be thrown.
const data = {
? user: {
? ? name: 'John',
? ? age: 25
? }
};
const username = data?.user?.name ?? 'Unknown';
console.log(username);? // Output: "John"
const address = data?.user?.address ?? 'No address found';
console.log(address);? // Output: "No address found"
In this example, the Optional Chaining Operator is combined with the nullish coalescing operator (??). If any of the accessed properties in the chain are null or undefined, the expression evaluates to the specified default value.
The Optional Chaining Operator provides a concise and safe way to handle object property access in JavaScript, especially when dealing with nested objects. It helps to prevent errors that would occur when accessing properties on null or undefined values.
Nullish Coalescing Operator (??):
The Nullish Coalescing Operator (??) is a feature introduced in ECMAScript 2020 (ES10) that provides a convenient way to handle null or undefined values. It allows you to choose a default value when dealing with nullish values in a concise and readable manner.
The syntax for the nullish coalescing operator is as follows:
const result = value1 ?? value2;
Here, value1 is the value that you want to check for nullishness. If value1 is neither null nor undefined, it will be returned as the result. However, if value1 is null or undefined, value2 will be returned.
Let's look at some code snippets to understand its usage:
const username = null;
const defaultUsername = "Guest";
const result = username ?? defaultUsername;
console.log(result); // Output: Guest
In this example, the variable username is null, so the nullish coalescing operator returns the value of defaultUsername, which is "Guest".
const value = 0;
const defaultValue = 42;
const result = value || defaultValue;
console.log(result); // Output: 42
Here, value is 0, which is a falsy value in JavaScript. In this case, if we used the logical OR operator (||) instead of the nullish coalescing operator, the result would be defaultValue even though value is not null or undefined. However, by using the nullish coalescing operator, the result is 0.
const config = {
? api: {
? ? url: "https://api.example.com",
? ? timeout: 5000,
? },
};
const apiUrl = config.api.url ?? "https://default-api.example.com";
const apiTimeout = config.api.timeout ?? 3000;
const apiCacheEnabled = config.api.cacheEnabled ?? true;
console.log(apiUrl); // Output: https://api.example.com
console.log(apiTimeout); // Output: 5000
console.log(apiCacheEnabled); // Output: true
In this example, we have a config object with an api property. By using the nullish coalescing operator, we can safely access nested properties and provide default values if any of them are null or undefined.
Promise.allSettled():
Promise.allSettled() is a method introduced in ECMAScript 2020 (ES10) that allows you to handle multiple promises simultaneously and get the results once all promises have settled, regardless of whether they are fulfilled or rejected. This is different from Promise.all(), which short-circuits and immediately rejects if any of the promises reject.
Here's an example to illustrate how Promise.allSettled() works:
const promises = [
? Promise.resolve(42),
? Promise.reject("Error occurred"),
? Promise.resolve("Another result"),
];
Promise.allSettled(promises)
? .then((results) => {
? ? results.forEach((result) => {
? ? ? if (result.status === "fulfilled") {
? ? ? ? console.log("Fulfilled:", result.value);
? ? ? } else if (result.status === "rejected") {
? ? ? ? console.log("Rejected:", result.reason);
? ? ? }
? ? });
? });
In the code above, we have an array promises that contains three promises: one that fulfills with the value 42, one that rejects with the error message "Error occurred", and another one that fulfills with the string "Another result".
By calling Promise.allSettled(promises), we wait for all the promises to settle (either fulfill or reject). The returned promise resolves to an array of objects representing the settlement status of each promise.
In the then callback, we iterate over the results array and determine the status of each promise using the status property. If the promise fulfilled, we log the value using result.value. If the promise was rejected, we log the reason using result.reason.
When you run this code, you'll see the following output:
Fulfilled: 42
Rejected: Error occurred
Fulfilled: Another result
As you can see, Promise.allSettled() ensures that all promises are processed and provides the settlement information for each promise, allowing you to handle both fulfilled and rejected promises in a unified way.
#OptionalCatchBinding #Arrayprototypeflat #ArrayprototypeflatMap #ObjectfromEntries #StringprototypetrimStart #StringprototypetrimEnd #Symbolprototypedescription #FunctionprototypetoString #BigInt #OptionalChainingOperator #NullishCoalescingOperator #PromiseallSettled