MEMEPh. ideas that are worth sharing...

7 Exciting New JavaScript Features

Foreword


The production process of an ECMAScript standard includes five stages from Stage 0 to Stage 4. The submission of each stage to the next stage requires TC39 approval. The new features described in this article are in Stage 3 or Stage 4, which means they should be supported in browsers and other engines soon.

 

First, the private variables of the class

One of the latest proposals is a way to add private variables to classes. We will use the # symbol to denote the private variables of the class . This eliminates the need to use closures to hide private variables that you don't want to expose to the outside world.

class Counter {
#x = 0;
#increment() {
this.#x++;
} 

onClick() {
this.#increment();
} 

}


const c = new Counter();
c.onClick(); // normal
c.#increment(); // error

A member variable or member function decorated with # becomes a private variable, and if you try to access it outside the Class, an exception will be thrown. This feature is now available in the latest versions of Chrome and Node.js.

 

Second, The optional chain operator

You've probably come across a situation where you get the notorious error when you need to access properties nested several levels inside an object Cannot read property 'stop' of undefined, and then you have to modify your code to handle every possible undefined in the property chain objects, such as:

let nestedProp = obj && obj.first && obj.first.second;

Before accessing obj.first.second, the values ​​of obj and obj.first are verified to be non-null (and not undefined). The purpose is to prevent errors from occurring. If you simply and directly access obj.first.second without checking obj and obj.first, errors may occur.

With optional chaining, you can do the same thing simply by writing:

let nestedProp = obj?.first?.second;

If obj or obj.first is null/undefined, the expression will short-circuit and return undefined.

 

Third, the gap coalescing operator

In the development process, we often encounter such a scenario: if the variable is empty, the default value is used. We implement it like this:

let c = a ? a : b // way 1
let c = a || b // way 2

Both of these methods have the obvious drawback that they will overwrite all false values ​​like (0, '', false) which might be valid inputs in some cases.

To solve this problem, it was proposed to create a "nullish" coalescing operator, denoted by ??. With it, we only set the default value when the first item is null or undefined .

let c = a ?? b;
// Equivalent to let c = a !== undefined && a !== null ? a : b;

For example there is the following code:

const x = null;
const y = x ?? 500;
console.log(y); // 500
const n = 0
const m = n ?? 9000;
console.log(m) // 0

 

Fourth. BigInt

One of the reasons JS has always been terrible at Math is that numbers larger than 2^53 cannot be represented exactly , which makes it very difficult to deal with fairly large numbers.

1234567890123456789 * 123;
// -> 151851850485185200000 // The calculation result loses precision

Fortunately, BigInt (big integer) is here to solve this problem. You can use the same operators on BigInt as normal numbers, such as +, -, /, *, %, etc.

Creating a value of type BigInt is also as simple as appending n to the number. For example, 123 becomes 123n. You can also use the global method BigInt(value) to convert, the input parameter value is a number or a number string.

const aNumber = 111;
const aBigInt = BigInt(aNumber);
aBigInt === 111n // true
typeof aBigInt === 'bigint' // true
typeof 111 // "number"
typeof 111n // "bigint"

Just add n to the end of the number to correctly calculate large numbers:

1234567890123456789n * 123n;
// -> 151851850485185185047n

There is one catch though, in most operations you can't mix BigInt with Number. Comparing Number and BigInt is OK, but not adding them.

1n < 2
// true

1n + 2
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

This feature is now available in the latest versions of Chrome and Node.js.

 

Five, static field

It allows classes to have static fields, similar to most OOP languages. Static fields can be used in place of enums, as well as private fields.

class Colors {
// public static fields
static red = '#ff0000';
static green = '#00ff00';


// private static fields
static #secretColor = '#f0f0f0';

}

font.color = Colors.red;
font.color = Colors.#secretColor; // error

This feature is now available in the latest versions of Chrome and Node.js.

 

Six, Top-level await

The async/await feature in ES2017 (ES8) only allows the use of the await keyword within async functions. The new proposal aims to allow the use of the await keyword in top-level content, such as simplifying the process of dynamic module loading:

const strings = await import(`/i18n/${navigator.language}`);

This feature is useful for debugging async content (like fetch) in the browser console without wrapping it in an async function.

Another usage scenario is that it can be used at the top level of an ES module that is initialized asynchronously (like establishing a database connection). When importing such an "async module", the module system will wait for it to be resolved before executing modules that depend on it. This way of handling asynchronous initialization is easier than currently returning an initialization promise and waiting for it to resolve. A module doesn't know if its dependencies are asynchronous.

// db.mjs
export const connection = await createConnection();
// server.mjs
import { connection } from './db.mjs';
server.start();

In this example, nothing is done until the connection is done in server.mjs in db.mjs.

This feature is now available in the latest version of Chrome.

 

Seven, WeakRef

Generally speaking, in JavaScript, references to objects are strongly retained, which means that as long as a reference to an object is held, it will not be garbage collected.

const ref = { x: 42, y: 51 };
// As long as we access the ref object (or any other reference points to it), this object will not be garbage collected

Currently in Javascript, WeakMap and WeakSet are the only way to weakly reference objects: adding an object as a key to a WeakMap or WeakSet does not prevent it from being garbage collected.

const wm = new WeakMap();
{
const ref = {};
const metaData = 'foo';
wm.set(ref, metaData);
wm.get(ref);
// return metaData
}
// At the scope of this block, we already have no reference to the ref object.
// So while it's a key in wm, we can still access it, but it can be garbage collected.
const ws = new WeakSet();
ws.add(ref);
ws.has(ref);// return true

JavaScript's WeakMap is not really a weak reference : in fact, it holds a strong reference to its contents as long as the key is still alive. A WeakMap weakly references its contents only after the key has been garbage collected.

WeakRef is a higher level API that provides true weak references, Weakref instances have a method deref that returns the referenced original object, or undefined if the original object has been collected.

const cache = new Map();
const setValue =  (key, obj) => {
  cache.set(key, new WeakRef(obj));
};


const getValue = (key) => {
  const ref = cache.get(key);
  if (ref) {
    return ref.deref();
  }
};


// this will look for the value in the cache
// and recalculate if it's missing
const fibonacciCached = (number) => {
  const cached = getValue(number);
  if (cached) return cached;
  const sum = calculateFibonacci(number);
  setValue(number, sum);
  return sum;
};

All in all, the reference of objects in JavaScript is a strong reference, WeakMap and WeakSet can provide part of the weak reference function, if you want to achieve a real weak reference in JavaScript, you can use WeakRef and finalizer (Finalizer) to achieve.

This feature is now available in the latest versions of Chrome and Node.js.