概要
ECMAScript の仕様は TC39 という組織によって決められているが、この記事ではまだ ECMAScript の仕様に導入されていないが TC39 が proposal (提案)として認めている新機能(ESNext とも呼ばれる)をいくつか紹介する。各見出しは該当の GitHub ページにリンクされている。
今すぐこれらの機能を使いたい場合は babel の plugin を使用するのがいいだろう。また、ブラウザの中でも特に Google Chrome は新機能の実装に積極的であり、仕様に導入される前の proposal を早めに Chrome に実装することがよくある。これについて最新情報を知りたい場合は @ChromiumDev を見ればよいだろう。
proposal ごとに stage (「提案されただけの段階」から「仕様に導入された段階」までのどれくらいの進捗度かを表す)が割り振られているが、今後変わっていくものであるためここでは書かない。stage ごとの一覧は以下のページで見られる。
Optional Chaining
ネストされたオブジェクトの深いプロパティに安全にアクセスしたいとき、一段階深く行くたびに undefined
や null
であるかどうかチェックする必要がある(なぜなら undefined
や null
のプロパティにアクセスしようとするとエラーが発生する)が、optional chaining ではその必要がなくなる。
Swift の同名の operator や C# の null-conditional operator に類似している。名前の由来はおそらくその Swift のものであり、Optional
型の値からOptional
型の値を得る operator であるため chain (連ねて使用すること)できるからだと思われる。
例えば o.a.b.c
にアクセスしたいとき、以下のように、まず o
が nullish (undefined
か null
)であるかチェックし、次に o.a
が nullish であるかチェックし...と冗長なコードになってしまう。
Note: x == null
は x
が nullish のときのみ true
になることに注意。
const o_a_b_c = (o == null || o.a == null || o.a.b == null) ? undefined : o.a.b.c
optional chaining により次のように簡潔に書ける。
const o_a_b_c = o?.a?.b?.c
key を expression で指定するプロパティアクセスや関数呼び出しもできる。
o?.[propName] // o == null ? undefined : o[propName]
f?.(arg) // f == null ? undefined : f(arg)
Nullish Coalescing
何らかの値が null
か undefined
である場合は代わりに何か自分の指定したデフォルト値を使用したいということがある。nullish coalescing はこれを簡潔に書くためのシンタックスである。nullish coalescing と呼ばれる理由は、左に nullish な値があると右の値と融合(coalescing)させられて結果一つの値となるからである。
Before
// o.a が null か undefined なら代わりに 1 を使用する
const o_a = o.a == null ? 1 : o.a
After
const o_a = o.a ?? 1
先述の optional chaining と組み合わせると、JSON形式のレスポンスから何らかの値を取り出したいときに便利だろう。
const posts = response?.data?.user?.[0]?.posts ?? []
Note: 同様の目的でよく使われる ||
は、左の値が falsy (Boolean に変換すると false
になる値) であれば右の値が使われてしまうという点で ??
とは異なる。
const o = {
a: 0,
b: false,
c: ''
}
o.a || 10 // 10
o.b || 10 // 10
o.c || 10 // 10
o.a ?? 10 // 0
o.b ?? 10 // false
o.c ?? 10 // ''
Promise.allSettled & Promise.any
どちらも複数の promise を受け取る関数だが、Promise.allSettled
はすべての promise が settle (resolve または reject すること)した時点で resolve し、Promise.any
はいずれかの promise が resolve した時点で resolve し、すべて reject した時点で reject する。既存の Promise.all
や Promise.race
と比較すると以下のようになる。
Promise.allSettled |
Promise.any |
Promise.all |
Promise.race |
|
---|---|---|---|---|
resolve するタイミング | すべての promise が settle | いずれかの promise が resolve | すべての promise が resolve | いずれかの promise が resolve |
resolve の値 | * | そのまま | resolve 値の配列 | そのまま |
reject するタイミング | なし | すべての promise が reject | いずれかの promise が reject | いずれかの promise が reject |
reject の値 | - | reject 値の配列 | そのまま | そのまま |
(settle = resolve か reject すること)
* Promise.allSettled
の resolve 値は、各 promise の最終的な状態(resolve/reject とその値)を表すオブジェクトの配列となる。
const p1 = Promise.resolve(1)
const p2 = Promise.reject(">_<")
Promise.allSettled([p1, p2]).then(console.log)
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: '>_<' }
// ]
do Expression
do { ... }
ブロック内で最後に実行された statement (文)の値が do
ブロック全体の値となる。これにより、一つの値を求めるいくつかの statement を一つの expression にまとめることができ、(途中で使った)変数のスコープを狭く抑えることもできる。
Scala などでは通常のブロック { ... }
が同じ機能を持つ。
const x = 5
const result = do { // result === 2
if (x > 10) {
1
} else if (x > 0) {
2
} else {
3
}
}
const a = do { // a === 5
const b = 3
const c = 4
const a_squared = b * b + c * c
Math.sqrt(a_squared)
}
Pipeline Operator
ある値 x
に function f1
を適用し、その返り値に f2
を適用し...と繰り返したいとき、fn(...(f2(f1(x)))...)
と書けば、function が右から左に並び、括弧も多くなるので可読性が低下する。pipeline operator |>
により以下のように簡潔に書けるようになる。
F# や OCaml に同じ operator がすでに存在し、bash の pipe |
も似た機能を持っている。pipeline と呼ばれるのは、function はものを入れると反対から何かが出てくるパイプのようなものとみなして、function をつなげてより大きな function、すなわちパイプラインを構成するというイメージがあるからである。
const result = x |> f1 |> f2 |> f3 // result === f3(f2(f1(x)))
Partial Application
function に対して引数を渡して実行することを apply (名詞: application) というが、functional programming (関数型プログラミング)の界隈では複数の引数を取る function に対して引数を partial (部分的)に与えて新たな function を得ることを parital application という。この proposal はこの機能の JavaScript 版である。
function sum(a, b, c) {
return a + b + c
}
const sumWith3 = sum(?, 3, ?)
console.log(sumWith3(1, 2)) // 6
const sumWith3And5 = sumWith3(5, ?)
console.log(sumWith3And5(8)) // 16
先述の pipeline operator を使用するときに便利である。
const result = x
|> f1
|> f2(?, 3)
|> f3(4, ?, 5)
// f3(4, f2(f1(x), 3), 5) と等価
Note: this
を内部で使用している function について、わざわざ bind
する必要はない。obj.f(?)(3)
は obj.f(3)
と等価である。
Class Fields
クラスのフィールド(他のオブジェクト指向言語でいうところのインスタンス変数)を Java などと同じように constructor
の外で宣言できるようになる。
Before
class C {
constructor() {
this.c = 0
}
}
After (完全に等価なコードではない)
class C {
c = 0 // class field
}
Note: 上2つのシンタックスには機能的な違いがある。実は後者は内部的には Object.defineProperty()
によって data property1 として定義される。これがどのような違いを生むかと言うと、例えば次のように accessor property1 をもったクラスを継承するときに、新しいシンタックスだとそのプロパティが data property へと上書きされてしまう。
class C {
// accessor property
get a() {
return 10
}
set a(value) {
console.log("setter called")
}
}
// Beforeの方
class D extends C {
constructor() {
super()
this.a = 1
}
}
const d = new D() // "setter called"が出力される
d.a = 2 // ここでも出力される
// Afterの方
class E extends C {
a = 1
}
const e = new E() // "setter called"が出力されない
e.a = 2 // ここでも出力されない
Private Fields
先述の class field について、#
を先頭につけることで外からアクセスできない private な field となる。Java や C# における private instance variable に相当する。
class Counter {
#c = 0 // private field
get count() {
return this.#c
}
increment() {
this.#c++
}
}
const c = new Counter()
console.log(c.count) // 0
c.increment()
console.log(c.count) // 1
console.log(c.#c) // SyntaxError
子クラスから親クラスの private field にはアクセスできない。
class C {
#a = 0
}
class D extends C {
printa() {
console.log(this.#a) // Syntax Error
}
}
ただし、同じクラスの他のインスタンスの private field にはアクセスできる。
class C {
#a = 0
constructor(a) {
this.#a = a
}
printa() {
console.log(this.#a)
}
add(other) {
return new C(this.#a + other.#a)
}
}
const c1 = new C(1)
const c2 = new C(2)
const c3 = c1.add(c2)
c3.printa() // 3
Private Methods & Getter/Setters
先述の private field と同様に method や getter/setter にも#
をつけることで private にできる。
class C {
get #a() {
return 1
}
set #a(value) {
}
#method() {
}
}
Static Fields & Methods
static
をつけることで (public) field、private field、private method を static (インスタンスではなくクラスのプロパティ)にできる。Java や C# における static variable/method に相当する。public static method はすでに ECMAScript 2015 で導入されている。
class A {
static publicStaticVar = 4
static #privateStaticVar = 4
static #privateStaticMethod() {
return 4
}
}