🐥

ES2016~2021の新機能についてまとめた

2024/04/18に公開

はじめに

普段業務上でTypeScriptを用いたReactを書いているのですが、ESの新機能がいつ、どのバージョンで追加されたのかをあまり把握していませんでした。
本記事では、ES2016~2021で追加された新機能について調査しまとめています。
他記事を参考に、自分が見返したときに理解できるようにまとめた記事になりますので、もし不足事項等あればご指摘ください。

なお、正規表現やメモリ周りなど、コードに直接的に関係ない部分や普段の業務でほとんど使わない部分は本記事に含めていません。

以下記事を参考にまとめています。
https://www.tohoho-web.com/js/what.htm#es2016

ES2016(ES7)

array.includes(elm)

includesは配列arrayが指定した要素を含んでいる場合にtrueを返します。

cosnt colors = ["Red", "Green", "Blue"];

console.log(colors.includes("Green")); // true

indexOfでも同様の比較をすることができますが、includesの方がより直感的に調べることが可能です。
array.indexOf()は配列内に指定した要素を含んでいる場合は一番最初の配列要素の添字を、含んでいない場合は-1を返します。

cosnt colors = ["Red", "Green", "Blue"];

console.log(colors.indexOf("Blue")); // 1
console.log(colors.indexOf("Blue") != -1); // true

べき乗演算子

その名の通りべき乗を演算します。以下の場合xには3の2乗が代入されます。

const x = 3 ** 2;

console.log(x); // 9

※IE11ではサポートされていません。

ES2017(ES8)

オブジェクト参照

object.values(obj)

オブジェクトがもつ列挙可能なプロパティの値の配列を返します。

const weather = { place: 'tokyo', temperature: 25 };

console.log(Object.values(weather)); // ['tokyo', 25]

object.entries(obj)

オブジェクトのプロパティに対して、[key, value] の配列を返します。

const weather = { place: 'tokyo', temperature: 25 };

console.log(Object.values(weather)); // ['place', 'tokyo'], ['temperature', 25]]

パディング

string.padStart(length[, str])

padStart() は文字列の前方に対してパディングを行います。
lengthはパディング後の長さ、stringはパディングする文字を指定します。

以下の場合、3文字の123に対して、lengthに5、stringに0を指定しているため、前方に2文字分の0を追加します。

const number = "123";

console.log(number.padStart(5, "0"));// "00123"

string.padEnd(length[, str])

同様に、padEnd()は文字列の後方にパディングを行います。

const number = "123";

console.log(str.padEnd(5, "_")); // "123__"

もし、stringがlengthよりも長いときは何も行いません。
また、strを省略すると半角スペースでパディングします。

プロパティ記述子参照(Object.getOwnPropertyDescriptors(obj))

指定したオブジェクトの、列挙可能なすべてのプロパティについて、プロパティ記述子を取得します。

const obj = { a: 100 };
const descs = Object.getOwnPropertyDescriptors(obj);
console.log(descs); // {a: { value: 100, writable: true, enumerable: true, configurable: true }}

プロパティ記述子について
JavaScriptのオブジェクトプロパティは、書き込み可否や各種設定可否などいくつかの属性を持ち、これらはプロパティ記述子として保持されています。
以下が参考になります。
https://www.tohoho-web.com/js/object.htm#defineProperty
https://qiita.com/hikotaro_san/items/a6680fd4fecb3ebb1088

関数末尾のカンマ (,)

関数定義や関数呼び出しの最後の引数にカンマ(,)を記述することが可能になりました。
以下の場合はarg3、cの後にあるカンマを指します。

const sample = (arg1, arg2, arg3,) => {
  // 処理
};
sample(a,b,c,)

非同期処理(async, await)

Promiseに加え、async/awaitが使用可能です。Promiseに対応した非同期関数を、同期関数のように呼び出すことが可能になります。
同期関数のように呼び出したい、非同期関数を呼び出す際にawaitawaitを呼び出す関数にasyncをつけます。

async const sample = () => {
    let number = 2;
    try {
        number = await exponentiation(sampleNumber);
        console.log(number); // 4
        number = await exponentiation(sampleNumber);
        console.log(number); // 16
    } catch(e) {
        console.log(e);
    }
};

※IE11ではサポートされていません。

ES2018(ES9)

オブジェクトのスプレッド構文とレスト構文 (...obj)

スプレッド構文

