JavaScript勉強会

JavaScriptの学習日記

JavaScriptで配列やオブジェクトを比較するときはJSONに変換

以前、JavaScriptの比較について学びました。

 

jsstudy.hatenablog.com

 

JavaScriptの比較で、間違えやすい点があったのでメモしておきます。

 

JavaScriptで配列やオブジェクトを比較するときの注意点

JavaScriptでは、配列やオブジェクトの中身が同じ内容かどうか?を比較検証するとき、生のデータのままでは比較がうまくできない場合があります。

予想外の挙動でうまくいかない場合は、配列やオブジェクトをいったんJSONに変換して、文字列として比較すると対処できます。

 

JavaScriptの変数の特徴

原因は、JavaScriptの変数の仕組みにあります。

 

(参考)

qiita.com

 

これから説明で"値の参照"という言葉を使いますが、これは例えるならたくさんある引き出しのどこに値をしまったかを表すメモのようなものです。

それから便宜上、"値の参照"にS1,S2などとラベルをつけますが、この"値の参照"に関する処理は僕たちには見えない深淵でひっそりと行われるので、普通にJavaScriptを書いていて"値の参照"を直接見る機会はないです。

変数に値を代入すると"値の参照"が変数に入る

 

www.webprofessional.jp

 

JavaScriptにはポインターがなく、参照の方式は私たちが知っているほかの主要なプログラミング言語とは異なります。

JavaScriptでは、ある変数が別の変数を参照すること(参照渡し)には対応していません。

 

これはどういうことでしょうか?

以下のJavaScriptのコードで実験してみます。

 

comparison_test.js

// 配列で比較
let a = [1, 2, 3];
let b = a; // [1, 2, 3]
let c = [1, 2, 3];
console.log('aは' + a); console.log('bは' + b); console.log('cは' + c);
if (a === b) { console.log('配列aと配列bは一致 OK'); } else { console.log('配列aと配列bは不一致 NG'); }
if (a === c) { console.log('配列aと配列cは一致 OK'); } else { console.log('配列aと配列cは不一致 NG'); } // 配列をJSON文字列に変換して比較 let a_json = JSON.stringify(a); let b_json = JSON.stringify(b); let c_json = JSON.stringify(c);
console.log('a_jsonは' + a_json); console.log('b_jsonは' + b_json); console.log('c_jsonは' + c_json);
if (a_json === b_json) { console.log('JSONに変換したaとJSONに変換したbで一致 OK'); } else { console.log('JSONに変換したaとJSONに変換したbで不一致 NG'); }
if (a_json === c_json) { console.log('JSONに変換したaとJSONに変換したcで一致 OK'); } else { console.log('JSONに変換したaとJSONに変換したcで不一致 NG'); }

 

コンソールログの実行結果は以下にようになりました。

 

aは1,2,3
bは1,2,3
cは1,2,3

配列aと配列bは一致 OK
配列aと配列cは不一致 NG

 

a_jsonは[1,2,3]
b_jsonは[1,2,3]
c_jsonは[1,2,3]

JSONに変換したaとJSONに変換したbで一致 OK
JSONに変換したaとJSONに変換したcで一致 OK 

 

bはaを代入したものなので一致します。

cはaと同じ内容の配列[1, 2, 3]なので、cとaも一致するような気がしますが、実はこれが不一致になるのです!

 

a = [1, 2, 3]

c = [1, 2, 3]

でaとcは同じと判定したい場合には、都合が悪い仕様ですね?

 

不一致になる原因は、上記で紹介したQiitaの記事の解説内容となります。

  • 変数aが参照している[1, 2, 3]は、S1という保管場所(タンスの引き出しみたいなもの)に保存されているとします。
  • 変数bが参照している[1, 2, 3]は、aと同じS1という保管場所を参照しています。
  • 変数cが参照している[1, 2, 3]は、新たに用意されたS2という保管場所を参照しています。

(S1、S2というラベルは適当につけただけなので、他の名前でもOK)

 

つまり、

a === b は S1とS1を比較していることになるので一致します。

a === c はS1とS2を比較していることになるので不一致になります。

 

今知りたいのは、

「値の参照(保管場所、タンスの引き出し)が同じかどうか」

ではなく、

「引き出しの中に入っている中身(配列の内容そのもの)が同じかどうか」

です。

 

f:id:jsstudy:20190814104406j:plain

(via https://www.cecile.co.jp/detail/XM-211/

 

対処法の1つはJSON変換

Google検索で調べたら、いくつか対処方法が紹介されていました。

 

marycore.jp

 

配列やオブジェクトの比較で、値の内容そのものではなく、値の参照で比較してしまう仕組みを回避するには、強制的に内容を文字列として書き出す方法がありました。

 

上記のサンプルコードだと、a、b、cをいったんJSON(文字列)に変換してしまえば、文字列同士の比較に変えてしまうことができます。

a、b、cをそれぞれJSONに変換すると"[1, 2, 3]"という文字列になります

文字列として比較した場合なら、a_jsonもc_jsonも同じ"[1, 2, 3]"なので、一致します。

 

developer.mozilla.org

 

qiita.com

 

毎回この方法を書くのは面倒くさいので、配列やオブジェクトの中身を比較して判定する関数を用意しておけば良いと思います。

 

他の対処方法

lodashを利用する方法

自作の判定用の関数を用意しなくても、「lodash」などの関数型ライブラリーを利用すれば、配列やオブジェクトの比較をいろいろな方法で簡単にできるようです。

 

lodash.com

 

qiita.com

 

 

codeday.me

 

外側の配列をソートする場合、内側の配列はすでにソートされているので、_.isEqual()を使用できます。

var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];
_.isEqual(array1.sort(), array2.sort()); //true

 

 

codeday.me

 

differenceWith()をisEqual()コンパレータと組み合わせて使用し、isEmptyを呼び出してそれらが等しいかどうかを確認できます。

var isArrayEqual = function(x, y) {
  return _(x).differenceWith(y, _.isEqual).isEmpty();
};

var result1 = isArrayEqual(
  [{a:1, b:2}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

var result2 = isArrayEqual(
  [{a:1, b:2, c: 1}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

document.write([
  '<div><label>result1: ', result1, '</label></div>',
  '<div><label>result2: ', result2, '</label></div>',
].join(''));

 

lodashなどを使えば、自作関数を用意するよりも簡単にいろんなことができて便利そうですね!

 

その他、参考情報

その他、検索してたら参考になる情報がありました。ついでにメモ。

 

sbfl.net

 

sbfl.net

 

JavaScriptの特徴について、勘違いしやすい点に注意しておきたいと思います。

 

 

 

JavaScript関数型プログラミング 複雑性を抑える発想と実践法を学ぶ (impress top gear)

JavaScript関数型プログラミング 複雑性を抑える発想と実践法を学ぶ (impress top gear)

  • 作者: Luis Atencio,株式会社イディオマコムニカ加藤大雄
  • 出版社/メーカー: インプレス
  • 発売日: 2017/06/09
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る