フューチャー技術ブログ

ES2021/ES2022を知ろう

こんにちは、2017年入社の柏木です。この記事はフロントエンド連載の4記事目です。

少し前にES2021にて変更される新機能が発表されました。

JavaScriptを扱う上では知っとくべき!と意気込んだもののECMAScriptが何者なのかすら曖昧な理解だったので、この機会に学んだことをまとめてみようと思います。

この記事のゴール

  • ECMAScriptがJavaScriptにとってどのような役割を持つのかを知る
  • ここ最近のECMAScriptの動向を知る

記事の想定読者

  • JavaScriptは触ったことがあるけど、記法のことは詳しく知らない人

ES2021/ES2022とは

ESは ECMAScript(エクマスクリプト)の略称で、JavaScriptの標準規格のことです。2021や2022というのはその規格がリリースされる年度のことで、たとえばES2021は今年リリース、ES2022は来年にリリース予定の規格ということになります。

公開年にあわせた表記(例:ES2021)とEdition表記(例:ES11)がありますが、どちらも同じものを指しています。

ここでは分かりやすいように年度に合わせた表記とします。

なぜECMAScriptが必要なのか

JavaScriptはすでに広く知れ渡っていて枯れた技術と言えそうですが、なぜ今も標準規格(ECMAScript)の改良が必要なのでしょうか。

その答えはJavaScriptそのものの歴史と深く関連しています。

JavaScriptはもともと、ネットスケープコミュニケーションズという会社によって開発されました。(Googleから生まれたGo言語のようですね)この会社はウェブブラウザを作っており、JavaScriptも自分たちのウェブブラウザで動くよう設計されたものでした。

その後マイクロソフトがInternet ExplorerでもJavaScriptを使用できるようにしたことでJavaScriptが急速に普及していきましたが、前述の通りJavaScriptは特定のウェブブラウザに対応するように実装されていたためブラウザごとに言語仕様が異なっていました。

この状況を打破するため、Ecma Internationalという団体が言語仕様の標準化を行いました。ECMAScriptの誕生です。

Ecma Internationalとはスイスのジュネーブに設立されたNPO法人団体で、JavaScriptに限らず情報通信システムの分野でさまざまな言語の標準化を行っています。特定のメーカーで使用することを想定した言語を他でも使えるようにしたり、複数のメーカーが同じような仕組みを作るという無駄を防ぐため、1960年の創設から現在に至るまでより良い標準規格を目指して検討、改良が行われています。

プログラマーが使用する媒体の互換性をそこまで気にせずに開発ができるのも、こういった団体の努力によるおかげなんですね。感謝の一言に尽きます。

このようにブラウザを気にすることなく、またより快適にJavaScriptを実行することが、ECMAScriptの記法に則る目的です。

NOTE: ECMAScriptはブラウザ用のJavaScriptの規格である

ここでJavaScriptを実行する環境を「ブラウザ」と限定しているのは、前述の通りJavaScriptはブラウザのための言語として作られているためです。

JavaScriptが広まった当時、ブラウザ以外の環境でもJavaScriptを動かそうという動きが生まれましたが、そのような整備がなされていませんでした。

そこで生まれたのがCommonJSです。サーバーサイドJavaScriptとして知られるNode.jsもCommonJSの仕様に則っています。

ECMAScript/CommonJSのほかにもJavaScriptのモジュール化のしくみはあって、それぞれ動いているようですが、現時点で統一されるという話は特にないようです。

Node.jsがECMAScriptの新しい記法をどこまでサポートしているかは、こちらのサイト(node.green)で確認することができます。

Node.jsとECMAScriptの関係については、こちらに丁寧な説明がありましたのでぜひご覧ください。
JavaScriptが辿った変遷

ここ最近のECMAScriptの変遷

ECMAScriptの策定プロセス

JavaScriptをいろんなブラウザで便利に使えるようにしましょう、という目的で様々な新記法が登場しているECMAScriptですが、仕様の変更はどのように決められているのでしょうか。

ES2015までは、変更しようとしている全ての仕様について合意が取れるまでコミュニティ内で議論が行われていました。

しかしそれにより、ES4のリリースの際、多くの変更を入れようとしてコミュニティ内で議論が別れてしまい、最終的に合意が取れず提案破棄となってしまいました。

この時に数年のリソースが無駄になってしまった反省から、ES2015以降では安定したリリースを目指して提案の策定プロセスが変更されました。

個別の変更内容が5段階のStageに分けられ、リリース時期(毎年6月ごろ)までにStage4まで進んでいるものが、次期のECMAScriptのリリース対象となります。

Stage Name 説明
0 Strawperson アイディアレベルのもの。
1 Proposal 変更内容の根拠の説明、解決方法の概要、潜在的な変更点の特定を行う。
2 Draft 文法やそれが表す意味を正確に説明する。初期のテストも行う。
3 Candidate ほぼ仕様書。ここの突破には実装とフィードバックを必要とする。
4 Finished 策定完了の内容。

ES2021/ES2022から読み解くECMAScriptの方針

