thumbnail

【es2023-2019】最近のJavaScriptの新機能を振り返る【まとめ】

ここ数年で追加されたJavaScrip(ECMAScript)tの新機能を遡ってまとめてざっと振り返る記事です。
機能と使い方の要点を簡潔にまとめておさらいするような記事なので、より詳しい使用方法などは適宜リンクなどをたどって下さい。

目次

ES2023(ES14)

配列を後ろの要素から検索する

配列の要素を後ろから検索する、findLast()、findLastIndex()の2つのメソッドが増えました。
メソッドの引数は、先頭から検索するバージョンのfind()とfindIndex()と同じになります。
すなわち、検索してマッチさせる要素の条件を関数として記述します。

const ary = [1, 3, 8, 5, 2]

// 配列の後ろから検索して3より大きい初めての値
ary.findLast(elm => elm > 3) // 5

// 配列の後ろから検索して、3より大きい初めての値のインデックス
ary.findLastIndex(elm => elm > 3) // 3

ファイルの先頭行の!# ~ はコメント扱い

すなわち、ブラウザ環境でシェバンをコメントとして無視するようになりました。
シェバン(shebang)とは、シバンなどとも言い、Unix系システムにおいて、ファイル先頭行にかかれる!# ~ から始まる一文です。

Unix系のシステムのNode環境でシェバンを書いて実行していたプログラムを、ブラウザ環境等でも使いまわせるようになったということです。

ES2022(ES13)

トップレベルならawaitを使うのにasyncが不要

コードのトップレベルならば、わざわざasyncで関数を作らずにawait句を直接かけます。

// トップレベル
const hoge = await fetch(`https://jsonplaceholder.typicode.com/todos/1`)
                    .then(res => res.json())
console.log(hoge)

あくまでトップレベルでの話なので、関数の中では従来通りasync句が必要です。

// 関数の中はasync句が必須
const func = async () => {
  const hoge = await fetch(`https://jsonplaceholder.typicode.com/todos/1`)
                      .then(res => res.json())
  console.log(hoge)
}
func()

利点

  • 書き捨てでawait句のコードが書ける
  • 非同期処理をエクスポートできる

2番目の項目が重要で、importしたモジュールがトップレベルawaitを持っていた場合の処理順などに影響します
静的importか動的importかによって処理順が変わるのですが、詳しい解説がこちらに乗っているのでリンクさせていただきます。

クラスにプライベートなメンバが宣言可能に

クラスのメンバの前に#をつけることでprivateにできます。

class MyClass {
  #name = "taro"
  age = 10
  showName() {
    console.log(this.#name)
  }
  #showAge() {
    console.log(thie.age)
  }
}
const c = new MyClass
c.age = 20
console.log(c.age)
// c.#name = 40 //エラー
c.showName()
// c.#showAge() //エラー

クラスに静的メンバが宣言可能に

static句で静的メンバが使えるようになりました。静的メンバとは、クラスをインスタンス化せずとも利用できるメンバのことです。

class MyClass {
  static #name = "taro"
  static age = 10
  static showName() {
    console.log(this.#name)
  }
  static #showAge() {
    console.log(thie.age)
  }
}
console.log(MyClass.age)
// console.log(MyClass.#name) //エラー
MyClass.showName()
// MYClass.#showAge() //エラー

クラスに静的初期化ブロックが宣言可能に

クラス内でstaticブロックが利用できます。staticブロックはインスタンス宣言時よりも前のクラス評価時に実行されます。

staticメンバ自身、あるいはそれを利用した初期化処理等に利用できます。

class MyClass {
  static name = "taro"
  static age
  static {
    MyClass.age = 10
  }
}
console.log(MyClass.age) //10

in演算子によるブランドチェック

あるクラスのインスタンスかどうか判定するのに instanceof演算子が利用できますが、setPrototypeOf() を使用すると、あるクラスのインスタンスでないのにtrueを返してしまいます。

class MyClass {}
class YourClass {}
const my = new MyClass
const your = new YourClass
Object.setPrototypeOf(my, your)
console.log(my instanceof YourClass) //true

プライベートメンバとそれに使えるin演算子によって、以下のように厳密にインスタンス判定が可能となります。

class MyClass {
  #brand
  static isMyClass(instance) {
    return #brand in instance
  }
}
class YourClass {
  #brand
  static isYourClass(instance) {
    return #brand in instance
  }
}
const my = new MyClass
const your = new YourClass
Object.setPrototypeOf(my, your)
console.log(YourClass.isYourClass(my))//false

