Edited at

JavaScriptのエキサイティングな新機能7選

以下はMostafa Gaafarによる記事、7 New Exciting JavaScript Features You Need to Knowの日本語訳です。


7 New Exciting JavaScript Features You Need to Know

JavaScript ( ECMA Script ) は進化する言語であり、たくさんのproposalやアイデアが出番を待ち受けています。

TC39 (Technical Committee 39) という委員会がJavaScript標準と新機能の定義を担当しています。

そして今年は彼らの活動が活発になっています。

以下は、現在ステージ3にある提案の一部の紹介です。

ステージ3は完成する直前の段階です。

これはつまり、この機能がブラウザやその他のJavaScriptエンジンにすぐに実装されることを表しています。

実際、以下のいくつかは既にブラウザに実装されています。


1. Private fields

ChromeとNodeJS12に実装済。

はい、そうです。

JavaScriptはようやくprivateを手に入れました。

もうprivateプロパティを作るためにthis._doPrivateStuff()したりクロージャに閉じ込めたりWeakMapを使ったりといった小細工を弄する必要はありません。

構文は以下のようになります。

// private修飾子は`#`

class Counter {
#x = 0;

#increment() {
this.#x++;
}

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

}

const c = new Counter();
c.onClick(); // OK
c.#increment(); // エラー

Proposal:https://github.com/tc39/proposal-class-fields


2. Optional Chaining ?.

オブジェクト内のネストの奥深くにあるプロパティにアクセスしようとして、悪名高いCannot read property 'stop' of undefinedエラーに遭遇した人は多いでしょう。

そうなったら次は、チェーン内の未定義オブジェクトに対応できるようにコードを変更します。

const stop = please && please.make && please.make.it && please.make.it.stop;

// あるいは'object-path'みたいなライブラリを使う
const stop = objectPath.get(please, "make.it.stop");

Optional Chainingがやってくると、同じことを簡単に書けるようになります。

const stop = please?.make?.it?.stop;

Proposal:https://github.com/tc39/proposal-optional-chaining


3. Nullish Coalescing ??

オプション値が存在しない場合にデフォルト値を使用するという実装はとても一般的です。

const duration = input.duration || 500;

この書き方の問題点は、||を使うと、有効な入力値である可能性のある0や''、falseといったfalseっぽい値が全て500になってしまうことです。

そんなわけでundefinedおよびnullのみを識別するNull合体演算子を新設します。

const duration = input.duration ?? 500;

Proposal:https://github.com/tc39/proposal-nullish-coalescing


4. BigInt 1n

ChromeとNodeJS12に実装済。

JavaScriptの数値演算がひどいものだった理由のひとつは、2^53を超える数値を扱うことができないということです。

幸いなことに、BigIntがこの問題を解決します。

理屈抜きで使い方を示すとこう。

// 数値リテラルにnを付けるとBigIntになる

const theBiggestInt = 9007199254740991n;

// constructorでもよい
const alsoHuge = BigInt(9007199254740991);

// constructorには文字列も渡せる
const hugeButString = BigInt('9007199254740991');

