💐

JavaScriptのES2021で追加された新機能まとめ

2021/06/24に公開
5

2022/06/22追記
最新仕様ES2022がリリースされたので、新しく記事を書きました。

https://zenn.dev/tonkotsuboy_com/articles/es2022-whats-new


JavaScriptの仕様はECMAScriptで、ECMAScript 2015(ES2015)、ECMAScript 2016(ES2016)...というように毎年進化を続けています。

これまでの仕様はES2020でしたが、先日2021年6月22日にES2021が正式仕様として承認されました。

ブラウザ対応も完了しており、全モダンブラウザ(Google Chrome・Firefox・Safari・Microsoft Edge)でES2021の全機能が使えます。

本記事では、ES2021すべての新機能をまとめて紹介します。

大きな数値を_区切りで書ける

構文
数値_数値_数値

▼ 簡単な例

100_000_000;  // 1億(100,000,000)

説明

10000000という数値を見たとき、それが「10,000,000」なのか「1,000,000」なのか、ぱっと見て区別をつけるのは難しいです。数字を_(アンダースコア)で区切り、数値を読みやすくできるようになります。

100_000_000;  // 1億(100,000,000)
12_234; // 1万2234(12,234)
`${1_000_000}ドルの夜景`;  // 100万ドルの夜景

_で区切られていても、通常の数値として扱えるので計算など各種数値の操作が可能です。

console.log(222_222 * 2);
// 結果: 444444

16進数の区切りでも入れられる

区切りは3桁ごとに入れる必要はなく、任意の箇所で入れられます。ただ、10進数の場合は3桁区切りにしておいたほうが他の開発者の混乱を招かないでしょう。

// OKだが読みづらい
1_23_4;

区切りは数値であれば何でも使えます。16進数で色表現をする際の表現としても便利です。

0xff_00_00; // 0xff0000
0xa0_b0_c0; // 0xa0b0c0

関連資料

マッチした文字列をすべて置換できる文字列.replaceAll()

構文 戻り値
文字列.replaceAll(文字列または正規表現, 文字列) String

▼ 簡単な例

"👺👺👺😈😈😈".replaceAll("😈", "🔥");
// 結果: "👺👺👺🔥🔥🔥"

説明

replaceAll()とは、引数にマッチした文字列をすべて置換できるメソッドです。

従来、文字列の置換用のメソッドとして、文字列.replace()メソッドがありました。replace()メソッドは、正規表現または文字列を引数に取り、マッチした文字列を置換するメソッドです。引数が文字列の場合、「最初にマッチした文字列だけを」置換します。すべての文字列を置換するためには、引数に正規表現を使う必要がありました。

replaceAll()は、replace()と異なり文字列の引数でもすべての文字列を置換できます。

文字列.replaceAll()の挙動確認

「猫田猫男は猫好きだ」という文字列の、「猫」を「犬」に変換する例で考えてみましょう。文字列"猫"を引数にしてreplace()メソッドを使った場合、最初の「"猫"」しか置換されません。

"猫田猫男は猫好きだ".replace("猫", "犬");
// 結果: "犬田猫男は猫好きだ"

すべての「猫」を「犬」に変換するためには、引数を正規表現にしてgフラグを指定し、「/猫/g」とする必要があります。

