[JavaScript] undefined と null の発生と挙動 2020

JavaScript の `undefined` と `null` がそれぞれどんなときに現れてどんな風に振る舞うのかを挙げます。

## `undefined` の発生

- [`globalThis.undefined`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/undefined)
- 未初期化の変数の値
  - `let x; x`
- オブジェクトの未定義プロパティアクセス
  - `({}).x`
  - `[][0]`
  - `Array(1)[0]`
- 関数に実引数が渡されない場合の仮引数の値
  - `(x => x)()`
- 関数が明示的に値を返さない場合の戻り値
  - `(() => {})()`
  - `(() => { return })()`
- 任意の式に [`void` 演算子](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/void)を適用した値
  - `void 1`
- Optional Chaining で左辺値が `null` または `undefined` の場合
  - `null?.x`
  - `undefined?.x`
- [`Array.prototype.find()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find) や [`Map.prototype.get()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map/get) で要素が見つからない場合
  - `[].find(() => false)`  
  - `(new Map).get('x')`

## `null` の発生

- [`null`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/null) リテラル
- プロトタイプのないオブジェクトのプロトタイプ
  - `Object.getPrototypeOf(Object.prototype)`
  - `Object.getPrototypeOf(Object.create(null))`
- [`String.prototype.match()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/match) や [`RegExp.prototype.exec()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec) でマッチしない場合
  - `''.match(/a/)`
  - `/a/.exec('')`
- Web Platform API で何かが見つからない場合や設定可能な何かが未設定の場合
  - `localStorage.getItem('')`
  - `sessionStorage.getItem('')`
  - `document.documentElement.parentElement`
  - `document.getElementById('')`
  - `document.querySelector('x')`
  - `document.createElement('a').getAttribute('x')`
  - `document.createElement('a').onclick`
  - etc...

## `undefined` と `null` の挙動

- [`typeof`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/typeof)
  - `typeof undefined === "undefined"`
  - `typeof null === "object"`
- 真偽値化
  - `Boolean(undefined) === false`
  - `Boolean(null) === false`
- 文字列化
  - `String(undefined) === "undefined"`
  - `String(null) === "null"`
- 数値化
  - `isNaN(+undefined)`
  - `+null === 0`
  - `isNaN(parseInt(undefined, 10))`
  - `isNaN(parseInt(null, 10))`
  - `isNaN(parseFloat(undefined))`
  - `isNaN(parseFloat(null))`
- 32 bit 整数化
  - `(undefined | 0) === 0`
  - `(null | 0) === 0`
- [`JSON.stringify`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)
  - `JSON.stringify(undefined) === undefined`
  - `JSON.stringify(null) === "null"`
  - オブジェクトプロパティ: `undefined` は無視される
    - `JSON.stringify({ x: undefined }) === '{}'`
    - `JSON.stringify({ x: null }) === '{"x":null}'`
  - 配列要素: `undefined` は `null` に変換される
    - `JSON.stringify([undefined]) === '[null]'`
    - `JSON.stringify([null]) === '[null]'`
    - `JSON.stringify(Array(1)) === '[null]'`
- 関数のデフォルト値が利用されるのは `undefined` のみ
  - `((x = 3) => x)() === 3`
  - `((x = 3) => x)(undefined) === 3`
  - `((x = 3) => x)(null) === null`
- 分割代入のデフォルト引数が利用されるのは `undefined` のみ
  - `const { x = 3 } = {}; x === 3`
  - `const { x = 3 } = { x: undefined }; x === 3`
  - `const { x = 3 } = { x: null }; x === null`
- [`Array.prototype.sort()`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) および `Array.prototype.toSorted()` の順序
  - 比較関数が未指定（`undefined`）の場合
    - `undefined` → 無条件に末尾要素となる
    - `null` → 文字列 `"null"` として比較される
    - 例: `[null, undefined, NaN, 'M', 'O', 'm', 'o', 'z'].sort()`  
      → `['M', NaN, 'O', 'm', null, 'o', 'z', undefined]`
  - 比較関数が指定された場合
    - `undefined` → 比較関数に渡されることなく、無条件に末尾要素となる
    - `null` → 比較関数に渡され、戻り値の正負に基づいて並べ替えられる
  - `Array(1)` など `length` 指定で得られる「存在しない」値は `undefined` と区別され、 `undefined` より後ろに配置される
    - 例: `[].concat(Array(1), undefined, Array(1)).sort()`  
      → `[undefined, 空 × 2]`
- [`CSSStyleDeclaration`](https://developer.mozilla.org/ja/docs/Web/API/CSSStyleDeclaration) のプロパティへの代入
  - `document.body.style.display = undefined`  
    → 何も起こらない
  - `document.body.style.display = null`  
    → 当該プロパティがクリアされる（空文字列も同様）
- プロトタイプを持たないオブジェクトの作成方法は `Object.create(null)`  
  （`Object.create(undefined)` はエラー）
- 等価性
  - `undefined !== null`
  - `undefined == null`
  - `document.all == null`