ESLintのルールから考えるJavaScriptのundefined判定

JavaScriptのundefined判定にはいくつか記載方法がありますが、言語仕様を理解しないまま記述すると、ブラウザや開発環境によっては思わぬバグを生み出すことがあります。

この記事では、JavaScriptにおけるundefinedの判定方法を複数紹介し、ESLintのルールから適切な判定について検討してみようと思います。

## ESLintとは

[ESLint](https://eslint.org/)は、JavaScriptやTypeScriptの静的コード解析ツールでです。
単純な構文エラーの検出やルールを設定することで一貫性のあるコーディングスタイルを維持しやすくなります。

## JavaScriptにおけるundefinedの判定パターン

JavaScriptでundefined判定をしたいとき、いくつかの書き方が考えられます。
パターンとしてはざっとこんなものでしょうか。

1. 厳密等価演算子（===）を使用する

    ```ts
    if (foo === undefined) {
      // do something
    }
    ```

1. typeof演算子を使用する

    ```ts
    if (typeof foo === "undefined") {
      // do something
    }
    ```

1. windowを使用する（実行環境がブラウザである前提）

    ```ts
    if (foo === window.undefined) {
      // do something
    }
    ```


1. void演算子を使用する

    ```ts
    if (foo === void 0) {
      // do something
    }
    ```

1. nullと等価演算子を使用する（正確にはnull or undefinedの判定）

    ```ts
    if (foo == null) {
      // do something
    }
    ```

初学者や普段別言語で開発をしている方にとって、第一印象はこんな感じになるのではないでしょうか。

| 書き方                         | 印象                                                                     |
| ------------------------------ | ------------------------------------------------------------------------ |
| 1. 厳密等価演算子（===）を使用 | 直感的にわかる                                                           |
| 2. typeof演算子を使用         | 直感的にわかるけどちょっと長い。なぜ typeofを使っているのだろうか。     |
| 3. window を使用           | 直感的にわかるけどちょっと長い。なぜ window を使っているのだろうか。 |
| 4. void 演算子を使用           | 直感的ではない                                                           |
| 5. null と等価演算子を使用     | 直感的ではない、null も含めた判定になっている                            |

なぜこれだけの書き方があり、直感的でない書き方をされることがあるのかをESLintのルールを起点として考えていきます。

## ESLint のルールからundefined判定を考える

ESLintで設定可能なルールのひとつである[no-undefined](https://eslint.org/docs/latest/rules/no-undefined)からJavaScriptの言語仕様とundefined判定について考えていきます。

no-undefinedはundefinedを直接使用を禁止するものです。
ドキュメントではエラー例として下記が記載されています。

```ts
/*eslint no-undefined: "error"*/
var foo = undefined;

var undefined = "foo";

if (foo === undefined) {
  // ...
}

function foo(undefined) {
  // ...
}

bar(undefined, "lorem");
```

正しい例として下記が記載されています。

```ts
/*eslint no-undefined: "error"*/

var foo = void 0;

var Undefined = "foo";

if (typeof foo === "undefined") {
  // ...
}

global.undefined = "foo";

bar(void 0, "lorem");
```

**no-undefinedのルール下**では、undefinedの判定パターンで記載した「厳密等価演算子（===）を使用する」はエラーになります。

### なぜundefinedの直接使用を禁止するルールがあるのか

簡単に言うと**JavaScriptではundefinedは予約語ではない**からです。
つまり下記のようなことが可能であり、undefinedが本来のundefinedと異なっている可能性が捨てきれないためです。

```ts
// ES3以前は下記で上書き可能
undefined = 'foo'

// ES5以降もスコープ変数の変数名として使用可能
const func = () => {
  const undefined = "foo";

  // do something
};
```

実際このようなコードを書くことは考えにくく極端な例ではありますが、言語仕様としてundefinedが本来の意図である未定義とは異なる状態になりうるため、直接利用を禁止するルールが用意されています。

また、前述の「typeof演算子を使用」、「windowを使用」、「void演算子を使用」という記述方法は未定義という本来のundefinedと確実に比較する方法になっています。

※ES11以降の環境であればwindowではなくglobalThisを用いることも可能です。

## 直接参照の不確実性を排除する

undefinedを直接利用することが危険な要因として、不確実性があることに触れてきました。

ただ、記述の簡便さや直感的かを考えると直接利用したいと考える方もいるでしょう。

その場合は、[no-undefined](https://eslint.org/docs/latest/rules/no-undefined)の関連ルールに記載されている[no-global-assign](https://eslint.org/docs/latest/rules/no-global-assign)と[no-shadow-restricted-names](https://eslint.org/docs/latest/rules/no-shadow-restricted-names)を設定することでundefinedの不確実性を排除し、直接参照による実装を安心してすることができるようになります。

## 結局、どう書くのか

所属しているチームのメンバーにもよりますが、まず安全性、次に可読性を判断基準として考えると下記の順に検討するといいのではと考えてます。

1. no-global-assignとno-shadow-restricted-namesを設定し、厳密等価演算子（===）を使用
1. no-undefinedを設定し、typeof演算子を使用
1. なんらかの理由でESLintの設定ができない場合、typeof演算子を使用

よろしければ、皆さんの考えも教えてください。

## まとめ

- JavaScriptのundefinedは常に未定義を指しているとは限らない不確実性がある
- 不確実性を考慮し、確実に未定義であることを参照するために複数の記載方法がある
- ESLintを用いることで不確実性を排除し安全に直接利用することが可能になる

動くことをゴールにせず、言語仕様や実行環境、そして所属している開発組織に合わせた可読性の高い実装とはなんだろうかと日々考えながら開発と向き合っていきたいです。