at演算子で配列の後ろからのアクセスが簡単に

配列や文字列で従来のブラケット[]アクセスの他に、at演算子でアクセスが可能となります。
このatの便利な点は、負のインデックスを使用することで配列の後ろからアクセスが可能になることです。

つまり後ろから数えて何番目かを簡単に指定できます。python等では従来からある機能ですね。

const hoge = ["foo", "bar", "piyo"]
console.log(hoge.at(1)) // "bar"
console.log(hoge.at(-1)) // "piyo"
console.log(hoge.at(-2)) // "bar"
//従来はhoge[hoge.length - 1]としてアクセスしてた

hasOwnPropertyを改良したhasOwn

あるオブジェクトがあるプロパティを持つかどうか判定するために、Obejct.property.hasOwnPropertyが利用されてきました。(Object.hasPropertyはプロパティ汚染があるため非推奨)。Obejct.property.hasOwnPropertyという記述はとても長いため、そのショートハンドとして、Object.hasOwnが利用化できるようになりました。

const obj = {
  hoge: 2
}
console.log(Object.hasOwn(obj, "hoge")) // true

causeプロパティによるエラーのチェーン

次のようなコードでは、従来はエラーがチェーンしていてもどこでエラーが発生しているか伝えるのが困難でした。

function A() {
  try {
    B();
    a
  } catch (e) {
    throw new Error("Error A");
  }
}
function B() {
  try {
    C();
    b
  } catch (e) {
    throw new Error("Error B");
  }
}
function C() {
  try {
    c;
  } catch (e) {
    throw new Error("Error C");
  }
}
try {
  A();
} catch (e) {
  console.log(e); 
  // A,B,Cのどの関数でエラーが発生していようがError A
}

新しく追加されたcauseプロパティを利用すると、エラーが発生したところから親をたどれます。

function A() {
  try {
    B();
    a
  } catch (e) {
    throw new Error("Error A", {
      cause: e,
    });
  }
}
function B() {
  try {
    C();
    b
  } catch (e) {
    throw new Error("Error B", {
      cause: e,
    });
  }
}
function C() {
  try {
    c
  } catch (e) {
    throw new Error("Error C", {
      cause: e,
    });
  }
}
try {
  A();
} catch (e) {
  console.log(e); // Error A
  console.log(e.cause); // Error B
  console.log(e.cause.cause); // Error C
  console.log(e.cause.cause.cause); //ReferenceError: c is not defined
}

正規表現にdフラグの追加

正規表現にdフラグを付けることで、正規表現でマッチした文字列のインデックス情報がindicesとして付与されます。

const reg = /あ(.*)おか(.*)こ/d
const result = "あいうえききおかかきけこ".match(reg)
console.table(result)
// indices │ [ 0, 12 ] │ [ 1, 6 ] │ [ 8, 11 ] 

ES2021(ES12)

大きい数字を区切って人間が見やすくする

数値を_(アンスコ)をで区切って人間が見やすく記述することが可能です。マシンからしたらアンスコがあってもなくても変わりません。

const num = 1_000_000_000
// 1000000000と同じ

論理代入演算子

論理演算子と代入演算子を同時に記述可能な演算子が3つ追加されました。

a ||= 0 // a || a = 0と同じ
a &&= 0 // a && a = 0と同じ
a ??= 0 // a ?? a = 0と同じ

replaceAll()メソッドによる文字列の一括置換

replaceAllメソッドが導入され、文字列の一括置換が簡単にできるようになりました。

const str = "string A string B string C string D"
console.log(str.replaceAll("string", "hoge"))
// hoge A hoge B hoge C hoge D

このように直感的に置換できます。従来はreplaceメソッドと正規表現を組み合わせる必要がありました。

Promise.any()で一番早くresolveした処理を採用

Promise.anyは、複数の非同期処理を同時に走らせ、一番最初にresolveした値を採用します。

const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => reject("A"), 1000)
})
const promiseB = new Promise((resolve, reject) => {
  setTimeout(() => resolve("B"), 3000)
})
// Aはrejectされるので、Bが表示される
try {
  const promise = await Promise.any([promiseA, promiseB])
  console.log(promise)
} catch (e) {
  console.error(e)
}

これと似たメソッドにPromise.raceがありますが、違いは、一番最初にresolveもしくは『reject』された時点で終了します。anyと違ってrejectも含みますので、resolveされるよりも前にrejectされる処理がある場合エラーとなります。

