【JavaScript】オブジェクトの浅い比較(shallow compare)のロジックと比較の具体例

JavaScript

オブジェクトを浅い比較する際のロジックについて

オブジェクトの浅い比較(shallow compare, shallow equal checking)がtrueになるケースは以下の通りです。

  • オブジェクトの参照先が同じ場合
  • オブジェクトの参照先が異なる時は、オブジェクトのすべての『プロパティが一致』している場合

上記の『プロパティが一致』の定義は以下の通りです。

  • プロパティがプリミティブ型の場合、スカラ値が同じである
  • プロパティがオブジェクト型の場合、オブジェクトの参照先が同じである

プリミティブ型とは整数や理論値、文字列といったスカラ値を持つものです。オブジェクト型とはObjectやArrayといったものです。

浅い比較の具体例について

オブジェクトの参照先が同じ場合

オブジェクトの参照先が同じ、つまり互いの変更が影響を及ぼし合う関係のオブジェクトどうしは浅い比較においてtrueです。
以下は参照先が同じオブジェクトの浅い比較結果の例です。

import { shallowEqual } from 'react-redux'

const a = { value: "hoge", value2: "fuga" }
const b = a

console.log(shallowEqual(a, b))
// true


// 参考: 参照先が同じなので、値の変更が互いに影響を及ぼしあう
a.value = "hogehoge"
console.log(a)
// {value: 'hogehoge', value2: 'fuga'}
console.log(b)
// {value: 'hogehoge', value2: 'fuga'}


オブジェクトの参照先が異なる場合

浅い比較ではまずオブジェクトの参照先の一致を確認しますが、参照先が異なる場合でも浅い比較においてtrueになるケースがあります。
以下では参照先が異なるオブジェクトを浅い比較する際のパターンについて紹介します。

ネスト構造を持たないオブジェクトの場合

ネスト構造を持たない、つまりオブジェクトのプロパティがすべてプリミティブ型で構成されている場合はスカラ値が一致しているかを検証します。
すべてのプロパティのスカラ値が一致していれば浅い比較においてtrueです。

import { shallowEqual } from 'react-redux'


const a = { value: "hoge", value2: "fuga" }
const b = { value: "hoge", value2: "fuga" }

console.log(shallowEqual(a, b))
// true

浅いコピーの場合も、すべてのプロパティが一致していれば浅い比較においてtrueになります。

import { shallowEqual } from 'react-redux'

const a = { value: "hoge", value2: "fuga" }

// Object.assingを利用した浅いコピー
const b = Object.assign({}, a)

// 参考: スプレッド演算子を使った浅いコピーは以下のように記述できる
// const b = { ...a }

console.log(shallowEqual(a, b))
// true

浅いコピーの詳細解説はJavaScriptの浅い比較・浅い(深い)コピーの挙動まとめで紹介しています。

ネスト構造のオブジェクトの場合

オブジェクトのプロパティがオブジェクト型の場合、参照先が同じであればそのプロパティは一致していると解釈されます。
以下はオブジェクトの見た目は一致していますが、オブジェクト型のプロパティの参照先が異なるため浅い比較においてfalseになる例です。

import { shallowEqual } from 'react-redux'

const a = {
  value1: "hoge",
  value2: {
    value2_1: "fuga",
    value2_2: "piyo"
  }
}

const b = {
  value1: "hoge", // value1はプリミティブ型: スカラ値が一致しているのでtrue
  value2: { // value2はオブジェクト型: 参照先が異なるのでfalse
    value2_1: "fuga",
    value2_2: "piyo"
  }
}

// 浅い比較において、上記のオブジェクトは見かけの値は一致しているがfalseになる
console.log(shallowEqual(a, b))
// false

浅い比較でtrueになるネスト構造のオブジェクトの例は以下の通りです。

import { shallowEqual } from 'react-redux'

const a = {
  value1: "hoge",
  value2: {
    value2_1: "fuga",
    value2_2: "piyo"
  }
}

const b = {
  value1: "hoge", // value1はプリミティブ型: スカラ値が一致しているのでtrue
  value2: a.value2 // value2はオブジェクト型: 参照先が一致しているのでtrue
}

console.log(shallowEqual(a, b))
// true

さいごに

「浅い比較の関心はオブジェクトの参照先のみ」と思っている方も多いですが、オブジェクトの参照先が異なる場合でも浅い比較においてtrueになるケースがあるので覚えておくとよいでしょう。
浅い比較を検証するライブラリはいくつかあるのでロジックの詳細はライブラリのコードで確認するとよいでしょう。たとえばfbjs/packages/fbjs/src/core/shallowEqual.jsreact-redux/src/utils/shallowEqual.tsなどが参考になります。

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

参考資料