29 mins read

Overview Of ECMAScript 2021 – ES2021 – “ES12” Features

Hello, JavaScript enthusiasts!

ECMAScript 2021, also known as ES12, introduced several new features and improvements to JavaScript. Here’s a summary of the key updates:

Some of the key features introduced in ES8 include:

  1. Logical Assignment Operators
  2. Numeric Separators
  3. String.prototype.replaceAll()
  4. Promise.any()
  5. WeakRefs
  6. FinalizationRegistry
  7. WeakRefs and FinalizationRegistry
  8. Import.meta.url
  9. Logical Operators with Assignment

1. Logical AND Assignment (&&=)

In ES12 (ECMAScript 2021), logical assignment operators were introduced as a way to simplify and condense common logical operations in JavaScript. These operators combine logical operations with assignments, making the code more concise. Here’s a rundown of how they work:

Logical AND Assignment (&&=)

a &&= b;

This operator works as follows:

  • If a is truthy, then a is assigned the value of b.
  • If a is falsy, then a remains unchanged.
let a = 5;
let b = 10;
a &&= b;  // Since a (5) is truthy, a is now 10.
console.log(a);  // Output: 10

a = 0;
a &&= b;  // Since a (0) is falsy, a remains 0.
console.log(a);  // Output: 0

Logical OR Assignment (||=)

a ||= b;

This operator works as follows:

  • If a is falsy, then a is assigned the value of b.
  • If a is truthy, then a remains unchanged.
let a = null;
let b = 10;
a ||= b;  // Since a (null) is falsy, a is now 10.
console.log(a);  // Output: 10

a = 5;
a ||= b;  // Since a (5) is truthy, a remains 5.
console.log(a);  // Output: 5

Logical Nullish Assignment (??=)

a ??= b;

This operator works as follows:

  • If a is null or undefined, then a is assigned the value of b.
  • If a has any other value (including falsy values like 0, NaN, or ''), then a remains unchanged.
let a = null;
let b = 10;
a ??= b;  // Since a (null) is nullish, a is now 10.
console.log(a);  // Output: 10

a = 0;
a ??= b;  // Since a (0) is not nullish, a remains 0.
console.log(a);  // Output: 0

These operators help reduce boilerplate code and make logical operations with assignments more intuitive and expressive.

2. Numeric Separators

In ES12 (ECMAScript 2021), numeric separators were introduced to improve the readability of numeric literals in JavaScript. This feature allows you to use underscores (_) to separate groups of digits in numeric literals, making large numbers easier to read and understand.

Here’s a brief overview of how numeric separators work:

Basic Usage

You can place underscores between digits in numeric literals to visually separate groups of digits. This can be particularly useful for large numbers:

const million = 1_000_000;       // Readable representation of 1,000,000
const binary = 0b1010_1011_1100_1111;  // Binary literal with separators
const hex = 0xFF_EC_DE_5E;       // Hexadecimal literal with separators
const float = 1_234_567.89;      // Floating-point number with separators

Placement Rules

  • In Integers: You can use underscores between digits in integer literals. You cannot place them at the beginning, end, or next to the decimal point. For example, 1_000_000 is valid, but _1_000_000 is not.
  • In Floating Points: You can use underscores to separate digits before and after the decimal point. For example, 1_234_567.89 is valid, but 1_234_567._89 is not.
  • In Binary, Octal, and Hexadecimal Literals: You can use underscores in binary (0b), octal (0o), and hexadecimal (0x) literals. For example, 0b1010_1011 and 0xFF_EC_DE are valid.

Invalid Uses

  • Leading and Trailing: You cannot have underscores at the beginning or end of the number, e.g., _1_000 or 1_000_.
  • Adjacent to Decimal Point: You cannot place underscores adjacent to the decimal point, e.g., 1_000._000 is invalid.
  • Multiple Consecutive Underscores: Consecutive underscores are not allowed, e.g., 1__000 is invalid.

Here are some more examples to illustrate:

const largeNumber = 123_456_789;    // 123,456,789
const hexValue = 0xA1_B2_C3_D4;    // 2692546148
const binaryValue = 0b1101_0011;   // 211 (in decimal)
const floatValue = 3.14_15_92;     // 3.141592
const bigIntValue = 1_000_000_000n; // BigInt value: 1000000000