const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => reject("A"), 1000)
})
const promiseB = new Promise((resolve, reject) => {
  setTimeout(() => resolve("B"), 3000)
})
// AはBより先にrejectされるので、エラーAが発生する
try {
  const promise = await Promise.race([promiseA, promiseB])
  console.log(promise)
} catch (e) {
  console.error(e)
}

詳しくは次の記事をご覧ください。

弱参照の追加

WeakRefで弱参照が作れます。弱参照とはオブジェクトがガベージコレクションの対象になるかもしれない参照です。

const obj = { name: "hoge" }
const weakObj = new WeakRef(obj) 
console.log(weakObj.deref()) //derefでオブジェクトにアクセス

次の記事に詳しい使い方がまとめられているので、リンクさせていただきます。

ES2020(ES11)

非常に大きな数値を扱えるBigInt型

従来のNumber型では大きすぎて表すことのできない数値を表現したり操作することが可能となります。
Number型は2の53乗を超えると精度が落ちてしまいますが、BigIntを用いれば正確に整数を扱えます。
使い方は、整数値の最後にnをつけるだけです。

const num = 200n // bigint型
const num2 = BigInt(300) // これでももOK

Numberとは違い、Mathオブジェクトのメソッドで利用できなかったり、Number型の値と混ぜて演算することができません。

その他演算の規則は公式のドキュメントをご覧ください。

オプショナルチェーン(?.)で安全にチェーン

オブジェクトのプロパティにnullやundefinedの値があってもチェーンして書ける機能です。

存在しないプロパティにアクセスするとエラーが発生しますが、オプショナルチェーンを利用すると、存在しないプロパティにアクセスしてもundefinedとなり安全にコードの記述が可能です。

存在しないプロパティがいくら続こうがエラーを出さずにチェーンして書き続けることが可能です。

const obj = {
  a: "a",
  b : {
    c: "c"
  }
}
// console.log(obj.d.e) // TypeError
console.log(obj.d?.e) // undefined
console.log(obj.d?.e?.f) // undefined

オブジェクト以外にもnullかもしれない関数や配列などにも使用できます。

console.log(hoge?.()) // nullかもしれない関数の呼び出し
console.log(ary?.[1]) // nullかもしれない配列へのアクセス

null合体演算子(??)

null合体演算子は2項演算子で、 A ?? B のように使います。その意味は、Aがnullかundefinedの場合にBを評価し、そうでないならAとを評価するといったものです。

const A = null
const B = A ?? "fuga"
console.log(B) // fuga
const C = "hoge"
const D = C ?? "fuga"
console.log(D) // hoge

従来から論理演算子の A || B もありましたが、これはAがfalsy(偽とみなせる値)のときにBが評価されるといったものですので、null合体演算子のほうが対象が狭くなります。

動的インポート

await句やthen句で非同期にモジュールをimportすることが可能となります。

// A.mjs
// export const hoge = "hoge"

// B.mjs
import("./A.mjs").then(console.log)
// [Module: null prototype] { hoge: 'hoge' }

importしたものをそのままexportする

importしたものをそのままexportする処理が一文で書けるようになりました。

// Aからimportしたものをそのままexport
export * as A from "./A.js"

モジュールのメタ情報を得る

import.metaで現在のモジュールのURLなどのメタ情報にアクセスができます。

console.log(import.meta) // {url: https:~ }

Promise.allSettledによる非同期処理

Promise.allSettled()を使うと、同時に複数の非同期処理を行えます。
これと似たメソッドにPromise.all()がありますが、あちらは非同期処理のうちどれか一つでもrejectされるとエラーとなります。その一方で今回採用されたallSettledは、rejectされてもエラーを吐かずに非同期処理が完了します。

const promiseA = new Promise((resolve, reject) => {
  setTimeout(() => reject("A"), 1000)
})
const promiseB = new Promise((resolve, reject) => {
  setTimeout(() => resolve("B"), 3000)
})
const [a, b] = await Promise.allSettled([promiseA, promiseB])
console.log("resolved!", a, b)
// resolved! { status: 'rejected', reason: 'A' } { status: 'fulfilled', value: 'B' }

このように値とともにreject / fullfiledのステータスも吐きます。

Promiseの並列処理に関しては、詳しくは次の記事をご覧ください。

ブラウザとNode.js共通のグローバルオブジェクト(globalThis)

