【JavaScript】Null合体(Nullish coalescing)とオプショナルチェーン(Optional chaining)の基本

JavaScript

Null合体演算子(Nullish coalescing operator)について

Null合体演算子(??)とはvalue ?? defaultValueのように記述し、左辺(value)がnullishな値(nullもしくはundefined)の時は右辺(defaultValue)、そうでない時は左辺valueを返す演算子です。
value ?? defaultValuevalue != null ? value : defaultValueは同じ結果になります。

nullとundefinedは等価演算において等しいです。ですので、『value != null』によって『nullもしくはundefinedではない』という判定ができます。

なお、 Null合体代入演算子(Logical nullish assignment operator)というNull合体演算子と名前が似ている演算子もあります。
Null合体代入演算子の詳細解説はNull合体代入演算子(Logical nullish assignment operator)の基本で紹介しています。

Null合体演算子を利用したリファクタリング例

Null合体演算子を利用したリファクタリング例を紹介します。

今回利用するサンプルコード

引数がなければHello、あれば引数の文字列を返すcreateMessageというメソッドがあったとします。
Null合体演算子を利用せずに実装すると以下のようになります。

const createMessage = (text) => {
  if (text != null) {
    return text
  } else {
    return "Hello"
  }
}

const defaultMessage = createMessage()
console.log(defaultMessage) // Hello

const morningMessage = createMessage("Good morning")
console.log(morningMessage) // Good morning

三項演算子を使って以下のようにも表現できます。

const createMessage = (text) => {
  const message = text != null ? text : "Hello"
  return message
}

const defaultMessage = createMessage()
console.log(defaultMessage) // Hello

const morningMessage = createMessage("Good morning")
console.log(morningMessage) // Good morning

Null合体演算子を利用して書きなおした例

Null合体演算子を利用すると以下のように書き換えられます。

const createMessage = (text) => text ?? "Hello"

const defaultMessage = createMessage()
console.log(defaultMessage) // Hello

const morningMessage = createMessage("Good morning")
console.log(morningMessage) // Good morning

Null合体演算子とOR演算子の違いについて

Null合体演算子と似た短絡評価(ショートサーキット)を行う演算子にOR演算子(||)があります。
短絡評価とは『左辺を評価した段階で式の結果が決まらない場合のみ右辺を評価する(式の結果が決まった時点で後続の評価をしない)』という評価方法のことをいいます。

OR演算子はvalue || defaultValueと記述すると、左辺(value)がfalsyな値(偽値、falseyな値)なら右辺(defaultValue)、そうでなければ左辺(value)を返す演算子です。
falsyな値とはfalseと評価される値のことを指します。

value || defaultValuevalue ? value : defaultValueは同じ結果になります。

JavaScriptにおけるfalsyな値は以下の8つです。1

JavaScirptのfalsyな値
  • false
  • 0
  • -0
  • “”
  • null
  • undefined
  • NaN

value || defaultValueでは上記8種類全てがdefaultValueになります。

null || 'default' // 'default'
undefined || 'default' // 'default'
false || 'default' // 'default'
0 || 'default' // 'default'
-0 || 'default' // 'default'
0n || 'default' // 'default'
"" || 'default' // 'default'
NaN || 'default' // 'default'

一方、value ?? defaultValueではundefinednullのみがdefaultValueとなります。

null ?? 'default' // 'default'
undefined ?? 'default' // 'default'
false ?? 'default' // false
0 ?? 'default' // 0
-0 ?? 'default' // -0
0n ?? 'default' // 0n
"" ?? 'default' // ""
NaN ?? 'default' // NaN

オプショナルチェーン演算子(Optional chaining operator)

オプショナルチェーン演算子(?.)とはobj?.valueのように記述し、objがnullishな値の時はundefined、そうでない場合はvalueを返す演算子です。

obj?.valueobj != null ? obj.value : undefinedは同じ結果になります。

オプショナルチェーン演算子を利用するとnullishなオブジェクトのプロパティにアクセスした場合もエラーが発生しません。

let user
console.log(user.name) // TypeError: Cannot read property 'name' of undefined

console.log(user?.name) // undefined

nullishな可能性のあるオブジェクトのプロパティへアクセスする場合はオプショナルチェーン演算子を利用するとよいです。

オプショナルチェーン演算子とNull合体演算子を組み合わせた例

以下はnameプロパティが存在しなければAnonymous、存在していればnameを返す例です。
オプショナルチェーン演算子とNull合体演算子を組み合わせることで『プロパティが存在しなかった場合のプロパティのデフォルト値』が設定できます。

let anonymousUser
const anonymousUserName = anonymousUser?.name ?? 'Anonymous'
console.log(anonymousUser) // undefined
console.log(anonymousUserName) // Anonymous

const user = { name: "Bob" }
const userName = user?.name ?? 'Anonymous'
console.log(user) // { name: 'Bob' }
console.log(userName) // Bob

オプショナルチェーン演算子とOR演算子を組み合わせると意図しない挙動になる可能性がある

先ほどのコードはNull合体演算子をOR演算子に書き換えても意図した挙動になります。

let anonymousUser
const anonymousUserName = anonymousUser?.name || 'Anonymous'
console.log(anonymousUser) // undefined
console.log(anonymousUserName) // Anonymous

const user = { name: "Bob" }
const userName = user?.name || 'Anonymous'
console.log(user) // { name: 'Bob' }
console.log(userName) // Bob

Null合体演算子とOR演算子は書き換え可能に見えますが、プロパティにfalsyな値がセットされる場合は注意が必要です。

以下はpointプロパティがなければ100、あればpointの値を返す例です。
0はJavaScriptにおいてfalsyな値であるため、OR演算子で実装するとpointに0をセットしても100が返されてしまいます。

let defaultBonus
const defaultBonusPoint = defaultBonus?.point || 100
console.log(defaultBonusPoint) // 100

const superBonus = { point: 5000 }
const superBonusPoint = superBonus?.point || 100
console.log(superBonusPoint) // 5000

const zeroBonus = { point: 0 }
const zeroBonusPoint = zeroBonus?.point || 100
console.log(zeroBonusPoint) // 100 ← 0はfalsyな値なのでOR演算子の場合は右辺がセットされる。意図した挙動にならない可能性があるので注意。

Null合体演算子であれば上記のような問題は発生しません。

let defaultBonus
const defaultBonusPoint = defaultBonus?.point ?? 100
console.log(defaultBonusPoint) // 100

const superBonus = { point: 5000 }
const superBonusPoint = superBonus?.point ?? 100
console.log(superBonusPoint) // 5000

const zeroBonus = { point: 0 }
const zeroBonusPoint = zeroBonus?.point ?? 100
console.log(zeroBonusPoint) // 0

オプショナルチェーンはプロパティの値もしくはnullishな値しか返さないので、Null合体演算子と組み合わせるのが無難です。

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考