Summary

Numeric separators make it easier to read and work with large numbers by allowing you to group digits logically. They help in maintaining code readability and reduce the likelihood of errors when dealing with numeric values in JavaScript.

3. String.prototype.replaceAll()

In ES12 (ECMAScript 2021), the String.prototype.replaceAll() method was introduced to simplify the process of replacing all occurrences of a substring or pattern in a string. Prior to this method, replacing all occurrences of a substring required using a regular expression with the global flag (g), which could be cumbersome and less intuitive.

The replaceAll() method creates a new string with all occurrences of a specified substring or pattern replaced by a new substring. This method is simpler and more readable than using regular expressions for this purpose.

Replacing All Occurrences of a Substring

const text = "Hello world! Welcome to the world!";
const newText = text.replaceAll("world", "universe");
console.log(newText);  // Output: "Hello universe! Welcome to the universe!"

Replacing All Occurrences Using a Regular Expression

const text = "Hello world! Hello universe!";
const newText = text.replaceAll(/hello/i, "Hi");
console.log(newText);  // Output: "Hi world! Hi universe!"

In this case, the regular expression /hello/i matches “hello” regardless of case, and the global flag (g) ensures all occurrences are replaced.

Important Notes

  1. replaceAll() vs replace(): The replace() method only replaces the first occurrence or a specified occurrence of the substring. To replace all instances, you would typically need to use a regular expression with the global flag. replaceAll() simplifies this by handling all replacements directly.
    const text = "foo bar foo";
    const replacedWithReplace = text.replace(/foo/, "baz");  // Only replaces the first occurrence
    const replacedWithReplaceAll = text.replaceAll("foo", "baz");  // Replaces all occurrences
    
    console.log(replacedWithReplace);      // Output: "baz bar foo"
    console.log(replacedWithReplaceAll);   // Output: "baz bar baz"
    
  2. Immutable Strings: Strings in JavaScript are immutable, so replaceAll() returns a new string with the replacements, leaving the original string unchanged.
  3. Error Handling: If the searchValue is a regular expression that does not have the global flag, it will throw a TypeError.
    const text = "foo foo foo";
    try {
      text.replaceAll(/foo/, "bar");  // Error: "searchValue" must be global RegExp or string
    } catch (e) {
      console.error(e);  // Output: TypeError: replaceAll() requires a global RegExp or a string
    }
    

Overall, String.prototype.replaceAll() enhances the readability and convenience of string manipulation in JavaScript, especially when you need to perform multiple replacements.

4. Promise.any()

In ES12 (ECMAScript 2021), the Promise.any() method was introduced to handle cases where you need to wait for the first promise to fulfill successfully among a set of promises. This method is useful when you want to proceed with the result of the first resolved promise and ignore the rest, even if some of the promises are rejected.

Overview

Promise.any() takes an iterable of Promise objects and, as soon as one of the promises fulfills, returns a single Promise that resolves with the value from that promise. If all of the given promises are rejected, Promise.any() returns a promise that rejects with an AggregateError — a new subclass of Error that groups together all the individual errors.

Basic Example

const p1 = Promise.reject('Error 1');
const p2 = Promise.resolve('Success 2');
const p3 = Promise.reject('Error 3');

Promise.any([p1, p2, p3])
  .then(result => {
    console.log(result);  // Output: "Success 2"
  })
  .catch(error => {
    console.error(error);
  });

In this example, p2 is the first promise to fulfill successfully, so Promise.any() resolves with the value 'Success 2'.

Handling All Promises Being Rejected

If all the promises are rejected, Promise.any() will reject with an AggregateError, which contains an array of all the rejection reasons.

const p1 = Promise.reject('Error 1');
const p2 = Promise.reject('Error 2');
const p3 = Promise.reject('Error 3');

Promise.any([p1, p2, p3])
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error);  // Output: AggregateError: All promises were rejected
    console.error(error.errors);  // Output: ["Error 1", "Error 2", "Error 3"]
  });

In this case, since all the promises are rejected, Promise.any() returns an AggregateError with all the individual errors.

