LoginSignup
6
9

More than 3 years have passed since last update.

JavaScriptの"仕様にまだ導入されていない新機能"紹介

Last updated at Posted at 2019-06-30

概要

ECMAScript の仕様は TC39 という組織によって決められているが、この記事ではまだ ECMAScript の仕様に導入されていないが TC39 が proposal (提案)として認めている新機能(ESNext とも呼ばれる)をいくつか紹介する。各見出しは該当の GitHub ページにリンクされている。

今すぐこれらの機能を使いたい場合は babel の plugin を使用するのがいいだろう。また、ブラウザの中でも特に Google Chrome は新機能の実装に積極的であり、仕様に導入される前の proposal を早めに Chrome に実装することがよくある。これについて最新情報を知りたい場合は @ChromiumDev を見ればよいだろう。

proposal ごとに stage (「提案されただけの段階」から「仕様に導入された段階」までのどれくらいの進捗度かを表す)が割り振られているが、今後変わっていくものであるためここでは書かない。stage ごとの一覧は以下のページで見られる。

Optional Chaining

ネストされたオブジェクトの深いプロパティに安全にアクセスしたいとき、一段階深く行くたびに undefinednull であるかどうかチェックする必要がある(なぜなら undefinednull のプロパティにアクセスしようとするとエラーが発生する)が、optional chaining ではその必要がなくなる。

Swift の同名の operator や C# の null-conditional operator に類似している。名前の由来はおそらくその Swift のものであり、Optional型の値からOptional型の値を得る operator であるため chain (連ねて使用すること)できるからだと思われる。

例えば o.a.b.c にアクセスしたいとき、以下のように、まず o が nullish (undefinednull)であるかチェックし、次に o.a が nullish であるかチェックし...と冗長なコードになってしまう。

Note: x == nullx が 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

何らかの値が nullundefined である場合は代わりに何か自分の指定したデフォルト値を使用したいということがある。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.allPromise.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
  }
}

  1. data property は{ prop: 1 }のようにして作られる通常のプロパティであるのに対し、accessor property とは getter と setter によって定義されたプロパティである。 

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9