POS開発 フロントエンド チートコード — JSコア

3. JavaScript 基本文法

変数宣言

const TAX_RATE = 0.1;       // 再代入不可(基本はconstを使う)
let total = 0;               // 再代入可能
// var は使わない(スコープの問題があるため)

データ型・型変換

// ── 型の確認 ──
typeof "hello"     // "string"
typeof 42          // "number"
typeof true        // "boolean"
typeof undefined   // "undefined"
typeof null        // "object"  ← 歴史的バグ、nullチェックには === null を使う
Array.isArray([])  // true

// ── 型変換 ──
Number("123")      // 123
Number("abc")      // NaN
Number("")         // 0   ← 空文字は0になるので注意
parseInt("100円")  // 100  ← 先頭の数値部分だけ変換
parseFloat("3.14")// 3.14
String(500)        // "500"
Boolean(0)         // false  (0, "", null, undefined, NaN → false)
Boolean("text")    // true   (それ以外は全てtrue)

// ── NaNの判定 ──
Number.isNaN(value)  // NaNかどうか(isNaN()よりこちらを推奨)
Number.isFinite(42)  // true(有限の数値かどうか)

比較演算子

// === 厳密等価(型も値も一致)← 基本はこちらを使う
1 === 1        // true
1 === "1"      // false(型が違う)

// == 抽象等価(型変換してから比較)← バグの元なので避ける
1 == "1"       // true
0 == false     // true
null == undefined // true

// 大小比較
5 > 3          // true
5 >= 5         // true

論理演算子

// AND / OR / NOT
true && false    // false
true || false    // true
!true            // false

// 短絡評価(ショートサーキット)
const name = user && user.name;   // userがtruthyならuser.nameを返す
const value = input || "デフォルト"; // inputがfalsyなら右辺を返す

// Null合体演算子 ??(null/undefinedのみ右辺を返す)
const qty = order.quantity ?? 1;
// || との違い: 0 || 1 → 1,  0 ?? 1 → 0(0はnullでもundefinedでもない)

truthy と falsy

// ── falsy な値(if文でfalseとして扱われる)──
// false, 0, ""(空文字), null, undefined, NaN の6つだけ
if (!false)      { } // true
if (!0)          { } // true
if (!"")         { } // true
if (!null)       { } // true
if (!undefined)  { } // true
if (!NaN)        { } // true

// ── truthy な値(上記以外はすべてtrue)──
if ("0")    { } // true(文字列の"0"はtruthy!)
if ([])     { } // true(空配列もtruthy!)
if ({})     { } // true(空オブジェクトもtruthy!)

// ── POS実用例 ──
// 在庫があるかチェック(0はfalsy)
const stock = 0;
if (stock) {
  console.log("在庫あり");   // ← stock=0 だと実行されない
}
// 数値の0を正しく判定するなら明示的に比較する
if (stock !== undefined && stock !== null) {
  console.log(`在庫: ${stock}個`);  // stock=0 でも実行される
}

// 入力値の空チェック(空文字はfalsy)
const input = document.getElementById('barcode').value;
if (!input) {
  console.log("バーコードを入力してください");
}

条件分岐

// if - else if - else
if (price >= 10000) {
  discount = 0.1;
} else if (price >= 5000) {
  discount = 0.05;
} else {
  discount = 0;
}

// 三項演算子
const label = stock > 0 ? "在庫あり" : "在庫切れ";

// switch
switch (paymentMethod) {
  case "cash":   handleCash();  break;
  case "card":   handleCard();  break;
  case "ic":     handleIC();    break;
  default:       handleOther();
}

// オプショナルチェーン ?.
const city = user?.address?.city;       // 途中がnull/undefinedでもエラーにならない
const len  = items?.length ?? 0;        // ?.と??の組み合わせ

ループ

const items = ["りんご", "バナナ", "みかん"];

// for(インデックスが必要な場合)
for (let i = 0; i < items.length; i++) {
  console.log(`${i}: ${items[i]}`);
}

// for...of(配列の要素を順に取得、最も読みやすい)
for (const item of items) {
  console.log(item);
}