スプレッド構文を利用することで、配列式や文字列を展開可能です。

var obj1 = {x: 200, y: 300};
var obj2 = {w: 400, h: 500};

var obj3 = {...obj1, ...obj2};
console.log(obj3); // {x: 200, y: 300, w: 400, h: 500}

レスト構文

逆に、レスト構文では、複数の要素を持つオブジェクトを複数の要素に集約可能です。

var { x, y, ...rest } = obj3;
console.log(rest); // {w: 400, h: 500}

Promiseのfinally構文

.catch()の後に、.then()を加えることで、成功/失敗問わず、常に実行される処理を追加することができました。
ES2018では、.finally使うことで、より明示的に記述することができます。

async const sample = () => {
    aFunc3(100).then((data) => {
        console.log(data);
        return aFunc3(data);
    })
    .then((data) => {
        console.log(data);
        return aFunc3(data);
    })
    .catch((e) => {
        console.log("catch");
        console.log(e);
    })
    .finally(() => {
        console.log('*** Finally ***');
    });
}

Promiseのfor await (... of ...)構文

非同期なイテラブルオブジェクトに対して、ループを回すことが可能です。

async const pickAwaitOf = () => {
   for await (num of asyncIterableObject) { // 非同期なオブジェクト
     console.log(num); // 0, 1, 2, 3, 4
   }
};

ES2019(ES10)

catch引数の不要化

例外処理を扱う際に使用されるcatchにおいて引数が省略可能になりました。

try {
   :
} catch { // catch(e)の(e)が不要
   :
}

Symbol.description

シンボルのdescriptionを取得します。

const symbol = Symbol("fruit");

console.log(symbol.description); // "fruit"

JSON superset

Well-formed JSON.stringify

function.toString() でコメントも文字列化

関数定義を文字列変換して返します。

const fruitsList = (x, y) => {
    /* 果物をリストする */
    console.log(x,y);
    return;
};

console.log(fruitsList.toString());
// "(x, y) => { /* 果物をリストする */ console.log(x,y); return; }"

Object.fromEntries()

[key, value]の配列をオブジェクトに変換します。

const obj = Object.fromEntries([["x", 100], ["y", 200]]);
console.log(obj);             // { x: 100, y: 200 }

string.trimStart()と string.trimEnd()

trimStart()は文字列前方、trimEnd()は文字列後方のホワイトスペース(空白文字)を取り除いた文字列を返します。

"   ABC   ".trimStart(); //  "ABC   "
"   ABC   ".trimEnd(); // => "   ABC"

array.flat() と array.flatMap()

flat()は多次元配列を低次元の配列にフラット化します。

const arr = [[[1, 2], [3, 4]], [5, 6]];
console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]

同様にflatMap()は、配列に対してmap()を行った後、結果として得た多次元配列をflat()します。

const arr = ["Blue Green", "Red Yellow"];
console.log(arr.flatMap(x => x.split(" "))); // ["Blue", "Green", "Red", "Yellow"]

ES2020(ES11)

オプショナル連結/オプショナルチェーン(var?.attr)

このような?はオプショナル連結/オプショナルチェーンと呼びます。
これがあることによって、var部分がnullやundefinedのときもTypeErrorにならず、undefinedを返すようになります。

// オプショナルチェーンを使わない書き方
if (response.body) {			
  console.log(response.body.length);	
}
// オプショナルチェーンを使った書き方
console.log(response.body?.length);	

for-in ループにおける順序保証

for-inを使うことでオブジェクトに対してループ処理を行うことが可能です。ES2020からはこのループ処理における順序が保証されるようになりました。

const myData = { name: "Yamada", age: 26, country: "Japan" };
for (var d in myData) {
  console.log(d, myData[d]); // > "name" "Yamada" "age" 26 "country" "Japan"
}

ヌル合体(Nullish Coalescing)演算子(??)

??を使うことで、任意の値がnullまたはundefinedの判定を簡潔に行うことが可能です。

// exampleがnullまたはundefinedのときには60、そうでないときはexampleが入る
const hoge = example ?? 60;

globalThis

グローバルオブジェクトを示します。
ブラウザ上で実行するJavaScriptでは、windowオブジェクトと同値、Node.js で実行した場合は globalオブジェクトと同値になります。

console.log(globalThis);	// => Window {window: ...}

string.matchAll()

指定の文字列stringに対して、任意の正規表現が一致するものをリストで返します。

