もう一度JavaScriptを理解する part2

# 初めに
今回はSpecail valueについてまとめていきたいと思います。

part2は
[You Don't Know JS: Types & Grammar - Chapter 2: Values](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/types%20%26%20grammar/ch2.md)
の勉強メモです。

[part1](https://qiita.com/hu-yu/items/c52499418cb448783ac4#letconst)では`undeclared`、`TDZ`、`undefined`を触れていますが、今回はもう少しまとめていきたいと思います。

# undeclared vs. undefined
`undeclared`は宣言されていないということで、
```jsx
var a = 1 // declared global variable
b = 2 // undeclared global variable

console.log(b) // 2
```
**（`'use strict'`なら未宣言の変数は`ReferenceError`になります。）**
`b`のような宣言使わない、しかし存在している変数が`undeclared`状態です。

では`declared`状態と何か違うかな？
`undeclared`状態の変数はどこに存在しているでしょう？

前から同じ疑問持っているので少し調べたら、同じくグローバルに置かれると、
>Both exist in the global object
[Effect of declared and undeclared variables - stackoverflow](https://stackoverflow.com/questions/15985875/effect-of-declared-and-undeclared-variables)

両者は同じくグローバルオブジェクトに存在している、そして
両者のプロパティでは`configurable attribute`が違うことで未宣言変数は`delete`演算子がで削除できるとわかりました。

また、`YDNJS`シリーズの作者によりますと、
>an "undeclared" variable is one that has not been formally declared in the accessible scope.

`undeclared`変数はどこのスコープにも成立していない状態に対して、
`declared`変数は、自分のいるスコープにちゃんと成立しています。

`declared`変数はcompile-timeでは`var`なら`compilation`の解析（parsing）段階で`undefined`という初期値を得て、`let/const`なら`uninitialized`初期化されていない状態を得る。

別々run-timeに入ったら新しい値が付与されるまで同じく`undefined`（`let/const`変数はrun-timeで初期値をゲットする）という値を持っていますが、`let/const`変数はrun-timeまでTDZ（Temporal Dead Zone）に囲まれている。

# TDZ
TDZ（Temporal Dead Zone）は`let/const`宣言の`uninitialized`状態に指しています。

`var`での変数宣言の`undefined`は「ここには値がないからとりあえず`default value`与えよう」ということに対して、ES6から`let/const`が現れ、`let/const`での変数宣言から値が付与されるまで`uninitialized`状態として「ここに変数が存在するけど初期化されていないよ、だから誰も動かせてはいけません」とされています。

## typeof undefined
```jsx
var x
console.log(typeof x) // undefined

let z
console.log(typeof z) // undefined
```
`undefined`はプリミティブ型の一つとして、値は`undefined`自身しかありません。
しかし上の例では`x`と`y`は`undefined型`でしょうか？

**変数は値を保存するが、自身にはデータ型が所有しません。**
`x`と`y`はまだ値が付与されてなく、今は`no value`だから`undefined`初期値を持っているということで、`undefined型`と見えますが、実質上ここでは`undefined`という値を表しています。

なぜ例のように`const`使うと`SyntaxError`が出てくる？`undefined`という初期値がついてるじゃないんですか？

`const`は設計上では**唯一の値**を付与するために創られた宣言（Declaration）です。そして`TDZ`はそれを成立させるために創られており、`const`は`let`みたい値が何度も上書きするようにできないので、値が付与されるまで`TDZ Error`が投げられてくるのです。

# undefined vs. null
`undefined`は「ここには値が存在するけれど、（まだ定義されていないから）値がない」。
**変数が宣言されたが、値が付与されていない　⇒　undefined
関数を呼び出すとき、引数が提供されていない　⇒　undefined
関数に`return value`指定しない場合、`undefined`を返す　⇒　undefined
オブジェクトの`Property value`がない　⇒　undefined**

（
`{Key: Value}`  ＝  `{Property Name: Property Value}`
`Property attributes`  →  [[value]], [[Writable]], [[Enumerable]], [[Configurable]]
）

```jsx
var a
console.log(a) // undefined

function logStr(str) {
  console.log(str)
}

logStr() // undefined

console.log(logStr())
// undefined // str is undefined
// undefined // return value is undefined
// function's return value, default value is 'undefined'

const obj = {}
logStr(obj.name) // undefined
```

一方、`null`は「ここにはオブジェクトという値が存在しない」、あるいは「意図的に存在すべきではない」と意味しています。
`null`はプリミティブ値の一つですが、歴史的な理由で`null`型ではなく`object`です。
また、`null`はオブジェクトのプロトタイプチェーンの起点（何も継承していないオブジェクト）でもあります。
```jsx
console.log(typeof null) // object
console.log(typeof undefined) // undefined

console.log(Object.getPrototypeOf(Object.prototype)) // null
```
`null`は`undefined`と同じく`falsy falue`としてブーリアン型に型変換するんですが、`undefined`のように`typeof`でデータ型特定できないし、とても厄介です。
でもその特性を用いて、複数の条件で洗い出すことができます。
```jsx
const x = null

if (!x && typeof x === 'object') {
  console.log('Yes, you got a null')
}
// note: null is the only one object value in falsy value
```


# NaN
`NaN`（not a number、非数）
演算や処理結果としてデータ型が数値が解釈できないもの（parseInt('abc')、Number(undefined)）、
ゼロのゼロ除算（0/0）、無限大の除算（Infinity/Infinity）など無意味な演算結果、
これら`NaN`で表現されています。
[NaN - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/NaN#%E8%A7%A3%E8%AA%AC)
`NaN`自身は数値型でありながら、自身と同等ではないという特性を持ち、
```jsx
console.log(typeof NaN) // number
console.log(NaN === NaN) // false
console.log(NaN == NaN) // false
```
`NaN`の判定は、`isNaN()`と`Number.isNaN()`がありますが、

```jsx: isNaN
console.log(isNaN({})) // true
console.log(isNaN([])) // false

// console.log(isNaN(BigInt(9007199254740991)))
// console.log(isNaN(BigInt('9007199254740991')))
// TypeError: Cannot convert a BigInt value to a number


console.log(isNaN(123)) // false
console.log(isNaN(-1.23)) // false
console.log(isNaN(1 - 3)) // false
console.log(isNaN(0)) // false

console.log(isNaN('')) // false // *string
console.log(isNaN('123')) // false // *string
console.log(isNaN('Hello')) // true
console.log(isNaN('2022/07/05')) // true
console.log(isNaN(true)) // false // *boolean
console.log(isNaN(undefined)) // true // *undefined
console.log(isNaN(null)) // false // *object

console.log(isNaN(NaN)) // true
console.log(isNaN('NaN')) // true

console.log(isNaN(0 / 0)) // true
console.log(isNaN(1 / 0)) // false // Infinity
console.log(isNaN(Infinity / Infinity)) // true
console.log(isNaN(Infinity / -Infinity)) // true
```
`isNaN()`は`NaN`である場合だけ`true`を返すはずだが、`isNaN()`ではまず引数を数値型（Number）に強制的に変換してから判定するので、
```jsx: Number()
console.log(Number({})) // NaN
console.log(Number([])) // 0
console.log(Number('')) // 0
console.log(Number('123')) // 123
console.log(Number(true)) // 1
console.log(Number(undefined)) // NaN
console.log(Number(null)) // 0
```
変な結果につながりました。。
`Number.isNaN()`はこの問題を修正するため、本当の意味の`NaN`を検出するとき`true`を返します。
```jsx: Number.isNaN()
console.log(Number.isNaN({})) // false
console.log(Number.isNaN([])) // false
console.log(Number.isNaN(BigInt(9007199254740991))) // false
console.log(Number.isNaN(BigInt('9007199254740991'))) // false

console.log(Number.isNaN('')) // false
console.log(Number.isNaN('123')) // false
console.log(Number.isNaN(true)) // false
console.log(Number.isNaN(null)) // false

console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN('NaN')) // false // *string

console.log(Number.isNaN(0 / 0)) // true
console.log(Number.isNaN(1 / 0))  // false // Infinity
console.log(Number.isNaN(Infinity / Infinity)) // true
console.log(Number.isNaN(Infinity / -Infinity)) // true
```
# Positive-zero(+0) vs. Negative-zero(-0)
作者によると、数学上では+0、-0は変位（displacement）で別々の方向を示すことと同じ、この二つを混同してはならない。そして+0、-0は`NaN`とは全然違う概念です。しかし、
```jsx
console.log(0 === -0) // true
```
JSの厳密等価子では、+0と-0は同じものとして扱われ、
でも、`Infinity`と`-Infinity`確かに存在しています。
```jsx
console.log(1 / 0) // Infinity
console.log(1 / -0) // -Infinity
```
ならば判定方法は、
```jsx
const negativeZero = -0
function NegZero(num) {
  return num === 0 && (1 / num) === -Infinity
}

console.log(NegZero(negativeZero)) // true
```
または、同一値の判定メソッド`Object.is()`を使いましょう。
```jsx
console.log(Object.is(negativeZero, -0)) // true
```

# おまけ Object.is()

```jsx
console.log(Object.is(undefined, undefined)) // true
console.log(Object.is(null, null)) // true
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(+0, +0)) // true
console.log(Object.is(-0, -0)) // true
```