// forEach(コールバック関数で処理)
items.forEach((item, index) => {
  console.log(`${index}: ${item}`);
});

// for...in(オブジェクトのキーを列挙)
const prices = { apple: 100, banana: 200 };
for (const key in prices) {
  console.log(`${key}: ${prices[key]}`);
}

// while
let count = 0;
while (count < 5) {
  console.log(count);
  count++;
}

// break / continue
for (const item of items) {
  if (item === "バナナ") continue;  // スキップして次へ
  if (item === "みかん") break;     // ループを終了
}

関数

// 関数宣言(巻き上げ: 宣言前に呼び出し可能)
function calcTax(price, rate = 0.1) {
  return Math.floor(price * rate);
}

// アロー関数(1行なら return 省略可)
const calcTax = (price, rate = 0.1) => Math.floor(price * rate);

// 複数行のアロー関数
const calcTotal = (items) => {
  const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
  const tax = Math.floor(subtotal * 0.1);
  return { subtotal, tax, total: subtotal + tax };
};

// 関数式(変数に関数を代入、巻き上げされない)
const calcDiscount = function(price, rate) {
  return Math.floor(price * rate);
};

// 即時実行関数 (IIFE)
(() => {
  const secret = "外からアクセスできない";
})();

関数スコープ

// var は「関数スコープ」(関数の中でだけ閉じる)
function example() {
  if (true) {
    var x = 10;    // if の外でもアクセスできてしまう
  }
  console.log(x);   // 10 ← ブロックの外に漏れる!
}

// let / const は「ブロックスコープ」({} の中で閉じる)
function example2() {
  if (true) {
    let y = 10;    // if の中だけ有効
    const z = 20;  // if の中だけ有効
  }
  // console.log(y);  // ReferenceError(アクセス不可)
}

// ← だから var は使わず、const / let を使う

コールバック関数

// コールバック = 関数を「引数として」別の関数に渡すパターン

// 基本形: 処理完了後に呼び出す関数を渡す
function processOrder(order, callback) {
  const total = order.price * order.qty;
  callback(total);  // 処理後にコールバックを実行
}

processOrder({ price: 500, qty: 3 }, (total) => {
  console.log(`合計: ${total}円`);  // "合計: 1500円"
});

// よく使われるコールバックの例
// 1. イベントリスナー(第2引数がコールバック)
btn.addEventListener('click', () => { /* この関数がコールバック */ });

// 2. forEach(引数の関数がコールバック)
items.forEach((item) => { /* この関数がコールバック */ });

// 3. setTimeout(第1引数がコールバック)
setTimeout(() => {
  console.log("3秒後に実行");
}, 3000);

テンプレートリテラル

const name = "商品A";
const price = 1500;

// 変数埋め込み
const msg = `${name}の価格は${price}円です`;

// 式の埋め込み
const result = `税込: ¥${Math.floor(price * 1.1).toLocaleString()}`;

// 複数行HTML生成
const html = `
  <div class="item">
    <span class="name">${name}</span>
    <span class="price">¥${price.toLocaleString()}</span>
  </div>
`;

分割代入・スプレッド構文

// ── オブジェクトの分割代入 ──
const product = { name: "コーヒー", price: 350, stock: 20 };
const { name, price } = product;

// リネーム付き(変数名の衝突を避ける)
const { name: productName, price: unitPrice } = product;

// デフォルト値
const { discount = 0 } = product;  // なければ0

// ネストした分割代入
const order = { customer: { name: "田中" } };
const { customer: { name: customerName } } = order;

// ── 配列の分割代入 ──
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// 要素のスキップ(不要な位置はカンマだけ書く)
const [, , third] = ["a", "b", "c"];  // third="c"

// デフォルト値
const [x = 0, y = 0] = [42];  // x=42, y=0

// 関数の戻り値を分割代入(よく使うパターン)
function parsePrice(input) {
  return [parseInt(input), input.includes("税込")];
}
const [amount, taxIncluded] = parsePrice("350円(税込)");

// 値のスワップ
let a = 1, b = 2;
[a, b] = [b, a];  // a=2, b=1

