JavaScript ユーティリティメソッド
型変換について学んできたところで、毎日使うユーティリティメソッドを一挙に学びましょう。スイスアーミーナイフのようなものです。それぞれ単純ですが、必要な時にはどれも欠かせません。
📖 まとめ
parseInt と parseFloat
文字列の先頭から数値を解析し、最初の非数値文字で停止します。
HTML
<script>
console.log(parseInt("42px")); // 42
console.log(parseInt("3.14")); // 3(整数部分のみ)
console.log(parseFloat("3.14")); // 3.14
console.log(parseFloat("3.14abc")); // 3.14
console.log(parseInt("abc42")); // NaN(数値で始まらない)
</script>
parseInt の第2引数は基数を指定します。
HTML
<script>
console.log(parseInt("FF", 16)); // 255(16進数)
console.log(parseInt("10", 2)); // 2(2進数)
</script>
落とし穴:基数を指定しないと、0x で始まる文字列は16進数として解析されます。常に第2引数に 10 を渡してください。
isNaN と Number.isNaN
| メソッド | 動作 |
|---|---|
isNaN(value) |
まず数値に変換してから NaN かどうかをチェック |
Number.isNaN(value) |
値が NaN かを厳密にチェック(変換なし) |
HTML
<script>
console.log(isNaN("hello")); // true(Number("hello") → NaN、その後 true)
console.log(Number.isNaN("hello")); // false("hello" 自体は NaN ではない)
console.log(isNaN(undefined)); // true
console.log(Number.isNaN(undefined));// false
</script>
結論:Number.isNaN() を使いましょう。暗黙的に型を変換しません。
toFixed
小数点以下の桁数を制御し、文字列を返します。
HTML
<script>
console.log((3.14159).toFixed(2)); // "3.14"
console.log((3.1).toFixed(4)); // "3.1000"(ゼロでパディング)
console.log((3.145).toFixed(2)); // "3.14" または "3.15"(浮動小数点精度の問題。丸め保証なし)
</script>
toFixed は文字列を返すため、計算に使う場合は Number() で数値に戻してください。
toString
数値を文字列に変換します。基数の指定にも対応しています。
HTML
<script>
console.log((255).toString()); // "255"
console.log((255).toString(16)); // "ff"
console.log((8).toString(2)); // "1000"
console.log((100).toString(8)); // "144"
</script>
真偽値と配列にも toString があります。
HTML
<script>
console.log(true.toString()); // "true"
console.log([1,2,3].toString()); // "1,2,3"
</script>
Number.isInteger
値が整数かどうかを判定します。
HTML
<script>
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(5.0)); // true(5.0 は単に 5)
console.log(Number.isInteger(5.5)); // false
console.log(Number.isInteger("5")); // false(文字列は整数ではない)
console.log(Number.isInteger(NaN)); // false
</script>
Math.trunc と Math.sign
Math.trunc は小数部分を単純に切り捨てます(丸めなし)。
HTML
<script>
console.log(Math.trunc(4.9)); // 4
console.log(Math.trunc(-4.9)); // -4
console.log(Math.trunc(4.1)); // 4
</script>
Math.sign は数値の符号を返します。
HTML
<script>
console.log(Math.sign(5)); // 1
console.log(Math.sign(-5)); // -1
console.log(Math.sign(0)); // 0
console.log(Math.sign(-0)); // -0
console.log(Math.sign(NaN)); // NaN
</script>
typeof の限界
typeof は通常の型チェックに使えますが、いくつかの古典的な落とし穴があります。
HTML
<script>
console.log(typeof 42); // "number" ✅
console.log(typeof "hello"); // "string" ✅
console.log(typeof true); // "boolean" ✅
console.log(typeof undefined); // "undefined" ✅
console.log(typeof null); // "object" ❌ これは歴史的なバグ!
console.log(typeof []); // "object" ❌ 配列もオブジェクト
console.log(typeof {}); // "object" ✅
console.log(typeof function(){}); // "function" ✅
</script>
typeof null === "object" は JavaScript の「ベテランバグ」です。1995年以来存在し、修正することはできません。null のチェックには value === null を使いましょう。配列のチェックには Array.isArray() を使いましょう。
例:価格計算機
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>価格計算機</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.calc { max-width: 440px; margin: 16px auto; padding: 24px; background: #f9f9f9; border-radius: 12px; }
.row { margin: 12px 0; display: flex; align-items: center; gap: 10px; }
label { width: 80px; font-weight: bold; }
input { padding: 8px 12px; font-size: 16px; border: 2px solid #ccc; border-radius: 6px; flex: 1; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; background: #4a90d9; color: #fff; width: 100%; margin-top: 8px; }
button:hover { background: #357abd; }
.result { margin-top: 16px; padding: 16px; background: #fff; border-radius: 8px; border: 2px solid #5cb85c; }
.result div { margin: 4px 0; }
.total { font-size: 22px; font-weight: bold; color: #d9534f; }
</style>
</head>
<body>
<h2 style="text-align:center;">価格計算機</h2>
<div class="calc">
<div class="row">
<label>単価:</label>
<input type="text" id="price" value="29.90" placeholder="例:29.90" />
</div>
<div class="row">
<label>数量:</label>
<input type="text" id="qty" value="3" placeholder="例:3" />
</div>
<div class="row">
<label>割引:</label>
<input type="text" id="discount" value="0.85" placeholder="例:0.85(15%引き)" />
</div>
<button id="calc">計算する</button>
<div class="result" id="result" style="display:none;"></div>
</div>
<script>
document.getElementById("calc").addEventListener("click", function() {
const priceStr = document.getElementById("price").value.trim();
const qtyStr = document.getElementById("qty").value.trim();
const discountStr = document.getElementById("discount").value.trim();
const price = parseFloat(priceStr);
const qty = parseInt(qtyStr, 10);
const discount = parseFloat(discountStr);
if (Number.isNaN(price) || price <= 0) {
alert("無効な単価です!正の数を入力してください。");
return;
}
if (Number.isNaN(qty) || qty <= 0 || !Number.isInteger(qty)) {
alert("無効な数量です!正の整数を入力してください。");
return;
}
if (Number.isNaN(discount) || discount <= 0 || discount > 1) {
alert("無効な割引です!0から1の間の小数を入力してください。");
return;
}
const subtotal = price * qty;
const total = subtotal * discount;
const saved = subtotal - total;
document.getElementById("result").style.display = "block";
document.getElementById("result").innerHTML = `
<div>単価:${price.toFixed(2)}円</div>
<div>数量:${qty}</div>
<div>小計:${subtotal.toFixed(2)}円</div>
<div>割引:${(discount * 100).toFixed(0)}%引き(-${saved.toFixed(2)}円)</div>
<div class="total">合計:${total.toFixed(2)}円</div>
`;
});
</script>
</body>
</html>
例:入力検証ツール
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>入力検証</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.demo { max-width: 500px; margin: 16px auto; }
.row { margin: 12px 0; display: flex; align-items: center; gap: 10px; }
label { width: 80px; font-weight: bold; }
input { padding: 8px 12px; font-size: 16px; border: 2px solid #ccc; border-radius: 6px; flex: 1; }
input.valid { border-color: #5cb85c; background: #f0fff0; }
input.invalid { border-color: #d9534f; background: #fff0f0; }
.msg { font-size: 13px; margin-left: 90px; }
.msg.valid { color: #5cb85c; }
.msg.invalid { color: #d9534f; }
.methods { margin-top: 20px; padding: 16px; background: #f5f5f5; border-radius: 8px; }
.methods div { margin: 4px 0; font-family: monospace; font-size: 14px; }
</style>
</head>
<body>
<h2 style="text-align:center;">入力検証デモ</h2>
<div class="demo">
<div class="row">
<label>年齢:</label>
<input type="text" id="age" placeholder="年齢を入力" />
</div>
<div class="msg" id="ageMsg"></div>
<div class="row">
<label>電話番号:</label>
<input type="text" id="phone" placeholder="電話番号を入力" />
</div>
<div class="msg" id="phoneMsg"></div>
<div class="row">
<label>金額:</label>
<input type="text" id="amount" placeholder="金額を入力" />
</div>
<div class="msg" id="amountMsg"></div>
<div class="methods" id="methods"></div>
</div>
<script>
function validateAge(value) {
const num = parseInt(value, 10);
if (value.trim() === "") return { valid: false, msg: "空欄にできません" };
if (Number.isNaN(num)) return { valid: false, msg: "有効な整数ではありません" };
if (!Number.isInteger(Number(value))) return { valid: false, msg: "年齢は整数でなければなりません" };
if (num < 0 || num > 150) return { valid: false, msg: "年齢の範囲:0〜150" };
return { valid: true, msg: `有効です。${num}歳ですね` };
}
function validatePhone(value) {
const trimmed = value.trim();
if (trimmed === "") return { valid: false, msg: "空欄にできません" };
if (!/^\d+$/.test(trimmed)) return { valid: false, msg: "数字のみ入力してください" };
if (trimmed.length !== 11) return { valid: false, msg: "電話番号は11桁でなければなりません" };
return { valid: true, msg: "フォーマットは正しいです" };
}
function validateAmount(value) {
const num = parseFloat(value);
if (value.trim() === "") return { valid: false, msg: "空欄にできません" };
if (Number.isNaN(num)) return { valid: false, msg: "有効な数値ではありません" };
if (num <= 0) return { valid: false, msg: "金額は0より大きくなければなりません" };
return { valid: true, msg: `有効です。金額:${num.toFixed(2)}円` };
}
function setupValidation(inputId, msgId, validator) {
const input = document.getElementById(inputId);
const msg = document.getElementById(msgId);
input.addEventListener("input", function() {
const result = validator(input.value);
input.className = result.valid ? "valid" : "invalid";
msg.className = "msg " + (result.valid ? "valid" : "invalid");
msg.textContent = result.msg;
updateMethods();
});
}
setupValidation("age", "ageMsg", validateAge);
setupValidation("phone", "phoneMsg", validatePhone);
setupValidation("amount", "amountMsg", validateAmount);
function updateMethods() {
const age = document.getElementById("age").value;
const phone = document.getElementById("phone").value;
const amount = document.getElementById("amount").value;
document.getElementById("methods").innerHTML = `
<strong>ユーティリティメソッドの結果:</strong>
<div>parseInt("${age}", 10) → ${parseInt(age, 10)}</div>
<div>Number.isNaN(parseInt("${age}")) → ${Number.isNaN(parseInt(age, 10))}</div>
<div>parseFloat("${amount}") → ${parseFloat(amount)}</div>
<div>typeof "${phone}" → "${typeof phone}"</div>
`;
}
</script>
</body>
</html>
例:typeof と型チェック
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>typeof 型チェック</title>
<style>
body { font-family: sans-serif; padding: 20px; }
table { border-collapse: collapse; margin: 16px 0; width: 100%; max-width: 700px; }
td, th { border: 1px solid #ddd; padding: 10px 14px; text-align: left; }
th { background: #4a90d9; color: #fff; }
.bug { background: #fff0f0; }
.ok { background: #f0fff0; }
.tip { background: #fff8e1; padding: 12px; border-radius: 6px; border-left: 4px solid #f0ad4e; margin: 16px 0; max-width: 700px; }
</style>
</head>
<body>
<h2>各型に対する typeof チェック</h2>
<div id="output"></div>
<script>
const values = [
[42, "数値"],
["hello", "文字列"],
[true, "真偽値"],
[undefined, "undefined"],
[null, "null ⚠️"],
[[1,2,3], "配列 ⚠️"],
[{a:1}, "オブジェクト"],
[function(){}, "関数"],
[NaN, "NaN"],
[Infinity, "Infinity"],
];
const rows = values.map(([val, label]) => {
const typeResult = typeof val;
const isBug = (val === null && typeResult === "object") ||
(Array.isArray(val) && typeResult === "object");
const correctWay = val === null ? "value === null" :
Array.isArray(val) ? "Array.isArray(value)" :
"typeof で問題なし";
return `<tr class="${isBug ? 'bug' : 'ok'}">
<td>${label}</td>
<td><code>${JSON.stringify(val)}</code></td>
<td><code>"${typeResult}"</code></td>
<td>${isBug ? "❌ 不正確" : "✅ 正確"}</td>
<td>${isBug ? correctWay : "—"}</td>
</tr>`;
});
document.getElementById("output").innerHTML = `
<table>
<tr><th>期待される型</th><th>値</th><th>typeof 結果</th><th>正確?</th><th>正しいアプローチ</th></tr>
${rows.join("")}
</table>
<div class="tip">
💡 2つのエッジケースを覚えておきましょう:<code>typeof null</code> は <code>"object"</code> を返します(歴史的なバグ)。
<code>typeof []</code> も <code>"object"</code> を返します(配列は独立した型ではない)。
null のチェックには <code>=== null</code> を、配列のチェックには <code>Array.isArray()</code> を使いましょう。
</div>
`;
</script>
</body>
</html>
❓ よくある質問
Q
parseInt("08") が 0 を返すことがあるのはなぜですか?A 一部の古いエンジンでは、
0 で始まる文字列が8進数として解析されていました。"08" は8進数として無効なため、0 を返していました。解決策:常に第2引数に 10 を渡すこと。つまり parseInt("08", 10) → 8 となります。Q
toFixed が丸めを保証しないのはなぜですか?A 浮動小数点数は2進数で正確に表現できないためです。
(0.1 + 0.2).toFixed(1) は "0.3" になるとは限りません。正確な丸めには、toFixed を呼び出す前に Math.round(num * 100) / 100 を使いましょう。Q
parseFloat と Number() の違いは何ですか?A
parseFloat("42px") は 42 を返します(先頭から解析し、最初の非数値文字で停止)。一方、Number("42px") は NaN を返します(文字列全体を解析し、非数値文字があれば失敗)。混合テキストから数値を抽出するには parseFloat を、厳密な検証には Number() を使いましょう。📝 演習
parseChineseNumber(str)関数を書きましょう。"3.5元"→3.5、"100人"→100、"約200"→200のように文字列から数値を解析してください(parseFloatを使用)。プレーンテキストの場合はNaNを返すように処理してください。formatMoney(amount)関数を書きましょう。数値を引数に取り、カンマ区切りの桁区切りと小数点以下2桁の文字列を返してください。例:1234567.89→"1,234,567.89"(ヒント:toFixedとtoStringを配列メソッドと一緒に使う)。getType(value)関数を書きましょう。typeofの欠点を補完し、nullには"null"を、配列には"array"を、それ以外はtypeofの結果を返してください。



