9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2021-11-05

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

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

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

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

var は古い JavaScript からある書き方で、letconst とスコープが異なり、混乱を招く可能性がある (すなわちバグができる可能性が高まる) ため、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 の挙動が異なります。

参考「let - JavaScript | MDN

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...infor...ofconst を使える (prefer-const)

for...infor...ofconst を使用すると、ループのたびに別の定数として扱われます。

悪い例
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, '&lt;').replace(/>/g, '&gt;');
html = new TextEncoder().encode(html);
良い例
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)

悪い例
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
Null 合体演算子 ?? と論理和 || の違い
// 
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() を使用したほうが便利

※他にもあります。

配列 Array が持つ反復メソッドの使い方の例
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. NodeListHTMLCollection はスプレッド構文 [...obj] で配列 Array に変換できる

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

HTMLCollection から Array に変換
const imgArray = [...document.getElementsByClassName('image')];
const imgSrcArray = imgArray.map(node => node.src);
NodeList から Array に変換
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);
9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?