Key Points

  • Fulfillment vs. Rejection: Promise.any() only resolves when the first promise fulfills. It ignores rejected promises until one of the promises resolves successfully. If all promises are rejected, it handles this scenario by rejecting with AggregateError.
  • AggregateError: This is a special type of error introduced in ES12 to represent multiple errors. It is used by Promise.any() to aggregate all rejection reasons.
  • Iterables: The iterable passed to Promise.any() can be any iterable object, like an array. However, all elements in the iterable must be promises or values that can be converted to promises.
  • Order of Fulfillment: If multiple promises fulfill at the same time, Promise.any() resolves with the result of the first promise in the iterable that fulfills.

Summary

Promise.any() provides a way to handle scenarios where you are interested in the first successful result from a set of promises, rather than waiting for all of them to settle or handling errors immediately. It simplifies handling multiple asynchronous operations where you want to proceed as soon as one operation succeeds.

5. WeakRefs

In ES12 (ECMAScript 2021), the WeakRef class was introduced as part of the ECMAScript specification to provide a way to hold weak references to objects. Weak references allow you to hold a reference to an object without preventing that object from being garbage-collected. This can be useful in scenarios like caching, where you want to hold on to objects while they are still needed but allow them to be cleaned up when they are no longer in use elsewhere.

Overview of WeakRef

  • Purpose: WeakRef provides a way to reference an object without preventing it from being garbage-collected. This is particularly useful for scenarios where you want to maintain a reference to an object that may be freed if there are no other references to it.
  • Behavior: A WeakRef does not prevent its target object from being collected by the garbage collector. If the target object is collected, accessing it through a WeakRef will result in a null or undefined reference.

Methods

deref(): This method returns the target object if it is still reachable. If the object has been garbage-collected, deref() returns undefined.

Example Usage

Here’s a basic example demonstrating the use of WeakRef:

let obj = { name: 'Example Object' };
let weakRef = new WeakRef(obj);

// Accessing the object through WeakRef
console.log(weakRef.deref());  // Output: { name: 'Example Object' }

// Dropping the strong reference
obj = null;

// Attempt to access the object again
console.log(weakRef.deref());  // Output: undefined (object might have been collected)

Use Cases

  • Caching: WeakRef can be used in caching mechanisms where you want to cache objects but allow them to be garbage-collected when memory is low. This way, you don’t need to manually manage the lifecycle of the cached objects.
  • Memoization: In scenarios where you want to memoize results of expensive computations but allow those results to be cleaned up if they are no longer needed, WeakRef can be helpful.

Important Considerations

  • Garbage Collection: Objects referenced by WeakRef are not guaranteed to remain alive. If the object is collected, WeakRef.deref() will return undefined. Thus, you should always handle the case where the object might have been garbage-collected.
  • Usage: WeakRef should be used cautiously as it can make code harder to reason about, especially in complex scenarios where the garbage collection behavior affects program logic.
  • Not for Direct Manipulation: Unlike WeakMap and WeakSet, WeakRef is not typically used for directly managing weak references but rather for more specialized cases where weak references are needed.

Summary

WeakRef provides a way to hold references to objects without preventing them from being garbage-collected. This feature is useful in certain scenarios like caching and memoization, where you want to manage object lifetimes without preventing garbage collection. It’s important to handle cases where the object may have been collected and to understand that WeakRef does not guarantee the object's persistence.

6. FinalizationRegistry

In ES12 (ECMAScript 2021), the FinalizationRegistry class was introduced to provide a way to perform cleanup operations when objects are garbage-collected. This is useful for managing resources or performing finalization tasks for objects that may be cleaned up by the JavaScript engine.

Overview of FinalizationRegistry

Purpose: FinalizationRegistry allows you to register a callback to be invoked when objects are garbage-collected. This can be helpful for cleaning up resources or performing any final operations related to the objects that are no longer needed.

Behavior: When an object registered with a FinalizationRegistry is garbage-collected, the callback associated with that object is invoked, allowing you to clean up or perform necessary operations related to the object.

Registering an Object

registry.register(object, heldValue, unregisterToken);
  • object: The object that you want to register with the FinalizationRegistry. This is the object you want to track for garbage collection.
  • heldValue: An optional value that can be associated with the object. This value is passed to the cleanupCallback when the object is collected.
  • unregisterToken: An optional token that can be used to unregister the object before it is garbage-collected.

Unregistering an Object

registry.unregister(unregisterToken);

