LoginSignup
123
111

More than 3 years have passed since last update.

JavaScript の"=="の挙動をハッキリさせておく

Last updated at Posted at 2019-10-13

イントロダクション

JavaScript では異なる型の値を == で比較すると、片方の値がもう片方の型に変換されたり、どちらも別の型に変換されたりする。== 以外の演算子でもこのようなことが起きるが、これを type coercion といい、JavaScript の中でも特に覚えにくい機能である。

type coercionの例
[] == ""

//  ↓  [] が string に変換される

"" == ""

//  ↓  文字列比較

true

ここでは ECMAScript 2019 Language Specification に基づいて、== の型変換がどのように行われているのかを説明する。ECMAScript 2020 では新しく BigInt という型が追加されるが、ここでは扱わない。

JavaScript における型は以下がすべてである。typeof で得られるものとは違うので注意されたい。

  • Number: 100, -1, +0, -0, Infinity, -Infinity, NaN など
  • String: "abc", "" など
  • Boolean: false, true
  • Symbol: Symbol("name"), Symbol.iterator など
  • Undefined: undefined
  • Null: null
  • Object: 上記以外

型変換が起きる組み合わせ

まずは、どの型の組み合わせで型変換が起きるのかを下の表で確認しよう。具体的な変換アルゴリズムは後で説明する。表にある primitive とは Object 以外の値という意味である。

NOTE: 型変換は複数回起こりうるので注意。例えば object と string を比較するとき、まず object が primitive に変換される。変換された結果 number であった場合、number と string なので今度は string が number に変換され、最終的に number どうしの厳密比較 === が行われる。

strict equal

以下でそれぞれの型変換がどのように行われるかを説明する。

Boolean ➡️ Number

  • false ➡️ 0
  • true ➡️ 1

String ➡️ Number

Number() 関数に渡したときと全く同じように変換される。

  • "32" ➡️ 32
  • "1.602e-19" ➡️ 1.602e-19 (指数表記)
  • "Infinity" ➡️ Infinity
  • "0b1011" ➡️ 11 (2進数)
  • "0o707" ➡️ 455 (8進数)
  • "0xa0" ➡️ 160 (16進法)
  • "abc" ➡️ NaN

Object ➡️ primitive

これは少し複雑であるため、順を追って説明する。まずはその object が [Symbol.toPrimitive] メソッドを持っているかがチェックされる。このメソッドを最初から持っている object は Date object と Symbol object1 のみである(逆に言えば、これ以外の object はデフォルトでこのメソッドを持っていない)。

[Symbol.toPrimitive] メソッド を持つとき (≒ Date object か Symbol object のとき)

そのメソッドに引数 "default" を渡して実行し、その戻り値が変換結果となる。ただし、もしこの戻り値が Object であれば TypeError となる。

obj[Symbol.toPrimitive]("default")

例えば Date object の場合以下のようになる。

const obj = new Date('2019-10-14T10:20:30Z')
obj[Symbol.toPrimitive]("default")  // "Mon Oct 14 2019 19:20:30 GMT+0900 (Japan Standard Time)"

実際、下の式は true になる(タイムゾーンの設定が日本であることが前提)。

obj == "Mon Oct 14 2019 19:20:30 GMT+0900 (Japan Standard Time)"  // true

Symbol object の場合、このメソッドは内部の Symbol value を返す。

const sym = Symbol("name")  // Symbol value
const symObj = Object(sym)  // Symbol object

symObj[Symbol.toPrimitive]("default") === sym  // true
symObj == sym  // true

[Symbol.toPrimitive] メソッド を持たないとき

以下が順に実行される。

  1. valueOf メソッドを持ち、実行結果が非 Object ならばそれを変換結果として終了。
  2. toString メソッドを持ち、実行結果が非 Object ならばそれを変換結果として終了。
  3. どちらも成功しなければ TypeError となる。

例えば普通のオブジェクトや配列は valueOf メソッドは自分自身を返し toString メソッドは String を返すので、toString() メソッドの実行結果が変換結果となる。

({ a: 1 }).toString()  // "[object Object]"
({ a: 1 }) == "[object Object]"  // true

[1, 2, 3].toString()  // "1,2,3"
[1, 2, 3] == "1,2,3" // true

例えば以下のように人工的に valueOf メソッドをセットすることで、変換結果を変えることもできる。

({valueOf: () => 2019}) == 2019  // true

厳密等号 ===

== で比較するとき、最初から同じ型であるかもしくは型変換の結果同じ型になると === による厳密比較が行われる。同じ型の値を === で比較したときのルールをそれぞれの型について説明する。

Number と Number

どちらかが NaN ならば false (どちらも NaN であっても false) となり、それ以外の場合は普通の数値比較の結果を返す。

Note: 実は 0-0 は内部的には異なる値だが、=== で比較しても等しいとみなされる。

String と String

単純に文字列として等しければ true、そうでなければ false となる。

Boolean と Boolean

単純に比較した結果となる。

Undefined と Undefined

常に true となる。

Null と Null

常に true となる。

Object と Object

参照(メモリアドレス)の比較となり、たとえ値が同じでもメモリの別の場所に存在すれば false となる。

const o1 = { a: 1 }
const o2 = { a: 1 }

o1 === o1  // true
o1 === o2  // false

Symbol と Symbol

Object と同様に参照の比較となる。たとえ同じ description (Symbol() の引数) を持っていても、別々に作られたならば異なるとみなされる。

const s1 = Symbol("my symbol")
const s2 = Symbol("my symbol")

s1 === s2  // false

仕様の該当箇所

  • == による比較: 7.2.14
  • === による比較: 7.2.15
  • Date object の [Symbol.toPrimitive] メソッド: 20.3.4.45
  • Symbol object の [Symbol.toPrimitive] メソッド: 19.4.3.5

  1. Symbol object と Symbol value は異なることに注意。Symbol object の型はあくまで Object であり、Java でいうラッパークラスのようなものである。型が Symbol である値を Symbol value といい、Symbol(name) で得られるのはこちらである。 

123
111
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
123
111