// ── 分割代入の実用例 ──
// 関数パラメータで直接分割代入
function printReceipt({ name, price, qty = 1 }) {
  console.log(`${name} x${qty} = ¥${price * qty}`);
}
printReceipt({ name: "コーヒー", price: 350 });

// APIレスポンスから必要な部分だけ取得
const { data: { items, total }, status } = await fetch(url).then(r => r.json());

// ── スプレッド構文(配列) ──
const newItems = [...items, "新商品"];             // 末尾に追加
const prepend = ["先頭商品", ...items];           // 先頭に追加
const copy    = [...items];                       // 浅いコピー(元を変えずに操作したい時)
const concat  = [...arr1, ...arr2];               // 結合

// 特定要素を除外して新しい配列を作る(イミュータブル削除)
const removed = items.filter((_, i) => i !== 2);  // index2を除外

// 特定要素を置換して新しい配列を作る(イミュータブル更新)
const replaced = items.map((item, i) =>
  i === 2 ? { ...item, price: 400 } : item
);

// ── スプレッド構文(オブジェクト) ──
const updated = { ...product, price: 400 };       // 一部上書き
const merged  = { ...defaults, ...userConfig };  // マージ(後の値が優先)

// ── レスト構文(...を左辺で使う = 残りを集める) ──
const { id, ...others } = { id: 1, name: "A", price: 100 };
// id=1, others={ name:"A", price:100 }(特定キーを除外したい時に便利)

// 関数の可変長引数
function sum(...nums) { return nums.reduce((a, b) => a + b, 0); }
sum(1, 2, 3);  // 6

// 関数の引数に展開
const nums = [3, 1, 4, 1, 5];
Math.max(...nums);  // 5

三項演算子 vs 論理演算 使い分け

// 2つの値から選ぶ → 三項演算子
const label = isActive ? "有効" : "無効";

// デフォルト値の設定 → ?? (null/undefined) か || (全falsy)
const qty = inputValue ?? 1;

// 条件付きで実行 → &&
isLoggedIn && showDashboard();

// 条件付きでプロパティを含める
const payload = {
  name,
  price,
  ...(discount > 0 && { discount }),  // discountが0より大きい場合のみ含める
};

4. 文字列・数値操作

文字列メソッド

const str = "  Hello World  ";

// 検索
str.includes("World")       // true   含まれるか
str.startsWith("  Hello")  // true   先頭一致
str.endsWith("  ")          // true   末尾一致
str.indexOf("World")        // 8      位置(見つからない場合-1)

// 変換
str.trim()                   // "Hello World"  前後の空白除去
str.toUpperCase()             // "  HELLO WORLD  "
str.toLowerCase()             // "  hello world  "

// 切り出し
str.slice(2, 7)              // "Hello"   開始位置〜終了位置(終了は含まない)
str.slice(-7)                // "orld  "  後ろから

// 置換
str.replace("World", "JS")  // "  Hello JS  "  最初の1つ
str.replaceAll(" ", "")      // "HelloWorld"    全て置換

// 分割・結合
"a,b,c".split(",")           // ["a", "b", "c"]
["a", "b", "c"].join("-")    // "a-b-c"

// パディング(桁揃え)
"5".padStart(3, "0")         // "005"  レシート番号等に便利
"Hi".padEnd(10, ".")         // "Hi........"

// 繰り返し
"-".repeat(30)                // "------------------------------"

数値・Math

// 丸め
Math.floor(3.7)       // 3    切り捨て(税計算でよく使う)
Math.ceil(3.2)        // 4    切り上げ
Math.round(3.5)       // 4    四捨五入
Math.trunc(3.9)       // 3    小数部除去

// 最大・最小・絶対値
Math.max(1, 5, 3)      // 5
Math.min(1, 5, 3)      // 1
Math.abs(-42)          // 42

// ランダム
Math.random()           // 0以上1未満のランダムな小数
Math.floor(Math.random() * 100)  // 0〜99のランダムな整数

// 表示用フォーマット
(1500).toLocaleString()           // "1,500"  3桁区切り
(0.1).toFixed(2)                   // "0.10"   小数点以下2桁
(1500).toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })
// "¥1,500"  通貨フォーマット

日付(Date)

