やあ。今日も今日とてレガシーとモダンが入り交じるコードベースの海原を泳ぎ回っているところかい?
フロントエンドの実務をやっていると、毎日のように目にするのが `if` 文や `&&`、`||` といった論理演算子だ。コードレビューをしていて、こんなコードを見かけてヒヤッとしたことはないかい?
// ユーザーが入力したフォームのデータがあるかチェックしているつもりのコード
const formData = { name: “”, tags: [] };
if (formData.tags) {
// 「おっ、tags配列が存在するから中身があるな!」と進んだ結果……
console.log(“タグ処理を実行します”);
}
このコード、何が問題だかピンとくるかい? そう、空の配列 `[]` は JavaScript の世界では「Truthy(真とみなされる値)」なんだよ。APIから返ってきたデータやフォームの入力値を雑に評価した結果、中身がからっぽなのに処理が走り、夜中にエラーアラートが鳴り響く……なんていうのは、現場のあるあるであり、最も避けたい悪夢の一つだ。
今日は、JavaScriptの根幹をなす「Truthy / Falsy」の仕様と、特に中級者がハマりがちな「空配列や空オブジェクトの罠」について、ブラウザの裏側の動きも含めて徹底的に解説しよう。明日からのコードレビューでドヤ顔できる知識を持ち帰っていってくれ。
—
1. そもそも Truthy / Falsy って何だっけ?(おさらい)
JavaScriptには、厳密な `true` / `false` 以外にも、条件分岐の中で強制的にBooleanへと変換される仕組みがある。これを暗黙の型変換(Implicit Coercion)と呼ぶ。
まず、世の中のすべての値のうち、`if (value)` と書いたときに無条件で `false` になるFalsyな値は、以下のたった6つしかない。これ以外はすべてTruthyだ。
1. `false`
2. `0` (数値のゼロ)
3. `-0` (負のゼロ)
4. `0n` (BigIntのゼロ)
5. `””` (空文字列)
6. `null`
7. `undefined`
8. `NaN` (Not-a-Number)
……おや? このリストをよーく見てほしい。
ここに `[]`(空配列)や `{}`(空オブジェクト)は入っているかい? 入っっていないよね。
そう、JavaScriptの仕様上、オブジェクト(配列や関数も含む)は、メモリ上にインスタンスとして存在する時点で、中身が空っぽであっても「100% Truthy」なのだ。
—
2. なぜ空配列や空オブジェクトは Truthy なのか?(V8エンジンの裏側)
「いやいや、中身が空なんだから `false` になってくれよ!」と、言語の設計に文句を言いたくなる気持ちは痛いほどわかる。だが、これにはちゃんと理由がある。
ブラウザのエンジン(ChromeのV8など)の視点になって考えてみよう。
JavaScriptの `if` 文や論理演算子が評価されるとき、エンジンはメモリアドレス(参照)を見ている。
`[]` を定義した瞬間、ヒープメモリ上に新しい配列の領域が確保され、その「アドレス」が変数に格納される。エンジンにとって重要なのは、「そこに参照先(箱)が存在するかどうか」であって、「箱の中にミカンが入っているかどうか」ではない。
箱が存在する以上、それは「存在する(Truthy)」と判定される。もし、 `if (val)` と書くたびにエンジンが「配列の中身を走査して空かどうか調べる」なんてことをしていたら、パフォーマンスが死んでしまう。言語仕様として、評価コストを一定(O(1))に保つために、「参照がある=Truthy」という単純明快なルールになっているのさ。
—
3. 実務でやりがちな「やばいパターン」と修正案
現場でよく見るアンチパターンと、それをどう堅牢に書くべきか(ベストプラクティス)を見ていこう。
パターンA: 配列の存在チェックと長さチェックの混同
❌ やってはいけないコード:
const items = getUserItems(); // 取得できなければ [] が返る設計
// 配列がある(つもり)なので処理を走らせる
if (items) {
items.forEach(item => {
console.log(item.name);
});
}
何が起きるか: バックエンドからデータ取得に失敗して `[]`(空配列)が返ってきた場合、`items` は Truthy なので `if` の中に入り、`forEach` は何もせずスルーされる。一見エラーは起きないが、「データがない時のフォールバック処理(「アイテムがありません」と表示するなど)」が一切発動せず、UIが沈黙してしまう。
⭕ 堅牢なプロのコード:
const items = getUserItems();
// 配列の「存在」ではなく「長さ(Length)」を評価する
if (Array.isArray(items) && items.length > 0) {
items.forEach(item => {
console.log(item.name);
});
} else {
// 空っぽ、あるいは配列ですらない場合のフォールバックを明示的に書く
console.log(“表示するアイテムはありません。”);
}
パターンB: オブジェクトのプロパティ存在チェックの罠
APIレスポンスなどで、データがない場合に空オブジェクト `{}` が返ってくるケースは多々ある。
❌ やってはいけないコード:
const userSettings = api.getSettings(); // データがないと {} が返る
// 設定が存在するかチェックしているつもり
if (userSettings) {
// userSettings.theme が undefined なのに処理を続けようとして爆発する
applyTheme(userSettings.theme);
}
⭕ 堅牢なプロのコード:
オブジェクトの場合は、そもそもキーが存在するかどうかを `Object.keys()` や `hasOwn()` で確認するか、今風なら「Optional Chaining」と「Nullish Coalescing」を組み合わせるのがスマートだ。
const userSettings = api.getSettings();
// キーの数を調べるか、安全なプロパティアクセスを使う
const hasSettings = userSettings && Object.keys(userSettings).length > 0;
if (hasSettings) {
// オプショナルチェイニングで安全に取得しつつ、なければデフォルト値
const theme = userSettings?.theme ?? “light”;
applyTheme(theme);
} else {
applyTheme(“light”); // デフォルト適用
}
—
4. 論理演算子 (`&&`, `||`, `??`) の実戦的な使い分け
最後に、現場でレビューイによく指導する論理演算子の使い分けの極意を授けよう。
`||` (OR) の罠
「値がなければデフォルト値を入れる」という目的で `||` を使うことがあるが、これもちょうど `0` や `””`(空文字)を扱いたいときにバグる。
const userInputCount = 0;
// 0 は Falsy なので、左辺がフォールバックされてしまい 10 になってしまう!
const count = userInputCount || 10;
console.log(count); // 10 (意図しない挙動)
対策: `null` と `undefined` のみを検知してフォールバックしてくれる Nullish Coalescing演算子 (`??`) を使おう。
const userInputCount = 0;
// 0 や “” は有効な値として扱いたい場合は ?? が正解
const count = userInputCount ?? 10;
console.log(count); // 0 (期待通り)
—
まとめ
JavaScriptの Truthy / Falsy の仕様は、言語の歴史的背景やパフォーマンス上の合理性があって成り立っている。しかし、それを理解せずに「なんとなく `if (data)` と書いておけば動くだろう」という甘い認識でいると、手痛いバグを踏むことになる。
今日のポイントを振り返ってみよう:
1. 配列 `[]` やオブジェクト `{}` は、中身が空でも絶対的に Truthy である。
2. 配列は `length` や `Array.isArray()` で中身を担保する。
3. デフォルト値のフォールバックには、安易な `||` ではなく `??` を検討する。
こうした細かな仕様の解像度を上げていくことが、昨日よりもワンランク上のフロントエンドエンジニアになるための近道だ。
さあ、自分の書いたコードベースに戻って、危なっかしい `if (array)` が眠っていないかパトロールしに行こうぜ!

コメント