BigIntには通常の数値と同じ演算子、+-/*%などを使うことができます。

ただし、ほとんどの操作ではBigIntと通常の数値リテラルを混ぜることはできません。

BigIntとNumberを比較することはできますが、加算はできません。

1n < 2 // true

1n + 2 // Uncaught TypeError: Cannot mix BigInt and other types

Proposal:https://github.com/tc39/proposal-bigint


5. static Fields

ChromeとNodeJS12に実装済。

これはとても簡単です。

多くのOOP言語同様、クラスにstaticフィールドを設定できます。

これは列挙型の代替にも使用可能で、privateとも共存可能です。

class Colors {

// public static
static red = '#ff0000';
static green = '#00ff00';

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

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

Proposal:https://github.com/tc39/proposal-static-class-features


6. Top Level await

Chromeに実装済。

コードのトップレベルでawaitを使用できます。

いちいち非同期関数でラップせずに済むようになるので、コンソール上でfetchのような非同期処理をデバッグするときなど非常に便利です。

Async/Awaitの復習が必要なら、私の過去記事を参照ください。

もうひとつの便利な使用例は、非同期で初期化が行われるESモジュールをトップレベルで使えるようになることです。

たとえば接続を確立する必要があるデータベースなどです。

このような非同期モジュールをawaitでインポートすると、そのモジュールは初期化が終わるまで待機し、その後そのモジュールに依存する次のモジュールを実行します。

これによって、返ってきたPromiseが解決されるまで待つという現在のスタンダードよりも、ずっと簡潔に非同期を処理することができます。

モジュールは、依存先モジュールが非同期なのか同期なのかを知る必要はありません。


db.mjs

export const connection = await createConnection();



server.mjs

import { connection } from './db.mjs';

server.start();

この例では、db.mjsの処理が完了するまでserver.mjsは待機します。

Proposal:https://github.com/tc39/proposal-top-level-await


7. WeakRef

ChromeとNodeJS12に実装済。

弱い参照は、いつまでも存続しているとはかぎらない参照です。

const、let、varで生成した変数は、その変数がアクセス可能であるかぎり決してメモリから削除されることはありません。

これらは全て強い参照です。

弱い参照で生成されたオブジェクトは、GCによって削除される可能性があります。

WeakRefインスタンスには、参照された元オブジェクトを返すメソッドderefがあります。

元オブジェクトが削除されていたらundefinedになります。

これは、メモリにずっと保存しておきたい程ではない重要性の低いオブジェクトをキャッシュしておきたいときに役立ちます。

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();
}
};

// キャッシュにあればそれを返す、なければ再計算する
const fibonacciCached = (number) => {
const cached = getValue(number);
if (cached) return cached;
const sum = calculateFibonacci(number);
setValue(number, sum);
return sum;
};

不規則に削除される可能性があるため、リモートデータなどをキャッシュするために使うのは良い考えではありません。

それらにはLRUキャッシュなどを使った方がよいでしょう。

Proposal:https://github.com/tc39/proposal-weakrefs

これらのクールな新機能を使ってみて、私と同じように興奮してください。

また、ここで取り上げなかった他のプロポーザルもTC39のGitHubに公開されています。


コメント欄

「privateの#は頭おかしい」「完全に同意」「唖然とするわ」「privateキーワード追加しろよ」「いやまあ、色々と事情があるんだよ、きっと」

「Null合体演算子が楽しみ。Optional ChainingはKyle Simpsonから聞いただけだけど便利そう。」

「BigInt大歓迎。」

「弱参照知らなかった。うまく使えばWebアプリのパフォーマンスを大幅上昇できるかも。」

「TS使ってる身としてはそう目新しいものでもない。だが良いことだ。」

「Chromeで使えるのいいね。」


感想

初見のときから思ってたんだけど、privateアクセス修飾子の#はあまりに気持ち悪いですね。

staticはキーワードとして導入したのに、どうしてprivateは入れなかったのでしょうか。

もちろん理由はproposalに書かれてるのですが、主な理由としては『privateフィールドが存在していることすら外部に出したくない』だそうです。

いや、そこまでする必要あるのか?

PHPどころかJavaですら『privateフィールドが存在する』こと自体は外部からわかるぞ。

そんなわけでprivateフィールド#aのあるクラスに外からa=1とすると、privateフィールド#aとは別のpublicフィールドaが生えます。

class A{

#a = 0;
}

a = new A();
a.a = 1; // エラー出ない

privateフィールドというより、外部と完全に切り離された内部変数みたいなかんじですね。

まあ、このあたりの仕様や文法を決めるにあたっては何年もの議論があったらしいので、後からちょっと覗いた程度では窺い知れぬ何か深い事情があるのでしょう。きっと。

結果としてJavaScriptのprivateフィールドは、リフレクションのような手段をもってしてもアクセスできない、それどころか存在するかどうかすら検出できないという、非常に本気のステルス性を手に入れました。

でもChromeのコンソールとかでは普通に見えるんですけどね。

BigIntはNumberと混ぜられないのが面倒ですね。

GMPを見習ってどうぞ。


PHP

$n = gmp_init('9007199254740991');

var_dump($n+1); // GMP(9007199254740992)

PHPの場合、GMPオブジェクトに数値演算すると結果もGMPオブジェクトとなり、透過的に計算できるので便利です。

どうしてこうしなかったのでしょうか?

とまあ少々疑問に感じる点もないでもないものの、いずれの機能も、導入されれば役に立つであろうことは間違いないはずです。

また、proposalでは、他にも多くの新機能や構文が自分の登場する順番を待っています。

色々見てみると楽しいかもしれません。

個人的には追加よりむしろ末尾;補完を削除してほしいのですが、これはどう考えても無理だろうな。