策定プロセスを考えると必要度の高い変更から検討・実装が行われリリースに至っているようですね。
逆に言えば、ここで取り入れられたそれぞれの変更内容がこれからJavaScriptが目指そうとしている方向を示しているのではないでしょうか!?

直近2、3年の新機能からJavaScriptの動向を私なりに解釈してみました。

以下、ES2021、ES2022の新機能についてざっくりと説明します。コードは主にECMAScriptの公式Githubを参考にさせていただいています。

また、昨今TypeScriptの人気が根強く、読者の皆様の中にはTypeScriptの実装を行ってらっしゃる方も多いのではないかと思います。

TypeScriptはJavaScriptのスーパーセットであるという考え方に則っており、ECMAScriptの新しい変更もどんどん使えるようになっているので、TypeScriptで使用可能なバージョンもあわせて記載します。

ES2021にリリースされた変更点

ES2021でリリースされた変更点は下記のとおりです。

String.prototype.replaceAll

  • 当てはまるすべての部分に対して文字列置換を行います
  • 既存のreplace()メソッドは置換対象となる最初の1部分に対してのみ有効であるため、全てを置換したい場合は正規表現を用いる必要がありました
  • このメソッドであれば正規表現ミスによる置換漏れを防ぐことができますし、やりたいことも一目で分かりやすいです
let str = "I like programming. I like my job."
str.replaceAll("like", "love") // 出力結果:"I love programming. I love my job."
  • TypeScriptではデフォルトではまだサポートされていませんが、tsconfig.jsonの設定を下記のように変更することで使えるようになります。(ES2021のその他の変更も使用できるようになります)
tsconfig.json
{
"compilerOptions": {
"lib": ["ES2021"],
},
}

Promise.any

  • 同時に実行されているプロミスのうち、どれか一つでも処理がresolvedになった時点でそのプロミスを返却します
  • すべてのプロミスがrejectとなる場合は、AggregateError(1つの処理で複数のエラーを返す必要がある場合の型)が返却されます
  • 処理が成功したことは知りたいけどどれかを知る必要がない場合はanyを使うと便利そうです。
  • TypeScriptではこちらもまだデフォルトではサポートされていません。上記のようにtsconfig.jsonの設定を変更することで利用可能です
Promise.any([
new Promise((resolve, reject) => setTimeout(reject, 100, '1st')),
new Promise((resolve, reject) => setTimeout(resolve, 200, '2nd'))
])
.then(value => console.log(`一番最初に完了した処理: ${value}`))
.catch (err => console.log(err))

// 出力結果
// 一番最初に完了した処理: 2nd

WeakRefs

  • 下記を実現可能にします。
    1. オブジェクトへの「弱い参照」の作成
    2. オブジェクトがガーベッジコレクションされた後のユーザー定義のファイナライザー実行
  • 「弱い参照」とは、ガーベッジコレクションの実行を妨げないオブジェクトの参照のことです。
  • 通常、オブジェクトが生成されるとメモリに保持されますが(「強い参照」)、「弱い参照」の場合はメモリ解放が必要なときはガーベッジコレクションの回収対象になります。
  • この二つを上手く使えば、削除されたオブジェクトなどすでに参照されることがないデータのメモリリークを防ぐことができます。
  • しかし、ガーベッジコレクションの機能は複雑なので(ガベージコレクションがいつ、どのように発生するかは、使用している JavaScript エンジンの実装に依存するため)、正しく使用するには慎重に検討する必要があり、可能であれば使用は避けた方が良いと公式でも注意書きが残されています。
  • TypeScriptの公式ドキュメントには記載がありませんでしたが、こちらのIssueによるとバージョン4.1以降で利用可能だそうです。
    • 試しにPlayGroundで下記コードを実行してみましたが、確かに4.1以降のバージョンでコンパイルできるようになっていました。
typeScript.ts
const obj = new Map();
const cache = new WeakRef(obj);
// 出力結果
// v4.0.5
// Cannot find name 'WeakRef'.
// v4.1.5
// なし

Logical Assignment Operators

  • 下記のような書き方ができるようになります
  • 初期値を入れるときに余計な分岐が不要になり、実装がスッキリします
  • TypeScriptではバージョン4.0から利用可能です
// a || (a = b);
a ||= b;

// a && (a = b);
a &&= b;

// a ?? (a = b);
a ??= b;

Numeric separators

  • _ を数値の間に入れることができます。(入れても数値として認識されます)
  • 10進数だけでなく全ての数値リテラルで可能です。これにより数値が読みやすくなります
  • TypeScriptではバージョン2.7から利用可能です
1_000_000_000           // 10億
101_475_938.38 // 101万、小数点も合わせて使用可能

ES2022にリリース予定の変更点

現時点でFinished Proposalに追加されている変更点を紹介します。

Class Fields

  • これらの変更はTypeScriptではバージョン3.8から利用可能です
  • Private instance methods and accessors
    • プライベートメソッドとアクセッサ(getter/setter)が追加されます
    • メソッド名の先頭に#をつけることでプライベートメソッドになります
    • クラスのStateや振る舞いをプライベートに保つことで、呼び出し側での意図しない変更を防ぐことができます
  • Class Public Instance Fields & Private Instance Fields
    • プライベートなフィールドを定義できるようになります
    • 外部からの参照や書き込みが可能なパブリックフィールドとは対照に、フィールドを定義しているクラスのみがそのフィールドにアクセスできるようになっています
