https://qiita.com/uhyo/items/44c2f79873de13186743
JavaScriptでは、オブジェクトからプリミティブへの暗黙の変換が発生することがあります1。その結果、例えば次のような楽しい事態が生じます。
console.log(["foo", "bar"] == "foo,bar"); // true
console.log([""] == 0); // true
console.log((123 ^ {}) === 123); // true
const obj1 = ["😂"];
const obj2 = ["😂"];
console.log(obj1 == "😂", "😂" == obj2); // true true
console.log(obj1 == obj2); // false
このような挙動は面白いので、Twitterとかで誰かが話題にするたびに多少は話題になります。しかしいい加減飽きたので、皆さんにはこんなの常識として理解しておいていちいち騒がないでいただきたく、この記事を用意しました。
この記事では、JavaScriptにおけるプリミティブ変換に関する仕様を余すこと無く紹介します。JavaScriptの経験が無い人には細部の理解が難しいかもしれませんが、それでも雰囲気くらいは掴めるのではないかと思います。
※本記事の内容は執筆時点の仕様(ES2017)に基づくものです。
まず手始めに、オブジェクトをプリミティブに変換する明示的な方法を紹介します。オブジェクトは、文字列・数値・真偽値の3種類のプリミティブに変換することができます。そのために、String
, Number
, Boolean
というビルトイン関数を使用します。
明示的な変換の例
console.log(String({foo: "bar"})); // "[object Object]"
console.log(String(["12", 345])); // "12,345"
console.log(Number({foo: "bar"})); // NaN
console.log(Number(["123"])); // 123
console.log(Boolean({foo: "bar"})); // true
console.log(Boolean(["0"])); // true
この例がなぜこのような結果になるのかは、後々解説します。
この記事では、上で紹介した方法以外は暗黙の変換として扱うことにします。オブジェクトからプリミティブへの暗黙の変換は、色々な場面で発生します。
一つは、==
演算子でオブジェクトとプリミティブを比較した場合です。例えば["foo", "bar"] == "foo,bar"
という比較を行った場合、左辺がオブジェクト(配列)、右辺が文字列なので、オブジェクトが文字列に変換されます。この場合左辺の配列が"foo,bar"
という文字列に変換されるので比較の結果はtrueになります。
ちなみに、JavaScriptを書く読者の方は恐らくご存知の通り、==
演算子はJavaScriptにおいてはたいへん悪名高い演算子です。その理由はまさにこの暗黙の型変換を行うからです。より安全な比較演算子である===
は暗黙の型変換を行いません。オブジェクトと文字列を比較した時点で異なる値として扱われ、結果はfalse
となります。
また、-
や^
など、数値同士の演算を行う演算子をオブジェクトに適用した場合も、やはりオブジェクトが数値に変換されます。例えば["123"] - ["12"]
という式は両辺がオブジェクト(配列)ですが、左辺が123
、右辺が12
に変換されるため、結果は111
という数値になります。
他にはプロパティ名として使用した場合(obj[ ["123"] ]
)やテンプレート文字列中に出現した場合(123${obj}9
)、Number.parseInt
の引数として与えられた場合など、さまざまな場面でオブジェクトはプリミティブに変換されます。
全ての場合を列挙するのは大変なので、本当に完全に理解したいという方は仕様書を"ToPrimitive", "ToString", "ToNumber"などで全文検索してください。
実は、オブジェクトからプリミティブへの変換は3種類あります。これは変換時に付随するhint値というパラメータによって区別されます。hint値には、変換後のプリミティブとして文字列が期待されていることを表すstring
と、数値が期待されていることを表すnumber
と、特に期待する型がないdefault
の3種類があります。
例えば、obj * 10
という式でobj
がオブジェクトだった場合を考えます。*
は算術演算子でありオペランドとして数値が期待されますから、obj
は数値に変換される必要があります。よって、obj
はhint値がnumber
でプリミティブに変換されます。