for (var m of "2020-12-31".matchAll(/[0-9]+/g)) {
  console.log(m);
}
/ => ["2020", index: 0, input: "2020-12-31", groups: undefined]
// => ["12", index: 5, input: "2020-12-31", groups: undefined]
// => ["31", index: 8, input: "2020-12-31", groups: undefined]

ダイナミックインポート(Dynamic Import)

通常のimportを使うことによって、外部ファイルなどからエクスポートされた関数や変数を読み込むことが可能です。

import "./module.js"; 
import { a1, a2 } from "./module.js"; 

ダイナミックインポートを使うことによって、非同期にモジュールをインポートし、インポート完了時に関数を実行可能です。

import("./module.js").then(mod => {
   console.log(mod.a1);
});

export * as ns from module 構文

importしたものをexportすることについて、直接1行で記述可能です。

import * as mod from "./module.js";
export { mod };
// ↓
export * as mod from "./module.js";

import.meta

インポートされたモジュールのURL等、メタ情報を保持します。

console.log(import.meta);

Promise.allSettled()

複数タスクがある場合に、いずれか1つが成功/失敗しても、

ES2021(ES12)

論理代入演算子 (||=, &&=, ??=)

既存の演算子をまとめた記法になります。

const numberA = xx ||= 5; // xxがfalseと判断される場合にxxに値を代入
const numberB = xx || (a = 5); // 上に同じ

const numberC = xx &&= 5; // xxがtrueと判断される場合にxxに値を代入
const numberD = xx && (xx = 5); // 上に同じ

const numberE = xx ??= 5; // xxがnullかundefinedの時にxxに値を代入
const numberF = xx ?? (xx = 5);// 上に同じ

数値セパレータ (1_000_000)

その名の通り、_は数値を区切る役割を持ちます。計算時にはこの_は無視されるため、,と同等に扱えます。

1_234_567

string.replaceAll()

string型において、正規表現に当てはまる値を全て置換する関数です。

const example = 'EXAMPLETEXT';

console.log(example.replace("E", "e")); // eXAMPLETEXT
console.log(example.replaceAll("E", "e")); // eXBMPLeTeXT

Promise.any()

// p1かp2のどちらかが完了したら'taskA or task B is finished.'を出力
function sample_race() {
    p1 = taskA();
    p2 = taskB();
    Promise.race([p1, p2]).then(() => {
        console.log("taskA or task B is finished.");
    });
}

Promise.race()との違いは以下のようになります。

  • Promise.race() :どれかのタスクが成功または失敗のどちらでも終了する
  • Promise.any():どれかのタスクが成功しているとき、終了する

弱参照(WeakRefs)

通常の参照(=強参照)の場合

以下のようなコードがあるとき、1つ目の{ name: "apple", color: "red" }は最後に参照がなくなっています。必要ない存在になります。
ですが、JavaScriptのようにガベージコレクションのある言語では、一度登場したオブジェクトはメモリに保存されるため、メモリに保存されます。不要なオブジェクトがメモリ上に溜まってからまとめて削除する処理を行います。(=ガベージコレクション)
このコードが書いてあるプログラムが実行された後に、1つ目の{ name: "apple", color: "red" }はガベージコレクションによって削除される可能性があります。

let obj = { name: "apple", color: "red" };
// この時点ではobjは{ name: "apple", color: "red" }を参照
obj = { name: "remon", color: "yellow" };
// この時点ではobjは{ name: "remon", color: "yellow" }を参照し、
// { name: "apple", color: "red" }への参照はなくなる

console.log(obj); // { name: "remon", color: "yellow" }

弱参照の場合

強参照に対して、弱参照では、参照したいオブジェクトがガベージコレクションによって削除されていることが有り得ます。
強参照で

let obj = ({ name: "apple", color: "red" });
const wref = new WeakRef(obj);
// この時点でobjは{ name: "apple", color: "red" }を参照

obj = null
// objの参照を削除すると、弱参照のみが残る

// この間に({ name: "apple", color: "red" })の参照が削除されているかもしれない

console.log(weakRefObj.deref());
// { name: "remon", color: "yellow" }
// オブジェクトが削除されている場合、undefined

弱参照については以下の記事を参考にしています。
https://qiita.com/uhyo/items/5dc97667ba90ce3941cd

まとめ

想定より内容が大きくなってしまったため、ES2022については別途記事を書く予定です。

Discussion