イマドキな JavaScript で書かない・使わないもの: var, function, then, jQuery, その他

# 0. まとめ

- `var` を書かない
    - `let` もなるべく書かない
    - なるべく `const` を使う
    - 関数の引数は `const` のように扱う
- `function` をなるべく書かない
    - アロー関数またはクラスやオブジェクトのメソッド定義を使う
    - 関数宣言でなく関数式を使用する
    - 理論上どうしても `function` が必要な場合もある
    - ジェネレータ関数 `function*` は使用出来る
- `then()` を書かない
    - `await` を使う
- 理想的には jQuery を使わない
    - 純粋な WEB API を使用して DOM を操作する
        - `getElementById()`, `getElementsByClassName()`
        - `querySelector()`, `querySelectorAll()`
        - `insertAdjacentHTML()`
        - 等
    - jQuery 非依存のライブラリを使用する
    - `fetch()` で通信する
- `XMLHttpRequest` を使わない
    - `fetch()` で通信する
- `FileReader` を使わない
    - `Blob` のインスタンスメソッドを使用する
        - `text()`
        - `arrayBuffer()`, `bytes()`
        - `stream()`
    - 文字コードを指定する場合は `TextDecoder` を併用する
    - 読み込み中の処理を行う場合は `ReadableStream` を併用する
- 配列を要素別に引数として扱いたいときに `apply()` を使用しない
    - スプレッド構文 `...` を使用する
    - `Array.prototype.reduce()` を使用する
- 数値や文字列の桁揃え (パディング) で文字列連結と `slice()` を使用しない
    - `padStart()` を使用する

# 1. `var` を書かない

## 1.1. なるべく `const` を使う (`prefer-const`)

なるべく `let` でなく `const` を使用します。

別記事に分離しました。