class X {
// プライベートフィールド
#foo;
method() {
console.log(this.#foo)
}
}

const testObj = new X();
testObj.method()
// 出力結果:undefined
testObj.#foo
// 出力結果:
// Uncaught SyntaxError: Private field '#foo' must be declared in an enclosing class
  • Static class fields and private static methods
    • JavaScriptでも静的メソッドや静的プロパティが定義できるようになります。
    • これらは生成したインスタンスではなくクラスから直接呼び出されます。
    • 異なるクラスで同じ名称の変数やメソッドを定義できるので、グローバル変数のように競合を気にすることなくネーミングできます。
class CustomDate {
// ...
static epoch = new CustomDate(0);
}

RegExp Match Indices

  • 部分文字列の配列に対し、indicesプロパティが追加されます
  • これまでも文字列抽出の操作で、マッチした文字列の情報を含む配列やマッチした文字列のインデックス情報などを返却していましたが、より高度なシナリオではこれだけでは十分ではないとのこと
  • 正規表現にマッチしたそれぞれの部分の配列の開始・終了のインデックスのペアを含むインデックス配列を返却します
  • パフォーマンス上の理由から、dフラグが設定されている時のみ有効になるそうです
  • TypeScriptではこちらの記法はまだサポートされていないようでした
const re1 = /a+(?<Z>z)?/d;
// indicesは入力文字列の先頭からの相対値を表します
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";

Top-level await

  • モジュール単位で非同期関数と見なすことができるようになります
  • これまではasync/awaitを定義できるのはあるモジュールの関数単位でした。このままだと、非同期の関数が書かれているモジュールの読み込みが、関数呼び出しが実行されるタイミングより遅かった場合、undefinedで返却されてしまいます
  • モジュール全体を非同期関数として動作させることによって、関数実行がモジュール読み込みより先に行われることを防ぐことができます
  • TypeScriptではバージョン3.8から利用可能です
//実行する環境によって依存関係を決定したり
const strings = await import(`/i18n/${navigator.language}`);
// モジュールがリソースを表現したりすることができます
const connection = await dbConnector();

まとめと今後追加される変更点の予想

ES2021、ES2022の変更内容を改めて振り返ると、以下の特徴があるように感じました:

  1. 他の言語の仕様を取り入れている
    • 他の言語ではよく見るPrivateの概念が標準規格として導入されたり、Rubyにインスパイアされたオペレータの記法が取り入れられたりしていることから、他の言語でできることはどんどんJavaScriptでもできるようにしようという流れがあると感じました
    • これにより、特に他言語の経験があってJavaScriptを初めて触るという人にとって、とっつきにくさが軽減するのではないかと考えられます
  2. より一目でわかりやすい表記が実現されている
    • 数値セパレータの導入やreplaceAll()の導入など、「なくても同じことはできるがあるとより初見で分かりやすい機能」が多く取り入れられていると感じます
    • 初見でわかりやすいということは開発をする上でのストレス軽減に繋がるので、JavaScriptのファン増加にいい影響をもたらしそうです

ES2022のリリースに向けて、いまもいくつかの機能がStage 3の査定へと進んでいます。

上記のような所感を踏まえ、今後ES2022のリリースまでにStage 4へと進みそうな新機能を現在検討中のActive proposals(Stage 3)の中から 勝手に 私なりにピックアップしてみました!
来年のリリースを楽しみにウォッチしようと思います。

.at()

  • 配列の「負の添字」を可能にするという提案です。arr.at(-1)と実装したときに、負の数が最後の要素から逆にカウントされるようにします
  • これまでに多くの開発者から切望されていたとのこと。Pythonでもできるそうなので、こういったシンプルかつ使いやすい機能は必要度も高いのではないでしょうか

Accessible Object.prototype.hasOwnProperty()

  • ObjectのObject.prototype.hasOwnPropertyをより使いやすくするという提案です。下記のように実装できます
  • prototypeを駆使して実装するのは敷居が高かったので、Objectの組み込みメソッドとして用いることができるのは個人的にはアツいと感じました
if (Object.hasOwn(object, "foo")) {
console.log("has property foo")
}

他にも社内では、数年前からプロポーザルに上がっているTemporalという日時を扱える新しい機能がいよいよStage 3に進んでおり盛り上がっていました。

最後に

ES2021とJavaScriptの発展は深い関連があることが分かり、技術も資本主義社会の中で発展しているんだということを実感しました。

JavaScriptのようにすでに広く使用されている言語でも絶えず標準規格のアップデートが続けられていることに驚きましたが、「これまでの苦労を後の世代の人が二度と味わわないように」といった先人のパッションがあるからこそなんだろうなと思いました。

それぞれの特徴を活かした開発や技術選定を行うためには、日々新しくなる技術がどういう思想でアップデートされているのか想像することも必要だと感じました。

参考