404 Not Found

404 Not Found


nginx

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 parseFloatNumber() の違いは何ですか?
A parseFloat("42px")42 を返します(先頭から解析し、最初の非数値文字で停止)。一方、Number("42px")NaN を返します(文字列全体を解析し、非数値文字があれば失敗)。混合テキストから数値を抽出するには parseFloat を、厳密な検証には Number() を使いましょう。

📝 演習

  1. parseChineseNumber(str) 関数を書きましょう。"3.5元"3.5"100人"100"約200"200 のように文字列から数値を解析してください(parseFloat を使用)。プレーンテキストの場合は NaN を返すように処理してください。
  2. formatMoney(amount) 関数を書きましょう。数値を引数に取り、カンマ区切りの桁区切りと小数点以下2桁の文字列を返してください。例:1234567.89"1,234,567.89"(ヒント:toFixedtoString を配列メソッドと一緒に使う)。
  3. getType(value) 関数を書きましょう。typeof の欠点を補完し、null には "null" を、配列には "array" を、それ以外は typeof の結果を返してください。
100%