JavaScriptの次の仕様ES2020で追加されることが決定した新機能まとめ

451
264
535

JavaScriptの仕様はEcma Internationalによって定められています。現状での最新仕様は ES2019(ECMAScript 2019)ですが、次の仕様はES2020(ECMAScript 2020)となります。例年どおりに行けば、今年2020年の6月頃に正式仕様となるでしょう。

本記事ではES2020で追加されることが決定した新機能を解説します。ES2020の各新機能はGoogle Chrome v80以降で試せますので、実際に実行してみるとより挙動が理解しやすいでしょう。

nullundefinedになりうる値へのアクセスが簡単になるOptional Chaining

Optional Chainingとは、?.構文を用いてnullundefinedになりうる値へ安全にアクセスできる仕組みです。

構文 意味
A?.B Anullundefinedでないとき、Bを返す

利用シーン

  • nullundefinedになるかもしれない値にアクセスしたいとき

何がメリットなのか?

APIからユーザーの住所を取得して出力するプログラムを考えてみましょう。サーバーからは次のようなオブジェクトが返ってくる想定です。

▼ APIから返ってくる想定のオブジェクト

const userData = {
  name: "鈴木",
  address: {
    // データによっては存在しないかもしれない
    city: "港区"
  }
};

上記のオブジェクトからcityを出力したいのですが、ユーザーデータによってはaddressが存在しないかもしれません。従来は、addressの存在を考慮に入れて次のようなコードを記述する必要がありました。

▼ 従来のコード

const city = userData.address && userData.address.city;
console.log(city);
// 結果: addressがある場合は「港区」が出力される

Optional Chainingを使うと、次のようにコードが記述できます。?が Optional Chaining用の演算子です。

▼ Optional Chainingを使った新コード

const city = userData.address?.city;
console.log(city);
// 結果: addressがある場合は「港区」が出力される。
//       addressがない場合はundefinedになる(エラーにならない)。

▼ Optional Chainingの実行結果

?.構文は何個でも使用できるので、userDatanullになりえるのであれば次のように記述できます。

?.構文を複数使う例

const city = userData?.address?.city;
// 結果: userDataもaddressもある場合は「港区」が出力される。
//      それ以外はundefinedになる(エラーにならない)。

DOM要素へquerySelector()を用いてアクセスする場合にも便利に使えます。

▼ HTMLコード

<p>こんにちは</p>

pタグ内のテキストを出力する例

const paragraphText = document.querySelector("p")
  ?.innerHTML;
console.log(paragraphText);
// 結果: p要素がある場合は、p要素内のテキストを参照して「こんにちは」が出力される。
//      それ以外はundefinedになる(エラーにならない)。

nullundefinedのときだけ値を返せるNullish coalescing Operator

Nullish coalescing Operatorは、「A ?? B」という形で用い、AnullundefinedのときだけBを返します。「coalescing」は聞き慣れない単語かもしれませんが、「合体」という意味を持ちます。

構文 意味
A ?? B AnullundefinedのときB

利用シーン

  • 0falseなどにfalseに評価されうる値をワンライナーで正しく扱いたい時

何がメリットなのか?

次のようなオブジェクトから、foo値を取得して出力する関数を考えてみましょう。もしfooが未設定(nullまたはundefined)ならば、「値なし」という文字列を出力したいです。

▼ 対象のオブジェクト

const myObject1 = {
  foo: 1
};

const myObject2 = {
  foo: 0
};

const myObject3 = {
  foo: false
};

const myObject4 = {
  foo: null
};

