Old-School Front-End / 2026 ・ No.07

いにしえのフロントエンド入門

第7回 JavaScript ① ── 基本:明細の合計を計算する関数を書く

公開

左に js/app.js のコード、右にコンソールを並べた画面。コードには toAmount、calcTotal、needsReceipt、formatYen の4つの関数。コンソールにはテスト12件の合格の印と、calcTotal(items) が 42240、formatYen が '42,240 円'、needsReceipt が true
今回のゴール。①計算だけを担当する4つの関数を書き、②手作りのテストで確かめる(筆者のデモページを、クラウド上の Chromium で撮影。2026年10月17日。コンソールの画面は、console.log をページに書き出す手作りの再現で、本物の開発者ツールではありません。ブラウザの枠は説明のために描き足したもの)

AI に頼まず、一行ずつ手で書く「いにしえの」フロントエンド入門。第7回から、いよいよ JavaScript です。
HTML が骨、CSS が服なら、JavaScript は筋肉。最初の仕事は、新井くんが電卓でやっていた明細の合計です。
値の種類、変数、関数、くり返し。そして、お金を扱うときに必ず踏む「足したのに、つながった」罠まで。
画面(DOM)にはまだ触らず、計算だけを担当する関数を作って、自分でテストします。

一

今回のゴール

新井先輩、合計の計算を AI に書いてもらいました! ……でも、大阪出張の合計が「0147201280014720 円」って出るんです。

古井147兆円の出張か。経理が腰を抜かすぞ。

新井数字が全部つながってるだけに見えますね……。

古井正解だ。JavaScript の「+」には顔が二つある。今日はそこから始めよう。

いまここ:第7回

1 準備 2 HTML の骨組み 3 フォーム 4 CSS の基本 5 レイアウト 6 Bootstrap 7 JS の基本 8 DOM・イベント 9 入力チェック 10 通信 11 ログイン 12 承認の流れ 13 集計と印刷 14 jQuery 15 仕上げ いまここ 準備 HTML CSS JavaScript 実戦 仕上げ

今回作るのは js/app.js。第1回で決めたフォルダの形の、最後の空き部屋です。中身は、計算だけを担当する4つの関数。

  • toAmount ── 入力欄の文字(「14,720」や全角の「14720」)を、金額の数に直す
  • calcTotal ── 明細の一覧から合計を出す
  • needsReceipt ── 合計が3万円以上なら「領収書が必須」と答える
  • formatYen ── 42240 を「42,240 円」の形にする

画面の入力欄から値を取ったり、合計を画面に出したりするのは次回(第8回)。計算と画面を分けておくと、計算だけを取り出して確かめられます。今回はそれを、自分で書いたテストでやってみます。

二

JavaScript をどこに書くか

CSS を link で読み込んだように、JavaScript は script 要素で読み込みます。問題はどこに書くか。HTML は上から順に読まれるので、置き場所によって「実行される時点」が変わります。三通り試して、そのとき document.body(ページの本体)があるかを調べました。

script の置き場所と、実行される時点(実測)

HTML を上から読んでいく <script> in head 読み終わる前に実行 <head> … <body> <h1> … document.body → null <script defer> 読み終わってから実行 <head> … <body> <h1> … document.body → body </body> の直前 最後に実行 <head> … <body> <h1> … document.body → body 縦の線が実行される時点。今回は head に defer 付きで書く

head にそのまま書くと、まだ本体を読んでいない時点で実行されるので、画面の部品を探しても見つかりません。defer(「後回しにして」という意味)を付けると、HTML を読み終わってから実行されます。今回から、申請フォームの head にこう書きます。

<link rel="stylesheet" href="css/custom.css">
<script src="js/app.js" defer></script>

書いた JavaScript を試す場所は、開発者ツールのコンソールです。Chrome なら Windows は Ctrl+Shift+J、Mac は Cmd+Option+J で開きます。> の後に式を書いて Enter を押すと、すぐに結果が返ってきます。電卓としても使えるので、ぜひ手を動かしながら読んでください。

古井JavaScript は1995年に、たった10日ほどで試作された言語だ。

新井10日! それでこんなに使われてるんですか。

古井だから、急いで作ったなりのクセもある。今日はそのクセとの付き合い方を覚えるんだ。

三

値の種類

JavaScript の値には種類(型)があります。経費精算で出てくるものを並べました。

値の種類と、経費精算での例

number 14720 数。足し算・引き算ができる 例:金額 string '14720' 文字。引用符で囲む 例:入力欄の値は必ずこれ boolean true true か false の二択 例:領収書は必須か null null 「わざと空」を表す 例:金額の欄が空欄 undefined undefined 「まだ何も入っていない」 例:書き忘れた変数 object { amount: 14720 } 名前付きの値のまとまり 配列 [ ] は、その一覧 同じ 14720 でも、number と string では振る舞いがまったく違う

