New Features in ES2020
The ECMAScript 2020 (ES2020) specification brought several significant enhancements to JavaScript, making the language more robust, readable, and developer-friendly. From new ways to handle asynchronous operations to safer property access and arbitrary-precision integers, ES2020 introduced features that have quickly become indispensable in modern JavaScript development. Let's dive into some of the most impactful additions.
1. Optional Chaining (`?.`)
Optional chaining provides a much safer and cleaner way to access properties deep within an object structure without having to write explicit null or undefined checks at each level. If a reference is nullish (null or undefined), the expression short-circuits and returns undefined.
Before ES2020:
const user = {
profile: {
address: {
street: '123 Main St'
}
}
};
const street = user && user.profile && user.profile.address && user.profile.address.street;
console.log(street); // "123 Main St"
// Accessing a potentially non-existent property required verbose checks
const city = user && user.profile && user.profile.location && user.profile.location.city;
console.log(city); // undefined (cumbersome check)
With Optional Chaining:
const user = {
profile: {
address: {
street: '123 Main St'
}
}
};
const street = user?.profile?.address?.street;
console.log(street); // "123 Main St"
const city = user?.profile?.location?.city; // Much cleaner!
console.log(city); // undefined
// Optional chaining also works with function calls and array access
const getUser = () => null;
const username = getUser?.().name; // undefined (if getUser() returns null/undefined)
const items = ['apple', 'banana'];
const firstItem = items?.[0]; // 'apple'
const thirdItem = items?.[2]; // undefined (if index doesn't exist)
2. Nullish Coalescing Operator (`??`)
The nullish coalescing operator (`??`) is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand. This is a crucial distinction from the logical OR operator (||), which returns the right-hand side for any "falsy" value (0, '', false, null, undefined).
Using || (potential issues):
const userName = null;
const defaultName = 'Guest';
const finalName = userName || defaultName;
console.log(finalName); // "Guest"
const count = 0;
const defaultCount = 10;
const finalCount = count || defaultCount;
console.log(finalCount); // 10 (Oops! 0 is a valid count, but || treats it as falsy and assigns default)
const emptyString = '';
const defaultString = 'Default';
const finalString = emptyString || defaultString;
console.log(finalString); // "Default" (Again, '' is falsy, so default is assigned)
Using `??`:
const userName = null;
const defaultName = 'Guest';
const finalName = userName ?? defaultName;
console.log(finalName); // "Guest"
const count = 0;
const defaultCount = 10;
const finalCount = count ?? defaultCount;
console.log(finalCount); // 0 (Correct! 0 is not null or undefined, so it's kept)
const emptyString = '';
const defaultString = 'Default';
const finalString = emptyString ?? defaultString;
console.log(finalString); // '' (Correct! '' is not null or undefined, so it's kept)
3. Promise.allSettled()
While Promise.all() is excellent for scenarios where you need all promises to succeed (and fails fast if even one promise rejects), Promise.allSettled() offers a more resilient alternative. It waits for all given promises to settle (either fulfill or reject) and returns an array of objects, each describing the outcome of a promise. This is incredibly useful when you want to execute multiple asynchronous operations independently and gather all their results, regardless of success or failure.
const p1 = Promise.resolve('Success 1');
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('Error 2'), 100)); // Will reject
const p3 = Promise.resolve('Success 3');
const p4 = new Promise((resolve, reject) => setTimeout(() => resolve('Success 4'), 50)); // Will fulfill
Promise.allSettled([p1, p2, p3, p4])
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index + 1} fulfilled with value: ${result.value}`);
} else {
console.log(`Promise ${index + 1} rejected with reason: ${result.reason}`);
}
});
});
// Expected output (order might vary slightly for p2 and p4 due to setTimeout, but all will settle):
// Promise 1 fulfilled with value: Success 1
// Promise 4 fulfilled with value: Success 4
// Promise 3 fulfilled with value: Success 3
// Promise 2 rejected with reason: Error 2
4. BigInt
JavaScript's standard Number type has a limitation: it can only safely represent integers up to 253 - 1 (Number.MAX_SAFE_INTEGER) and down to -(253 - 1) (Number.MIN_SAFE_INTEGER). Beyond these limits, precision issues can occur. BigInt is a new primitive type that allows you to work with arbitrarily large integers. You can create a BigInt by appending n to the end of an integer literal or by calling the BigInt() constructor.
// Standard JavaScript Number (with precision loss example)
const maxSafe = Number.MAX_SAFE_INTEGER;
console.log(maxSafe); // 9007199254740991
console.log(maxSafe + 1); // 9007199254740992
console.log(maxSafe + 2); // 9007199254740992 (Precision loss occurs here!)
// Using BigInt for arbitrary precision
const reallyBigInt = 9007199254740991n; // Append 'n' to make it a BigInt
console.log(reallyBigInt); // 9007199254740991n
console.log(reallyBigInt + 1n); // 9007199254740992n
console.log(reallyBigInt + 2n); // 9007199254740993n (Correct precision retained!)
const anotherBigInt = BigInt("123456789012345678901234567890"); // From a string
console.log(anotherBigInt); // 123456789012345678901234567890n
// Important: Operations with BigInts must involve only other BigInts.
// console.log(1n + 1); // TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(1n + BigInt(1)); // 2n (Explicitly convert the Number to BigInt)
5. globalThis
Before ES2020, accessing the global object in JavaScript environments was inconsistent. In browsers, it was window, in Node.js, global, and in Web Workers, self. This inconsistency led to boilerplate code or relying on environment-specific checks to get the global object. globalThis provides a universal way to access the global object, regardless of the execution environment, unifying the experience across platforms.
// Before ES2020 (example of inconsistent access handling)
// const getGlobal = () => {
// if (typeof self !== 'undefined') { return self; }
// if (typeof window !== 'undefined') { return window; }
// if (typeof global !== 'undefined') { return global; }
// throw new Error('unable to locate global object');
// };
// const myGlobal = getGlobal();
// With globalThis (universal access)
console.log(typeof globalThis.setTimeout); // "function" (works in browser, Node.js, web workers)
globalThis.myGlobalVariable = 'Hello from global!';
console.log(globalThis.myGlobalVariable); // 'Hello from global!'
6. Dynamic import()
Dynamic import() allows you to load ECMAScript modules asynchronously and conditionally at runtime. This is particularly useful for code splitting, where you only load necessary modules when they are actually needed, significantly improving initial page load performance and reducing memory footprint. It returns a Promise that resolves to the module object.
// Imagine 'myModule.js' contains:
// export function greet(name) { return `Hello, ${name}!`; }
// export const version = '1.0';
async function loadAndGreet() {
console.log('Attempting to load module...');
try {
// Dynamically import the module. Path is relative to the current file.
const module = await import('./myModule.js');
console.log('Module loaded successfully!');
console.log(module.greet('World')); // Hello, World!
console.log(`Module version: ${module.version}`); // Module version: 1.0
} catch (err) {
console.error('Failed to load module:', err);
}
}
// You can also use the .then() syntax
// import('./myModule.js')
// .then(module => {
// console.log('Module loaded with .then()!');
// console.log(module.greet('Developer'));
// })
// .catch(err => {
// console.error('Failed to load module with .then():', err);
// });
// Calling the function to demonstrate dynamic import
// loadAndGreet();
Note: For this example to run, myModule.js would need to exist at the specified path, and the execution environment must support dynamic ES module imports (e.g., modern browser or Node.js with ES modules enabled).
7. String.prototype.matchAll()
The matchAll() method returns an iterator of all results matching a string against a regular expression, including capturing groups. This is a significant improvement over String.prototype.match() when you need to iterate over all matches and access their detailed capture groups, especially when using regular expressions with the /g (global) flag.
const text = "Cats are great. Dogs are too. Birds sing.";
const regex = /\b(\w+)\s+are\s+(great|too)\b/g;
// Using match() with /g only gives you an array of full matched strings, not capture groups for all.
// console.log(text.match(regex)); // ["Cats are great", "Dogs are too"]
// Using matchAll() to iterate over all matches and access capture groups
for (const match of text.matchAll(regex)) {
console.log(`
Full match: ${match[0]}
Animal: ${match[1]}
Adjective: ${match[2]}
Index: ${match.index}
Input: ${match.input}
`);
}
// Expected output:
// Full match: Cats are great
// Animal: Cats
// Adjective: great
// Index: 0
// Input: Cats are great. Dogs are too. Birds sing.
// Full match: Dogs are too
// Animal: Dogs
// Adjective: too
// Index: 16
// Input: Cats are great. Dogs are too. Birds sing.
ES2020 introduced a suite of powerful features that significantly enhance JavaScript's capabilities and ergonomics. From simplifying conditional access with optional chaining and nullish coalescing to robust asynchronous handling with Promise.allSettled() and supporting large integers with BigInt, these additions empower developers to write cleaner, more efficient, and more reliable code. Embracing these features allows you to leverage the full potential of modern JavaScript and build more resilient applications.