170
Help us understand the problem. What are the problem?

posted at

updated at

【JavaScript】ES2021の新機能

ES2022 / ES2021

2016年あたり以降、JavaScriptの新機能はTC39というところで決められています。

Proposalsを見てみると、箸にも棒にも掛からないものからなんか凄いものまで様々なアイデアが並んでいます。
ここから仕様を洗練していき、ブラウザ上に実装まで行われたものはFinished Proposalsとなります。

ここでは、2021年版であるES2021に取り入れられたものを見てみることにします。

ES2021

Promise.any

複数のPromiseを渡したときに、ひとつでも成功した時点で解決されるPromiseです。

既存のPromise.allは『全てのPromiseが成功したら』でありPromiseのANDですが、Promise.anyはPromiseのORということになります。

const promise1 = xxx;
const promise2 = xxx;
const promise3 = xxx;

Promise.all([promise1, promise2, promise3])
    .then(values => {
        // 全てのPromiseがfulfillされたときに呼ばれる
    })
    .catch(error => {
        // ひとつでもrejectされたら呼ばれる
    });

Promise.any([promise1, promise2, promise3])
    .then(first => {
        // ひとつでもfulfillされたら呼ばれる
    })
    .catch(error => {
        // 全てのPromiseがrejectされたときに呼ばれる
    });

複数の宛先にリクエストを投げ、どれかひとつさえ取得できれば残りは不要、みたいなときに使えるでしょう。

anyの実装によって、Promiseの集約メソッドは4種類になります。

const promise1 = xxx;
const promise2 = xxx;
const promise3 = xxx;

Promise.race([promise1, promise2, promise3])
    .then(first => {
        // fulfill/rejectに関わらずひとつでもPromiseが解決したら呼ばれる
    });

Promise.allSettled([promise1, promise2, promise3])
    .then(values => {
        // fulfill/rejectに関わらず全てのPromiseが解決したら呼ばれる
    });

最初に返ってきた値を使うPromise.race、全てが返ってくるまで待つPromise.allSettledと、キャッチするタイミングが異なる関数が幾つか存在します。
それぞれ使うのに適切な場面があるので、うまく使い分けるとよいでしょう。

String.prototype.replaceAll

これまでJSには、一致する文字列を全て置換する機能がありませんでした。
replaceは、最初のひとつだけしか置換しません。

'hoge hoge'.replace('hoge', 'fuga'); // "fuga hoge"

StackOverflowの質問には4000超というなかなか見たことのないupvoteがついています。

そんなわけでES2021になってようやくreplaceAllが登場しました。

'hoge hoge'.replaceAll('hoge', 'fuga'); // "fuga fuga"

単純にヒットした文字を全て置換します。

正規表現も使用可能ですが、何故かグローバルフラグ/gが必須になります。

'a1212b'.replace(/(\d)(\d)/, '$2$1'); // a2112b 動くが1回だけ
'a1212b'.replace(/(\d)(\d)/g, '$2$1'); // a2121b 動く

'a1212b'.replaceAll(/(\d)(\d)/, '$2$1'); // TypeError: String.prototype.replaceAll called with a non-global RegExp argument
'a1212b'.replaceAll(/(\d)(\d)/g, '$2$1'); // a2121b 動く

実はグローバルフラグを付けると通常のreplaceでも全置換になります。
なので、これまでも正規表現を使えば全置換は可能だったのですが、単純な文字列置換にわざわざ正規表現を持ち出したくはないですよね。
実際プロポーザルの動機には「正規表現にすれば全置換できるけどエスケープしたりしないといけないのでつらい」的なことが書いてありました。

replacereplaceAllで正規表現を使ったときの動作が微妙に異なるのはなんかわかりにくいですが、単純な文字列置換が主目的であり、正規表現はおまけだと考えるとよいかもしれません。
正規表現を扱えるユーザならreplaceAllなんかあってもなくてもどうにでもできるでしょうしね。

Logical Assignment Operators

いまいち適切な日本語が見当たらないのですが、論理演算子+代入演算子です。

// 論理和代入
a ||= b;
a || (a = b); // ↑と同じ

// 論理積代入
a &&= b;
a && (a = b); // ↑と同じ

// null合体代入
a ??= b;
a ?? (a = b); // ↑と同じ

a ||= bは、atrueっぽい値であればそのままで、falseっぽい値であればbにする、ということになります。
論理演算子||と代入演算子=の合体ということで論理代入演算子とでも呼べばいいですかね。

Numeric separators

数値セパレータです。

 100000000 === 100_000_000; // true

数値を_で区切って表記できるようになります。
あくまで表記上人間が見やすいようにするだけのもので、実体としては_が無い状態と同一です。

10進数以外の数値にも使えます。
また__と2個以上連続する、小数点や進数記号の前後につける、などの表記は禁止されます。
何故かRFCではハブられているのですが、PHPの数値セパレータとほぼ同じ文法です。

42;   // OK
4_2;  // OK
4__2: // SyntaxError: Only one underscore is allowed as numeric separator
42_;  // SyntaxError: Numeric separators are not allowed at the end of numeric literals
_42;  // これはただの変数

0.12;  // OK
0.1_2; // OK
0._12; // SyntaxError: Invalid or unexpected token

0xABCD;  // OK
0xAB_CD; // OK
0x_ABCD; // SyntaxError: Invalid or unexpected token

WeakRefs

弱い参照です。

PHPに実装されたときにも思ったのですが、毎回インスタンス使い捨ての言語で弱参照を使う意味がよくわからないんですよね。
Qiitaにちょうど詳しい解説があったので、詳細はそちらを見てください。
が、やっぱり必要性はよくわからん。
Nodeなら使い道があるのかな?

RFCの例
class FileStream {
  static #cleanUp(heldValue) {
    console.error(`File leaked: ${file}!`);
  }

  static #finalizationGroup = new FinalizationRegistry(FileStream.#cleanUp);

  #file;

  constructor(fileName) {
    this.#file = new File(fileName);
    FileStream.#finalizationGroup.register(this, this.#file, this);
    // 略 ファイルを読み込む
  }

  close() {
    FileStream.#finalizationGroup.unregister(this);
    File.close(this.#file);
  }

  async *[Symbol.iterator]() {
    // 略 this.#fileからデータを読む
  }
}

const fs = new FileStream('path/to/some/file');

for await (const data of fs) {
  // 略 なんかする
}
fs.close();

これはRFCに載っていた例です。
new FinalizationRegistryで、GCされたときに呼ばれるコールバック関数を登録する。
・コンストラクタのregisterで、GC対象のオブジェクトを登録する。
・ファイナライザのunregisterで、登録したオブジェクトを解除する。

closeする前にGCでオブジェクトが消去されると、FileStream.#cleanUpメソッドが呼ばれてコンソールにログが吐かれます。

ドキュメントには『重要なロジックには使うな』とか書かれてあってどうすんだこれ。

感想

JavaScriptのドラスティックな変化ってのはだいたいES2017-2018あたりでやり尽くされたかんじがあり、それ以降は"ちょっとした便利な機能の追加"ってのが多い気がしますね。
ES2021も、文法が大きく変わるような変更は入っておらず、今後書くのがちょっと楽になるようなものがメインです。

もちろんそれぞれの追加機能も便利ではあるのですが、せっかくだから何かasync/awaitレベルの革新的な機能もほしいところですね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
170
Help us understand the problem. What are the problem?