JavaScriptの仕様はEcma Internationalによって定められています。現状での最新仕様は ES2019(ECMAScript 2019)ですが、次の仕様はES2020(ECMAScript 2020)となります。例年どおりに行けば、今年2020年の6月頃に正式仕様となるでしょう。
本記事ではES2020で追加されることが決定した新機能を解説します。ES2020の各新機能はGoogle Chrome v80以降で試せますので、実際に実行してみるとより挙動が理解しやすいでしょう。
null
かundefined
になりうる値へのアクセスが簡単になるOptional Chaining
Optional
Chainingとは、?.
構文を用いてnull
やundefined
になりうる値へ安全にアクセスできる仕組みです。
構文 | 意味 |
---|---|
A?.B |
A がnull かundefined でないとき、B を返す |
利用シーン
null
やundefined
になるかもしれない値にアクセスしたいとき
何がメリットなのか?
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の実行結果
?.
構文は何個でも使用できるので、userData
もnull
になりえるのであれば次のように記述できます。
▼ ?.
構文を複数使う例
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になる(エラーにならない)。
null
かundefined
のときだけ値を返せるNullish coalescing Operator
Nullish coalescing Operatorは、「A ?? B
」という形で用い、A
がnull
かundefined
のときだけB
を返します。「coalescing」は聞き慣れない単語かもしれませんが、「合体」という意味を持ちます。
構文 | 意味 |
---|---|
A ?? B |
A がnull かundefined のときB |
利用シーン
0
、false
などに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です。myObject2
、myObject3
が引数として渡された場合にobject.foo
がfalse
と評価されて「値なし」が出力されるからです。
▼ 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.foo
がnull
かundefined
の場合のみ「値なし」を返すので、目的の挙動となります。
▼ 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秒後に読み込まれている様子
- 関連資料:「import() - tc39」
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
- 関連資料:「globalThis - tc39」
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
という記述をします。BigInt
はBigInt
同士でしか計算ができないので、Number
とあわせて使う場合はNumber
もBigInt
に変換するようにしましょう。
▼ BigInt
の計算例
console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n);
// 結果: 9007199254740993n
▼ 実行結果
出力された9007199254740993n
は数学世界での整数9007199254740993
を指します。
- 関連資料:「BigInt - tc39」
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とポリフィルを利用すれば対応可能です(BigInt
、for...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の新機能の情報は、次の記事にもまとまっています。あわせて参照くださいませ。