大事なのは、同じ 14720 でも、数(number)と文字(string)は別物だということ。そして、HTML の入力欄から取り出した値は、たとえ type="number" の欄でも、必ず文字で届きます(第3回で見た name=value も、すべて文字でした)。値の種類は typeof で調べられます。

> typeof 14720
< 'number'
> typeof '14720'
< 'string'
四

変数:const と let

値に名前を付けて取っておくのが変数です。いまの JavaScript では、二つの書き方を使い分けます。

const RECEIPT_LIMIT = 30000;   // const:あとから入れ替えない
let total = 0;                 // let:あとから入れ替える
total = total + 14720;         // 入れ替えられる
RECEIPT_LIMIT = 50000;         // const は入れ替えられない → エラー
  • まず const ── 入れ替える予定がないものは const。うっかり上書きすると、その場でエラーで教えてくれる
  • 必要なときだけ let ── 合計のように、足しながら育てていくものは let
  • var は使わない ── 古い書き方。ネットの古い記事や、それを学んだ AI がよく使う。見かけたら let か const に直す

名前は、何が入っているか分かる英語にします。x や data より、total や items。決まった値(3万円)は、RECEIPT_LIMIT のように大文字で書いておくと、「ここは規程の数字」とひと目で分かります。

五

「+」の二つの顔

冒頭の「147兆円」の正体です。

「+」は、足す? つなげる?(実測)

14720 + 12800 → 27520 足し算 数どうし '14720' + '12800' → '1472012800' つなげる 文字どうし 0 + '14720' → '014720' つなげる 片方でも文字なら '14720' - '12800' → 1920 なぜか引き算 - は数に直す + だけが「足す」と「つなげる」の二役。入力欄の値は文字なので、必ず数に直してから足す

「+」は、両方が数なら足し算、どちらかが文字ならつなげるになります。入力欄の値は文字なので、そのまま足すと……。

コンソールの画面。文字列の配列 ['14720', '12800', '14720'] を for 文でそのまま足すと、結果は '0147201280014720' という文字列になり、typeof は 'string'。toAmount で数に直してから足すと 42240
AI が書きがちなバグ。入力欄の値(文字列)をそのまま足すと、足し算ではなく「つなげる」になる(筆者のデモページを、クラウド上の Chromium で撮影。2026年10月17日。コンソールの画面は、console.log をページに書き出す手作りの再現で、本物の開発者ツールではありません。ブラウザの枠は説明のために描き足したもの)

合計を 0 から始めても、0 + '14720' の時点で文字の '014720' になり、あとは延々とつながっていきます。しかも、ややこしいことに「-」は文字を数に直してから引くので、引き算だけは正しく動いてしまいます。だから入力の値は、足す前に必ず数に直す。これが今回いちばんの教訓です。

ほかにも、金額を扱うと出会う罠を集めて、実際に実行してみました。

コンソールの画面を2列に並べたもの。文字列の足し算、Number('') が 0、Number('1,000') が NaN、parseInt('1,000') が 1、全角数字が NaN、0.1 + 0.2 が 0.30000000000000004、NaN === NaN が false、typeof null が 'object' など17の式と結果
金額を扱うときに出会う罠の一覧。どれもブラウザで実際に実行した結果(筆者のデモページを、クラウド上の Chromium で撮影。2026年10月17日。コンソールの画面は、console.log をページに書き出す手作りの再現で、本物の開発者ツールではありません。ブラウザの枠は説明のために描き足したもの)
# Chromium 141 のコンソールで実行(Node.js 22 でも同じ結果)
> '14720' + '12800' + '14720' → '147201280014720'
> 0 + '14720' + '12800' + '14720' → '0147201280014720'
> '14720' - '12800' → 1920
> Number('') → 0
> Number('1,000') → NaN
> parseInt('1,000') → 1
> Number('14720') → NaN
> '14720'.normalize('NFKC') → '14720'
> 0.1 + 0.2 → 0.30000000000000004
> 42240 + NaN → NaN
> NaN === NaN → false
> typeof NaN → 'number'
> typeof '14720' → 'string'
> typeof 14720 → 'number'
> typeof null → 'object'
> typeof undefined → 'undefined'
> typeof [] → 'object'
  • Number('') が 0 ── 空欄が、黙って 0 円になる。「入力していない」と「0円」は区別したい
  • parseInt('1,000') が 1 ── カンマの手前までで読むのをやめる。千円が1円になる
  • 全角の数字は NaN ── 日本語の入力では、全角の「14720」がよく入ってくる
  • NaN(Not a Number) ── 「数ではない」を表す特別な値。何を足しても NaN になり、合計を丸ごと台なしにする。しかも NaN === NaN は false なので、比べるときは Number.isNaN や Object.is を使う
  • 0.1 + 0.2 ── コンピュータは小数をぴったり表せないことがある。お金は整数で扱う(円なら円、ドルならセント)のが鉄則