Webブラウザのグローバルオブジェクトはwindow、Node.jsはglobalと名前が異なります。
ES2020で導入されたglobalThisを用いれば、コードがどちらの環境で実行されているか関係なく、グローバルオブジェクトにアクセスできます。

// ブラウザ
console.log(globalThis === window) // true
// Node
console.log(globalThis === global) // true

正規表現でマッチした文字列をイテレータで返す(matchAll)

文字列.matchAll(正規表現)で、マッチした文字列の情報をイテレータで返します。

const str = "1jg4qgog4hj9q"
console.log([...str.matchAll(/[0-9]+/g)])
/**
 * [
  [ '1', index: 0, input: '1jg4qgog4hj9q', groups: undefined ],
  [ '4', index: 3, input: '1jg4qgog4hj9q', groups: undefined ],
  [ '4', index: 8, input: '1jg4qgog4hj9q', groups: undefined ],
  [ '9', index: 11, input: '1jg4qgog4hj9q', groups: undefined ]
]
 */

for-in文の順番が保証される

従来はfor-in文で繰り返されるオブジェクト等の順番は必ずしも先頭からイテレートされるのではなく、順不同でした。ES2020からはその順番が保証されます。

ES2019(ES10)

文字列の片側だけ空白をトリミング(trimStart, trimEnd)

trimStartで左側、trimEndで右側だけ文字列の空白をtrimすることが可能になりました。

const str = "   Hello, World!   "
console.log(str.trimStart()) // "Hello, World!   "
console.log(str.trimEnd()) // "   Hello, World!"

2要素の配列からオブジェクトを作る(Object.fronEntries)

Object.fronEntriesを使って、[キー, バリュー]のペアの配列から、{ キー: バリュー }のオブジェクトを作ります。
ペアの配列は2次元配列としていくつでも指定可能です。

const ary = [
  ["name", "Taro"],
  ["age", 24],
  ["type", "fairy"]
]
console.log(Object.fromEntries(ary))
// { name: 'Taro', age: 24, type: 'fairy' }

配列の平坦化(flat, flatMap)

flatで深いネストもフラットに

flat(N)でN階層目までネストされた配列をフラットにできます。Nを省略するとN=1と同じになります。

const ary = [
  [1, 2], // 1階層ネスト
  [
    [3, 4], // 2階層
    [5, 6],
    [
      [7, 8] // 3階層
    ]
  ]
]
console.log(ary.flat()) // [ 1, 2, [ 3, 4 ], [ 5, 6 ], [ [ 7, 8 ] ] ]
console.log(ary.flat(2)) // [ 1, 2, 3, 4, 5, 6, [ 7, 8 ] ]
console.log(ary.flat(3)) // [ 1, 2, 3, 4, 5, 6, 7, 8 ]

flatMapでmapとflatを同時に行う

flatMapを使えば、mapした配列に対して1階層flatにできます。

const ary = ["Hello World!", "Soft and Wet"]
console.log(ary.map(a => a.split(" "))) 
// [ [ 'Hello', 'World!' ], [ 'Soft', 'and', 'Wet' ] ]

console.log(ary.flatMap(a => a.split(" ")))
// [ 'Hello', 'World!', 'Soft', 'and', 'Wet' ]

ECMAScriptとJSONにおける区切り文字の統一

以前はJavaScriptの文字列に行区切り\u2028と段落区切り\u2029を含めることが出来ずエラーとなっていました。
その一方でJSONではどちらも用いることが可能でした。
ES2019からはJacaScriptでもこれら区切り文字が使用可能となり、ECMAScriptはJSONのスーパーセットになりました。

catch句の引数の記述を省略可能

catchには引数の指定が必須でしたが、使用しないならば省略できるようになりました。

try {
  throw new Error
} catch { // catch(e)としなくていい
  console.error("ERROR")
}

関数にtoString()を使用したときコメントも文字列となる

タイトル通り、関数.toString()としたときにコメントも文字列になります。

function hoge() {
  /* hogehoge  */
  return null
}
console.log(hoge.toString())
// 出力
// function hoge() {
//   /* hogehoge  */
//   return null
// }

Symbolの引数を返す

シンボル.descriptionでSymbol定義時に引数に指定した値を返します。

const s = Symbol("hoge")
console.log(s.description) // hoge

まとめ

以上ES2023からES2019まで遡って新機能をまとめて振り返りました。
TypeScriptではとっくに採用していた機能も多いですね。