// 現在日時の取得
const now = new Date();
now.getFullYear()   // 2026
now.getMonth()      // 0-11(0=1月 ← 注意)
now.getDate()       // 1-31(日)
now.getDay()        // 0-6(0=日曜)
now.getHours()      // 0-23
now.getMinutes()    // 0-59

// 日付の生成
new Date('2026-04-10')          // ISO形式
new Date(2026, 3, 10)           // 年, 月(0始まり), 日

// 表示用フォーマット
now.toLocaleDateString('ja-JP')
// "2026/4/10"

now.toLocaleString('ja-JP')
// "2026/4/10 14:30:00"

now.toLocaleDateString('ja-JP', {
  year: 'numeric', month: '2-digit', day: '2-digit',
  weekday: 'short'
})
// "2026/04/10(金)"

// ISO文字列(APIに送る場合)
now.toISOString()  // "2026-04-10T05:30:00.000Z"

// タイムスタンプ(ミリ秒)
Date.now()          // 現在のタイムスタンプ
now.getTime()       // Dateオブジェクトからタイムスタンプ

// 日付計算(ミリ秒単位で加減算)
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);

// setDateを使った加算
const nextWeek = new Date(now);
nextWeek.setDate(nextWeek.getDate() + 7);

正規表現

// 基本パターン
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
emailRegex.test("user@example.com")  // true

// 電話番号チェック
const phoneRegex = /^0\d{1,4}-?\d{1,4}-?\d{4}$/;
phoneRegex.test("090-1234-5678")   // true

// 半角数字のみ
/^\d+$/.test("12345")               // true

// matchで取り出し
const str = "注文番号: ORD-12345";
const match = str.match(/ORD-(\d+)/);
match[1]  // "12345"

// replaceで変換
"2026-04-10".replace(/(\d{4})-(\d{2})-(\d{2})/, "$1年$2月$3日")
// "2026年04月10日"

文字列の検索で条件分岐

// 商品名に特定の文字列が含まれるか判定
const productName = "有機野菜サラダ";
if (productName.includes("有機")) {
  console.log("オーガニック商品ラベルを表示");
}

// 検索キーワードで商品をフィルタリング
const keyword = searchInput.value;
const filtered = products.filter(p =>
  p.name.includes(keyword)
);

// 大文字・小文字を区別しない検索
const query = "coffee";
const found = products.filter(p =>
  p.name.toLowerCase().includes(query.toLowerCase())
);

// バーコードの先頭で分類判定
const barcode = "4901234567890";
if (barcode.startsWith("49")) {
  console.log("日本の商品(GS1 Japan)");
}

// 複数キーワードのいずれかを含むか
const note = "冷蔵・要冷蔵";
const coldKeywords = ["冷蔵", "冷凍", "チルド"];
if (coldKeywords.some(kw => note.includes(kw))) {
  console.log("要冷蔵商品");
}

6. 配列・オブジェクト

配列メソッド

const products = [
  { name: "コーヒー", price: 350, category: "drink" },
  { name: "サンド",   price: 500, category: "food" },
  { name: "紅茶",     price: 300, category: "drink" },
  { name: "ケーキ",   price: 450, category: "food" },
];

// map: 変換して新しい配列を返す
const names = products.map(p => p.name);
// ["コーヒー", "サンド", "紅茶", "ケーキ"]

// filter: 条件に合う要素だけ抽出
const drinks = products.filter(p => p.category === "drink");

// find: 条件に合う最初の1つを返す(見つからなければundefined)
const coffee = products.find(p => p.name === "コーヒー");

// findIndex: 条件に合う最初のインデックス(見つからなければ-1)
const idx = products.findIndex(p => p.name === "紅茶");  // 2

// reduce: 集約(合計計算など)
const total = products.reduce((sum, p) => sum + p.price, 0);  // 1600

// sort: 並べ替え(※元の配列を変更する → スプレッドでコピー推奨)
const sorted = [...products].sort((a, b) => a.price - b.price);  // 安い順
const desc   = [...products].sort((a, b) => b.price - a.price);  // 高い順

// some: 1つでも条件を満たすか
products.some(p => p.price >= 500);  // true

