JavaScript型まわりの「よくあるバグTOP10」+デバッグ方法
① NaN が自分自身と等しくない
バグ例
const x = NaN;
if (x === NaN) console.log("NaNです"); // 実行されない原因
NaN(Not a Number)は 自分自身とも等しくない という仕様。
解決策
Number.isNaN(x); // true
Object.is(x, NaN); // trueデバッグヒント: コンソールで Number.isNaN(value) を使う習慣をつける。
② null と undefined の混同
バグ例
let user = { name: null };
if (user.age == null) {
console.log("未設定です");
}原因
== nullはnullとundefinedの両方を true にする。- データの初期値やAPIレスポンスが
undefinedの場合に混乱。
解決策
if (user.age === null) { ... } // 意図的にnullを判定
if (user.age === undefined) { ... } // 未定義を判定デバッグヒント: typeof user.age を確認。"undefined" なら未定義。
③ typeof null === "object" に騙される
バグ例
const x = null;
if (typeof x === "object") console.log("オブジェクト"); // true(!)原因
- 歴史的バグ(JavaScript初期の仕様ミス)だが後方互換のため残っている。
解決策
x === null // null判定
Object.prototype.toString.call(x) // "[object Null]"デバッグヒント: どんな型かわからない時はObject.prototype.toString.call(value) を使うと確実。
④ == による意図しない型変換
バグ例
0 == "0" // true
0 == [] // true
"" == 0 // true
false == "0" // true原因
==は暗黙的な型変換(coercion)を行うため、結果が直感と異なる。
解決策
- 常に
===(厳密等価)を使う。 - もしくは明示的に型変換する:
Number(x) === 0
Boolean(x) === trueデバッグヒント: 比較の前に typeof を出力してみる。
⑤ 0.1 + 0.2 !== 0.3 問題
バグ例
console.log(0.1 + 0.2 === 0.3); // false原因
- IEEE754 の浮動小数点表現による丸め誤差。
解決策
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // trueデバッグヒント: 小数の比較には「許容誤差」を設ける。
⑥ 配列を typeof で判定してしまう
バグ例
typeof [] === "array"; // false(実際は "object")原因
- 配列は Object の一種。
typeofでは判別できない。
解決策
Array.isArray([]); // true
Object.prototype.toString.call([]); // "[object Array]"デバッグヒント: 「オブジェクトだけど配列かも?」と思ったら Array.isArray()!
⑦ プリミティブとオブジェクトの違いを誤解
バグ例
let a = 10;
let b = a;
b = 20;
console.log(a); // 10 ✅(独立)
let o1 = {x: 1};
let o2 = o1;
o2.x = 9;
console.log(o1.x); // 9 ❌(同じ参照)原因
- プリミティブは「値渡し」、オブジェクトは「参照渡し」。
解決策
- オブジェクトをコピーするときは:
const copy = { ...o1 }; // 浅いコピー
const deep = structuredClone(o1); // 深いコピーデバッグヒント: console.log(o1 === o2) で参照が同じか確認。
⑧ BigInt と Number を混ぜて演算
バグ例
10n + 5; // TypeError: Cannot mix BigInt and other types原因
BigIntとNumberは別型。混ぜて演算不可。
解決策
10n + 5n; // OK
Number(10n) + 5; // OK(明示変換)デバッグヒント: どちらかに統一する(型チェックを入れる)。
⑨ ラッパーオブジェクトの落とし穴
バグ例
const b = new Boolean(false);
if (b) console.log("実行される!"); // 実行される(!)原因
new Boolean()はオブジェクト。オブジェクトは常に truthy。
解決策
const b = false; // プリミティブでOKデバッグヒント: new String, new Number, new Boolean は避ける。
⑩ JSON.stringify() で情報が失われる
バグ例
原因
- JSON では
undefined・function・Symbolはシリアライズされない。
解決策
- シリアライズ前に不要データを除外する、または別形式で保存。
JSON.stringify(data, (key, value) =>
typeof value === "undefined" ? null : value
);デバッグヒント: stringify結果を一度コンソールで確認。
追加Tips:デバッグに使える便利ワザ
| 目的 | コード例 |
|---|---|
| 型を厳密に調べたい | Object.prototype.toString.call(x) |
| 値の種類を判別 | typeof x |
| 配列か確認 | Array.isArray(x) |
| 値がNaNか | Number.isNaN(x) |
| nullかundefinedか | x == null(両方true) |
| 参照が同一か | Object.is(a, b) |
| 型を明示変換 | String(), Number(), Boolean() |
まとめ(開発時チェックリスト)
✅ 比較には常に === を使う
✅ typeof null に注意
✅ NaN判定は Number.isNaN
✅ 配列は Array.isArray で判定
✅ オブジェクトコピー時は参照共有に注意
✅ BigIntとNumberは混ぜない
✅ JSON.stringify() は失われる情報がある
型バグ発見チェックツール(Node.js 用)
以下のスクリプトを typeBugChecker.js として保存し、node typeBugChecker.js yourfile.js で使えます。
#!/usr/bin/env node
// typeBugChecker.js
// ============================
// JavaScript 型バグ発見チェックツール
// ============================
import fs from "fs";
const code = process.argv[2];
if (!code) {
console.log("使用方法: node typeBugChecker.js <filename.js>");
process.exit(1);
}
const content = fs.readFileSync(code, "utf-8");
// チェックルール
const rules = [
{
id: "NaN-equality",
regex: /==\s*NaN|NaN\s*==|===\s*NaN|NaN\s*===/g,
message: "❌ NaN との比較は常に false。Number.isNaN(x) を使用してください。",
},
{
id: "loose-equality",
regex: /[^=!]==[^=]/g,
message: "⚠️ '==' は暗黙の型変換を行います。'===' を使用してください。",
},
{
id: "typeof-null",
regex: /typeof\s+[^\s]+\s*===?\s*["']object["']/g,
message: "⚠️ typeof null は 'object'。null 判定には `x === null` を使いましょう。",
},
{
id: "typeof-array",
regex: /typeof\s+[^\s]+\s*===?\s*["']array["']/g,
message: "❌ typeof [] は 'object'。配列判定には Array.isArray(x) を使用してください。",
},
{
id: "new-boolean",
regex: /new\s+(Boolean|String|Number)\s*\(/g,
message: "⚠️ ラッパーオブジェクトは避けましょう。プリミティブ型で十分です。",
},
{
id: "json-stringify",
regex: /JSON\.stringify\s*\([^)]*\)/g,
message: "⚠️ JSON.stringify() は undefined, Symbol, 関数をシリアライズしません。",
},
{
id: "null-undefined-eq",
regex: /==\s*null|null\s*==/g,
message: "⚠️ null == undefined は true。厳密比較 '===' を検討してください。",
},
{
id: "bigint-mix",
regex: /\d+n\s*\+\s*\d+|\d+\s*\+\s*\d+n/g,
message: "❌ BigInt と Number を混ぜて演算しています。",
},
{
id: "float-compare",
regex: /\d+\.\d+\s*===\s*\d+\.\d+/g,
message: "⚠️ 浮動小数点の直接比較は誤差を含む可能性があります。",
},
{
id: "assign-ref",
regex: /const\s+\w+\s*=\s*\w+\s*;/g,
message: "⚠️ オブジェクト参照のコピーに注意。{...obj} で浅いコピーできます。",
},
];
// 実行
let found = 0;
rules.forEach(rule => {
const matches = content.match(rule.regex);
if (matches) {
found += matches.length;
console.log(`\n${rule.message}`);
matches.forEach(m => console.log(` → 発見: ${m.trim()}`));
}
});
if (found === 0) {
console.log("✅ 型関連の潜在的バグは見つかりませんでした!");
} else {
console.log(`\n⚠️ 合計 ${found} 件の警告が見つかりました。`);
}
使い方
① 例としてテストファイルを作成
if (typeof null === "object") console.log("null is object?");
if (0 == "0") console.log("loose eq");
let x = NaN;
if (x === NaN) console.log("NaNです");
const b = new Boolean(false);
console.log(JSON.stringify({ a: undefined }));JavaScript② チェック実行
node typeBugChecker.js sample.jsBash③ 結果例
⚠️ '==' は暗黙の型変換を行います。'===' を使用してください。
→ 発見: 0 == "0"
❌ NaN との比較は常に false。Number.isNaN(x) を使用してください。
→ 発見: x === NaN
⚠️ typeof null は 'object'。null 判定には `x === null` を使いましょう。
→ 発見: typeof null === "object"
⚠️ ラッパーオブジェクトは避けましょう。プリミティブ型で十分です。
→ 発見: new Boolean(false)
⚠️ JSON.stringify() は undefined, Symbol, 関数をシリアライズしません。
→ 発見: JSON.stringify({ a: undefined })
⚠️ 合計 5 件の警告が見つかりました。拡張アイデア
- ESLint プラグインとして統合可能
- ルールを外部 JSON で管理
- VSCode の保存時自動チェックに対応可
- Webブラウザ版(
<textarea>に貼ってチェック)も簡単に作れる
ブラウザで動くGUI版(React+Tailwind)
以下のコードをそのままブラウザで動かせます(Next.js や Vite React でもOK)。
ファイル名例:TypeBugChecker.jsx
import React, { useState } from "react";
export default function TypeBugChecker() {
const [code, setCode] = useState("");
const [results, setResults] = useState([]);
const rules = [
{
id: "NaN-equality",
regex: /==\s*NaN|NaN\s*==|===\s*NaN|NaN\s*===/g,
message: "❌ NaNとの比較は常にfalse。Number.isNaN(x)を使いましょう。",
},
{
id: "loose-equality",
regex: /[^=!]==[^=]/g,
message: "⚠️ '==' は暗黙の型変換を行います。'===' を使用してください。",
},
{
id: "typeof-null",
regex: /typeof\s+[^\s]+\s*===?\s*["']object["']/g,
message: "⚠️ typeof null は 'object'。null判定には x === null を使用。",
},
{
id: "typeof-array",
regex: /typeof\s+[^\s]+\s*===?\s*["']array["']/g,
message: "❌ typeof [] は 'object'。Array.isArray(x) を使用。",
},
{
id: "new-boolean",
regex: /new\s+(Boolean|String|Number)\s*\(/g,
message: "⚠️ ラッパーオブジェクトは避けて、プリミティブ型を使いましょう。",
},
{
id: "json-stringify",
regex: /JSON\.stringify\s*\([^)]*\)/g,
message: "⚠️ JSON.stringify() は undefined, Symbol, 関数をシリアライズしません。",
},
{
id: "null-undefined-eq",
regex: /==\s*null|null\s*==/g,
message: "⚠️ null == undefined は true。厳密比較 '===' を検討。",
},
{
id: "bigint-mix",
regex: /\d+n\s*\+\s*\d+|\d+\s*\+\s*\d+n/g,
message: "❌ BigInt と Number を混ぜて演算しています。",
},
{
id: "float-compare",
regex: /\d+\.\d+\s*===\s*\d+\.\d+/g,
message: "⚠️ 浮動小数点の直接比較は誤差を含む可能性があります。",
},
];
const checkCode = () => {
const warnings = [];
rules.forEach((rule) => {
const matches = code.match(rule.regex);
if (matches) {
matches.forEach((m) => {
warnings.push({ message: rule.message, match: m.trim() });
});
}
});
setResults(warnings);
};
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-3xl mx-auto bg-white rounded-2xl shadow-md p-6 space-y-4">
<h1 className="text-2xl font-bold text-gray-800 mb-2">
🧠 JavaScript 型バグ発見ツール
</h1>
<textarea
className="w-full h-64 border rounded-xl p-3 font-mono text-sm bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-400"
placeholder="ここにJavaScriptコードを貼り付けてください..."
value={code}
onChange={(e) => setCode(e.target.value)}
/>
<button
onClick={checkCode}
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-xl font-semibold transition"
>
🔍 チェックする
</button>
<div className="mt-6">
<h2 className="text-xl font-semibold mb-2 text-gray-700">
結果:
</h2>
{results.length === 0 ? (
<p className="text-green-600 font-medium">
✅ 型関連の潜在的バグは見つかりませんでした!
</p>
) : (
<ul className="space-y-2">
{results.map((r, i) => (
<li
key={i}
className="bg-red-50 border border-red-200 p-3 rounded-xl text-sm"
>
<p className="font-semibold text-red-700">{r.message}</p>
<p className="text-gray-700 mt-1">→ 検出箇所: <code className="bg-gray-200 px-1 rounded">{r.match}</code></p>
</li>
))}
</ul>
)}
</div>
</div>
</div>
);
}
JSX動かし方(2通り)
方法1:Vite or Next.js プロジェクトに追加
- React プロジェクトを用意(例:
npx create-react-appやnpm create vite@latest) - 上記コードを
TypeBugChecker.jsxに保存 - App コンポーネントで
<TypeBugChecker />を表示
import TypeBugChecker from "./TypeBugChecker";
function App() {
return <TypeBugChecker />;
}
export default App;
JSX方法2:HTML単体でも動作させたい場合
以下のHTMLを index.html に保存し、ブラウザで開くだけ👇
(純粋なクライアントサイド版、React不要)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>JavaScript 型バグ発見ツール</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 p-6">
<div class="max-w-3xl mx-auto bg-white rounded-2xl shadow-md p-6 space-y-4">
<h1 class="text-2xl font-bold text-gray-800 mb-2">🧠 JavaScript 型バグ発見ツール</h1>
<textarea id="code" class="w-full h-64 border rounded-xl p-3 font-mono text-sm bg-gray-100"></textarea>
<button id="checkBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-xl font-semibold transition">🔍 チェックする</button>
<div id="results" class="mt-6"></div>
</div>
<script>
const rules = [
{ regex: /==\s*NaN|NaN\s*==|===\s*NaN|NaN\s*===/g, msg: "❌ NaNとの比較は常にfalse。Number.isNaN(x)を使いましょう。" },
{ regex: /[^=!]==[^=]/g, msg: "⚠️ '==' は暗黙の型変換を行います。'===' を使用してください。" },
{ regex: /typeof\s+[^\s]+\s*===?\s*["']object["']/g, msg: "⚠️ typeof null は 'object'。null判定には x === null を使用。" },
{ regex: /typeof\s+[^\s]+\s*===?\s*["']array["']/g, msg: "❌ typeof [] は 'object'。Array.isArray(x) を使用。" },
{ regex: /new\s+(Boolean|String|Number)\s*\(/g, msg: "⚠️ ラッパーオブジェクトは避けましょう。プリミティブ型で十分です。" },
{ regex: /JSON\.stringify\s*\([^)]*\)/g, msg: "⚠️ JSON.stringify() は undefined, Symbol, 関数をシリアライズしません。" },
{ regex: /==\s*null|null\s*==/g, msg: "⚠️ null == undefined は true。厳密比較 '===' を検討。" },
{ regex: /\d+n\s*\+\s*\d+|\d+\s*\+\s*\d+n/g, msg: "❌ BigInt と Number を混ぜて演算しています。" },
{ regex: /\d+\.\d+\s*===\s*\d+\.\d+/g, msg: "⚠️ 浮動小数点の直接比較は誤差を含む可能性があります。" },
];
document.getElementById("checkBtn").addEventListener("click", () => {
const code = document.getElementById("code").value;
const results = document.getElementById("results");
results.innerHTML = "";
const warnings = [];
rules.forEach(r => {
const matches = code.match(r.regex);
if (matches) {
matches.forEach(m => warnings.push({ msg: r.msg, match: m.trim() }));
}
});
if (warnings.length === 0) {
results.innerHTML = `<p class="text-green-600 font-medium">✅ 型関連の潜在的バグは見つかりませんでした!</p>`;
} else {
results.innerHTML = warnings.map(w =>
`<div class="bg-red-50 border border-red-200 p-3 rounded-xl text-sm mb-2">
<p class="font-semibold text-red-700">${w.msg}</p>
<p class="text-gray-700 mt-1">→ 検出箇所: <code class="bg-gray-200 px-1 rounded">${w.match}</code></p>
</div>`
).join("");
}
});
</script>
</body>
</html>
HTML機能まとめ
✅ NaN 比較
✅ typeof null / array
✅ 暗黙型変換 (==)
✅ ラッパーオブジェクト
✅ JSON.stringify の落とし穴
✅ BigInt混在演算
✅ 浮動小数点比較
ESLintルール版
このカスタムルールを使うと、プロジェクト全体で
NaN比較・暗黙型変換・typeof誤用・null/undefined混同などを自動検出できます。
ディレクトリ構成
my-eslint-plugin/
├── package.json
├── index.js
└── rules/
├── no-nan-compare.js
├── no-loose-eq.js
├── no-typeof-null.js
├── no-typeof-array.js
├── no-new-wrapper.js
├── no-bigint-mix.js
PLSQLpackage.json
{
"name": "eslint-plugin-type-safety",
"version": "1.0.0",
"description": "ESLint rules to detect common JavaScript type-related bugs",
"main": "index.js",
"keywords": ["eslint", "plugin", "type", "safety"],
"type": "module"
}JSONindex.js
import noNanCompare from "./rules/no-nan-compare.js";
import noLooseEq from "./rules/no-loose-eq.js";
import noTypeofNull from "./rules/no-typeof-null.js";
import noTypeofArray from "./rules/no-typeof-array.js";
import noNewWrapper from "./rules/no-new-wrapper.js";
import noBigintMix from "./rules/no-bigint-mix.js";
export const rules = {
"no-nan-compare": noNanCompare,
"no-loose-eq": noLooseEq,
"no-typeof-null": noTypeofNull,
"no-typeof-array": noTypeofArray,
"no-new-wrapper": noNewWrapper,
"no-bigint-mix": noBigintMix,
};
export const configs = {
recommended: {
rules: {
"type-safety/no-nan-compare": "error",
"type-safety/no-loose-eq": "warn",
"type-safety/no-typeof-null": "warn",
"type-safety/no-typeof-array": "warn",
"type-safety/no-new-wrapper": "warn",
"type-safety/no-bigint-mix": "error"
}
}
};
JavaScript各ルール実装例
rules/no-nan-compare.js
export default {
meta: {
type: "problem",
docs: {
description: "NaNとの比較は常にfalse。Number.isNaNを使用すべき。",
},
messages: {
avoidNaNCompare: "NaNとの比較は常にfalse。Number.isNaN(x)を使いましょう。",
},
},
create(context) {
return {
BinaryExpression(node) {
if (
(node.left.name === "NaN" || node.right.name === "NaN") &&
["==", "==="].includes(node.operator)
) {
context.report({ node, messageId: "avoidNaNCompare" });
}
},
};
},
};
JavaScriptrules/no-loose-eq.js
export default {
meta: {
type: "suggestion",
docs: {
description: "== / != は暗黙の型変換を行うため非推奨。",
},
messages: {
noLooseEq: "暗黙の型変換を避けるため '===' または '!==' を使用してください。",
},
},
create(context) {
return {
BinaryExpression(node) {
if (node.operator === "==" || node.operator === "!=") {
context.report({ node, messageId: "noLooseEq" });
}
},
};
},
};
JavaScriptrules/no-typeof-null.js
export default {
meta: {
type: "problem",
docs: {
description: "typeof null は 'object' を返すため注意が必要。",
},
messages: {
typeofNull: "typeof null は 'object'。null 判定には x === null を使用してください。",
},
},
create(context) {
return {
UnaryExpression(node) {
if (
node.operator === "typeof" &&
node.argument.name === "null"
) {
context.report({ node, messageId: "typeofNull" });
}
},
};
},
};
JavaScriptrules/no-typeof-array.js
export default {
meta: {
type: "problem",
docs: {
description: "typeof [] は 'object'。配列判定には Array.isArray(x) を使用。",
},
messages: {
typeofArray: "配列判定に typeof は使えません。Array.isArray(x) を使用してください。",
},
},
create(context) {
return {
BinaryExpression(node) {
if (
node.left.type === "UnaryExpression" &&
node.left.operator === "typeof" &&
/["']array["']/.test(node.right.raw)
) {
context.report({ node, messageId: "typeofArray" });
}
},
};
},
};
JavaScriptrules/no-new-wrapper.js
export default {
meta: {
type: "suggestion",
docs: {
description: "new Boolean/String/Number は避けるべき。",
},
messages: {
wrapperWarn: "ラッパーオブジェクトは避けましょう。プリミティブ型を使用してください。",
},
},
create(context) {
return {
NewExpression(node) {
const name = node.callee.name;
if (["Boolean", "String", "Number"].includes(name)) {
context.report({ node, messageId: "wrapperWarn" });
}
},
};
},
};
JavaScriptrules/no-bigint-mix.js
export default {
meta: {
type: "problem",
docs: {
description: "BigInt と Number の混在演算を禁止。",
},
messages: {
bigintMix: "BigInt と Number は混在できません。どちらかに統一してください。",
},
},
create(context) {
return {
BinaryExpression(node) {
if (
node.left.raw?.endsWith("n") && /^[0-9]+$/.test(node.right.raw) ||
node.right.raw?.endsWith("n") && /^[0-9]+$/.test(node.left.raw)
) {
context.report({ node, messageId: "bigintMix" });
}
},
};
},
};
JavaScriptESLint設定で使う
プロジェクトの .eslintrc.json に以下を追加
{
"plugins": ["type-safety"],
"extends": ["plugin:type-safety/recommended"]
}
JSONまたは特定のルールだけ個別指定:
{
"plugins": ["type-safety"],
"rules": {
"type-safety/no-nan-compare": "error",
"type-safety/no-loose-eq": "warn"
}
}JSON動作確認
npm install eslint
npx eslint --init
# プラグインパスを指定して実行
npx eslint --plugin type-safety ./src
Bash拡張アイデア
| 機能 | 内容 |
|---|---|
no-json-loss | JSON.stringify()で失われるキー(undefined, Symbol, function)を警告 |
no-float-eq | 小数点の厳密比較を警告 |
no-implicit-undefined | 関数戻り値がundefinedの可能性を検出 |
prefer-explicit-cast | 暗黙の型変換より明示的キャストを推奨 |
まとめ
| 対応バグ | 対応ルール名 |
|---|---|
| NaN比較 | no-nan-compare |
| == / != | no-loose-eq |
| typeof null | no-typeof-null |
| typeof array | no-typeof-array |
| new Booleanなど | no-new-wrapper |
| BigInt混合演算 | no-bigint-mix |
