[JavaScript] var を書かない＆なるべく const を使う

過去記事から分離、一部内容追加、説明を全体的に修整。

参考「[イマドキな JavaScript で書かない・使わないもの: var, function, then, jQuery, その他](https://qiita.com/kerupani129/items/b2c3619856b048c13394)」

※ IE 等では Babel やポリフィル等を使用する前提です。

# 1. `var` を書かない (`no-var`)

`var` は古い JavaScript からある書き方で、`let` や `const` とスコープが異なり、混乱を招く可能性がある (すなわちバグができる可能性が高まる) ため、`var` を書かないべきです。

ただし、古いプロジェクトから移行するコストが高い場合にあえて無視することもあります。

各スコープ:

- `const`, `let`: ブロックスコープ
- `var`: 関数スコープ

参考「[no-var - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/no-var.html)」
参考「[2.2 Disallow var - 2. References - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#references--disallow-var)」

`const`, `let` は IE 11 でも使用できます。
ただし、IE 11 では `for` 文内での `let` の挙動が異なります。

参考「[let - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/let)」

# 2. なるべく `const` を使う

プログラミング全般において、変更可能な (ミュータブルな) 変数よりも変更不可な (イミュータブルな) 変数の方が望ましいです。

可読性・保守性の向上、バグ削減などのメリットがあります。

## 2.1. 値を書き換えないものはとにかく `const` (`prefer-const`)

値を書き換えないものは全て `const` にします。

```js:悪い例
let response = await fetch(url);
let json = await response.json();
let message = json.message;
```

```js:良い例
const response = await fetch(url);
const json = await response.json();
const message = json.message;
```

MDN や Google の開発者向けの一部のヘルプページ等で、サンプルコードで `let` が多用されている場合がありますが、それは悪い書き方です。

参考「[prefer-const - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/prefer-const.html)」

## 2.2. `const` が使えないと勘違いされがちなもの

### 2.2.1. 配列やオブジェクトは `const` でも中身を書き換えられる

配列やオブジェクトの中身を操作したいときに `let` にするひとがいますが、変数の宣言が `const` でも中身を書き換えられます。

配列やオブジェクトをまるごと書き換えることはできなくなります。

```js
const array = [2, 5, 8];

array.push(15); // array: [2, 5, 8, 15]

array = [3, 6, 9, 18]; // TypeError
```

```js
const obj = {
    foo: 'Foo',
    bar: 'Bar',
};

obj.bar = 'New Bar';

obj = { // TypeError
    baz: 'Baz',
    qux: 'Qux',
};
```

### 2.2.2. `for...in` や `for...of` で `const` を使える (`prefer-const`)

`for...in` や `for...of` で `const` を使用すると、ループのたびに別の定数として扱われます。

```js:悪い例
for (let property in object) {
    console.log(property);
}

for (let value of array) {
    console.log(value);
}
```

```js:良い例
for (const property in object) {
    console.log(property);
}

for (const value of array) {
    console.log(value);
}
```

参考「[prefer-const - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/prefer-const.html)」

## 2.3. `for (;;)` は `let` を使用する

配列等ではなるべく反復メソッド (※後述) を利用するようにしますが、反復メソッドで処理することがむずかしい場合は素直に for 文 `for (;;)` を使用します。

for 文 `for (;;)` の性質上、変更可能な (ミュータブルな) 変数が必要なため `let` を使用します。

こういう場合には無理して `const` を使用しなくて良いと思います。

# 3. なるべく `const` で書くための手法

なるべく `const` で書くためのいくつかの手法があります。

- 同じ変数を使いまわさず、別名の定数を作る
- `if` 文の代わりに三項演算子等を使用する
- 配列などのコレクションの操作は `map()` や `reduce()` 等の反復メソッドをなるべく利用する
- 別関数に分離して直接 `return` する

## 3.1. 同じ変数を使いまわさず、別名の定数を作る

例として、変数が持つ値を変換するとき、同じ変数名を使いまわさずに別名の定数を作ります。

変数の別名を決める方法として、以下のようなものがあります:

- 英語の過去分詞や形容詞を付ける
- 変換後の型名を付ける

(※状況に応じて名前を決定してください。)

```js:悪い例
let html = '<div>nya</div>';

html = html.replace(/</g, '&lt;').replace(/>/g, '&gt;');
html = new TextEncoder().encode(html);
```

```js:良い例
const html = '<div>nya</div>';
const escapedHtml = html.replace(/</g, '&lt;').replace(/>/g, '&gt;');
const htmlUint8Array = new TextEncoder().encode(escapedHtml);
```

## 3.2. `if` 文の代わりに三項演算子等を使用する

- 三項演算子 `(c ? t : f)`
- オプショナルチェーン演算子 `?.`
- Null 合体演算子 `??`
- 論理和 `||`
    - `const c = a || b;` (Falsy な値の注意が必要)

### 3.2.1. 三項演算子 `(c ? t : f)`

```js:悪い例
let ynStr;
if ( isYes ) {
    ynStr = 'Yes';
} else {
    ynStr = 'No';
}
```

```js:良い例
const ynStr = (isYes ? 'Yes' : 'No');
```

※三項演算子は本来括弧 `()` を必要としませんが、演算子の優先順位が分かりにくくなることがあるため、括弧 `()` を付けた方が良いと思います。

参考「[条件 (三項) 演算子 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Conditional_Operator)」

### 3.2.2. 三項演算子を入れ子にしない (`no-nested-ternary`)

三項演算子を入れ子にするとコ－ドを理解しにくくなり、結果として保守性の低下やバグの発生率増加をまねくと考えられるので使用しないべきです。

```js:悪い例
const a = ((foo ? bar : baz) ? qux : quxx);

const b = (foo ? (bar ? baz : qux) : quxx);

const c = (foo ? bar : (baz ? qux : quxx));
```

```js:良い例
const aa = (foo ? bar : baz);
const a = (aa ? qux : quxx);

const bb = () => (bar ? baz : qux);
const b = (foo ? bb() : quxx);

const cc = () => (baz ? qux : quxx);
const c = (foo ? bar : cc());
```

ここではアロー関数で処理を分離して、遅延評価しています。

※後述の「別関数に分離して直接 `return` する」も参照。

参考「[15.6 Nested Ternaries - 15. Comparison Operators & Equality - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#comparison--nested-ternaries)」
参考「[no-nested-ternary - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/no-nested-ternary)」

### 3.2.3. オプショナルチェーン演算子 `?.`

`null` または `undefined` (**nullish**) に対してそのままプロパティにアクセスしようとすると TypeError になりますが、オプショナルチェーン演算子 `?.` を用いてアクセスすることで `undefined` を返すようにできます。

- nullish
    - `null`, `undefined`
    - nullish でない: `foo !== null && foo !== void 0`
- falsy
    - `null`, `undefined`, `false`, `""` (空文字列), `0`, `-0`, `0n` (BigInt), `NaN`
    - falsy でない: `foo`

```js:悪い例
const foo = null;

// 
let baz = void 0;
if ( foo !== null && foo !== void 0 ) {
    baz = foo.bar;
}
```

```js:悪い例
const foo = null;

// 
const baz = ((foo !== null && foo !== void 0) ? foo.bar : void 0); // 意味的には no-unneeded-ternary に反するかと思います
```

```js:良い例
const foo = null;

// 
const baz = foo?.bar;
```

参考「[オプショナルチェーン (?.) - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Optional_chaining)」
参考「[Nullish value - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN](https://developer.mozilla.org/ja/docs/Glossary/Nullish)」
参考「[Falsy (偽値) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN](https://developer.mozilla.org/ja/docs/Glossary/Falsy)」

### 3.2.4. Null 合体演算子 `??`

`null` または `undefined` (**nullish**) の場合に右辺値 (デフォルト値) にします。

- nullish
    - `null`, `undefined`
    - nullish でない: `foo !== null && foo !== void 0`
- falsy
    - `null`, `undefined`, `false`, `""` (空文字列), `0`, `-0`, `0n` (BigInt), `NaN`
    - falsy でない: `foo`

```js:悪い例
const foo = {};

// 
let baz = 'Default';
if ( 'bar' in foo ) {
    baz = foo.bar;
}
```

```js:悪い例
const foo = {};

// 
const baz = ('bar' in foo ? foo.bar : 'Default'); // 意味的には no-unneeded-ternary に反するかと思います
```

```js:良い例
const foo = {};

// 
const baz = foo.bar ?? 'Default';
```

参考「[Null 合体 (??) - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator)」
参考「[Nullish value - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN](https://developer.mozilla.org/ja/docs/Glossary/Nullish)」
参考「[Falsy (偽値) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN](https://developer.mozilla.org/ja/docs/Glossary/Falsy)」

### 3.2.5. 論理和 `||`

論理和 `||` も Null 合体演算子 `??` と同様の使い方ができますが、nullish でなく **falsy** な値のときに右辺値 (デフォルト値) になります。
左辺値が `false`, `""` (空文字列), `0` 等のときにも右辺値が返されるため、注意が必要です。

- nullish
    - `null`, `undefined`
    - nullish でない: `foo !== null && foo !== void 0`
- falsy
    - `null`, `undefined`, `false`, `""` (空文字列), `0`, `-0`, `0n` (BigInt), `NaN`
    - falsy でない: `foo`

```js:Null&nbsp;合体演算子&nbsp;??&nbsp;と論理和&nbsp;||&nbsp;の違い
// 
const num = 0;

console.log(num ?? 1024); // 出力: 0
console.log(num || 1024); // 出力: 1024

// 
const text = '';

console.log(text ?? '(default)'); // 出力: ''
console.log(text || '(default)'); // 出力: '(default)'
```

参考「[論理和 (||) - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_OR)」

### 3.2.6. おまけ: オプショナルチェーン演算子 `?.` と Null 合体演算子 `??` の組み合わせ

```js:悪い例
const options = {};

// 
let isYes = true;
if ( 'something' in options && 'isYes' in options.something ) {
    isYes = options.something.isYes;
}
```

```js:良い例
const options = {};

// 
const isYes = options.something?.isYes ?? true;
```

## 3.3. コレクションの操作はなるべく反復メソッドを利用する

### 3.3.1. 配列 `Array` の場合

配列 `Array` が持つ反復メソッドの例:

- `map()`: 配列を同じ length のまま変換する
- `filter()`: 配列の一部を取り出して新しい配列を返す
- `reduce()`: 配列から 1 つの値に変換する
    - 文字列にしたい場合は `map()` と `join()` を使用したほうが便利

※他にもあります。

```js:配列&nbsp;Array&nbsp;が持つ反復メソッドの使い方の例
const foo = [10, 20, 30, 40, 50];

const bar = foo.map(x => 2 * x); // bar: [20, 40, 60, 80, 100]

const baz = bar.filter(x => 50 < x); // baz: [60, 80, 100]

const qux = baz.reduce((acc, x) => acc + x, 0); // qux: 240
```

```js:悪い例
const dataArray = [{ id: 30 }, { id: 20 }, { id: 40 }, { id: 10 }];

let maxId = 0;
for (const data of dataArray) {
    maxId = Math.max(maxId, data.id);
}
```

```js:良い例
const dataArray = [{ id: 30 }, { id: 20 }, { id: 40 }, { id: 10 }];

const maxId = dataArray.reduce((acc, data) => Math.max(acc, data.id), 0);
```

参考「[11.1 Iterators: Nope - 11. Iterators and Generators - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#iterators--nope)」
参考「[インスタンスメソッド - Array - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods)」

### 3.3.2. `NodeList` や `HTMLCollection` はスプレッド構文 `[...obj]` で配列 `Array` に変換できる

`NodeList` や `HTMLCollection` は `map()` や `reduce()` 等の反復メソッドがありませんが、スプレッド構文を用いて配列に展開できます。

```js:HTMLCollection&nbsp;から&nbsp;Array&nbsp;に変換
const imgArray = [...document.getElementsByClassName('image')];
const imgSrcArray = imgArray.map(node => node.src);
```

```js:NodeList&nbsp;から&nbsp;Array&nbsp;に変換
const favImgArray = [...document.querySelectorAll('img.favorite')];
const favImgSrcArray = favImgArray.map(node => node.src);
```

参考「[スプレッド構文 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)」

## 3.4. 別関数に分離して直接 `return` する

`const` を使用するかどうかに関わらず、機能を適切に別関数やメソッドに分割することで、保守性の向上やバグ数の削減につながります。

三項演算子で書くことが難しい、または三項演算子で書くと可読性が下がる場合等に使うこともあります。

```js:悪い例
// 
const getProfileHtml = profile => '<span>ID: ' + profile.id + ', Name: ' + profile.name + '</span>';

// 
const profiles = [{ id: 22, name: 'kitty' }, { id: 1, name: 'puppy' }, { id: 33, name: 'bunny' }];

let profilesHtml;
if ( profiles.length !== 0 ) {
    profilesHtml = '<div>' + profiles.map(profile => getProfileHtml(profile)).join('<br>') + '</div>';
} else {
    profilesHtml = '<div>No Profile</div>';
}
```

```js:良い例
// 
const getProfileHtml = profile => '<span>ID: ' + profile.id + ', Name: ' + profile.name + '</span>';

// 
const getProfilesHtml = profiles => {
    if ( profiles.length !== 0 ) {
        return '<div>' + profiles.map(profile => getProfileHtml(profile)).join('<br>') + '</div>';
    } else {
        return '<div>No Profile</div>';
    }
};

// 
const profiles = [{ id: 22, name: 'kitty' }, { id: 1, name: 'puppy' }, { id: 33, name: 'bunny' }];

const profilesHtml = getProfilesHtml(profiles);
```