// every: 全て条件を満たすか
products.every(p => p.price < 1000);  // true

// includes: 含まれるか(プリミティブ配列向け)
["cash", "card"].includes("cash");  // true

// indexOf: 要素のインデックスを返す(見つからなければ-1)
["cash", "card", "ic"].indexOf("card");  // 1
["cash", "card", "ic"].indexOf("qr");    // -1

// join: 配列を文字列に結合
["コーヒー", "サンド", "紅茶"].join("、");  // "コーヒー、サンド、紅茶"
["2024", "01", "15"].join("-");             // "2024-01-15"

配列の追加・削除・変換

const arr = [1, 2, 3];

// 追加・削除(元の配列を変更する)
arr.push(4);        // 末尾に追加   → [1,2,3,4]
arr.pop();          // 末尾を削除   → [1,2,3]  ※削除した値を返す
arr.unshift(0);     // 先頭に追加   → [0,1,2,3]
arr.shift();        // 先頭を削除   → [1,2,3]  ※削除した値を返す

// ── splice: 指定位置で削除/挿入(元の配列を変更する=破壊的) ──
// splice(開始index, 削除する個数, ...挿入する要素)
const cart = ["コーヒー", "サンド", "紅茶", "ケーキ"];

cart.splice(1, 1);
// cart → ["コーヒー", "紅茶", "ケーキ"]  ※削除された ["サンド"] を返す

cart.splice(1, 0, "ジュース");
// cart → ["コーヒー", "ジュース", "紅茶", "ケーキ"]  ※削除0個=挿入のみ

cart.splice(2, 1, "抹茶", "水");
// cart → ["コーヒー", "ジュース", "抹茶", "水", "ケーキ"]  ※削除+挿入=置換

cart.splice(-1, 1);
// cart → ["コーヒー", "ジュース", "抹茶", "水"]  ※負のindexは末尾から

// ── slice: 一部を切り出して新しい配列を返す(非破壊) ──
// slice(開始index, 終了index)  ※終了indexの要素は含まない
const items = ["A", "B", "C", "D", "E"];

items.slice(1, 3);    // ["B", "C"]       index1〜2
items.slice(2);       // ["C", "D", "E"]  index2から最後まで
items.slice(-2);      // ["D", "E"]       末尾から2つ
items.slice();        // ["A","B","C","D","E"]  全体のコピー

// POS実用例: ページネーション
const page = 2, perPage = 10;
const pageItems = allItems.slice((page - 1) * perPage, page * perPage);

// ── splice vs slice 早見表 ──
//  splice → 元の配列を変更する(破壊的)、削除した要素を返す
//  slice  → 元の配列を変更しない(非破壊)、切り出した新配列を返す

// ── 元の配列を変更しない操作(イミュータブル) ──
const newArr = [...arr, 4];                       // 末尾に追加
const without = arr.filter((_, i) => i !== 1);   // index1を除外
const sliced = arr.slice(0, 2);                  // index0〜1を抽出

// flat: ネスト配列を平坦化
[[1,2], [3,4]].flat()    // [1,2,3,4]

// flatMap: mapしてからflatする
orders.flatMap(o => o.items)  // 全注文の商品を1つの配列に

// 重複排除
const unique = [...new Set([1,2,2,3])];  // [1,2,3]

メソッドチェーン

// filter → map → sort を連結
const result = products
  .filter(p => p.category === "drink")     // 飲料だけ
  .map(p => ({ ...p, taxIncluded: Math.floor(p.price * 1.1) }))  // 税込価格追加
  .sort((a, b) => a.price - b.price);     // 安い順

// reduce でグループ化
const grouped = products.reduce((acc, p) => {
  (acc[p.category] ??= []).push(p);
  return acc;
}, {});
// { drink: [...], food: [...] }

オブジェクト操作

const product = { name: "コーヒー", price: 350, stock: 20 };

// キー・値・エントリの取得
Object.keys(product);     // ["name", "price", "stock"]
Object.values(product);   // ["コーヒー", 350, 20]
Object.entries(product);  // [["name","コーヒー"], ["price",350], ...]

// エントリからオブジェクトに変換
Object.fromEntries([
  ["name", "コーヒー"],
  ["price", 350]
]);  // { name: "コーヒー", price: 350 }