unregisterToken: The token used to unregister the object. This prevents the cleanupCallback from being called for that object.

Example Usage

Here's an example demonstrating how FinalizationRegistry works:

// Define the cleanup callback
function cleanupCallback(heldValue) {
  console.log(`Cleaning up: ${heldValue}`);
}

// Create a FinalizationRegistry instance
const registry = new FinalizationRegistry(cleanupCallback);

// Register an object with the registry
let obj = { name: 'Example Object' };
const token = {};  // Unregister token
registry.register(obj, 'Object was here', token);

// At this point, `obj` still exists, so the cleanup callback has not been called yet
console.log('Object registered');

// Remove strong reference to the object
obj = null;

// Force garbage collection (in some environments, you might need to manually trigger this)
// The cleanup callback should be called when `obj` is collected

Key Points

  1. Cleanup Callback: The cleanupCallback function is invoked with the heldValue when the registered object is garbage-collected. This allows you to perform cleanup operations related to the object.
  2. Unregistering: You can unregister objects from the FinalizationRegistry using an optional token. This stops the cleanup callback from being invoked for that specific object.
  3. Garbage Collection Timing: The exact timing of when the cleanupCallback is called is not guaranteed and depends on when the JavaScript engine decides to perform garbage collection. This means you should not rely on precise timing for cleanup operations.
  4. Usage Scenarios: FinalizationRegistry is particularly useful for resource management in cases where you need to release external resources or perform cleanup operations when objects are no longer in use.
  5. Object Identity: The registry tracks objects by their identity, not by their value. Therefore, if the object is replaced with another object that has the same value but a different reference, the FinalizationRegistry will not treat them as the same object.

Summary

FinalizationRegistry provides a mechanism for handling cleanup tasks related to objects that are about to be garbage-collected. It allows you to register objects and specify a callback to be invoked when those objects are no longer reachable. This is useful for managing resources, performing cleanup, and ensuring that finalization tasks are handled appropriately when objects are removed from memory.

7. WeakRefs and FinalizationRegistry

In ES12 (ECMAScript 2021), WeakRef and FinalizationRegistry were introduced as part of the standard to manage memory and cleanup resources more effectively. Both features facilitate handling objects and executing operations upon their garbage collection, but they serve distinct purposes and are suited to different scenarios.

WeakRef

WeakRef provides a way to hold a weak reference to an object, which allows that object to be garbage-collected even if it is referenced by a WeakRef. This can be useful for scenarios like caching, where you want to keep a reference to an object but allow it to be collected if no other strong references exist.

Key Features
  • Weak Reference: A WeakRef does not prevent its target object from being collected by the garbage collector.
  • Deref Method: You can use the deref() method to access the target object if it is still reachable. If the object has been collected, deref() returns undefined.
let obj = { name: 'Example Object' };
let weakRef = new WeakRef(obj);

// Access the object through WeakRef
console.log(weakRef.deref());  // Output: { name: 'Example Object' }

// Drop the strong reference
obj = null;

// Attempt to access the object again
console.log(weakRef.deref());  // Output: undefined (object might have been collected)

FinalizationRegistry

The FinalizationRegistry allows for the registration of cleanup callbacks that are triggered when objects are garbage-collected. This is useful for managing resource cleanup or finalization tasks when objects are no longer in use.

Key Features
  • Cleanup Callback: Allows you to register a callback function that is invoked when the registered object is garbage-collected.
  • Registration: You can register objects with a FinalizationRegistry and optionally associate a value with the object.
  • Unregistering: You can unregister objects using a token to prevent the callback from being invoked.
// Define the cleanup callback
function cleanupCallback(heldValue) {
  console.log(`Cleaning up: ${heldValue}`);
}

// Create a FinalizationRegistry instance
const registry = new FinalizationRegistry(cleanupCallback);

// Register an object with the registry
let obj = { name: 'Example Object' };
const token = {};  // Unregister token
registry.register(obj, 'Object was here', token);

// At this point, `obj` still exists, so the cleanup callback has not been called yet
console.log('Object registered');

// Remove strong reference to the object
obj = null;

// Force garbage collection (in some environments, you might need to manually trigger this)
// The cleanup callback should be called when `obj` is collected

Using WeakRef and FinalizationRegistry Together

