過去記事から分離、一部内容追加、説明を全体的に修整。
参考「イマドキな JavaScript で書かない・使わないもの: var, function, then, jQuery, その他」
※ IE 等では Babel やポリフィル等を使用する前提です。
1. var
を書かない (no-var
)
var
は古い JavaScript からある書き方で、let
や const
とスコープが異なり、混乱を招く可能性がある (すなわちバグができる可能性が高まる) ため、var
を書かないべきです。
ただし、古いプロジェクトから移行するコストが高い場合にあえて無視することもあります。
各スコープ:
-
const
,let
: ブロックスコープ -
var
: 関数スコープ
参考「no-var - Rules - ESLint - Pluggable JavaScript linter」
参考「2.2 Disallow var - 2. References - Airbnb JavaScript Style Guide」
const
, let
は IE 11 でも使用できます。
ただし、IE 11 では for
文内での let
の挙動が異なります。
2. なるべく const
を使う
プログラミング全般において、変更可能な (ミュータブルな) 変数よりも変更不可な (イミュータブルな) 変数の方が望ましいです。
可読性・保守性の向上、バグ削減などのメリットがあります。
2.1. 値を書き換えないものはとにかく const
(prefer-const
)
値を書き換えないものは全て const
にします。
let response = await fetch(url);
let json = await response.json();
let message = json.message;
const response = await fetch(url);
const json = await response.json();
const message = json.message;
MDN や Google の開発者向けの一部のヘルプページ等で、サンプルコードで let
が多用されている場合がありますが、それは悪い書き方です。
参考「prefer-const - Rules - ESLint - Pluggable JavaScript linter」
2.2. const
が使えないと勘違いされがちなもの
2.2.1. 配列やオブジェクトは const
でも中身を書き換えられる
配列やオブジェクトの中身を操作したいときに let
にするひとがいますが、変数の宣言が const
でも中身を書き換えられます。
配列やオブジェクトをまるごと書き換えることはできなくなります。
const array = [2, 5, 8];
array.push(15); // array: [2, 5, 8, 15]
array = [3, 6, 9, 18]; // TypeError
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
を使用すると、ループのたびに別の定数として扱われます。
for (let property in object) {
console.log(property);
}
for (let value of array) {
console.log(value);
}
for (const property in object) {
console.log(property);
}
for (const value of array) {
console.log(value);
}
参考「prefer-const - Rules - ESLint - Pluggable JavaScript linter」
2.3. for (;;)
は let
を使用する
配列等ではなるべく反復メソッド (※後述) を利用するようにしますが、反復メソッドで処理することがむずかしい場合は素直に for 文 for (;;)
を使用します。
for 文 for (;;)
の性質上、変更可能な (ミュータブルな) 変数が必要なため let
を使用します。
こういう場合には無理して const
を使用しなくて良いと思います。
3. なるべく const
で書くための手法
なるべく const
で書くためのいくつかの手法があります。
- 同じ変数を使いまわさず、別名の定数を作る
-
if
文の代わりに三項演算子等を使用する - 配列などのコレクションの操作は
map()
やreduce()
等の反復メソッドをなるべく利用する - 別関数に分離して直接
return
する
3.1. 同じ変数を使いまわさず、別名の定数を作る
例として、変数が持つ値を変換するとき、同じ変数名を使いまわさずに別名の定数を作ります。
変数の別名を決める方法として、以下のようなものがあります:
- 英語の過去分詞や形容詞を付ける
- 変換後の型名を付ける
(※状況に応じて名前を決定してください。)
let html = '<div>nya</div>';
html = html.replace(/</g, '<').replace(/>/g, '>');
html = new TextEncoder().encode(html);
const html = '<div>nya</div>';
const escapedHtml = html.replace(/</g, '<').replace(/>/g, '>');
const htmlUint8Array = new TextEncoder().encode(escapedHtml);
3.2. if
文の代わりに三項演算子等を使用する
- 三項演算子
(c ? t : f)
- オプショナルチェーン演算子
?.
- Null 合体演算子
??
- 論理和
||
-
const c = a || b;
(Falsy な値の注意が必要)
-
3.2.1. 三項演算子 (c ? t : f)
let ynStr;
if ( isYes ) {
ynStr = 'Yes';
} else {
ynStr = 'No';
}
const ynStr = (isYes ? 'Yes' : 'No');
※三項演算子は本来括弧 ()
を必要としませんが、演算子の優先順位が分かりにくくなることがあるため、括弧 ()
を付けた方が良いと思います。
参考「条件 (三項) 演算子 - JavaScript | MDN」
3.2.2. 三項演算子を入れ子にしない (no-nested-ternary
)
三項演算子を入れ子にするとコ-ドを理解しにくくなり、結果として保守性の低下やバグの発生率増加をまねくと考えられるので使用しないべきです。
const a = ((foo ? bar : baz) ? qux : quxx);
const b = (foo ? (bar ? baz : qux) : quxx);
const c = (foo ? bar : (baz ? qux : quxx));
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」
参考「no-nested-ternary - Rules - ESLint - Pluggable JavaScript linter」
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
-
const foo = null;
//
let baz = void 0;
if ( foo !== null && foo !== void 0 ) {
baz = foo.bar;
}
const foo = null;
//
const baz = ((foo !== null && foo !== void 0) ? foo.bar : void 0); // 意味的には no-unneeded-ternary に反するかと思います
const foo = null;
//
const baz = foo?.bar;
参考「オプショナルチェーン (?.) - JavaScript | MDN」
参考「Nullish value - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN」
参考「Falsy (偽値) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN」
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
-
const foo = {};
//
let baz = 'Default';
if ( 'bar' in foo ) {
baz = foo.bar;
}
const foo = {};
//
const baz = ('bar' in foo ? foo.bar : 'Default'); // 意味的には no-unneeded-ternary に反するかと思います
const foo = {};
//
const baz = foo.bar ?? 'Default';
参考「Null 合体 (??) - JavaScript | MDN」
参考「Nullish value - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN」
参考「Falsy (偽値) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN」
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
-
//
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」
3.2.6. おまけ: オプショナルチェーン演算子 ?.
と Null 合体演算子 ??
の組み合わせ
const options = {};
//
let isYes = true;
if ( 'something' in options && 'isYes' in options.something ) {
isYes = options.something.isYes;
}
const options = {};
//
const isYes = options.something?.isYes ?? true;
3.3. コレクションの操作はなるべく反復メソッドを利用する
3.3.1. 配列 Array
の場合
配列 Array
が持つ反復メソッドの例:
-
map()
: 配列を同じ length のまま変換する -
filter()
: 配列の一部を取り出して新しい配列を返す -
reduce()
: 配列から 1 つの値に変換する- 文字列にしたい場合は
map()
とjoin()
を使用したほうが便利
- 文字列にしたい場合は
※他にもあります。
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
const dataArray = [{ id: 30 }, { id: 20 }, { id: 40 }, { id: 10 }];
let maxId = 0;
for (const data of dataArray) {
maxId = Math.max(maxId, data.id);
}
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」
参考「インスタンスメソッド - Array - JavaScript | MDN」
3.3.2. NodeList
や HTMLCollection
はスプレッド構文 [...obj]
で配列 Array
に変換できる
NodeList
や HTMLCollection
は map()
や reduce()
等の反復メソッドがありませんが、スプレッド構文を用いて配列に展開できます。
const imgArray = [...document.getElementsByClassName('image')];
const imgSrcArray = imgArray.map(node => node.src);
const favImgArray = [...document.querySelectorAll('img.favorite')];
const favImgSrcArray = favImgArray.map(node => node.src);
参考「スプレッド構文 - JavaScript | MDN」
3.4. 別関数に分離して直接 return
する
const
を使用するかどうかに関わらず、機能を適切に別関数やメソッドに分割することで、保守性の向上やバグ数の削減につながります。
三項演算子で書くことが難しい、または三項演算子で書くと可読性が下がる場合等に使うこともあります。
//
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>';
}
//
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);