0. まとめ
-
var
を書かない-
let
もなるべく書かない - なるべく
const
を使う - 関数の引数は
const
のように扱う
-
-
function
をなるべく書かない- アロー関数またはクラスやオブジェクトのメソッド定義を使う
- 関数宣言でなく関数式を使用する
- 理論上どうしても
function
が必要な場合もある - ジェネレータ関数
function*
は使用出来る
-
then()
を書かない-
await
を使う
-
- 理想的には jQuery を使わない
- 純粋な WEB API を使用して DOM を操作する
-
getElementById()
,getElementsByClassName()
-
querySelector()
,querySelectorAll()
insertAdjacentHTML()
- 等
-
- jQuery 非依存のライブラリを使用する
-
fetch()
で通信する
- 純粋な WEB API を使用して DOM を操作する
-
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」
1.2. 関数の引数は const
のように扱う (no-param-reassign
)
関数の引数の値を変更すると混乱を招き、バグの原因にもなるので値を変更しません。
const foo = bar => {
if ( ! bar ) { // bar が falsy なとき
bar = 23;
}
};
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」
参考「7.12 Mutate Params - 7. Functions - Airbnb JavaScript Style Guide」
参考「7.13 Reassign Params - 7. Functions - Airbnb JavaScript Style Guide」
2. function
をなるべく書かない
function
による関数宣言のデメリットは以下のようなものがあります:
-
this
の扱いが特殊で使いづらい - 関数の「巻き上げ」が可読性と保守性を損なう
2.1. コールバック関数はアロー関数を使用する (prefer-arrow-callback
)
function
は this
の挙動が特殊で、予期せぬ動作をしてバグの原因になったり、クラスやオブジェクトの this
を呼ぶことが不便であったりするため、function
を使用しない方が混乱を避けられます。
単にコールバック関数を生成したい場合はアロー関数を使用します。
参考「prefer-arrow-callback - Rules - ESLint - Pluggable JavaScript linter」
const foo = [10, 20, 30, 40, 50];
const bar = foo.map(function (x) { return x * x; }); // bar: [100, 400, 900, 1600, 2500]
const foo = [10, 20, 30, 40, 50];
const bar = foo.map(x => x * x); // bar: [100, 400, 900, 1600, 2500]
addEventListener()
では this
を使用せず、コールバック関数の引数 event
から event.currentTarget
でイベントがアタッチされた要素を取得出来ます。
element.addEventListener('click', function () {
const currentTarget = this;
});
element.addEventListener('click', event => {
const currentTarget = event.currentTarget;
});
2.2. クラスやオブジェクトのメソッド定義を使う (object-shorthand
)
クラスやオブジェクトのメソッドの定義では function
を表記せず簡潔に定義出来ます。
※古いバージョンではオブジェクトのメソッド定義で function
が必要でしたが、現在は不要です。
const obj = {
foo: function() {},
bar: function() {},
};
const obj = {
foo() {},
bar() {},
};
参考「9.1 Use class - 9. Classes & Constructors - Airbnb JavaScript Style Guide」
参考「クラス - JavaScript | MDN」
参考「object-shorthand - Rules - ESLint - Pluggable JavaScript linter」
参考「メソッドの定義 - オブジェクト初期化子 - JavaScript | MDN」
2.3. 関数宣言でなく関数式を使用する (func-style
)
function
による関数宣言は「巻き上げ」が起こり、可読性と保守性を損なうため、関数宣言でなく関数式を使用します。
function
による関数式は this
の扱いが面倒な場合があるため、なるべくアロー関数を使用します。
function foo() {
// ...
}
const foo = () => {
// ...
};
const bar = function () { // メモ: 無名関数にすべきかどうかは環境によります
// ...
};
参考「func-style - Rules - ESLint - Pluggable JavaScript linter」
参考「7.1 Declarations - 7. Functions - Airbnb JavaScript Style Guide」
2.4. 理論上どうしても function
が必要な場合
関数を生成したいプログラムで、function
でないと実現不可能な場合もあります。
参考「JavaScript の デコレータ の使い方 - Qiita」
3. ジェネレータ関数 function*
は使用して良い
ジェネレータ関数 function*
は関数 function
とは別物で、用途が異なるため使用して良いです。
(※ジェネレータ自体の使い方は本記事では不説明。)
参考「function* 式 - JavaScript | MDN」
3.1. 関数宣言でなく関数式を使用する (func-style
)
ジェネレータ関数 function*
の関数宣言も関数 function
の関数宣言と同様に「巻き上げ」が起こり、可読性と保守性を損なうため、関数宣言でなく関数式を使用します。
function* foo() {
// ...
}
const foo = function* () { // メモ: 無名関数にすべきかどうかは環境によります
// ...
};
※ ESLint のドキュメントでは全て関数 function
で例が載っていますが、ジェネレータ関数 function*
も ESLint での func-style
の対象になっています。
参考「func-style - Rules - ESLint - Pluggable JavaScript linter」
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」
参考「11.2 Generators: Nope - 11. Iterators and Generators - Airbnb JavaScript Style Guide」
4. Promise
利用時に then()
を書かない
then()
の処理を書きやすくしたものが await
です。
then()
は使用せずに await
を使用してください。
参考「Promise - JavaScript | MDN」
参考「await - JavaScript | MDN」
※ 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」
参考「Document.querySelectorAll() - Web API | MDN」
参考「element.insertAdjacentHTML - Web API | MDN」
jQuery.ajax()
を使わなくても fetch()
で簡単に通信出来ます。
参考「WindowOrWorkerGlobalScope.fetch() - Web API | MDN」
5.2. jQuery 非依存のライブラリを使用する場合
ライブラリの例として、「Bootstrap 5」は jQuery 非依存になりました。
参考「jQuery依存を脱した「Bootstrap 5」正式リリース、IEもついにサポート対象外に。右からの横書き「RTL」など新機能 - Publickey」
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()
で簡単に通信出来ます。
7. FileReader
を使わない
Blob
のインスタンスメソッドを用いて読み込みを行えます。
別記事にしました。
参考「[JavaScript] FileReader を使わない - Qiita」
8. 配列を要素別に引数として扱いたいときに apply()
を使用しない
8.1. スプレッド構文 ...
を使用する (prefer-spread
, no-useless-call
)
配列または NodeList
等の配列風オブジェクトに関して、要素別に、関数やメソッドの引数として扱いたいとき、apply()
でなくスプレッド構文 ...
を使用します。
関数引数におけるスプレッド構文は、function
による関数だけでなくアロー関数でも使用出来ます。
const foo = function (a, b, c) { return a * b + c; };
const bar = foo.apply(null, [2, 10, 3]); // bar === 23
const foo = (a, b, c) => a * b + c;
const bar = foo(...[2, 10, 3]); // bar === 23
参考「prefer-spread - Rules - ESLint - Pluggable JavaScript linter」
参考「no-useless-call - Rules - ESLint - Pluggable JavaScript linter」
8.2. Array.prototype.reduce()
を使用する
apply()
やスプレッド構文の例として Math.max()
や Math.min()
が良く用いられますが、そもそも要素数が多い配列を引数として扱おうとすると問題が起こるため、Array.prototype.reduce()
を使用すべきです。
const array = [20, 30, 10, 50, 40]; // メモ: 本番環境では個数が変わる可能性がある仮定
const max = Math.max.apply(null, array);
// or
const max = Math.max(...array);
const array = [20, 30, 10, 50, 40]; // メモ: 本番環境では個数が変わる可能性がある仮定
const max = array.reduce((acc, x) => Math.max(acc, x));
参考「Math.max() - JavaScript | MDN」
9. 数値や文字列の桁揃え (パディング) で文字列連結と slice()
を使用しない
古い JavaScript の書き方で、数値の桁揃え等で文字列連結して slice()
で文字数分切り取るという手法が使われていましたが、現在は多くの環境で padStart()
を利用出来るため、padStart()
を使用します。
const foo = 23;
const paddedFoo = ('00' + foo).slice(-3); // paddedFoo === '023'
const foo = 23;
const paddedFoo = foo.toString().padStart(3, '0'); // paddedFoo === '023'
※桁があふれた場合の挙動は異なります (slice()
は切り捨て、padStart()
は元文字列を保持) 。
※実用的には桁揃えする数の正当性を確認する必要があります。