In some scenarios, you might use both WeakRef and FinalizationRegistry together. For example, you can use a WeakRef to maintain a weak reference to an object, while a FinalizationRegistry ensures that a cleanup callback is triggered when the object is garbage-collected.

// Define the cleanup callback
function cleanupCallback(heldValue) {
  console.log(`Cleaning up: ${heldValue}`);
}

// Create a FinalizationRegistry instance
const registry = new FinalizationRegistry(cleanupCallback);

// Create a WeakRef instance
let obj = { name: 'Example Object' };
const weakRef = new WeakRef(obj);

// Register the object with the FinalizationRegistry
registry.register(obj, 'Object was here');

// Remove strong reference
obj = null;

// Check if WeakRef still holds the object
console.log(weakRef.deref());  // Output: undefined (object might have been collected)

// The cleanupCallback will be invoked when the object is garbage-collected

Summary

  • WeakRef: Provides a weak reference to an object, allowing the object to be garbage-collected if no strong references exist. Use WeakRef to implement features like caches or to reference objects without preventing their collection.
  • FinalizationRegistry: Registers cleanup callbacks that are invoked when objects are garbage-collected. Use FinalizationRegistry to handle resource cleanup or finalization tasks for objects when they are no longer reachable.

Both features are designed to manage memory and cleanup more effectively and should be used according to the specific needs of your application, ensuring efficient resource management and proper cleanup of unused objects.

8. Import.meta.url

In ES12 (ECMAScript 2021), the import.meta.url feature was introduced as part of the ECMAScript modules (ESM) specification. This feature provides a way to access the URL of the module in which the import.meta is used. It's useful for various purposes, such as resolving relative URLs or understanding the location of the module within the application.

Overview of import.meta.url

  • Purpose: import.meta.url gives you access to the URL of the current module. This URL can be useful for resolving paths relative to the module or for debugging purposes.
  • Usage Context: This feature is available only in ECMAScript modules, which are supported in environments that comply with the ES module specification.
Basic Example
// In a file named `module.js`
console.log(import.meta.url);
// Output might be: "file:///path/to/your/module.js" (in a Node.js environment) or an absolute URL in a browser environment
Resolving Relative Paths

You can use import.meta.url to resolve paths relative to the module. This is especially useful in environments like Node.js or when working with module bundlers.

// In a file named `module.js`
import path from 'path'; // Node.js module for handling file paths

const currentUrl = new URL(import.meta.url);
const resolvedPath = path.resolve(currentUrl.pathname, '../data/file.txt');

console.log(resolvedPath);
// Output might be: "/path/to/your/data/file.txt"

In this example, new URL(import.meta.url) converts the module URL to a URL object, and path.resolve is used to compute a file path relative to the module.

Use Cases
  • Dynamic Imports: import.meta.url is useful for dynamically importing modules or resources based on the module's location. For example, you might use it to construct paths to other modules or assets in a way that is relative to the current module.
  • Relative Resource Loading: When loading resources or assets relative to a module, import.meta.url provides a reliable way to construct URLs or file paths that work consistently across different environments.
  • Debugging and Diagnostics: Knowing the URL of the module can be helpful for debugging and diagnostics, as it provides context about where the module is located.y