六

関数とくり返し

合計の計算に名前を付けて、何度でも使えるようにしたものが関数です。

関数の形

function calcTotal(items) {   let total = 0;   for (const item of items) {     total += item.amount;   }   return total; } 名前 何をする関数か分かる名前 引数 items 受け取る値(明細の配列) 本体 1行ずつ足していく return 結果を返して終わる calcTotal(items) 42240 呼び出す 結果が返ってくる 関数は「入力を受け取り、結果を返す」箱。画面のことは知らなくてよい

calcTotal が受け取る items は、明細1行を表すオブジェクト(名前付きの値のまとまり)を並べた配列です。

const items = [
  { date: '2026-10-15', item: '新幹線(東京→新大阪)', amount: 14720 },
  { date: '2026-10-15', item: 'ホテル1泊', amount: 12800 },
  { date: '2026-10-16', item: '新幹線(新大阪→東京)', amount: 14720 },
];
items.length       // 3(行の数)
items[0].amount    // 14720(1行目の金額。数えるのは 0 から)

for (const item of items) は、配列の中身を1つずつ取り出して、同じ処理をくり返す書き方です。実際に1回ごとの total を記録しました。

くり返しのたびに増えていく total(実行結果)

回 item item.amount total 0 — — 0 1 新幹線(東京→新大阪) 14720 14720 2 ホテル1泊 12800 27520 3 新幹線(新大阪→東京) 14720 42240 for (const item of items) は、配列の中身を1つずつ item に入れて、本体をくり返す

新井AI は items.reduce((a, b) => a + b.amount, 0) って1行で書いてきました。こっちのほうが格好よくないですか?

古井同じことをしている。ただ、いまのお前が「なぜ動くか」を説明できるのは for のほうだろう? 説明できない1行より、説明できる5行だ。reduce は、慣れてからで遅くない。

七

入力を金額に直す

「+」の罠を避けるために、入力欄の文字を金額に直す関数 toAmount を作ります。全角の空白・数字・カンマが混ざった、いかにも日本の現場らしい入力を通してみました。

toAmount の中で起きていること(実行結果)

' 14,720 ' 全角の空白・数字・カンマ入り .normalize('NFKC') → ' 14,720 ' 全角を半角に .replaceAll(',', '') → ' 14720 ' カンマを取る .trim() → '14720' 前後の空白を取る /^[0-9]+$/.test(…) → true 数字だけか確かめる Number(…) → 14720 数(number)に直す 数字だけか確かめるのは、Number('0x10') が 16、Number('1e3') が 1000 になるから

文字列には、.normalize() や .trim() のような便利な道具(メソッド)が付いていて、点でつないで順に使えます。最後の /^[0-9]+$/ は「最初から最後まで数字だけ」を表す決まり文句(正規表現)で、詳しくは第9回で扱います。

もう一つの工夫は、返す値で「状態」を伝えることです。

  • 数(例:14720)── 金額として読めた
  • null ── 空欄だった。0円とは区別する
  • NaN ── 金額として読めなかった(「約1万」「-500」「12.5」など)

受け取った側は、null なら「入力してください」、NaN なら「金額は数字で」と、違うメッセージを出し分けられます(第9回の入力チェックで使います)。

八

app.js の全体

// 経費精算システム:計算の関数(第7回)
// 画面(DOM)にはまだ触らない。「入力 → 計算 → 結果」だけを担当する

const RECEIPT_LIMIT = 30000; // 領収書が必須になる合計金額(円)

/**
 * 入力欄の文字を、金額(円の整数)に変える
 * 空欄なら null、金額として読めなければ NaN を返す
 */
function toAmount(text) {
  const s = String(text)
    .normalize('NFKC')     // 全角の数字(123)を半角(123)に
    .replaceAll(',', '')   // 桁区切りのカンマを取る
    .trim();               // 前後の空白を取る
  if (s === '') return null;
  if (!/^[0-9]+$/.test(s)) return NaN; // 数字だけでできているか
  return Number(s);
}

/** 明細の配列から、合計金額を計算する */
function calcTotal(items) {
  let total = 0;
  for (const item of items) {
    total += item.amount;
  }
  return total;
}

/** 領収書が必須か(合計 3万円以上) */
function needsReceipt(total) {
  return total >= RECEIPT_LIMIT;
}

