オブジェクトの比較方法

JavaScript でオブジェクトを比較する際、単純な === では期待通りに動作しません。オブジェクトは参照型であるため、内容が同じでも別のオブジェクトは等しくないと判定されます。

参照の比較と値の比較

=== はオブジェクトの参照(メモリ上の位置)を比較します。内容が同じでも、別々に作られたオブジェクトは等しくありません。

const obj1 = { name: "田中" };
const obj2 = { name: "田中" };
const obj3 = obj1;

console.log(obj1 === obj2); // false(内容は同じだが別オブジェクト)
console.log(obj1 === obj3); // true(同じ参照)
参照の比較(===)

同じオブジェクトを指しているかを確認する

値の比較(内容比較)

オブジェクトの中身が等しいかを確認する

JSON.stringify() による比較

シンプルなオブジェクトであれば、JSON 文字列に変換して比較する方法があります。

const obj1 = { name: "田中", age: 25 };
const obj2 = { name: "田中", age: 25 };

console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true

ただし、この方法にはいくつかの問題があります。

プロパティの順序

プロパティの順序が異なると、内容が同じでも false になります。

特殊な値

undefined、関数、Symbol は JSON.stringify() で消えるため、正しく比較できません。

const a = { x: 1, y: 2 };
const b = { y: 2, x: 1 };

// 内容は同じだが順序が違う
console.log(JSON.stringify(a)); // '{"x":1,"y":2}'
console.log(JSON.stringify(b)); // '{"y":2,"x":1}'
console.log(JSON.stringify(a) === JSON.stringify(b)); // false

自作の比較関数

プロパティを一つずつ比較する関数を作成することで、より正確な比較ができます。

function shallowEqual(obj1, obj2) {
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) {
    return false;
  }
  
  return keys1.every(key => obj1[key] === obj2[key]);
}

const a = { x: 1, y: 2 };
const b = { y: 2, x: 1 };

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

この関数はシャローな比較(1階層のみ)です。ネストしたオブジェクトは参照で比較されます。

ディープな比較

ネストしたオブジェクトも含めて比較するには、再帰的な処理が必要です。

function deepEqual(obj1, obj2) {
  if (obj1 === obj2) return true;
  
  if (typeof obj1 !== "object" || typeof obj2 !== "object") {
    return false;
  }
  
  if (obj1 === null || obj2 === null) return false;
  
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) return false;
  
  return keys1.every(key => deepEqual(obj1[key], obj2[key]));
}

const a = { user: { name: "田中" } };
const b = { user: { name: "田中" } };

console.log(deepEqual(a, b)); // true

配列の比較

配列もオブジェクトなので、同様の問題があります。

const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];

console.log(arr1 === arr2); // false

// 要素ごとに比較
console.log(
  arr1.length === arr2.length &&
  arr1.every((val, i) => val === arr2[i])
); // true

ライブラリの活用

実際のプロジェクトでは、Lodash の _.isEqual() などのライブラリを使用するのが一般的です。エッジケースも含めて正しく処理してくれます。

// Lodash を使用した場合
// import _ from 'lodash';

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };

// _.isEqual(obj1, obj2); // true

オブジェクトの比較は意外と複雑なので、要件に合った方法を選択することが重要です。