Important Considerations

  1. Browser vs. Node.js: The format of import.meta.url differs depending on the environment. In browsers, it is usually a URL that includes the protocol (http://, https://, etc.), whereas in Node.js, it is a file URL.
  2. Module Resolution: When working with tools or bundlers, be aware that import.meta.url behavior might be influenced by how the tool resolves modules and paths.
  3. Not Available in Scripts: import.meta.url is only available in ES modules. It is not available in traditional script contexts or CommonJS modules.

Summary

import.meta.url provides the URL of the module in which it is used. This feature is particularly useful for resolving paths relative to the module or for managing dynamic imports. It enhances the ability to work with modules in a more context-aware manner, making it easier to handle resources and debug issues in both browser and Node.js environments.

9. Logical Operators with Assignment

In ES12 (ECMAScript 2021), the logical assignment operators were introduced to simplify and condense common logical operations involving assignment. These operators combine logical operations (&&, ||, ??) with assignment into a single, more concise syntax.

Logical Assignment Operators

1. Logical AND Assignment (&&=)

The &&= operator assigns a value to a variable only if the variable is truthy. It is a shorthand for combining a logical AND operation with assignment.

let a = 5;
let b = 10;
a &&= b;  // Since a (5) is truthy, a is now 10.
console.log(a);  // Output: 10

a = 0;
a &&= b;  // Since a (0) is falsy, a remains 0.
console.log(a);  // Output: 0
Logical OR Assignment (||=)

The ||= operator assigns a value to a variable only if the variable is falsy. It is a shorthand for combining a logical OR operation with assignment.

let a = null;
let b = 10;
a ||= b;  // Since a (null) is falsy, a is now 10.
console.log(a);  // Output: 10

a = 5;
a ||= b;  // Since a (5) is truthy, a remains 5.
console.log(a);  // Output: 5
Logical Nullish Assignment (??=)

The ??= operator assigns a value to a variable only if the variable is null or undefined. It is a shorthand for combining a logical nullish coalescing operation with assignment.

let a = null;
let b = 10;
a ??= b;  // Since a (null) is nullish, a is now 10.
console.log(a);  // Output: 10

a = 0;
a ??= b;  // Since a (0) is not nullish, a remains 0.
console.log(a);  // Output: 0

Key Points

  • Logical AND Assignment (&&=): Useful for conditionally updating a variable only if it is already truthy.
  • Logical OR Assignment (||=): Useful for setting a variable to a default value only if it is falsy.
  • Logical Nullish Assignment (??=): Useful for setting a variable to a default value only if it is null or undefined.

These logical assignment operators help streamline code by reducing boilerplate and making logical operations involving assignment more readable and concise. They are especially helpful in scenarios where you frequently need to update variables based on their current value and a logical condition.

Happy coding!

If you missed my recent posts on ES6 (ECMAScript 2015), ES7 (ECMAScript 2016), ES8 (ECMAScript 2017), ES9 (ECMAScript 2018), ES10 (ECMAScript 2019) and ES11 (ECMAScript 2020), 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)
Click here to explore ES11 (ECMAScript 2020)

FAQs

Logical Assignment Operators are shorthand for combining logical operations with assignment. They streamline common patterns:

  • x &&= y: Equivalent to x && (x = y).
  • x ||= y: Equivalent to x || (x = y).
  • x ??= y: Equivalent to x ?? (x = y).

These operators help in writing more concise code, especially when dealing with default values or conditions.

Numeric Separators allow you to use underscores (_) to separate digits in numeric literals, making large numbers easier to read. For example:

  • 1_000_000 is easier to read than 1000000.
  • 0b1010_1011 for binary literals.
  • 0xFF_FF_FF_FF for hexadecimal literals.

This feature helps developers quickly understand the magnitude of numbers, enhancing code readability.

The String.prototype.replaceAll() method allows you to replace all occurrences of a substring or pattern within a string. Unlike replace(), which only replaces the first occurrence or requires a global regular expression to replace multiple occurrences, replaceAll() simplifies the process:

const str = 'hello world world';
const newStr = str.replaceAll('world', 'there');
console.log(newStr); // Output: 'hello there there'

This method improves efficiency and readability when replacing multiple substrings.

Promise.any() takes an iterable of Promise objects and, as soon as one of the promises in the iterable fulfills, returns a single promise that resolves with the value from that promise. If no promises fulfill (all are rejected), it returns a promise that rejects with an AggregateError containing all rejection reasons.

const p1 = Promise.reject('Error 1');
const p2 = Promise.reject('Error 2');
const p3 = Promise.resolve('Success');

Promise.any([p1, p2, p3])
.then(value => console.log(value)) // Output: 'Success'
.catch(error => console.log(error));

WeakRef provides a way to hold a weak reference to an object, meaning that the object can be garbage-collected if there are no other strong references to it. This is useful for cases where you want to maintain a reference without preventing garbage collection:

let obj = { name: 'example' };
let weakRef = new WeakRef(obj);

obj = null; // Remove the strong reference

let deref = weakRef.deref();
console.log(deref); // Might be undefined if the object has been garbage collected

Weak references are used to avoid memory leaks by not preventing garbage collection of objects that are no longer needed.

One thought on “Overview Of ECMAScript 2021 – ES2021 – “ES12” Features

Leave a Reply

Your email address will not be published. Required fields are marked *