参考「[[JavaScript] var を書かない＆なるべく const を使う - Qiita](https://qiita.com/kerupani129/items/c56ae227b8ff6f026884)」

## 1.2. 関数の引数は `const` のように扱う (`no-param-reassign`)

関数の引数の値を変更すると混乱を招き、バグの原因にもなるので値を変更しません。

```js:悪い例
const foo = bar => {
    if ( ! bar ) { // bar が falsy なとき
        bar = 23;
    }
};
```

```js:良い例
const foo = bar => {
    const baz = bar || 23;
};

// メモ: falsy の場合は論理和 || 、nullish の場合は Null 合体演算子 ?? 、undefined の場合はデフォルト引数を用いる
```

※ ESLint のドキュメントでは全て `function` の関数宣言で例が載っていますが、関数式やアロー関数も ESLint での `no-param-reassign` の対象になっています。

参考「[no-param-reassign - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/no-param-reassign.html)」
参考「[7.12 Mutate Params - 7. Functions - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#functions--mutate-params)」
参考「[7.13 Reassign Params - 7. Functions - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#functions--mutate-params)」

# 2. `function` をなるべく書かない

`function` による関数宣言のデメリットは以下のようなものがあります:

- `this` の扱いが特殊で使いづらい
- 関数の「巻き上げ」が可読性と保守性を損なう

## 2.1. コールバック関数はアロー関数を使用する (`prefer-arrow-callback`)

`function` は `this` の挙動が特殊で、予期せぬ動作をしてバグの原因になったり、クラスやオブジェクトの `this` を呼ぶことが不便であったりするため、`function` を使用しない方が混乱を避けられます。

単にコールバック関数を生成したい場合はアロー関数を使用します。

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

```js:悪い例
const foo = [10, 20, 30, 40, 50];

const bar = foo.map(function (x) { return x * x; }); // bar: [100, 400, 900, 1600, 2500]
```

```js:良い例
const foo = [10, 20, 30, 40, 50];

const bar = foo.map(x => x * x); // bar: [100, 400, 900, 1600, 2500]
```

参考「[アロー関数式 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions)」

`addEventListener()` では `this` を使用せず、コールバック関数の引数 `event` から `event.currentTarget` でイベントがアタッチされた要素を取得出来ます。

```js:悪い例
element.addEventListener('click', function () {
    const currentTarget = this;
});
```

```js:良い例
element.addEventListener('click', event => {
    const currentTarget = event.currentTarget;
});
```

参考「[Event - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Event)」

## 2.2. クラスやオブジェクトのメソッド定義を使う (`object-shorthand`)

クラスやオブジェクトのメソッドの定義では `function` を表記せず簡潔に定義出来ます。

※古いバージョンではオブジェクトのメソッド定義で `function` が必要でしたが、現在は不要です。

```js:悪い例
const obj = {
    foo: function() {},
    bar: function() {},
};
```

```js:良い例
const obj = {
    foo() {},
    bar() {},
};
```

参考「[9.1 Use class - 9. Classes & Constructors - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#constructors--use-class)」
参考「[クラス - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes)」

参考「[object-shorthand - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/object-shorthand.html)」
参考「[メソッドの定義 - オブジェクト初期化子 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions)」

## 2.3. 関数宣言でなく関数式を使用する (`func-style`)

`function` による関数宣言は「巻き上げ」が起こり、可読性と保守性を損なうため、関数宣言でなく関数式を使用します。

`function` による関数式は `this` の扱いが面倒な場合があるため、なるべくアロー関数を使用します。

```js:悪い例
function foo() {
    // ...
}
```

```js:良い例
const foo = () => {
    // ...
};

const bar = function () { // メモ: 無名関数にすべきかどうかは環境によります
    // ...
};
```

参考「[func-style - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/func-style)」
参考「[7.1 Declarations - 7. Functions - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#functions--declarations)」

## 2.4. 理論上どうしても `function` が必要な場合

関数を生成したいプログラムで、`function` でないと実現不可能な場合もあります。

参考「[JavaScript の デコレータ の使い方 - Qiita](https://qiita.com/kerupani129/items/2b3f2cba195c0705b2e5)」

# 3. ジェネレータ関数 `function*` は使用して良い

ジェネレータ関数 `function*` は関数 `function` とは別物で、用途が異なるため使用して良いです。

(※ジェネレータ自体の使い方は本記事では不説明。)

参考「[function* 式 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/function*)」

## 3.1. 関数宣言でなく関数式を使用する (`func-style`)

ジェネレータ関数 `function*` の関数宣言も関数 `function` の関数宣言と同様に「巻き上げ」が起こり、可読性と保守性を損なうため、関数宣言でなく関数式を使用します。

```js:悪い例
function* foo() {
    // ...
}
```

```js:良い例
const foo = function* () { // メモ: 無名関数にすべきかどうかは環境によります
    // ...
};
```

※ ESLint のドキュメントでは全て関数 `function` で例が載っていますが、ジェネレータ関数 `function*` も ESLint での `func-style` の対象になっています。

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

## 3.2. ジェネレータ関数での `this` の扱い

ジェネレータ関数 `function*` も関数 `function` と同様に `this` の扱いに注意が必要ですが、以下の理由により関数 `function` 程の問題は起きません:

- そもそもジェネレータ関数内で `this` を使用すると副作用を持つことになるため、良くない
- ジェネレータ関数で `this` を使用しない書き方が出来る
    - 初期値等はジェネレータ関数の引数で受け取る
    - 処理に使用する値はローカル変数で管理
    - `yield`, `yield*` で値を受け取る
- クラスまたはオブジェクトのメソッドとしてジェネレータを定義すると `this` を使用出来る (静的 `static` でない場合)
    - 他の関数にジェネレータを渡したい場合、ジェネレータ関数を直接コールバック関数として渡さず、ジェネレータオブジェクトを返すアロー関数を使用すればいいため、ジェネレータ関数内の `this` を気にしなくて良い書き方が出来る



## 3.3. ジェネレータを使用出来ない場合

"_Airbnb JavaScript Style Guide_" では、古いバージョンの JavaScript への変換が上手く出来ないことを理由にジェネレータを使用しないよう推奨しています。

古いバージョンの JavaScript への変換が不要であれば、ジェネレータを使用可能です。

参考「[function* 式 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/function*)」
参考「[11.2 Generators: Nope - 11. Iterators and Generators - Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#generators--nope)」

# 4. `Promise` 利用時に `then()` を書かない

`then()` の処理を書きやすくしたものが `await` です。

`then()` は使用せずに `await` を使用してください。

参考「[Promise - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)」
参考「[await - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await)」

※ `catch()` は状況に応じて使用します。

# 5. jQuery を使わない

現在は、理想的には jQuery を使用しないべきです。

jQuery は昔は便利なライブラリでしたが、今では jQuery 自体は使用するメリットが小さいです。
今ではネイティブ JavaScript の純粋な Web API が改善され、だいぶ扱いやすくなっています。

jQuery のデメリットとして以下のようなものがあります:

- 読み込み時間・実行時間
- jQuery 依存ライブラリやアプリケーションの設計が古いまたは悪いことによる問題 (テスト容易性、再利用性、レンダリングブロック等の問題)
- jQuery のバージョン間の互換性の問題 (依存性、保守性の問題)

## 5.1. ネイティブ JavaScript で書く場合

ID やクラス名で要素を取得したいということが分かっているなら、`getElementById()` や `getElementsByClassName()` を使用したほうが高速に動作します。

jQuery のように複雑なセレクタを用いて要素を取得したい場合は、`querySelector()` か `querySelectorAll()` を使用します。

DOM の生成に `insertAdjacentHTML()` を使用する方法があります。

参考「[Document.querySelector() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Document/querySelector)」
参考「[Document.querySelectorAll() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll)」
参考「[element.insertAdjacentHTML - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentHTML)」

`jQuery.ajax()` を使わなくても `fetch()` で簡単に通信出来ます。

参考「[WindowOrWorkerGlobalScope.fetch() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch)」

## 5.2. jQuery 非依存のライブラリを使用する場合

ライブラリの例として、「Bootstrap 5」は jQuery 非依存になりました。

参考「[jQuery依存を脱した「Bootstrap 5」正式リリース、IEもついにサポート対象外に。右からの横書き「RTL」など新機能 － Publickey](https://www.publickey1.jp/blog/21/jquerybootstrap_5iertl.html)」

## 5.3. 読み込み時間・実行時間

通信時間を考慮したチューニングが行われている場合はある程度改善しますが、キャッシュが保存されていない状態だと jQuery の読み込みに数百ミリ秒かかることもあり、出来ることの割に時間がかかり過ぎです。
(動的読み込みしておらず、CDN を利用せず、バンドルされておらず、HTTP のバージョンが古い場合にはかなり悪いです。)

規模が大きな Web アプリケーションの場合にはそもそも jQuery よりも便利なライブラリ (Angular, React, Vue 等) がありますし、ネイティブ JavaScript で簡単に実装可能な部分に関しては jQuery を使用しないことで動作速度が改善します。

ブログ等、JavaScript が主体ではないような場合では読み込み速度を少しでも早くするのが理想的です。

## 5.4. 設計の問題やバージョン間の互換性の問題

本記事では深く触れませんが、jQuery を使用することによってソフトウェアの本質的ではないところで様々な問題が発生します。

jQuery を使用しないことでこれらの問題を回避出来ます。

## 5.5. それでも jQuery を使用する場合

ソフトウェア品質 (製品品質) を落としてでも開発コストを抑えたいか、または使用したいライブラリが jQuery 依存でありコストの問題で脱 jQuery がむずかしい場合は jQuery を使用します。

jQuery を使用することで初期の開発コストの削減につながりますが、保守性を考慮した上で本当にコストが削減出来るかは検討する必要があります。

# 6. `XMLHttpRequest` を使わない

`fetch()` で簡単に通信出来ます。

参考「[Fetch の使用 - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)」

# 7. `FileReader` を使わない

`Blob` のインスタンスメソッドを用いて読み込みを行えます。

別記事にしました。

参考「[[JavaScript] FileReader を使わない - Qiita](https://qiita.com/kerupani129/items/121b5f9ae14fa173445e)」

# 8. 配列を要素別に引数として扱いたいときに `apply()` を使用しない

## 8.1. スプレッド構文 `...` を使用する (`prefer-spread`, `no-useless-call`)

配列または `NodeList` 等の配列風オブジェクトに関して、要素別に、関数やメソッドの引数として扱いたいとき、`apply()` でなくスプレッド構文 `...` を使用します。

関数引数におけるスプレッド構文は、`function` による関数だけでなくアロー関数でも使用出来ます。

```js:悪い例
const foo = function (a, b, c) { return a * b + c; };

const bar = foo.apply(null, [2, 10, 3]); // bar === 23
```

```js:良い例
const foo = (a, b, c) => a * b + c;

const bar = foo(...[2, 10, 3]); // bar === 23
```

参考「[prefer-spread - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/prefer-spread)」
参考「[no-useless-call - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/no-useless-call)」

## 8.2. `Array.prototype.reduce()` を使用する

`apply()` やスプレッド構文の例として `Math.max()` や `Math.min()` が良く用いられますが、そもそも要素数が多い配列を引数として扱おうとすると問題が起こるため、`Array.prototype.reduce()` を使用すべきです。

```js:悪い例
const array = [20, 30, 10, 50, 40]; // メモ: 本番環境では個数が変わる可能性がある仮定

const max = Math.max.apply(null, array);
// or
const max = Math.max(...array);
```

```js:良い例
const array = [20, 30, 10, 50, 40]; // メモ: 本番環境では個数が変わる可能性がある仮定

const max = array.reduce((acc, x) => Math.max(acc, x));
```

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

# 9. 数値や文字列の桁揃え (パディング) で文字列連結と `slice()` を使用しない

古い JavaScript の書き方で、数値の桁揃え等で文字列連結して `slice()` で文字数分切り取るという手法が使われていましたが、現在は多くの環境で `padStart()` を利用出来るため、`padStart()` を使用します。

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

const paddedFoo = ('00' + foo).slice(-3); // paddedFoo === '023'
```

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

const paddedFoo = foo.toString().padStart(3, '0'); // paddedFoo === '023'
```

※桁があふれた場合の挙動は異なります (`slice()` は切り捨て、`padStart()` は元文字列を保持) 。
※実用的には桁揃えする数の正当性を確認する必要があります。

参考「[String.prototype.padStart() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/padStart)」