2011年5月23日月曜日

JavaScriptの == 演算子の挙動

色々と訳あってJavaScriptについて普段気にしてない部分を調べてました。
その中でも比較演算子 == は言語毎にクセが強いのでしっかり調べてみました。
(静的型付けの言語は素直だと思うけど、動的型付けだとPHPの例もあるしね)

自分でいくつかコードを書いてみてもどう動くのかイマイチ掴めなかったのでECMAScriptの仕様書を参照しました。
== は仕様では抽象的等価比較アルゴリズムというものに基づいて実装しているらしいです。
(多分分かりやすく)まとめると

  1. UndefinedとUndefinedは等しい
  2. NullとNullは等しい
  3. NaN はいずれのNumberとも等しくない
  4. +0と-0 は等しい
  5. StringとStringの比較は文字列の比較を行う
  6. BooleanとBooleanの比較はそのまま比較
  7. ObjectとObjectの比較は参照の比較を行う
    FunctionとFunctionの比較は結合オブジェクト(同じソースから作ったオブジェクト)ならtrue
  8. UndefinedとNullは等しいと扱われる
  9. StringとNumberの比較はStringをNumberにしてから比較する
  10. Booleanとその他 の比較はBooleanを数値にしてから比較する
  11. String/NumberとObject の比較はObjectをToPrimitiveしてから比較する

ToPrimitiveはまずvalueOfメソッドを呼び出して、結果がオブジェクトでなければ(ここの意味がまだよく分かってない。toStringメソッドを持っていなければって意味かな?)、toStringメソッドを呼び出す(hint を指定しないので hint Number と同じ挙動)。
ちなみにtoStringメソッドのデフォルトの実装はvalueOfメソッドを呼び出すみたい(これは実装依存かな?FirefoxとChromeは同じ挙動)。

上記を考慮すると

   1: document.write("0" == false); //=> true


まず10を適用して "0"== 0 にしてから9を適用して 0==0 になるからtrueになる、と。


7の結合オブジェクトってのが分かりにくいけど、ソースコード上に書いた同じfunctionから作った関数オブジェクトのことを結合オブジェクトと呼ぶらしく、これは等しいと見なされるらしい(でも実装依存?)。



   1: function hoge() {
   2:     return "hoge";
   3: }
   4:  
   5: var h1 = hoge;
   6: var h2 = hoge;
   7: document.write(h1 == h2); //=> true

んで、違うソースコードの場合は等しくない



   1: var f1 = function() {return "hoge";}
   2: var f2 = function() {return "hoge";}
   3: document.write(f1 == f2); //=> false

最後に11の挙動を確かめる。



   1: function User() {
   2:     this.toString = function() {
   3:         return "toString";
   4:     };
   5: }
   6:  
   7: var u = new User();
   8: document.write(u.valueOf() + "<br />"); //=> toString
   9: document.write(u.toString() + "<br />"); //=> toString
  10: document.write((u == "valueOf") + "<br />"); //=> false
  11: document.write((u == "toString") + "<br />"); //=> true



   1: function User() {
   2:     this.valueOf = function() {
   3:         return "valueOf";
   4:     };
   5: }
   6: var u = new User();
   7: document.write(u.valueOf() + "<br />"); //=> valueOf
   8: document.write(u.toString() + "<br />"); //=> [object Object]
   9: document.write((u == "valueOf") + "<br />"); //=> true
  10: document.write((u == "toString") + "<br />"); //=> false



   1: function User() {
   2:     this.valueOf = function() {
   3:         return "valueOf";
   4:     };
   5:     
   6:     this.toString = function() {
   7:         return "toString";
   8:     };
   9: }
  10: var u = new User();
  11: document.write(u.valueOf() + "<br />"); //=> valueOf
  12: document.write(u.toString() + "<br />"); //=> toString
  13: document.write((u == "valueOf") + "<br />"); //=> true
  14: document.write((u == "toString") + "<br />"); //=> false



   1: function User() {
   2:     this.valueOf = function() {
   3:         return function() {
   4:             return "valueOf";
   5:         };
   6:     };
   7:     
   8:     this.toString = function() {
   9:         return "toString";
  10:     };
  11: }
  12: var u = new User();
  13: document.write(u.valueOf() + "<br />"); //=> function () { return "valueOf"; }
  14: document.write(u.toString() + "<br />"); //=> toString
  15: document.write((u == "valueOf") + "<br />"); //=> false
  16: document.write((u == "toString") + "<br />"); //=> true

こうして見てみると確かにvalueOfメソッドの結果が使われている。
4つめがちょっと特殊でvalueOfメソッドの結果が使えないのでtoStringメソッドの結果を比較に使ってることが見られる。


さて、最後にちょっと分かりにくい挙動の確認。



   1: function User() {
   2:     this.valueOf = function() {
   3:         return "valueOf";
   4:     };
   5: }
   6: var u = new User();
   7: var a = ["valueOf"];
   8:  
   9: document.write(u.valueOf() + "<br />"); //=> valueOf
  10: document.write(a.valueOf() + "<br />"); //=> valueOf
  11: document.write((a == u.valueOf()) + "<br />"); //=> true
  12: document.write((a.valueOf() == u) + "<br />"); //=> false

JavaScript難しいっす。

0 件のコメント:

コメントを投稿