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

# イントロダクション

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

```js:type&nbsp;coercionの例
[] == ""

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

"" == ""

//  ↓  文字列比較

true
```

ここでは [ECMAScript 2019 Language Specification](https://www.ecma-international.org/ecma-262/10.0/index.html) に基づいて、`==` の型変換がどのように行われているのかを説明する。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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/e0550d2f-45bd-7aea-61ce-296942efa3d2.png)


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

## 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 object[^symobj] のみ**である(逆に言えば、これ以外の object はデフォルトでこのメソッドを持っていない)。

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

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

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

```js
obj[Symbol.toPrimitive]("default")
```

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

```js
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` になる(タイムゾーンの設定が日本であることが前提)。

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

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

```js
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()` メソッドの実行結果が変換結果となる。

```js
({ 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` メソッドをセットすることで、変換結果を変えることもできる。

```js
({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` となる。

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

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

## Symbol と Symbol

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

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

s1 === s2  // false
```

# 仕様の該当箇所

- `==` による比較: [7.2.14](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-abstract-equality-comparison)
- `===` による比較: [7.2.15](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-strict-equality-comparison)
- Date object の `[Symbol.toPrimitive]` メソッド: [20.3.4.45](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-date.prototype-@@toprimitive)
- Symbol object の `[Symbol.toPrimitive]` メソッド: [19.4.3.5](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-symbol.prototype-@@toprimitive)