||(論理OR)演算子(参考:「論理演算子 - JavaScript | MDN」を使って、次のようなgetFoo関数を定義するのはNGですmyObject2myObject3が引数として渡された場合にobject.foofalseと評価されて「値なし」が出力されるからです。

fooの取得関数のNG例

function getFoo(object) {
  return object.foo || "値なし";
}

console.log(getFoo(myObject1)); // 結果: 1
console.log(getFoo(myObject2)); // 結果: ☆「値なし」(0が期待値)
console.log(getFoo(myObject3)); // 結果: ☆「値なし」(falseが期待値)
console.log(getFoo(myObject4)); // 結果: 「値なし」

ES2020のNullish coalescing Operatorを使えば、object.foonullundefinedの場合のみ「値なし」を返すので、目的の挙動となります。

▼ Nullish coalescing Operatorを使った処理

function getFoo(object) {
  return object.foo ?? "値なし";
}

console.log(getFoo(myObject1)); // 結果: 1
console.log(getFoo(myObject2)); // 結果: 0
console.log(getFoo(myObject3)); // 結果: false
console.log(getFoo(myObject4)); // 結果: 「値なし」

▼ Nullish coalescing Operatorの実行結果

関数形式でモジュールを読み込むimport()

import()関数は、関数形式でモジュールを読み込むための仕組みです。dynamic importとも呼ばれます。

従来のimportとは異なり、JavaScriptコードの任意の場所で実行でき、任意のタイミングでモジュールを読み込めます。

メソッド 意味 戻り値
import(モジュール名) モジュールを読み込む Promise

利用シーン

  • モジュールを遅延読み込みしたいとき
  • 関数形式でモジュールを読み込みたい

何がメリットなのか?

次のモジュール「sub.js」を用いて解説します。

▼ モジュールsub.js

export const foo = "😺";

従来のimport文はJavaScriptのトップレベルでしか用いることができず、モジュールは即座に読み込まれる仕様でした。

▼ 従来のimport文でモジュールを読み込む処理

// 即座にsub.jsが読み込まれる
import { sub } from "./sub.js";

console.log(sub); // 結果:"😺"

ES2020で導入されるimport()関数は、モジュールを動的に読み込みます。巨大なモジュールを遅延して読み込む場合などに便利です。戻り値のPromiseの結果としてモジュールを受け取るため、次のようにすればsub.js内のsub定数にアクセスできます。

import()関数でモジュールを読み込む処理

// import()のタイミングでsub.jsが読み込まれる
import("./sub.js").then(module => {
  // subモジュールの定数を読み込んで出力します
  console.log(module.sub);
});

オブジェクトの分割代入(destructuring assignment)を使うのがよいでしょう。

import()とオブジェクトの分割代入を使用したモジュール読み込み

// import()のタイミングでsub.jsが読み込まれる
import("./sub.js").then(({ sub }) => {
  // subモジュールの定数を読み込んで出力します
  console.log(sub);
});

import()関数は任意のタイミングで実行できるので、次のように画面表示から2秒後にモジュールを読み込むということも可能です。また、ES2017で導入されたasync関数とawait式を使うとよりスッキリコードを記述できます。

▼ 画面表示から2秒後にモジュールを読み込んで定数を出力する例

setTimeout(async () => {
  const { sub } = await import("./sub.js");
  console.log(sub);
}, 2000);

sub.jsモジュールが2秒後に読み込まれている様子

Promiseの複数処理に便利なPromise.allSettled()

Promise.allSettled()は、複数のPromiseの処理を扱うためのメソッドです。複数のPromiseの1つがrejectされても処理を続行できます。

メソッド 意味 戻り値
Promise.allSettled([Promise1, Promise2, ...]) 各Promiseを実行する Promiseの結果のイテレータ

利用シーン

  • 複数のPromiseを、成功・失敗によらずすべて実行したいとき

何がメリットなのか?

Promiseの複数同時処理といえば、ES2015で導入されたPromise.all()というメソッドがあります。Promise.all()は複数のPromiseの処理を実行し、全部が成功した場合にはじめて処理が終了するものです。もし、1つでもrejectされるものがあればその時点で処理を終了します。

Promise.all()rejectされるものがあった場合

const promiseList = [
  Promise.resolve("😊"),
  Promise.resolve("😊"),
  Promise.reject("😡"),
  Promise.resolve("😊")
];

Promise.all(promiseList).then(
  resolve => console.log(`resolve: ${resolve}`),
  reject => console.log(`reject: ${reject}`)
);

// 結果:「reject: 😡」が出力される

Promise.all()の実行結果

ES2020のPromise.allSettled()では、rejectされるものがあってもすべての処理が実行されます。したがって、複数の配列のどれかが失敗したとしても処理をすべて実行したい場合に便利です。

Promise.allSettled()rejectされるものがあった場合

const promiseList = [
  Promise.resolve("😊"),
  Promise.resolve("😊"),
  Promise.reject("😡"),
  Promise.resolve("😊")
];

Promise.allSettled(promiseList).then(
  resolveList => {
    console.log("resolve");
    for (const resolve of resolveList) {
      console.log(resolve);
    }
  },
  reject => {
    console.log("reject");
    console.log(reject);
  }
);

// 結果:「resolve」のあと、各promiseの値が出力される

Promise.allSettled()の実行結果

関連資料:「Promise.allSettled - tc39

正規表現で一致したものをキャプチャグループとともに返すmatchAll()メソッド

対象文字列について、正規表現で一致したものをキャプチャグループ(参考:「グループと範囲 - JavaScript | MDN」)とともにイテレータで返すためのメソッドです。一致した部分の各インデックス、各キャプチャーグループを返すことが可能です。

メソッド 意味 戻り値
対象の文字列.matchAll(正規表現) 正規表現で一致したものを返す 文字列のイテレータ

利用シーン

  • 正規表現で一致する複数の文字列のインデックスなどを取り扱いたいとき

何がメリットなのか?

従来は、正規表現で一致した文字列のインデックスやキャプチャグループを取得するには、正規表現.exec()を使う必要がありました。

▼ 従来の処理

const myString = "JavaScriptを覚えよう";
const myRegex = /a/g;
let match;
while ((match = myRegex.exec(myString))) {
  console.log(match);
}

// 結果
// ["a", index: 1, input: "JavaScriptを覚えよう", groups: undefined]
// ["a", index: 3, input: "JavaScriptを覚えよう", groups: undefined]

文字列.matchAll()を使えば、イテレータ形式で処理ができて簡潔に記述可能です。

文字列.matchAll()を使った処理

const myString = "JavaScriptを覚えよう";
const myRegex = /a/g;
for (const match of myString.matchAll(myRegex)) {
  console.log(match);
}

// 結果
// ["a", index: 1, input: "JavaScriptを覚えよう", groups: undefined]
// ["a", index: 3, input: "JavaScriptを覚えよう", groups: undefined]

matchAll()の実行結果

ウェブブラウザでもNode.jsもグローバルオブジェクトを参照できるglobalThis

globalThisは、ウェブブラウザ(クライアントサイド)でもNode.js(サーバーサイド)もグローバルオブジェクトを参照できるオブジェクトです。

構文 意味
globalThis グローバルオブジェクトを参照する

利用シーン

  • ウェブブラウザの処理とNode.jsの処理で共通のコードを使いたいとき

何がメリットなのか?

JavaScriptは、ウェブブラウザ上だけではなくNode.jsでも実行されます。しかし、グローバルオブジェクトの参照を考えた場合、ウェブブラウザではwindowがグローバルオブジェクト、Node.jsではglobalがグローバルオブジェクトなので、コードを共通化できませんでした。

globalThisを使えば、ウェブブラウザ・Node.jsに共通でグローバルオブジェクトを参照できるので、コードの共通化に役立ちます。もちろん、従来どおりウェブブラウザでのwindow、Node.jsでのglobalも使用可能です。

次のコードを用いてウェブブラウザでの実行結果を確認してみましょう。

▼ index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <script>
      // globalThisの値を出力します
      console.log(globalThis);
    </script>
  </head>
  <body></body>
</html>

HTMLファイルをChromeで開くと、コンソールタブに次のように出力されます。globalThisによって、Windowが出力されていることがわかります。

▼ ウェブブラウザでglobalThisを参照している様子

※ ブラウザ:Google Chrome v80

続いて、Node.jsでの実行結果を見てみましょう。次のコードが記述されたJavaScriptファイル「script.js」を準備します。

▼ script.js

console.log(globalThis);

コマンドラインでnode script.jsと入力するとNode.jsを実行できます。次のキャプチャーが実行結果です。赤枠部分が実行結果ですが、globalThisによってGlobalが出力されていることがわかります。

▼ Node.jsでglobalThisを参照している様子

※ Node.js v13.7.0

2^53以上の整数を扱えるBigInt

BigIntは、Numberより大きな整数2^53以上の整数を扱えるオブジェクトです。

メソッド 意味 戻り値
BigInt(数値) 数値をBigIntの値にする BigIntの整数
構文 意味
数値n 数値をBigIntの値にする

利用シーン

  • 2^53 以上の整数の計算をしたいとき
  • 符号付きまたは符号なしの64ビット整数を扱いたいとき

何がメリットなのか?

JavaScriptの数値(Number)で正確に扱える最大整数は、2^53-1です。定数でいえば、Number.MAX_SAFE_INTEGERとなります。2^53以上の値を計算しようとすると、計算結果に誤差が生じます。

▼ 計算結果に誤差が生じる例

console.log(Number.MAX_SAFE_INTEGER); // 結果: 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 2); // 結果: 9007199254740992(9007199254740993ではない)

BitIntを使うと、2^53以上の整数も扱えるようになります。BigIntを扱うには、BigInt(数値)とするか、数値nという記述をします。BigIntBigInt同士でしか計算ができないので、Numberとあわせて使う場合はNumberBigIntに変換するようにしましょう。

BigIntの計算例

console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n);
// 結果: 9007199254740993n