// プロパティの存在チェック
"name" in product;    // true

// 動的キー
const key = "price";
product[key];  // 350

// 動的キーでオブジェクト生成
const field = "category";
const obj = { [field]: "drink" };  // { category: "drink" }

JSON操作

// オブジェクト → JSON文字列
const json = JSON.stringify(product);
// '{"name":"コーヒー","price":350,"stock":20}'

// 整形出力(デバッグ用)
console.log(JSON.stringify(product, null, 2));

// JSON文字列 → オブジェクト
const obj = JSON.parse(json);

// ディープコピー(ネストしたオブジェクトのコピー)
const copy = JSON.parse(JSON.stringify(original));
// ※Date, function, undefinedは失われる。より正確にはstructuredClone()を使う:
const copy = structuredClone(original);

Map / Set

// ── Map: キーが何でも使えるハッシュマップ ──
const cart = new Map();
cart.set("product_101", { name: "コーヒー", qty: 2 });
cart.get("product_101");   // { name: "コーヒー", qty: 2 }
cart.has("product_101");   // true
cart.delete("product_101");
cart.size;                  // 要素数

for (const [key, value] of cart) {
  console.log(key, value);
}

// ── Set: 重複のないコレクション ──
const categories = new Set(["food", "drink", "food"]);
// Set(2) { "food", "drink" }
categories.add("snack");
categories.has("food");    // true
categories.delete("food");
[...categories];             // 配列に変換

10. クラス構文とOOP 中級

Class基本(constructor / メソッド / getter・setter)

class CartItem {
  constructor(name, price, quantity = 1) {
    this.name = name;
    this.price = price;
    this.quantity = quantity;
  }

  // メソッド
  subtotal() {
    return this.price * this.quantity;
  }

  // getter — プロパティのようにアクセスできる
  get taxIncluded() {
    return Math.floor(this.subtotal() * 1.1);
  }

  // setter — 代入で呼ばれる
  set qty(value) {
    if (value < 1) throw new Error('数量は1以上');
    this.quantity = value;
  }
}

const item = new CartItem('コーヒー', 350, 2);
console.log(item.subtotal());    // 700
console.log(item.taxIncluded);    // 770
item.qty = 3;                    // setter経由

継承(extends / super)

class DiscountedItem extends CartItem {
  constructor(name, price, quantity, discountRate) {
    super(name, price, quantity);  // 親のconstructorを呼ぶ
    this.discountRate = discountRate;
  }

  // メソッドのオーバーライド
  subtotal() {
    const base = super.subtotal();  // 親のメソッドを呼ぶ
    return Math.floor(base * (1 - this.discountRate));
  }
}

const sale = new DiscountedItem('弁当', 500, 1, 0.2);
console.log(sale.subtotal());     // 400(20%引き)
console.log(sale instanceof CartItem);  // true

プライベートフィールド(#)

class PaymentProcessor {
  // # で始まるフィールドはクラス外からアクセス不可
  #apiKey;
  #endpoint;

  constructor(apiKey, endpoint) {
    this.#apiKey = apiKey;
    this.#endpoint = endpoint;
  }

  async charge(amount) {
    const res = await fetch(this.#endpoint, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.#apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ amount }),
    });
    return res.json();
  }
}

const payment = new PaymentProcessor('sk_xxx', '/api/pay');
// payment.#apiKey → SyntaxError(外部からアクセス不可)

静的メソッド(static)

class CartItem {
  constructor(name, price, quantity) {
    this.name = name;
    this.price = price;
    this.quantity = quantity;
  }

  // インスタンスを作らずに呼べるファクトリメソッド
  static fromBarcode(barcode, catalog) {
    const product = catalog.find(p => p.barcode === barcode);
    if (!product) throw new Error(`商品が見つかりません: ${barcode}`);
    return new CartItem(product.name, product.price, 1);
  }

  // 複数アイテムの合計を計算するユーティリティ
  static totalOf(items) {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }
}

// 使い方
const item = CartItem.fromBarcode('4901234567890', catalog);
const total = CartItem.totalOf(cartItems);