/** 金額を「42,240 円」の形にする */
function formatYen(amount) {
  return amount.toLocaleString('ja-JP') + ' 円';
}
  • 規程の数字は一か所に ── 3万円は RECEIPT_LIMIT に一度だけ書く。規程が変わっても直すのは1行
  • 「以上」は >= ── 規程が「3万円以上」なら >=、「3万円を超える」なら >。1文字の違いで、ちょうど3万円の申請の扱いが変わる
  • /** … */ の説明 ── 関数の前に、何を受け取り何を返すかを書いておく。エディタが関数にマウスを乗せたときに表示してくれる
  • 表示用の形は最後に ── 計算は数のまま行い、「42,240 円」の文字にするのは画面に出す直前だけ。文字にしてから足すと、また「つながる」
九

手作りのテスト

関数が正しく動くかを、毎回コンソールで手打ちして確かめるのは大変です。そこで、確かめる手順そのものをコードにしておきます。これがテストです。専用の道具もありますが、いにしえ流に、10行ほどの手作りで始めます。

// テスト
let passed = 0, failed = 0;
function check(label, actual, expected) {
  if (Object.is(actual, expected)) {
    passed++;
    console.log('✓ ' + label);
  } else {
    failed++;
    console.error('✗ ' + label + ' → ' + actual + ' (≠ ' + expected + ')');
  }
}

const items = [
  { date: '2026-10-15', item: '新幹線(東京→新大阪)', amount: 14720 },
  { date: '2026-10-15', item: 'ホテル1泊', amount: 12800 },
  { date: '2026-10-16', item: '新幹線(新大阪→東京)', amount: 14720 }
];

check('合計', calcTotal(items), 42240);
check('明細が0行', calcTotal([]), 0);
check('3万円以上', needsReceipt(42240), true);
check('ちょうど3万円', needsReceipt(30000), true);
check('29,999円', needsReceipt(29999), false);
check('カンマ付き', toAmount('14,720'), 14720);
check('全角の数字', toAmount('14720'), 14720);
check('空欄', toAmount(''), null);
check('数字以外', toAmount('約1万'), NaN);
check('マイナス', toAmount('-500'), NaN);
check('小数', toAmount('12.5'), NaN);
check('表示の形', formatYen(42240), '42,240 円');

console.log('テスト: ' + (passed + failed) + ' 件中 ' + passed + ' 件合格');

ブラウザで実行した結果です。

✓ 合計
✓ 明細が0行
✓ 3万円以上
✓ ちょうど3万円
✓ 29,999円
✓ カンマ付き
✓ 全角の数字
✓ 空欄
✓ 数字以外
✓ マイナス
✓ 小数
✓ 表示の形
テスト: 12 件中 12 件合格

テストで大事なのは、境目を試すことです。3万円のルールなら、29,999円・30,000円・それ以上の3つ。「ちょうど」の扱いは、仕様書でもコードでも一番まちがえやすいところです。

POINT

AI にコードを書いてもらったら、テストも一緒に頼み、そのテストを自分で読むのがおすすめです。「29,999円は false、30,000円は true」のように、テストは仕様を言葉より正確に表します。テストが規程とずれていたら、それはコードより先に直すべき「理解のずれ」です。冒頭の 147兆円も、「合計は 42240」というテストが1行あれば、出荷前に捕まえられました。

結 — 一枚まとめと次回予告

  1. JavaScript は script 要素に defer を付けて読み込む。試すときは開発者ツールのコンソール。
  2. 値には種類がある。入力欄の値は必ず文字(string)。typeof で確かめられる。
  3. 変数は まず const、入れ替えるものだけ let。var は使わない。
  4. 「+」は 文字が混ざるとつなげる。入力は足す前に数に直す。空欄・全角・カンマ・NaN・小数に気を付け、お金は整数で。
  5. 計算は 画面から切り離した関数にし、境目を含むテストで確かめる。

新井42,240 円、ちゃんと出ました! ……でも、これってコンソールの中だけですよね。画面の合計の欄は、まだ空っぽです。

古井次回は DOM とイベント。入力したら合計が変わり、ボタンで明細の行が増える。いよいよ画面が動き出すぞ。

参考

ページの写真は、筆者が作ったデモページを2026年10月17日にクラウド上の Chromium(Playwright)で撮影したものです。コンソールの画面は、console.log の出力をページに書き出す手作りの再現で、本物の開発者ツールの画面ではありません。罠の式とテストは Chromium 141 と Node.js 22 で実行し、結果が一致することを確かめました。script の置き場所の結果も同じ Chromium で調べた値です。登場人物は架空の人物です。

いにしえのフロントエンド入門 第7回 — JavaScript ①:基本と合計の関数 / 2026.10

← 記事一覧へ