"猫田猫男は猫好きだ".replace(//g, "犬");
// 結果: "犬田犬男は犬好きだ"

ES2021のreplaceAll()を使えば、正規表現を使わずとも引数の文字列すべてを置換できます。よりシンプルに文字列の一括置換ができるようになったと言えるでしょう。

"猫田猫男は猫好きだ".replaceAll("猫", "犬");
// 結果: 「"犬田犬男は犬好きだ"」

▼ 実行結果

関連資料

複数のPromiseのうち、最初のresolveを待つPromise.any

構文 戻り値
Promise.any(Promiseの配列) Promise

▼ 簡単な例

Promise.any([
  promise1, promise2, promise3
]).then(first => {
  // 3つのpromiseのうち、最初に解決したpromiseが出力される
  console.log(first)
});

説明

Promiseでは非同期処理を扱えますが、制作の現場では複数の非同期処理を同時に扱いたいことが多いでしょう。Promise.anyとは、複数のPromiseのうち、どれか1つでもresolveしたらその時点で解決されるPromiseです。最初のresolveがあるまでは、rejectは無視されます。

Promise.any()の挙動確認コード

次のコードでPromise.any()の挙動を確認してみましょう。3つのPromiseは、それぞれ次の挙動をします。

  • promise1: 1秒後に「reject」されます
  • promise2: 2秒後にresolveします
  • promise2: 3秒後にresolveします

promise1は一番完了が早いですが、rejectされるため無視されます。promise2がresolveされるのが一番早いため、Promise.anyではpromise2をもってresolveします。

// 1秒後に「reject」されるPromise
const promise1 = new Promise((resolve, reject) =>
  setTimeout(reject, 1000, "promise1")
);

// 2秒後にresolveするPromise
const promise2 = new Promise((resolve) =>
  setTimeout(resolve, 2000, "promise2")
);

// 3秒後にresolveするPromise
const promise3 = new Promise((resolve) =>
  setTimeout(resolve, 3000, "promise3")
);

Promise.any([promise1, promise2, promise3]).then((resolve) => {
  console.log(resolve);
});

▼ 実行結果

resolveされるPromiseが1つもない場合は、AggregateErrorでrejectされます。

Promise.race()との違い

Promise.any()に似た処理として、Promise.race()があります。

Promise.any()
引数のPromiseが最初に「resolveした時点で」終了する

Promise.race()
引数のPromiseが最初に「終了(resolveまたはreject)した時点で」終了する

Promise.race()の実行結果

他のPromiseの複数処理

ES2015で導入されたPromise.all()、ES2020で導入されたPromise.allSettled()など、Promiseの複数処理はいくつかあります。この機会にそれぞれとの違いを確認しておくとよいでしょうw。

https://twitter.com/tonkotsuboy_com/status/1252519470523772929?conversation=none

関連資料

??||&&=を組み合わせて使える演算子

構文
a ??= b
`a
a &&= b

▼ 簡単な例

let a = null;
a ??= "🐈";
console.log(a); // 結果: "🐈"

let b = "🐷";
b ??= "🐈";
console.log(b); // 結果: "🐷"

説明

??=演算子、||=演算子、&&=演算子とは、??||&&=を組み合わせられる演算子です。

??=演算子

??=演算子とは、anullundefinedのときに、abを代入するための演算子です。

ES2020では、??演算子が実装されました。a ?? bという形で用い、anullまたはundefinedのときのみbを返せます。||演算子はaがfalsyなもの(0""false)のときもbを返すのに対して、厳密にnullundefinedを判定できるので、開発の現場では重宝する処理です。

??=演算子は、??=を組み合わせたもの。次の挙動と同じ意味です。

// aがnullかundefinedのときに、aにbを代入する
a ?? (a = b);

const human = { name: "田中" }というオブジェクトのプロパティを通して、??=の挙動を確認してみましょう。

human.age ??= 18ageプロパティに18の代入を試みています。ageプロパティはundefinedなので、18が代入されます。

human.name ??= "鈴木"では、nameプロパティがundefinedではないので、"鈴木"は代入されません。

const human = { name: "田中" };
human.age ??= 18;
// hunan.ageはundefinedなので18が代入される
human.name ??= "鈴木";
// hunan.nameはnullではないので何も代入されない
console.log(human);
// 結果: {name: "田中", age: 18}

▼ 実行結果

||=演算子

aがfalsyなものの場合に、abを代入します。

let a = 0;
a ||= "🐈";
console.log(a); // 結果: "🐈"

let b = "🐷";
b ||= "🐈";
console.log(b); // 結果: "🐷"

||=は、たとえばHTML要素のinnerHTMLのデフォルト値を設定するようなケースで使えます。次のコードでは、.foo要素の中身が空の場合に、<p>値なし</p>の要素を代入できます。

document.querySelector(".foo").innerHTML ||= "<p>値なし</p>";

&&=演算子

aがtruthyなものの場合に、abを代入します。

let a = null;
a &&= "🐈";
console.log(a); // 結果: null

let b = "🐷";
b &&= "🐈";
console.log(b); // 結果: "🐈"

関連資料

弱参照を作れるWeakRef

構文 戻り値
new WeakRef(対象) WeakRef

▼ 簡単な例

const myObject = { name: "田中" };
// myObjectへの弱参照が作られる
const ref = new WeakRef(myObject);

説明

WeakRefは、JavaScriptにおいて弱参照を作れるオブジェクトです。参照はされているものの、参照先はガベージコレクションの対象となります。また、オブジェクトがガベージコレクトされた際、FinalizationRegistryオブジェクトによりコールバックを実行できるようになりました。ただし、ガベージコレクション周りの処理は複雑であり、可能であれば使用を避けることがよいとされています(「tc39 / proposal-weakrefs」参照)。

WeakRefの詳しい内容は、@uhyoさんの解説記事がわかりやすいので、そちらを参照するとよいでしょう。

FinalizationGroupFinalizationRegistryに読み替えてください

関連資料

対応環境

本記事で紹介したES2021の全機能は、各環境で使用可能です。

  • Google Chrome
  • Firefox
  • Safari
  • Microsoft Edge
  • TypeScript
  • Node.js

ECMAScript compatibility table」で、細かい対応バージョンを確認できます。
※ 執筆時点では、TypeScriptの最新対応状況が反映されていないようです

ES2021を使って便利に開発しよう

本記事では正式仕様としてリリースされたES2021の新機能を紹介しました。どれも開発をラクにしてくれるものばかりで、筆者も積極的に開発の現場で使っています。ECMAScriptは次のES2022に向けて仕様策定がすでに始まっています。top levelでのawaitやclass fieldなど、また便利な機能が入ってきそうです。新しい機能をキャッチアップし、楽しく開発していきましょう。

ES2021のLanguage Specificationは、こちらから確認できます。

TwitterでもJavaScriptやCSSの最新情報を発信しています!
https://twitter.com/tonkotsuboy_com

GitHubで編集を提案

Discussion

しっぽくんしっぽくん

文字列.stringAll()の挙動確認

文字列.replaceAll()の挙動確認
でしょうか?

鹿野 壮鹿野 壮

ご指摘ありがとうございます! typoでしたので修正しました📝

鹿野 壮鹿野 壮

Promise.any()Promise.race()の違いについて説明を加えました。

Yosuke ToyotaYosuke Toyota

記事の情報、ありがたいです。

1点、細かいところですみません。 hunan.age ⇒ hunan.name となるかと思いました。

human.name ??= "鈴木";
// hunan.ageはnullではないので何も代入されない