javascriptでは、いくつかの等価性判定の演算子、メソッドがある。
等価性判定の方法をざっと纏めておく。
参考:等価性の比較と同一性
文字列、数値、真偽値
等価演算子「==」だと、型が異なる場合には暗黙の型変換をして判定される。
console.log(1 == '1'); // true
console.log(0 == ''); // true
console.log(1 == true); // true
等価演算子では、抽象等価比較アルゴリズムを用いて比較するらしいが、この辺を意識してプログラムを組むのも難しいので、通常 「==」は用いない。
厳密等価演算子「===」を用いるのが一般的。
console.log(1 === '1'); // false
console.log(0 === ''); // false
console.log(1 === true); // false
console.log(1 === 1); // true
console.log(0 === 0); // true
console.log('1' === '1'); // true
console.log(true === true); // true
null
厳密等価演算子「===」で比較。
const a = null;
console.log(a === null); // true
console.log(a === 0); // false
console.log(a === undefined); // false
等価演算子だと、undefinedでもnullと等価だと判定されちゃう。
let b; // undefined
console.log(b == null); // true
非数
NaN(非数、Not a Number)の場合には厳密等価演算子でもfalseになること。
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
しようがないので、別のやり方で判定する必要がある。
そこで、isNaN関数があるので、それを用いることにしようとしても、ここでも暗黙の型変換の罠がある。
console.log(isNaN(NaN)); // true
console.log(isNaN('hoge')); // true <- 文字列も非数と判定
厳密なNaNの判定については、Number.isNaNで行う。
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('hoge')); // false
なお、配列のメソッドで非数が判定できないことがあるので注意。
// indexOfだと、NaNが見つけられない
[1, 2, NaN].indexOf(NaN); // -1
// includesだと、NaNがあるか判定できる
[1, 2, NaN].includes(NaN); // true
[1, 2, 'three'].includes(NaN); // false
// findIndexでNumber.isNaNでテストすれば、NaNの位置がわかる
[1, 2, NaN].findIndex(n => Number.isNaN(n)); // 2
[1, 2, 'three'].findIndex(n => Number.isNaN(n)); // -1
配列(要素がプリミティブ値のみの場合)
等価演算子、厳密等価演算子だと参照の比較(同一性判定)なので、要素の中身の比較はされない。
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
console.log(arr1 == arr2); // false
console.log(arr1 === arr2); // false
なので、中身の比較をして等価性判定するためにはちょっと工夫が必要。
for文で要素の中身を比較して頑張ろうとすると、以下になる。
function equalArray(arr1, arr2) {
const equalValue = (x, y) => {
return x === y || (Number.isNaN(x) && Number.isNaN(y));
};
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (!equalValue(arr1[i], arr2[i])) {
return false;
}
}
return true;
}
console.log(equalArray([1, 2, 3], [1, 2, 3])); // true
console.log(equalArray([1, 2, 3], [1, 2, 'three'])); // false
console.log(equalArray([1, 2, 3], [1, 2, 3, 4])); // false
console.log(equalArray([1, 2, NaN], [1, 2, NaN])); // true
console.log(equalArray([1, 2, NaN], [1, 2, 3])); // false
もっと簡単に、JSON.stringifyで文字列に直して比較しても大体OKなのだが、NaN、undefined、InfinityなどのJSONに対応していない値はnullに変換されてしまうため、厳密にそれらを区別したい場合は注意が必要。
function equalObject(arr1, arr2) {
return JSON.stringify(arr1) === JSON.stringify(arr2);
}
console.log(equalObject([1, 2, 3], [1, 2, 3])); // true
console.log(equalObject([1, 2, 3], [1, 2, 'three'])); // false
console.log(equalObject([1, 2, 3], [1, 2, 3, 4])); // false
console.log(equalObject([1, 2, NaN], [1, 2, NaN])); // true
console.log(equalObject([1, 2, NaN], [1, 2, 3])); // false
console.log(equalObject([1, 2, NaN], [1, 2, null])); // true ★ NaNはnullに変換される
オブジェクト(要素がプリミティブ値のみの場合)
無効なJSON値(NaN、undefined、Infinity)が要素に無ければ、JSON.stringifyで文字列に変換しての比較でいいのだが、そのまま比較するだけではキーの順序が揃っているもののみが等価と判定されてしまう。
console.log(equalObject({ a: '1', b: 2 }, { a: '1', b: 2 })); // true
console.log(equalObject({ b: 2, a: '1' }, { a: '1', b: 2 })); // false
Object.entriesでキーと値の配列に変換して、ソートした後、JSON.stringifyで比較すると、キーの順序が揃ってなくても等価と判定できる。
function equalObject2(obj1, obj2) {
return JSON.stringify(Object.entries(obj1).sort())
=== JSON.stringify(Object.entries(obj2).sort());
}
console.log(equalObject2({ a: '1', b: 2 }, { a: '1', b: 2 })); // true
console.log(equalObject2({ b: 2, a: '1' }, { a: '1', b: 2 })); // true
無効なJSON値が含まれてて、厳密に判定したければ、リストの場合と同じようにfor文で要素を比較して頑張る。
function equalObject3(obj1, obj2) {
const equalValue = (x, y) => {
return x === y || (Number.isNaN(x) && Number.isNaN(y));
};
const arr1 = Object.entries(obj1).sort();
const arr2 = Object.entries(obj2).sort();
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (!equalValue(arr1[i][0], arr2[i][0])
|| !equalValue(arr1[i][1], arr2[i][1])) {
return false;
}
}
return true;
}
console.log(equalObject3({ b: 2, a: '1', c: null }, { a: '1', b: 2, c: NaN })); // true
一般的な配列、オブジェクト
オブジェクトの要素がオブジェクトや配列だったり、配列の要素がオブジェクトだったりを考慮する場合、ここまでの応用でできないことはないが、ライブラリを使う方が安全。
lodash もしくは underscorejsで isEqual関数が提供されている。
console.log(_.isEqual(
{ a: '1', b: [2, { c: 3, d: 4 }] },
{ b: [2, { d: 4, c: 3 }], a: '1' }
)); // true
おわりに
- 非数NaNの扱いには注意。
- 配列やオブジェクトの比較は、だいたいはJSON.stringifyでOK(オブジェクトの場合はキー順序を考慮する必要あり)。ただし、無効なJSON値(NaN、undefined、Infinity)が要素に含まれている場合は注意。
- より一般的な配列やオブジェクトの比較は、lodash もしくは underscorejsで比較すればよい。
以上です。