▼ 実行結果

出力された9007199254740993nは数学世界での整数9007199254740993を指します。

for...inの順序固定

ここでいうfor...inとは、次のように列挙可能なプロパティを反復する処理のことです。

const myData = { name: "鈴木", age: 23, address: "TOKYO" };

for (const key in myData) {
  console.log(`${key}: ${myData[key]}`);
}

従来のECMAScriptの仕様では、for...inの実行順序は固定されていませんでした。ES2020では、その実行順序が指定されます。仕様は次のとおりです。

対応環境の状況

ウェブブラウザの対応状況

本記事で紹介したES2020の仕様は、次のブラウザで対応しています。2020年2月時点の現行ブラウザで対応しているものには◯、対応していないものは対応開始バージョンを示しています。

機能 Chrome Firefox Safari Edge
?.構文 v80 v74 TP 91 v81
??演算子 v80 TP 89 v81
import()
Promise.allSettled()
文字列.matchAll()
globalThis
BigInt ×
for...inの順序保証

※ SafariのTPはTechnology Previewを示します

ES2020の各機能に対応していないブラウザ、たとえばSafariやEdgeの現行バージョンやIE11なども、TypeScript・Babelとポリフィルを利用すれば対応可能です(BigIntfor...inを除く。import()についてはwebpackも必要)。実際、弊社ではES2020の機能を現場で使用しており、各ブラウザに対応しています。とくにOptional Chaining(?.構文)やNullish coalescing Operator(??演算子)のおかげで、従来は冗長に書いていた処理がスッキリ短く書けるケースも多くありました。

TypeScript・Babelの環境構築については、次の記事で詳しく紹介しています。

ES2020は便利な新機能がたくさん

筆者は書籍「 JavaScriptコードレシピ集」を出版しました。その中ではES2018までの機能をカバーしていますが、今年2020年に登場する最新バージョンES2020の新機能を解説する日本語ドキュメントも提供したいと思いました。

今回紹介したES2020の新機能のうち、個人的にはOptional Chaining、Nullish coalescing Operator、import()Promise.allSettled()がとても便利に感じます。新機能はこれまで面倒だった処理を簡潔にしてくれる効果があるので、積極的にキャッチアップしていきましょう。

JavaScriptの新機能の情報は、次の記事にもまとまっています。あわせて参照くださいませ。