What’s New in ECMAScript 2020 (ES11)
Hey, JavaScript Lovers!
ECMAScript 2020, also known as ES2020 or ES11, introduced several new features and improvements to JavaScript.
Some of the key features introduced in ES11 include:
- Optional Chaining (
?.
) - Nullish Coalescing Operator (
??
) - BigInt
- Promise.allSettled
- Dynamic Import
- import.meta
- Module namespace exports
- String.prototype.matchAll
- WeakRef
- FinalizationRegistry
1. Optional Chaining (?.
)
Optional chaining (?.
) is a feature introduced in ECMAScript 2020 (ES11) that allows you to safely access deeply nested properties in an object without having to check each level for existence. This feature can help you avoid errors when trying to access properties on objects that might be null
or undefined
.
Here’s a quick overview of how it works:
Object Property Access: object?.property
const user = { name: 'Alice' };
console.log(user?.name); // Output: 'Alice'
console.log(user?.age); // Output: undefined
Method Calls: object?.method()
const user = {
greet() { return 'Hello'; }
};
console.log(user?.greet()); // Output: 'Hello'
const noUser = null;
console.log(noUser?.greet()); // Output: undefined
Array Access: array?.[index]
const users = ['Alice', 'Bob'];
console.log(users?.[1]); // Output: 'Bob'
const empty = null;
console.log(empty?.[0]); // Output: undefined
Chaining Multiple Levels
const user = {
profile: {
contact: {
email: 'alice@example.com'
}
}
};
console.log(user?.profile?.contact?.email); // Output: 'alice@example.com'
console.log(user?.profile?.address?.city); // Output: undefined
Benefits
Prevents Errors: Optional chaining helps prevent runtime errors when trying to access properties on objects that could be null
or undefined
.
Cleaner Code: It reduces the need for multiple null checks, leading to cleaner and more readable code.
Caveats
Performance: While optional chaining is convenient, it can have a slight impact on performance in deeply nested structures compared to traditional null checks.
Compatibility: Ensure your environment supports ES2020 features or use a transpiler like Babel if you need compatibility with older browsers or environments.
Overall, optional chaining is a powerful feature that simplifies working with nested data structures and makes your code more robust and easier to maintain.
2. Nullish Coalescing Operator (??)
The nullish coalescing operator (??
) was introduced in ECMAScript 2020 (ES11) and provides a way to handle null
or undefined
values more precisely than the logical OR operator (||
).
Here’s a brief overview of how it works:
Behavior
- If
value1
is neithernull
norundefined
, thenresult
will bevalue1
. - If
value1
isnull
orundefined
, thenresult
will bevalue2
.
let user = {
name: "Alice",
age: null
};
// Using nullish coalescing
let age = user.age ?? 25;
console.log(age); // Output: 25 (because user.age is null)
// Using logical OR
let ageOr = user.age || 25;
console.log(ageOr); // Output: 25 (but note this would also return 25 for other falsy values like 0, '')
Key Differences from Logical OR (||
)
Logical OR (||
) treats all falsy values (0
, NaN
, ""
, false
, null
, undefined
) as the same and will coalesce to the right-hand side if the left-hand side is any of these falsy values.
Nullish coalescing (??
) specifically targets null
and undefined
only. Other falsy values like 0
, NaN
, or ""
are not considered nullish and will not trigger the right-hand side.
Use Cases
Default Values for Missing Properties: Use ??
to provide default values only when a variable is explicitly null
or undefined
, avoiding unintended behavior with other falsy values.
Handling Optional Values: For cases where you need a default value for optional or potentially missing values, ??
ensures you handle only null
or undefined
cases.
Important Note
The nullish coalescing operator has lower precedence than most operators (except for the ternary conditional operator). Therefore, if you combine ??
with other operators, be mindful of how they interact:
let value = 0;
let result = value ?? (value === 0 ? 'zero' : 'not zero');
console.log(result); // Output: 0
In this example, value
is 0
, which is not null
or undefined
, so the nullish coalescing operator returns 0
, and the right-hand side of the ??
is not evaluated.
3. BigInt
BigInt, introduced in ES11, allows you to work with integers larger than Number.MAX_SAFE_INTEGER
(2^53 – 1) and smaller than Number.MIN_SAFE_INTEGER
(-2^53 + 1).
Key Features
- Syntax: Add an
n
at the end of an integer literal to create a BigInt. - Arithmetic Operations: Supports addition, subtraction, multiplication, division (floor division), and modulus operations.
- Comparison: Can be compared with other BigInts and numbers but always returns
false
when compared with regular numbers directly. - Methods: Has methods like
toString()
,toLocaleString()
, andvalueOf()
for conversion and manipulation.
let largeNumber = 1234567890123456789012345678901234567890n;
let anotherLargeNumber = 9876543210987654321098765432109876543210n;
let sum = largeNumber + anotherLargeNumber;
console.log(sum); // Output: 11111111101111111111111111111111111111n
Usage Notes
Operations with Regular Numbers: You cannot mix BigInts with regular numbers directly. For example, bigIntValue + 1
will throw an error.
Interoperability: To work with BigInt values and numbers together, you need to convert them explicitly.
BigInt is ideal for scenarios requiring precise integer arithmetic beyond the range of the Number type, like financial calculations or cryptography.
4. Promise.allSettled
Promise.allSettled
, introduced in ES11, is a method that allows you to handle multiple promises and get their results once all of them have either fulfilled or rejected.
Behavior
Returns a Promise: This method returns a single Promise that resolves after all the given promises have either resolved or rejected.
Result Array: The resolved promise yields an array of objects describing the outcome of each promise. Each object has two properties:
status
:"fulfilled"
or"rejected"
.value
orreason
: Contains the result if fulfilled, or the reason if rejected.
let p1 = Promise.resolve(1);
let p2 = Promise.reject(new Error("Failure"));
let p3 = Promise.resolve(3);
Promise.allSettled([p1, p2, p3])
.then(results => {
console.log(results);
/*
Output:
[
{ status: "fulfilled", value: 1 },
{ status: "rejected", reason: Error: Failure },
{ status: "fulfilled", value: 3 }
]
*/
});
Use Cases
Handling Multiple Promises: Useful when you want to wait for all promises to settle, regardless of their outcomes.
Aggregating Results: Allows you to handle both success and failure outcomes in a unified way, often used when you need to process results from multiple asynchronous operations.
Promise.allSettled
is ideal for scenarios where you need to ensure that all promises have finished executing, without halting due to a single promise’s rejection.
5. Dynamic Import
Dynamic import, introduced in ES11, allows you to load JavaScript modules asynchronously using the import()
function. This feature is useful for code-splitting and loading modules on demand.
Behavior
Returns a Promise: The import()
function returns a promise that resolves to the module object.
Module Specifier: The argument can be a string literal or an expression that resolves to a string, which should be the path or URL to the module.
// Dynamically import a module
import('./myModule.js')
.then(module => {
// Use the module's exported functions or variables
module.myFunction();
})
.catch(error => {
console.error('Failed to load module:', error);
});
Use Cases
- Code Splitting: Load parts of your application only when needed, improving initial load time.
- Conditional Imports: Load modules based on user actions or conditions, enhancing performance and user experience.
- Feature Flags: Dynamically load features or components based on feature flags or configuration settings.
Dynamic imports are ideal for optimizing performance and managing dependencies in a modular and flexible way.
6. import.meta
import.meta
is an object introduced in ES11 (ECMAScript 2020) that provides metadata about the current module. It allows access to module-specific information and is often used with the dynamic import()
function.
Module URL: import.meta.url
provides the URL of the module, which is useful for constructing relative paths to other resources.
Suppose you have a module located at https://example.com/scripts/myModule.js
. Using import.meta.url
, you can obtain the URL of the current module:
// myModule.js
console.log(import.meta.url);
// Output: 'https://example.com/scripts/myModule.js'
// You can use this URL to resolve paths relative to the module
Use Cases
- Dynamic Resource Loading: Construct paths relative to the module URL for dynamically loading resources, such as images or other modules.
- Debugging and Logging: Get information about the module’s URL for debugging or logging purposes.
- Modular Architecture: When working with modular applications, use
import.meta.url
to handle paths and dependencies relative to the module.
Key Points
Static Properties: import.meta
itself does not have many static properties or methods; the primary feature is import.meta.url
.
Environment-Specific: The exact behavior and availability of import.meta
can vary depending on the module system or environment (e.g., browsers, Node.js).
Limitations
- Browser Support: Ensure your target environment supports
import.meta
andimport.meta.url
, as support may vary across different JavaScript engines or versions. - Module Only:
import.meta
is only available in ES modules and not in CommonJS or other module systems.
Overall, import.meta
is a powerful feature for modules, providing useful metadata and aiding in dynamic path resolution.
7. Module namespace exports
In ES11 (ECMAScript 2020), module namespace exports provide a way to export all named exports from one module and import them into another module as a single object. This allows you to create a consolidated object that holds all the exports from a module, making it easier to manage and work with grouped exports.
Suppose you have a module math.js
with multiple named exports:
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => a / b;
You can import all the named exports from math.js
as a single object in another module:
// main.js
import * as math from './math.js';
console.log(math.add(2, 3)); // Output: 5
console.log(math.subtract(5, 2)); // Output: 3
console.log(math.multiply(4, 3)); // Output: 12
console.log(math.divide(10, 2)); // Output: 5
In this example, the math
object will contain all the named exports from math.js
, accessible as properties of that object.
Benefits
- Organized Imports: Group all exports from a module into a single object, which can make code more readable and easier to manage.
- Namespace Management: Avoid name conflicts and manage multiple related exports in a clean and structured way.
- Simplified Code: Importing multiple items from a module in one statement reduces the number of import statements needed.
Key Points
Namespace Object: The imported object will contain all named exports from the module, but not the default export. If a module has a default export, you need to import it separately.
Dynamic Imports: You can use namespace imports with dynamic imports as well. For example:
import('./math.js')
.then(module => {
console.log(module.add(2, 3)); // Output: 5
});
Read-Only: The imported namespace object is read-only. You cannot modify the exports of the module through the namespace object.
Example with Default Export
If a module has both named and default exports, you can import them like this:
// utils.js
export const pi = 3.14;
export function square(x) {
return x * x;
}
export default function cube(x) {
return x * x * x;
}
// main.js
import cube, * as utils from './utils.js';
console.log(cube(3)); // Output: 27
console.log(utils.pi); // Output: 3.14
console.log(utils.square(4)); // Output: 16
In this example, cube
is the default export, and utils
is a namespace object containing all the named exports.
8. String.prototype.matchAll
String.prototype.matchAll
is a method introduced in ES11 (ECMAScript 2020) that allows you to find all matches of a regular expression in a string and return them as an iterator of RegExp
match objects. This method is particularly useful for extracting multiple matches from a string, especially when the regular expression has the global (g
) flag.
Parameters
regexp
: A regular expression object. It must have the global (g
) flag set; otherwise, matchAll
will throw a TypeError
.
Returns
An iterator of RegExp
match objects. Each match object contains the full match and capturing groups.
Here’s a basic example showing how to use matchAll
to find all matches of a regular expression in a string:
const text = 'The quick brown fox jumps over the lazy dog. The fox is clever.';
// Regular expression with the global flag
const regex = /fox/g;
const matches = text.matchAll(regex);
for (const match of matches) {
console.log(match);
}
/*
Output:
[
'fox',
index: 16,
input: 'The quick brown fox jumps over the lazy dog. The fox is clever.',
groups: undefined
]
[
'fox',
index: 39,
input: 'The quick brown fox jumps over the lazy dog. The fox is clever.',
groups: undefined
]
*/
Example with Capturing Groups
If your regular expression includes capturing groups, matchAll
will provide additional details in each match object:
const text = 'My phone number is 123-456-7890 and my office number is 987-654-3210.';
// Regular expression with capturing groups
const regex = "/(\d{3})-(\d{3})-(\d{4})/g";
const matches = text.matchAll(regex);
for (const match of matches) {
console.log(match);
}
/*
Output:
[
'123-456-7890',
'123',
'456',
'7890',
index: 21,
input: 'My phone number is 123-456-7890 and my office number is 987-654-3210.',
groups: undefined
]
[
'987-654-3210',
'987',
'654',
'3210',
index: 50,
input: 'My phone number is 123-456-7890 and my office number is 987-654-3210.',
groups: undefined
]
*/
Key Points
Global Flag Required: The regular expression must have the global (g
) flag for matchAll
to work. Without the g
flag, the method will throw an error.
Iterator: matchAll
returns an iterator, not an array. You can iterate over it using a for...of
loop or convert it to an array if needed:
const matchesArray = Array.from(text.matchAll(regex));
console.log(matchesArray);
Match Object: Each match object contains:
- The full match as the first element.
- Capturing groups as subsequent elements.
- The
index
of the match within the string. - The
input
string that was searched.
Use Cases
- Extracting Multiple Matches: When you need to extract all occurrences of a pattern with capturing groups.
- Iterating Matches: For scenarios where you need to perform operations on each match.
String.prototype.matchAll
is a powerful addition to JavaScript, making it easier to work with multiple matches of a regular expression and providing detailed match information for more complex string processing tasks.
9. WeakRef
WeakRef
is a feature introduced in ES11 (ECMAScript 2020) that provides a way to hold a weak reference to an object. A weak reference is a reference that does not prevent the garbage collector from reclaiming the object if there are no other strong references to it. This can be useful for cases where you want to cache objects or manage memory without preventing objects from being collected.
Features
- Weak Reference: Unlike strong references, weak references do not keep an object alive. If the object referenced by a
WeakRef
is only weakly referenced (i.e., there are no strong references to it), it can be garbage collected. - Handling Weak References: To access the referenced object, you use the
deref()
method on aWeakRef
instance.
Here’s a basic example demonstrating the use of WeakRef
:
let obj = { name: 'John' };
// Create a WeakRef to the object
let weakRef = new WeakRef(obj);
// Access the object through WeakRef
let derefObj = weakRef.deref();
console.log(derefObj); // Output: { name: 'John' }
// Delete the strong reference
obj = null;
// Attempt to access the object again
derefObj = weakRef.deref();
console.log(derefObj); // Output: undefined (object may have been garbage collected)
Key Points
- Garbage Collection: A
WeakRef
does not prevent the garbage collection of the object it references. If the only references to the object are weak references, the object may be collected by the garbage collector. deref()
Method: Thederef()
method returns the referenced object if it is still available orundefined
if the object has been collected.- Use Cases:
Caching: To cache objects without preventing their garbage collection. If you cache objects using weak references, the garbage collector can reclaim them when memory is needed.
Memory Management: For scenarios where you want to keep a reference to an object but do not want to prevent it from being collected, such as implementing certain kinds of caches or references that should not prevent objects from being collected.
Limitations
No Strong Guarantees: There are no guarantees that the object will still be available when you call deref()
. The object may have been collected by the time you try to access it.
No Notifications: WeakRef
does not provide notifications or callbacks when the object is collected.
Example with Cache
Here’s an example of how WeakRef
can be used in a simple caching scenario:
class Cache {
constructor() {
this.cache = new Map();
}
add(key, value) {
// Use WeakRef for values to allow garbage collection
this.cache.set(key, new WeakRef(value));
}
get(key) {
const weakRef = this.cache.get(key);
if (weakRef) {
const value = weakRef.deref();
if (value) {
return value;
} else {
// Remove the entry if the object was garbage collected
this.cache.delete(key);
}
}
return undefined;
}
}
const cache = new Cache();
let obj = { name: 'Alice' };
cache.add('user', obj);
// Accessing the cached object
console.log(cache.get('user')); // Output: { name: 'Alice' }
// Remove strong reference
obj = null;
// At some point, the garbage collector might collect the object
console.log(cache.get('user')); // Output: undefined (if the object was collected)
In this example, the Cache
class uses WeakRef
to store objects in a way that allows them to be garbage collected if there are no other strong references. This approach helps manage memory more efficiently by allowing objects to be collected when they are no longer needed.
10. FinalizationRegistry
FinalizationRegistry
is a feature introduced in ES11 (ECMAScript 2020) that provides a way to register cleanup callbacks that are invoked when an object is garbage collected. This can be useful for managing resources or performing cleanup tasks related to objects that are no longer in use.
Creating a FinalizationRegistry
const registry = new FinalizationRegistry((heldValue) => {
// Cleanup code here
});
The callback function you provide will be called with the heldValue
when the registered object is garbage collected.
Registering an Object
registry.register(object, heldValue, unregisterToken);
object
: The object you want to monitor for garbage collection.heldValue
: An optional value that will be passed to the callback when the object is collected.unregisterToken
: An optional token used to unregister the object. If provided, it can be used withregistry.unregister(unregisterToken)
to remove the registration.
Unregistering an Object
registry.unregister(unregisterToken);
This method removes the registration of the object from the registry, preventing the callback from being called when the object is garbage collected.
Here’s a basic example demonstrating how to use FinalizationRegistry
:
// Create a FinalizationRegistry with a cleanup callback
const registry = new FinalizationRegistry((heldValue) => {
console.log(`Cleaning up: ${heldValue}`);
});
// Function to create and register an object
function createAndRegister() {
let obj = { name: 'Resource' };
// Register the object with a held value
registry.register(obj, 'Resource Cleanup');
// When obj is garbage collected, the callback will be invoked
}
// Call the function and create a registered object
createAndRegister();
// At this point, obj is still reachable
console.log('Object is still in use.');
// Remove all strong references to obj
// The garbage collector will eventually collect the object and invoke the callback
Key Points
- Callback Invocation: The callback provided to
FinalizationRegistry
is invoked when the registered object is garbage collected. This allows you to perform cleanup tasks such as releasing resources or performing other finalization work. - Weak References: The
FinalizationRegistry
does not prevent the garbage collection of the objects it monitors. It only helps you manage cleanup when those objects are collected. - Unregistering: You can use the optional
unregisterToken
to remove a registration from the registry before the object is collected, preventing the callback from being invoked. - Garbage Collection Timing: The actual time when the cleanup callback is invoked is not deterministic. It depends on when the garbage collector decides to collect the objects.
Use Cases
Resource Management: Use FinalizationRegistry
to release resources such as file handles, network connections, or other external resources when objects are no longer needed.
Cleanup Tasks: Perform cleanup tasks or notifications when objects are garbage collected.
Example with Resource Management
Here’s an example showing how FinalizationRegistry
can be used to manage resources:
class Resource {
constructor(name) {
this.name = name;
console.log(`Resource ${name} created.`);
}
close() {
console.log(`Resource ${this.name} closed.`);
}
}
// Create a FinalizationRegistry for cleanup
const registry = new FinalizationRegistry((resource) => {
resource.close();
});
// Function to create a resource and register it
function createResource(name) {
const resource = new Resource(name);
registry.register(resource, resource);
return resource;
}
// Create and register a resource
createResource('MyResource');
// Remove strong references
// The resource will be cleaned up when garbage collected
In this example, the FinalizationRegistry
ensures that the close
method of the Resource
class is called when the resource is garbage collected, helping manage and release resources effectively.
If you missed my recent posts on ES6 (ECMAScript 2015), ES7 (ECMAScript 2016), ES8 (ECMAScript 2017) ES9 (ECMAScript 2018) and ES10 (ECMAScript 2019), you can catch up by clicking the links below to explore their features and updates.
Click here to explore ES6 (ECMAScript 2015)
Click here to explore ES7 (ECMAScript 2016)
Click here to explore ES8 (ECMAScript 2017)
Click here to explore ES9 (ECMAScript 2018)
Click here to explore ES10 (ECMAScript 2019)
Happy coding!
FAQs
What is Optional Chaining in ECMAScript 2020?
Optional Chaining (?.
) is a syntax feature that allows you to safely access nested properties of an object without having to check if each level exists. If a reference is null or undefined, the expression short-circuits and returns undefined.
const user = { profile: { name: 'Alice' } };
console.log(user.profile?.name); // 'Alice'
console.log(user.profile?.age); // undefined
console.log(user.address?.city); // undefined
How does the Nullish Coalescing Operator (??) work?
The Nullish Coalescing Operator (??
) returns the right-hand side operand when the left-hand side operand is null
or undefined
. This is different from the logical OR operator (||
), which also considers other falsy values (like 0
, NaN
, and ''
).
let value = null;
let defaultValue = 10;
console.log(value ?? defaultValue); // 10
value = 0;
console.log(value ?? defaultValue); // 0
What is Dynamic Import in ECMAScript 2020?
Dynamic Import allows you to import JavaScript modules asynchronously using the import()
function. This can be used to load modules on demand and improve the performance of applications by reducing initial load times.
import('./module.js')
.then(module => {
module.doSomething();
})
.catch(err => {
console.error('Failed to load module:', err);
});
What is BigInt in ECMAScript 2020?
BigInt is a new primitive type that allows you to represent and work with integers larger than 2^53 - 1
, which is the maximum safe integer in JavaScript. It is useful for applications that require high-precision arithmetic.
const bigNumber = BigInt('9007199254740991');
const anotherBigNumber = BigInt(123456789012345678901234567890);
console.log(bigNumber + 1n); // 9007199254740992n
What does Object.entries(), Object.values(), and Object.keys() offer in ECMAScript 2020?
These methods provide improved handling for objects:
Object.entries(obj)
returns an array of[key, value]
pairs.Object.values(obj)
returns an array of values.Object.keys(obj)
returns an array of keys.
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)); // [['a', 1], ['b', 2], ['c', 3]]
console.log(Object.values(obj)); // [1, 2, 3]
console.log(Object.keys(obj)); // ['a', 'b', 'c']
What is String.prototype.matchAll()?
String.prototype.matchAll()
returns an iterator of all matches for a regular expression, including capturing groups, in a string. This is useful for iterating over all matches and their groups.
const regex = /(\d+)/g;
const str = 'There are 15 apples and 23 oranges.';
const matches = str.matchAll(regex);
for (const match of matches) {
console.log(match[0]); // '15', '23'
console.log(match[1]); // '15', '23'
}
What is Promise.allSettled() used for?
Promise.allSettled()
returns a promise that resolves after all of the given promises have either resolved or rejected. It provides an array of objects representing the outcome of each promise, including whether it was fulfilled or rejected.
const p1 = Promise.resolve(1);
const p2 = Promise.reject('error');
const p3 = Promise.resolve(3);
Promise.allSettled([p1, p2, p3])
.then(results => {
console.log(results);
// [{ status: 'fulfilled', value: 1 }, { status: 'rejected', reason: 'error' }, { status: 'fulfilled', value: 3 }]
});
How can I check if my environment supports ECMAScript 2020 features?
To check for support of ECMAScript 2020 features, you can use:
- Feature Detection Tools: Libraries like Babel can help you write code compatible with older environments.
- Compatibility Tables: Resources such as MDN Web Docs or Can I use provide up-to-date information on feature support across different browsers and environments.
2 thoughts on “What’s New in ECMAScript 2020 (ES11)”