Skip to content

JavaScript チートシート

Web の言語 — フロントエンド、バックエンド、そしてその先へ。

01

はじめに

Hello World とコメント

JavaScript はブラウザと Node.js で実行されます。console.log() は主要なデバッグ出力です。JSDoc コメント(/** */)は IDE の型情報とドキュメントを提供します。より安全なパースのために 'use strict' または ES モジュールを使用してください。コメントは実行時に無視されます。

javascript
// Single-line comment
/* Multi-line
   comment */

// Console output
console.log("Hello, World!");
console.warn("Warning message");
console.error("Error message");
console.table([{a: 1}, {a: 2}]);

// JSDoc comments (for documentation)
/**
 * Adds two numbers
 * @param {number} a - First number
 * @param {number} b - Second number
 * @returns {number} The sum
 */
function add(a, b) { return a + b; }

Strict モードとモジュール

'use strict' はより厳密なパースを有効にし、サイレントエラーをキャッチします。ES モジュール(import/export)がモダンな標準です。CommonJS(require/module.exports)は Node.js の伝統的な方法です。グローバルスコープの汚染を避けるため常にモジュールを使用してください。ブラウザは <script type='module'> をサポートしています。

javascript
"use strict";  // enables strict mode (catches common mistakes)
x = 10;  // ReferenceError: x is not defined (strict mode)

// ES Modules (modern)
// import { greet } from './utils.js';
// export function greet(name) { return `Hello, ${name}`; }

// CommonJS (Node.js traditional)
// const fs = require('fs');
// module.exports = { greet: (name) => `Hello, ${name}` };

// Running JS
// Browser: include in <script> or via dev server
// Node.js: node script.js
// Deno: deno run script.ts

変数:let、const、var

常に const を優先してください。再代入が必要な場合のみ let を使用し、var は完全に避けてください。const は再代入を防ぎますが、変更は防ぎません — オブジェクト/配列の内容は変更できます。let/const はブロックスコープ、var は関数スコープで巻き上げられます(バグの原因)。TDZ は宣言前の変数使用を防ぎます。

javascript
// const - cannot be reassigned (use by default)
const PI = 3.14159;
const user = { name: "Alice" };
user.name = "Bob";  // OK - object contents can change
// user = {};  // TypeError - can't reassign const

// let - block-scoped, can be reassigned
let count = 0;
count = 1;
if (true) {
  let local = 5;  // only accessible in this block
}

// var - function-scoped, hoisted (AVOID in modern code)
var old = "legacy";  // hoisted, can cause bugs

// Temporal Dead Zone (TDZ)
// console.log(x);  // ReferenceError
// let x = 5;

データ型と typeof

JavaScript には 7 つのプリミティブ型(string、number、bigint、boolean、undefined、null、symbol)と参照型(オブジェクト、配列、関数)があります。プリミティブは不変で値でコピーされます。オブジェクトは変更可能で参照渡しされます。typeof null は歴史的バグにより 'object' を返します — null のチェックには === null を使用してください。

javascript
// Primitive types (immutable, passed by value)
const str = "hello";        // string
const num = 42;             // number (no separate int/float)
const big = 9007199254740993n;  // bigint
const bool = true;          // boolean
const undef = undefined;    // undefined (no value)
const empty = null;         // null (intentional empty)
const sym = Symbol("id");   // symbol (unique identifier)

// Reference types (mutable, passed by reference)
const arr = [1, 2, 3];      // object (array)
const obj = { a: 1 };       // object
const fn = function() {};   // object (function)

// typeof operator
console.log(typeof "hi");      // "string"
console.log(typeof 42);        // "number"
console.log(typeof true);      // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null);      // "object" (historical bug!)
console.log(typeof []);        // "object"
console.log(typeof {});        // "object"
console.log(typeof function(){}); // "function"

型変換と強制変換

JavaScript の型強制変換は非常に紛らわしいことで有名です。予期しない強制変換を避けるため、常に ==(緩い)の代わりに ===(厳密等価)を使用してください。+ 演算子はオペランドのいずれかが文字列の場合に連結し、他の演算子は数値に強制変換します。偽値:false、0、''、null、undefined、NaN。NaN のチェックには Number.isNaN() を使用してください(NaN !== NaN)。

javascript
// Explicit conversion
String(42);        // "42"
(42).toString();   // "42"
Number("42");      // 42
Number("3.14");    // 3.14
Number("");        // 0
Number("abc");     // NaN
parseInt("42px");  // 42
parseFloat("3.14abc");  // 3.14
Boolean(0);        // false
Boolean("");       // false
Boolean("x");      // true

// Implicit coercion (often confusing)
console.log("5" + 3);   // "53" (string concatenation)
console.log("5" - 3);   // 2 (numeric subtraction)
console.log("5" * "2"); // 10
console.log(1 + "2" + 3); // "123"
console.log(true + 1);  // 2

// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("0") console.log("truthy");  // runs! non-empty string

// Strict vs loose equality
console.log(1 == "1");   // true (loose, coerces)
console.log(1 === "1");  // false (strict, no coercion)
console.log(null == undefined);  // true
console.log(null === undefined); // false
02

文字列

文字列メソッド

文字列は不変です — メソッドは新しい文字列を返します。slice() は負のインデックスをサポートしますが、substring() はサポートしません。replace() は最初のマッチのみ置換します。すべて置換するには replaceAll()(ES2021)を使用してください。at()(ES2022)は負のインデックスを許可します。split() + join() は文字列内の文字を置換する慣用的な方法です。

javascript
const s = "Hello, World";

// Length & access
console.log(s.length);     // 12
console.log(s[0]);         // "H"
console.log(s.charAt(0));  // "H"
console.log(s.at(-1));     // "d" (ES2022, negative index)

// Case
console.log(s.toUpperCase());  // "HELLO, WORLD"
console.log(s.toLowerCase());  // "hello, world"

// Search
console.log(s.indexOf("World"));   // 7 (-1 if not found)
console.log(s.includes("World"));  // true
console.log(s.startsWith("Hello")); // true
console.log(s.endsWith("World"));   // true

// Extract
console.log(s.slice(0, 5));    // "Hello"
console.log(s.slice(-5));      // "World"
console.log(s.substring(0, 5)); // "Hello" (no negative)
console.log(s.split(", "));    // ["Hello", "World"]

// Modify
console.log(s.replace("o", "0"));   // "Hell0, World"
console.log(s.replaceAll("o", "0")); // "Hell0, W0rld"
console.log(s.trim());   // remove whitespace
console.log(s.padStart(15, "*")); // "***Hello, World"
console.log("a,b,c".split(",").join("-")); // "a-b-c"

テンプレートリテラル

テンプレートリテラル(バッククォート)は ${} による文字列補間、複数行文字列、タグ付きテンプレートを可能にします。文字列連結よりはるかに読みやすいです。タグ付きテンプレートは関数でテンプレートリテラルを処理できます — styled-components、graphql-tag などで使用されています。

javascript
const name = "Alice";
const age = 30;

// Template literals (backticks)
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);

// Multi-line strings
const html = `
  <div>
    <h1>${name}</h1>
    <p>Age: ${age}</p>
  </div>
`;

// Expressions inside ${}
console.log(`Next year: ${age + 1}`);
console.log(`Upper: ${name.toUpperCase()}`);
console.log(`${name.length} chars`);

// Tagged templates
function highlight(strings, ...values) {
  return strings.reduce((acc, str, i) =>
    acc + str + (values[i] ? `<b>${values[i]}</b>` : ''), '');
}
const result = highlight`Name: ${name}, Age: ${age}`;
// "Name: <b>Alice</b>, Age: <b>30</b>"

文字列検索と正規表現

JavaScript 文字列は match()、matchAll()、replace()、search()、split() で正規表現をサポートします。名前付きキャプチャグループ (?<name>...)(ES2018)は正規表現を読みやすくします。matchAll() はイテレータを返します(グローバル正規表現の match() より効率的)。パターンがマッチするかチェックするには .test() を使用してください。

javascript
const text = "The quick brown fox jumps over the lazy dog";

// Search methods
console.log(text.search(/brown/));  // 10 (index of match)
console.log(text.match(/\w+/g));   // ["The","quick","brown",...]
console.log(text.matchAll(/\w+/g)); // iterator of matches

// Replace with regex
console.log(text.replace(/o/g, "0"));  // replace all 'o'
console.log(text.replace(/(\w+)/g, "\$1!"));  // capture group

// Capture groups
const date = "2024-01-15";
const [, year, month, day] = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year, month, day);  // 2024 01 15

// Named capture groups (ES2018)
const m = date.match(/(?<year>\d{4})-(?<month>\d{2})/);
console.log(m.groups.year);   // 2024
console.log(m.groups.month);  // 01

// Test if pattern matches
const emailRe = /^[^@]+@[^@]+\.[^@]+$/;
console.log(emailRe.test("[email protected]"));  // true

文字列反復と Spread

文字列は for...of で反復可能です。Spread 演算子 [...] は文字列を文字に分割します — 絵文字の正しい処理に不可欠です。文字列比較は UTF-16 コード単位で辞書式に行われるため、ロケール対応のソートには localeCompare() を使用してください。絵文字や一部の文字は 2 コード単位の長さです。

javascript
const s = "Hello";

// Iterate characters
for (const char of s) {
  console.log(char);  // H, e, l, l, o
}

// Spread into array
const chars = [...s];  // ["H", "e", "l", "l", "o"]
console.log(chars);

// Spread with map/filter
const upper = [...s].map(c => c.toUpperCase()).join("");
console.log(upper);  // HELLO

// String comparison
console.log("a" < "b");   // true (lexicographic)
console.log("apple" < "banana");  // true
console.log("Z" < "a");   // true (uppercase < lowercase in ASCII)

// Locale-aware comparison
console.log("ö".localeCompare("o", "de"));  // locale-specific

// Repeat
console.log("ab".repeat(3));  // "ababab"

// Code points (handles emoji correctly)
const emoji = "😀";
console.log(emoji.length);        // 2 (surrogate pair!)
console.log([...emoji].length);   // 1 (correct)
console.log("😀".codePointAt(0)); // 128512
03

数値と数学

数値と演算子

JavaScript には単一の数値型(64ビット浮動小数点)しかありません — int/float の区別はありません。BigInt(n サフィックス)は 2^53 を超える整数を扱います。浮動小数点演算には精度問題があります(0.1 + 0.2 !== 0.3)— 比較には Number.EPSILON を使用してください。** はべき乗演算子(ES2016)です。

javascript
// JavaScript has one number type (IEEE 754 double)
const int = 42;
const float = 3.14;
const exp = 1e6;      // 1000000
const hex = 0xff;     // 255
const bin = 0b1010;   // 10
const oct = 0o755;    // 493
const big = 9007199254740993n;  // BigInt (arbitrary precision)

// Arithmetic
console.log(10 / 3);    // 3.333...
console.log(10 % 3);    // 1 (remainder)
console.log(2 ** 10);   // 1024 (exponent)
console.log(Math.floor(10 / 3));  // 3
console.log(Math.trunc(-3.7));    // -3 (toward zero)

// Increment/decrement
let x = 5;
console.log(x++);  // 5 (post-increment, returns old)
console.log(++x);  // 7 (pre-increment, returns new)

// Floating point issues
console.log(0.1 + 0.2);          // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);  // false!
// Fix: use Number.EPSILON or round
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON);  // true

Math オブジェクト

Math オブジェクトは定数と関数を提供します。すべての三角関数はラジアンを使用します。Math.random() は [0, 1) を返します — 整数範囲には乗算して floor してください。暗号論的な乱数には crypto.getRandomValues() を使用してください。Math.max/min は配列を直接受け入れません — ... で展開してください。

javascript
// Constants
console.log(Math.PI);       // 3.141592653589793
console.log(Math.E);        // 2.718281828459045
console.log(Math.SQRT2);    // 1.4142135623730951

// Rounding
console.log(Math.round(3.7));   // 4 (nearest)
console.log(Math.floor(3.7));   // 3 (down)
console.log(Math.ceil(3.2));    // 4 (up)
console.log(Math.trunc(-3.7));  // -3 (toward zero)
console.log(Math.sign(-5));     // -1 (sign: -1, 0, or 1)

// Power & roots
console.log(Math.pow(2, 10));   // 1024
console.log(Math.sqrt(144));    // 12
console.log(Math.cbrt(27));     // 3
console.log(Math.abs(-5));      // 5

// Min/Max
console.log(Math.max(1, 5, 3));       // 5
console.log(Math.min(1, 5, 3));       // 1
console.log(Math.max(...[1, 5, 3]));  // 5 (spread array)

// Trigonometry (radians)
console.log(Math.sin(Math.PI / 2));   // 1
console.log(Math.cos(0));             // 1
console.log(Math.PI / 180 * 90);      // radians from degrees

// Random
console.log(Math.random());           // 0 to <1
console.log(Math.floor(Math.random() * 100));  // 0-99 integer

数値メソッドと解析

parseInt() には常に基数を指定してください — 古いブラウザは先頭のゼロを8進数として解釈します。Number.isNaN() は信頼できます。グローバル isNaN() は強制変換します(isNaN('abc') は true)。toFixed() は数値ではなく文字列を返します。MAX_SAFE_INTEGER を超える数値は精度を失います — BigInt を使用してください。

javascript
// Number methods
const num = 1234.5678;
console.log(num.toFixed(2));      // "1234.57" (string)
console.log(num.toPrecision(3));  // "1.23e+3"
console.log(num.toString(2));     // binary string
console.log((255).toString(16));  // "ff" (hex)

// Number object methods
console.log(Number.isInteger(42));      // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(NaN));         // true (reliable)
console.log(Number.isNaN("NaN"));       // false (global isNaN would be true)
console.log(Number.parseInt("42px"));   // 42
console.log(Number.parseFloat("3.14")); // 3.14

// Parsing strings
console.log(parseInt("42", 10));   // 42 (always specify radix!)
console.log(parseInt("0xff", 16)); // 255
console.log(parseFloat("3.14abc")); // 3.14

// Safe integers
console.log(Number.MAX_SAFE_INTEGER);  // 9007199254740991
console.log(Number.isSafeInteger(2 ** 53));  // false

// NaN checks
const result = Number("abc");  // NaN
console.log(Number.isNaN(result));  // true
console.log(result === NaN);        // false! NaN !== NaN
04

データ構造

配列

配列は動的、順序付き、異種混合型を保持できます。push/pop は O(1)、shift/unshift/splice は O(n) です。find()/findIndex() は述語関数を受け取ります。forEach() は何も返しません。変換には map() を使用してください。配列はオブジェクトです — typeof [] は 'object' です。チェックには Array.isArray() を使用してください。

javascript
// Creating arrays
const nums = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null];
const empty = new Array(5);  // [empty x 5]

// Access & modify
console.log(nums[0]);      // 1
console.log(nums.length);  // 5
nums[0] = 0;               // modify
console.log(nums.at(-1));  // 5 (negative index, ES2022)

// Add/remove
nums.push(6);          // add to end, returns new length
nums.pop();            // remove from end, returns element
nums.unshift(0);       // add to start
nums.shift();          // remove from start
nums.splice(1, 2);     // remove 2 elements at index 1
nums.splice(1, 0, "a"); // insert at index 1

// Search
console.log(nums.indexOf(3));     // index or -1
console.log(nums.includes(3));    // true/false
console.log(nums.find(n => n > 3));    // first match
console.log(nums.findIndex(n => n > 3)); // index of first match

// Iterate
nums.forEach((val, idx) => console.log(idx, val));
for (const [idx, val] of nums.entries()) {
  console.log(idx, val);
}

配列メソッド(map、filter、reduce)

map/filter/reduce は関数型配列プログラミングの三位一体です。map は変換、filter は選択、reduce は集約を行います。チェーン可能で、元の配列を変更しません(reverse/sort を除く)。sort() はデフォルトで文字列に変換します — 数値には常に比較関数を指定してください。flat() はネストされた配列を平坦化します。

javascript
const nums = [1, 2, 3, 4, 5];

// map - transform each element (returns new array)
const doubled = nums.map(n => n * 2);       // [2, 4, 6, 8, 10]
const withIndex = nums.map((n, i) => `${i}:${n}`);

// filter - keep elements that pass test (returns new array)
const evens = nums.filter(n => n % 2 === 0); // [2, 4]

// reduce - accumulate to single value
const sum = nums.reduce((acc, n) => acc + n, 0);  // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120
const max = nums.reduce((a, b) => Math.max(a, b));

// Chaining
const result = nums
  .filter(n => n % 2 === 0)   // [2, 4]
  .map(n => n * 10)           // [20, 40]
  .reduce((sum, n) => sum + n, 0); // 60

// Other useful methods
console.log(nums.slice(1, 3));   // [2, 3] (copy portion)
console.log(nums.concat([6, 7])); // [1,2,3,4,5,6,7]
console.log([...nums, 6, 7]);     // spread (modern)
console.log(nums.reverse());      // reverse in-place
console.log(nums.sort((a, b) => a - b)); // numeric sort
console.log([3, 1, 2].sort());   // [1, 2, 3] (string sort!)
console.log(nums.flat());        // flatten one level
console.log([1, [2, [3]]].flat(Infinity)); // [1, 2, 3]

オブジェクト

オブジェクトはキーと値のコレクションです(キーは文字列またはシンボル)。静的キーにはドット記法、動的/特殊キーにはブラケット記法を使用します。計算プロパティ名 {[expr]: val} は ES6 です。Object.keys/values/entries は配列を抽出し、Object.fromEntries はエントリを逆変換します。for...in はキーを反復します(継承されたものを含む)。

javascript
// Object literal
const user = {
  name: "Alice",
  age: 30,
  "is-admin": false,  // keys with special chars need quotes
  greet() {           // method shorthand
    return `Hello, I'm ${this.name}`;
  }
};

// Access
console.log(user.name);       // dot notation
console.log(user["is-admin"]); // bracket notation (for dynamic/special keys)

// Computed property names (ES6)
const key = "dynamic";
const obj = { [key]: "value", [`id_${1}`]: 100 };

// Add/modify/delete
user.email = "[email protected]";  // add
user.age = 31;            // modify
delete user["is-admin"];  // delete

// Check property existence
console.log("name" in user);          // true
console.log(user.hasOwnProperty("name")); // true

// Iterate
for (const key in user) {
  console.log(key, user[key]);
}

// Object methods
console.log(Object.keys(user));    // ["name", "age", ...]
console.log(Object.values(user));  // ["Alice", 31, ...]
console.log(Object.entries(user)); // [["name","Alice"], ...]
console.log(Object.fromEntries([["a", 1]])); // {a: 1}

分割代入と Spread

分割代入はオブジェクト/配列から値を簡潔に抽出します。リネーム(key: newName)、デフォルト(= value)、rest(...rest)をサポートします。Spread(...)はイテラブル/オブジェクトを展開します — マージとシャローコピーに便利です。オブジェクトの Spread は重複キーを上書きします(後勝ち)。関数パラメータの分割代入はオプション設定に強力です。

javascript
// Object destructuring
const user = { name: "Alice", age: 30, email: "[email protected]" };
const { name, age } = user;          // extract by key
const { name: fullName, email = "N/A" } = user; // rename + default
const { ...rest } = user;            // rest pattern
// name -> undefined, fullName -> "Alice", rest -> {age, email}

// Array destructuring
const [a, b, c] = [1, 2, 3];
const [first, , third] = [1, 2, 3];  // skip elements
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2,3,4]

// Swap variables
let x = 1, y = 2;
[x, y] = [y, x];  // x=2, y=1

// Nested destructuring
const { data: { users } } = response;
const [[a, b], [c, d]] = [[1, 2], [3, 4]];

// Function parameters
function greet({ name, greeting = "Hello" }) {
  return `${greeting}, ${name}`;
}
greet({ name: "Alice" });  // "Hello, Alice"

// Spread operator
const arr1 = [1, 2], arr2 = [3, 4];
const merged = [...arr1, ...arr2];  // [1, 2, 3, 4]
const obj1 = { a: 1 }, obj2 = { b: 2 };
const combined = { ...obj1, ...obj2 };  // {a:1, b:2}
const clone = { ...user };  // shallow copy

Map、Set、WeakMap

Map は任意のキー型を許可し、挿入順序を維持します。Set は一意の値を格納します — 重複排除に最適です。WeakMap/WeakSet のキーは弱参照され(ガベージコレクション可能)、メモリリークを防ぎます。非文字列キーや頻繁な追加/削除が必要な場合は Map を使用してください。

javascript
// Map - key-value pairs, any key type, maintains insertion order
const map = new Map();
map.set("name", "Alice");
map.set(42, "number key");
map.set({ obj: true }, "object key");

console.log(map.get("name"));    // "Alice"
console.log(map.has("name"));    // true
console.log(map.size);           // 3
map.delete(42);
map.clear();

// Iterate Map
for (const [key, value] of map) {
  console.log(key, value);
}

// Set - unique values
const set = new Set([1, 2, 3, 2, 1]);
console.log(set.size);  // 3 (duplicates removed)
set.add(4);
set.has(2);  // true
set.delete(1);

// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]);        // {1,2,3,4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2,3}
const difference = new Set([...a].filter(x => !b.has(x)));  // {1}

// WeakMap/WeakSet - keys must be objects, GC-friendly
const weakMap = new WeakMap();
weakMap.set({}, "value");  // key can be garbage collected
05

制御フロー

If / Else と三項演算子

複雑なロジックには if/else を、単純な値の選択には三項演算子を使用してください。&& と || は短絡評価します(デフォルト値/条件付きに便利)。??(null 合体)は || とは異なり null/undefined のみをチェックします。?.(オプショナルチェーン)はエラーなしでネストされたプロパティに安全にアクセスします。

javascript
const score = 85;

// if / else if / else
if (score >= 90) {
  console.log("A");
} else if (score >= 80) {
  console.log("B");
} else {
  console.log("C");
}

// Ternary operator (expression)
const grade = score >= 60 ? "pass" : "fail";

// Nested ternary (avoid - hard to read)
const status = score >= 90 ? "excellent"
             : score >= 80 ? "good"
             : score >= 60 ? "pass"
             : "fail";

// Short-circuit evaluation
const name = user?.name || "Anonymous";  // default value
const value = condition && doSomething(); // execute if true

// Nullish coalescing (??) - only null/undefined, not falsy
const count = 0 ?? 10;  // 0 (not 10, because 0 is not null/undefined)
const name2 = null ?? "default";  // "default"

// Optional chaining (?.)
const city = user?.address?.city;  // undefined if any link is null
const length = user?.name?.length;  // undefined if user or name is null

Switch 文

switch は厳密等価(===)で比較するため、型が重要です。break を忘れないでください — ないと次の case にフォールスルーします。case を積み重ねてグループ化します(case 6: case 7:)。離散値には if/else の長いチェーンより switch がクリーンです。モダンなコードではオブジェクトルックアップテーブルを好む場合もあります。

javascript
const day = 3;

// Traditional switch (use break!)
switch (day) {
  case 1:
    console.log("Monday");
    break;  // without break, falls through!
  case 2:
    console.log("Tuesday");
    break;
  case 6:
  case 7:  // multiple cases share code
    console.log("Weekend");
    break;
  default:
    console.log("Weekday");
}

// Switch with return (no break needed)
function getColor(type) {
  switch (type) {
    case "success": return "green";
    case "error": return "red";
    case "warning": return "yellow";
    default: return "gray";
  }
}

// Strict equality (===)
switch (1) {
  case "1": console.log("string");  // NOT matched
  case 1: console.log("number");    // matched
}

ループ

配列/文字列には for...of(値)、オブジェクトには for...in(キー)を使用します — 配列に for...in は使わないでください。while は実行前にチェックし、do...while は少なくとも一度実行します。break は終了、continue はスキップします。for...of でインデックス+値を取得するには .entries() を使用してください。

javascript
// for loop
for (let i = 0; i < 5; i++) {
  console.log(i);
}

// for...of (iterable values - arrays, strings, maps)
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
  console.log(fruit);
}

// for...in (object keys - AVOID for arrays!)
const user = { name: "Alice", age: 30 };
for (const key in user) {
  console.log(key, user[key]);
}

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

// do...while (runs at least once)
let i = 0;
do {
  console.log(i);
  i++;
} while (i < 3);

// break & continue
for (let i = 0; i < 10; i++) {
  if (i === 5) break;      // exit loop
  if (i % 2 === 0) continue; // skip iteration
  console.log(i);
}

// Iterate with index
for (const [index, value] of fruits.entries()) {
  console.log(index, value);
}

イテレータとジェネレータ

イテレータは {value, done} を返す next() を実装します。ジェネレータ(function*)は yield でイテレータ作成を簡素化します — 実行を一時停止し、next() で再開します。ジェネレータは遅延評価で、無限にできます。カスタムイテラブル、シーケンス、非同期フローに使用してください。

javascript
// Iterable protocol (Symbol.iterator)
const range = {
  from: 1, to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { done: true };
      }
    };
  }
};

for (const num of range) console.log(num);  // 1, 2, 3, 4, 5
console.log([...range]);  // [1, 2, 3, 4, 5]

// Generator function (function*)
function* idGenerator() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const gen = idGenerator();
console.log(gen.next());  // {value: 1, done: false}
console.log(gen.next());  // {value: 2, done: false}
console.log(gen.next().value);  // 3

// Generator with yield*
function* nested() {
  yield 1;
  yield* [2, 3, 4];  // delegate to another iterable
  yield 5;
}
06

関数

関数宣言と式

関数宣言は巻き上げられます(定義前に呼び出し可能)。式は巻き上げられません。アロー関数は簡潔で、独自の 'this' を持ちません(囲みスコープから継承)。IIFE はプライベートスコープを作成します(モジュールでは不要)。関数は第一級オブジェクトです — 引数として渡し、返し、変数に格納できます。

javascript
// Function declaration (hoisted - can be called before definition)
greet("Alice");  // works (hoisted)
function greet(name) {
  return `Hello, ${name}!`;
}

// Function expression (not hoisted)
const greet2 = function(name) {
  return `Hi, ${name}!`;
};

// Named function expression (for recursion/stack traces)
const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1);
};

// Arrow function (ES6) - concise, no own this
const add = (a, b) => a + b;
const square = x => x * x;
const greet3 = () => "Hello!";
const log = x => { console.log(x); };  // block body needs return

// IIFE (Immediately Invoked Function Expression)
const result = (function() {
  const private = "secret";
  return private.toUpperCase();
})();

// Functions are first-class (can be passed/returned)
function apply(fn, value) {
  return fn(value);
}
console.log(apply(square, 5));  // 25

アロー関数と this

アロー関数は独自の 'this'、'arguments'、'super'、'new.target' を持ちません — 囲みスコープから継承します。これによりコールバック(特にクラスメソッド内)に最適です。ただし、コンストラクタや独自の 'this' が必要なメソッドには使用できません。オブジェクトメソッドには通常の関数を使用してください。

javascript
// Arrow function variations
const add = (a, b) => a + b;           // implicit return
const greet = name => `Hi ${name}`;    // single param, no parens
const log = () => console.log("hi");   // no params
const obj = (x, y) => ({ x, y });      // return object needs parens
const multi = (a, b) => {              // block body
  const sum = a + b;
  return sum * 2;
};

// Arrow functions don't have their own 'this'
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++;  // 'this' refers to Timer instance
    console.log(this.seconds);
  }, 1000);
}

// Regular function has its own 'this'
function Counter() {
  this.count = 0;
  // Regular function: 'this' is undefined (strict) or global
  document.addEventListener("click", function() {
    // this.count++;  // ERROR: this is not Counter
  });
  // Arrow function: 'this' is Counter
  document.addEventListener("click", () => {
    this.count++;  // works!
  });
}

// Arrow functions can't be constructors
// const obj = new arrowFunc();  // TypeError

クロージャ

クロージャは定義スコープの変数を「記憶」する関数です。データプライバシー(モジュールパターン)、メモ化、カリー化、部分適用を可能にします。JavaScript のすべての関数はクロージャです。内部関数は外側の変数のコピーではなく参照を保持します。

javascript
// A closure is a function that remembers its outer variables
function makeCounter() {
  let count = 0;  // private variable
  return function() {
    return ++count;
  };
}

const counter = makeCounter();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3
// count is private - can't access directly

// Module pattern (pre-ES6)
const calculator = (function() {
  const result = 0;  // private
  return {
    add(x) { return result + x; },
    multiply(x) { return result * x; }
  };
})();

// Practical: memoization
function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (!(key in cache)) {
      cache[key] = fn.apply(this, args);
    }
    return cache[key];
  };
}

const slowFib = memoize(n =>
  n < 2 ? n : slowFib(n - 1) + slowFib(n - 2)
);

// Currying with closures
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5));  // 10

引数と Rest/Spread

デフォルトパラメータはフォールバック値を提供します。Rest パラメータ(...name)は余分な引数を実際の配列に集めます — 従来の 'arguments' オブジェクトより優先してください。Spread(...)は配列を個々の引数に展開します。パラメータの分割代入により名前付きオプション設定オブジェクトが可能です — 一般的な API パターンです。

javascript
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
  return `${greeting}, ${name}!`;
}
greet();              // "Hello, Guest!"
greet("Alice");       // "Hello, Alice!"
greet("Bob", "Hi");   // "Hi, Bob!"

// Rest parameters (...args collects into array)
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4));  // 10

function log(tag, ...args) {
  console.log(tag, args);
}

// Spread (opposite of rest)
const nums = [1, 2, 3];
console.log(Math.max(...nums));  // 3 (spread array as args)
console.log(sum(...nums));       // 6

// arguments object (old way, avoid in modern code)
function old() {
  console.log(arguments);  // array-like, not a real array
  const args = Array.from(arguments);  // convert
}

// Destructuring parameters
function process({ name, age = 0 } = {}) {
  console.log(name, age);
}
process({ name: "Alice" });  // Alice 0
process();  // undefined 0 (default empty object)
07

OOP とクラス

クラスとコンストラクタ

ES6 クラスはプロトタイプの糖衣構文です。プライベートフィールド(#name)は ES2022 で真にプライベートです(_name 慣習とは異なります)。ゲッター/セッターは計算プロパティを可能にします。静的メンバーはインスタンスではなくクラスに属します。クラスフィールド(name = value)はコンストラクタなしでインスタンスプロパティを初期化します。

javascript
class Person {
  // Fields (class fields proposal - ES2022)
  species = "human";  // instance field
  
  // Private fields (ES2022)
  #ssn = "secret";    // truly private
  
  // Static field/method
  static count = 0;
  
  // Constructor
  constructor(name, age) {
    this.name = name;
    this.age = age;
    Person.count++;
  }
  
  // Instance method
  greet() {
    return `Hello, I'm ${this.name}`;
  }
  
  // Getter
  get info() {
    return `${this.name}, ${this.age}`;
  }
  
  // Setter
  set age(value) {
    if (value < 0) throw new Error("Invalid age");
    this._age = value;
  }
  
  get age() {
    return this._age;
  }
  
  // Static method
  static create(name) {
    return new Person(name, 0);
  }
}

const alice = new Person("Alice", 30);
console.log(alice.greet());  // Hello, I'm Alice
console.log(alice.info);     // Alice, 30
console.log(Person.count);   // 1
// alice.#ssn;  // SyntaxError - private!

継承とポリモーフィズム

extends は継承を作成し、super() は親コンストラクタを呼び出します('this' 使用前に必須)。メソッドを再定義してオーバーライドします。JavaScript は単一継承ですが、ミックスイン(クラスファクトリ)でコンポジションを提供します。instanceof はプロトタイプチェーンをチェックします。ポリモーフィズムはメソッドオーバーライドで機能します。

javascript
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    return `${this.name} makes a sound`;
  }
  
  // Static method can be inherited
  static create(type, name) {
    return new type(name);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);  // must call super() first
    this.breed = breed;
  }
  
  speak() {
    return `${this.name} barks`;  // override
  }
  
  fetch() {
    return `${this.name} fetches`;
  }
}

class Cat extends Animal {
  speak() {
    return `${this.name} meows`;
  }
}

// Polymorphism
const animals = [new Dog("Rex", "Lab"), new Cat("Whiskers")];
animals.forEach(a => console.log(a.speak()));
// Rex barks
// Whiskers meows

// instanceof checks
const dog = new Dog("Buddy", "Poodle");
console.log(dog instanceof Dog);    // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor.name);  // "Dog"

// Mixins (multiple inheritance alternative)
const Walker = (Base) => class extends Base {
  walk() { return `${this.name} walks`; }
};
class Robot extends Walker(Animal) {}

プロトタイプ

JavaScript はプロトタイプ継承を使用します — オブジェクトはプロトタイプチェーンを介して他のオブジェクトから継承します。__proto__ は非推奨です。Object.getPrototypeOf/setPrototypeOf を使用してください。クラスはこのシステムの糖衣構文です。組み込みプロトタイプの変更は危険です — コードを壊す可能性があります。深い継承よりコンポジションを優先してください。

javascript
// Every object has a prototype (chain of inheritance)
const obj = {};
console.log(obj.__proto__);  // Object.prototype
console.log(Object.getPrototypeOf(obj));  // preferred

// Constructor function (pre-class syntax)
function OldPerson(name) {
  this.name = name;
}
OldPerson.prototype.greet = function() {
  return `Hi, ${this.name}`;
};
const p = new OldPerson("Alice");
console.log(p.greet());  // Hi, Alice

// Prototype chain
// p -> OldPerson.prototype -> Object.prototype -> null

// Adding to prototype (affects all instances)
Array.prototype.last = function() {
  return this[this.length - 1];
};
console.log([1, 2, 3].last());  // 3

// Object.create (prototypal inheritance)
const animal = { type: "unknown" };
const dog = Object.create(animal);
dog.type = "dog";
console.log(dog.type);  // "dog" (own property)

// Check own vs inherited
console.log(dog.hasOwnProperty("type"));  // true
console.log("type" in dog);  // true (includes inherited)

// Get prototype chain
let proto = Object.getPrototypeOf(dog);
while (proto) {
  console.log(proto);
  proto = Object.getPrototypeOf(proto);
}
08

エラー処理

Try / Catch / Finally

try/catch/finally は例外を処理します。catch はエラーオブジェクト(.message と .stack を持つ)をバインドします。instanceof で特定のエラー型を異なる方法で処理してください。予期したエラーを処理した後、未知のエラーは再スローしてください。アプリケーション固有のエラー処理のために Error を拡張してカスタムエラーを作成してください。

javascript
try {
  const result = JSON.parse('{"invalid json"');
} catch (error) {
  console.error("Parse error:", error.message);
  console.error("Stack:", error.stack);
} finally {
  console.log("Always runs");
}

// Catch specific error types
try {
  const data = JSON.parse(input);
  if (!data.name) throw new TypeError("name is required");
} catch (error) {
  if (error instanceof SyntaxError) {
    console.log("JSON syntax error");
  } else if (error instanceof TypeError) {
    console.log("Type error:", error.message);
  } else {
    throw error;  // re-throw unknown errors
  }
}

// Error properties
const err = new Error("Something went wrong");
err.code = "CUSTOM_ERROR";
err.statusCode = 500;
throw err;

// Built-in error types
// Error, TypeError, RangeError, ReferenceError
// SyntaxError, URIError, EvalError

カスタムエラー

Error を拡張して、追加のコンテキスト(フィールド、コード)を持つカスタムエラー型を作成します。常に this.name をクラス名に一致させてください。instanceof で特定のエラー型をキャッチしてください。エラーチェーン(ES2022 { cause })はデバッグのために元のエラーを保持します。良いエラー階層によりエラー処理が精密になります。

javascript
// Custom error class
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class DatabaseError extends Error {
  constructor(message, query) {
    super(message);
    this.name = "DatabaseError";
    this.query = query;
  }
}

// Usage
function validateUser(user) {
  if (!user.email) {
    throw new ValidationError("email", "Email is required");
  }
  if (!user.email.includes("@")) {
    throw new ValidationError("email", "Invalid email format");
  }
}

// Handling custom errors
try {
  validateUser({ name: "Alice" });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Validation failed: ${error.field} - ${error.message}`);
  } else if (error instanceof DatabaseError) {
    console.log(`DB error in query: ${error.query}`);
  } else {
    console.log("Unexpected error:", error);
  }
}

// Error chaining (ES2022)
try {
  throw new Error("Original cause");
} catch (cause) {
  throw new Error("Failed to process", { cause });
}
09

非同期と Promises

コールバック

コールバックは後で呼び出されるために渡される関数です。エラーファースト規約(err, data)は Node.js で標準です。ネストされたコールバックは「コールバック地獄」を作ります — 深くネストされ、読みにくいコード。Promises と async/await がこれを解決します。setTimeout/setInterval は一般的なコールバックベースの API です。

javascript
// Callback pattern (old way)
function fetchData(url, callback) {
  setTimeout(() => {
    callback(null, { data: "result" });
  }, 1000);
}

// Callback with error-first convention (Node.js style)
fetchData("/api", (error, data) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log(data);
});

// Callback hell (pyramid of doom)
fetchUser(userId, (err, user) => {
  if (err) return handleError(err);
  fetchPosts(user.id, (err, posts) => {
    if (err) return handleError(err);
    fetchComments(posts[0].id, (err, comments) => {
      if (err) return handleError(err);
      // deeply nested...
    });
  });
});

// Event listeners are callbacks
button.addEventListener("click", (event) => {
  console.log("Clicked!", event.target);
});

// setTimeout / setInterval
setTimeout(() => console.log("After 1s"), 1000);
const id = setInterval(() => console.log("tick"), 1000);
clearInterval(id);  // stop

Promises

Promises は 3 つの状態を持つ将来の値を表します:pending、fulfilled、rejected。.then() は成功を処理し、.catch() はエラーを処理し、.finally() は常に実行されます。Promise.all() はすべてを待機し(フェイルファスト)、allSettled() はすべてを待機し(失敗しない)、race() は最初に確定したものを返し、any() は最初に成功したものを返します。

javascript
// Creating a promise
const promise = new Promise((resolve, reject) => {
  const success = true;
  if (success) {
    resolve("Data received");
  } else {
    reject(new Error("Failed"));
  }
});

// Consuming a promise
promise
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Done"));

// Chaining
fetch("/api/users")
  .then(response => response.json())
  .then(users => users.filter(u => u.active))
  .then(active => console.log(active))
  .catch(err => console.error("Error:", err));

// Promise.all - wait for all (fails if any fails)
Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json())
]).then(([users, posts]) => {
  console.log(users, posts);
});

// Promise.allSettled - wait for all (never fails)
Promise.allSettled([p1, p2]).then(results => {
  results.forEach(r => {
    if (r.status === "fulfilled") console.log(r.value);
    else console.log(r.reason);
  });
});

// Promise.race - first to settle wins
Promise.race([p1, p2]).then(first => console.log(first));

// Promise.any - first to succeed
Promise.any([p1, p2]).then(first => console.log(first));

Async / Await

async/await は Promises の糖衣構文です — 非同期コードを同期的に見せます。'await' は Promise が確定するまで関数を一時停止します。エラー処理のために常に await を try/catch で囲んでください。並行操作には Promise.all() を使用してください(ループ内の順次 await より高速)。トップレベル await は ES モジュールで動作します。

javascript
// async function always returns a Promise
async function fetchData() {
  // await pauses until the promise resolves
  const response = await fetch("/api/users");
  const data = await response.json();
  return data;
}

// Error handling with try/catch
async function getUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}`);
    }
    return await res.json();
  } catch (error) {
    console.error("Failed:", error);
    return null;
  }
}

// Sequential vs parallel
async function sequential() {
  const a = await fetch("/api/a");  // waits for a
  const b = await fetch("/api/b");  // then waits for b
  return [a, b];
}

async function parallel() {
  const [a, b] = await Promise.all([  // both at once
    fetch("/api/a"),
    fetch("/api/b")
  ]);
  return [a, b];
}

// Top-level await (ES2022, in modules)
// const data = await fetch("/api").then(r => r.json());

// Iterating with async
async function processUrls(urls) {
  for (const url of urls) {
    const data = await fetch(url);
    console.log(data);
  }
}

// Promise.all with map (parallel)
async function fetchAll(urls) {
  return Promise.all(urls.map(url => fetch(url)));
}
10

DOM 操作

要素の選択と変更

querySelector/querySelectorAll(CSS セレクタ)が要素を選択するモダンな方法です。textContent は innerHTML より安全です(XSS を防ぐ)。classList はクラスの add/remove/toggle/contains を提供します。dataset は data-* 属性にアクセスします。innerHTML を設定する前に常にユーザー入力をサニタイズしてください。

javascript
// Selecting elements
const el = document.querySelector("#app");        // first match
const all = document.querySelectorAll(".item");    // all matches (NodeList)
const byId = document.getElementById("app");
const byClass = document.getElementsByClassName("item");  // HTMLCollection

// Modifying content
el.textContent = "Hello";        // text only (safe from XSS)
el.innerHTML = "<b>Bold</b>";    // HTML (XSS risk with user input!)
el.innerText = "Visible text";   // respects CSS visibility

// Attributes
el.setAttribute("data-id", "123");
const id = el.getAttribute("data-id");
el.removeAttribute("disabled");
el.dataset.id;  // access data-* attributes
el.id = "new-id";
el.className = "active highlighted";
el.classList.add("active");
el.classList.remove("old");
el.classList.toggle("hidden");
el.classList.contains("active");

// Styles
el.style.color = "red";
el.style.backgroundColor = "blue";  // camelCase
el.style.cssText = "color: red; font-size: 16px;";

// Creating elements
const div = document.createElement("div");
div.textContent = "New element";
div.classList.add("box");
document.body.appendChild(div);
document.body.prepend(div);   // add to beginning
document.body.insertBefore(div, referenceEl);

イベント

addEventListener は on<event> プロパティより推奨されます(複数のリスナーを許可)。イベント委任(親でリッスン)は動的に追加された要素に効率的です。e.target はクリックされた要素、e.currentTarget はリスナーを持つ要素です。preventDefault() はデフォルト動作を停止し、stopPropagation() はバブリングを停止します。

javascript
// Add event listener
button.addEventListener("click", (event) => {
  console.log("Clicked!", event.target);
  console.log("Current target:", event.currentTarget);
});

// Common events
// click, dblclick, mousedown, mouseup, mousemove
// keydown, keyup, keypress
// submit, change, input, focus, blur
// load, DOMContentLoaded, resize, scroll

// Event object properties
input.addEventListener("keydown", (e) => {
  console.log(e.key);       // "Enter", "a", etc.
  console.log(e.code);      // "KeyA", "Enter"
  console.log(e.ctrlKey);   // true if Ctrl held
  e.preventDefault();       // stop default behavior
  e.stopPropagation();      // stop bubbling
});

// Event delegation (efficient for many elements)
document.addEventListener("click", (e) => {
  if (e.target.matches(".delete-btn")) {
    const id = e.target.dataset.id;
    deleteItem(id);
  }
});

// Custom events
const customEvent = new CustomEvent("userLogin", {
  detail: { userId: 123 }
});
element.dispatchEvent(customEvent);

element.addEventListener("userLogin", (e) => {
  console.log("User logged in:", e.detail.userId);
});

// Remove listener (must be same function reference)
const handler = () => console.log("click");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);
11

モジュールと JSON

ES モジュール

ES モジュール(import/export)はモダンな標準で、ブラウザと Node.js でサポートされています。デフォルトエクスポート(モジュールごとに1つ)と名前付きエクスポート(複数)があります。動的 import() は遅延読み込みを可能にします。モジュールは常に strict モードで、独自のスコープを持ちます。HTML の script タグで type='module' を使用してください。

javascript
// math.js - exporting
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }

// Default export (one per module)
export default function greet(name) {
  return `Hello, ${name}`;
}

// main.js - importing
import greet from "./math.js";              // default
import { add, PI } from "./math.js";        // named
import * as math from "./math.js";          // namespace
import { add as plus } from "./math.js";    // rename
import greet, { add } from "./math.js";     // default + named

// Dynamic import (returns Promise)
const module = await import("./math.js");
console.log(module.add(2, 3));

// Conditional/dynamic loading
if (featureEnabled) {
  const { default: Feature } = await import("./feature.js");
  new Feature();
}

// Re-export
export { add } from "./math.js";
export * from "./utils.js";

// In HTML
// <script type="module" src="app.js"></script>
// Modules are deferred and in strict mode by default

JSON

JSON.stringify() は JS の値を JSON 文字列に変換し、JSON.parse() は逆変換します。変換中にフィルタや変換を行うには replacers/revivers を使用してください。JSON は関数、undefined、Date(文字列になる)、循環参照をサポートしません。fetch().json() は JSON レスポンスを自動的に解析します。信頼できない入力の JSON.parse は常に try/catch で囲んでください。

javascript
// JSON is the standard data interchange format
// JSON supports: string, number, boolean, null, array, object

// JavaScript object to JSON string
const user = { name: "Alice", age: 30, active: true };
const jsonStr = JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'

// Pretty print
const pretty = JSON.stringify(user, null, 2);
// {
//   "name": "Alice",
//   "age": 30,
//   "active": true
// }

// JSON string to JavaScript object
const parsed = JSON.parse('{"name":"Bob","age":25}');
console.log(parsed.name);  // "Bob"

// Replacer function (filter/transform during stringify)
const filtered = JSON.stringify(user, (key, value) => {
  if (key === "age") return undefined;  // exclude
  return value;
});

// Reviver function (transform during parse)
const data = JSON.parse(jsonStr, (key, value) => {
  if (key === "date") return new Date(value);
  return value;
});

// Fetch JSON from API
async function getUsers() {
  const res = await fetch("/api/users");
  return res.json();  // parses JSON automatically
}

// JSON limitations
// - No functions, undefined, or dates
// - No circular references (throws error)
// - Keys must be strings (with quotes)

Date と時刻

JavaScript の Date は非常に扱いにくいことで有名です — 月は 0 ベース(1月 = 0)、日は 1 ベースです。Date オブジェクトは変更可能です。toISOString() は UTC、toLocaleString() はローカル時刻を返します。本格的な日付処理には date-fns や dayjs のようなライブラリを使用してください。Intl.DateTimeFormat はロケール対応のフォーマットを提供します。

javascript
// Create dates
const now = new Date();
const specific = new Date("2024-01-15T10:30:00");
const fromMs = new Date(1705315200000);
const fromParts = new Date(2024, 0, 15, 10, 30);  // month is 0-indexed!

// Get components
console.log(now.getFullYear());   // 2024
console.log(now.getMonth());      // 0-11 (January = 0!)
console.log(now.getDate());       // 1-31
console.log(now.getDay());        // 0-6 (Sunday = 0)
console.log(now.getHours());      // 0-23
console.log(now.getTime());       // milliseconds since epoch

// Set components
now.setFullYear(2025);
now.setMonth(11);  // December

// Formatting
console.log(now.toISOString());     // "2024-01-15T10:30:00.000Z"
console.log(now.toLocaleDateString()); // "1/15/2024" (locale)
console.log(now.toLocaleString());  // "1/15/2024, 10:30:00 AM"

// Timestamps
const start = Date.now();  // milliseconds since epoch
// ... do work ...
console.log(`Took ${Date.now() - start}ms`);

// Intl for formatting (modern)
const formatter = new Intl.DateTimeFormat("zh-CN", {
  year: "numeric", month: "long", day: "numeric"
});
console.log(formatter.format(now));  // "2024年1月15日"
12

ES6+ の機能

let、const とブロックスコープ

デフォルトで const を優先し、再代入が必要な場合は let、var は完全に避けてください。const は再代入を防ぎますが、オブジェクト/配列は変更可能です。let と const はブロックスコープで、宣言前は TDZ(一時的デッドゾーン)にあります(undefined として巻き上げられる var とは異なります)。これにより多くの微妙なバグを防げます。

javascript
// var is function-scoped (hoisted, leaks out of blocks)
// let and const are block-scoped (stay inside {})
{
  var x = 1;   // accessible outside the block
  let y = 2;   // block-scoped
  const z = 3; // block-scoped, cannot reassign
}
console.log(x); // 1
// console.log(y); // ReferenceError

// const prevents reassignment, NOT mutation
const arr = [1, 2, 3];
arr.push(4);      // OK — mutating the array
// arr = [5];     // TypeError — reassigning const

// Temporal Dead Zone: let/const can't be used before declaration
// console.log(a); // ReferenceError (not undefined like var)
let a = 10;

アロー関数と this

アロー関数は簡潔で、囲みスコープから 'this' を継承します — コールバックや外側の 'this' が必要なメソッドに最適です。ただし、コンストラクタとして使用できず、'arguments' オブジェクトを持ちません。オブジェクトのメソッドで 'this' がオブジェクトを指す必要がある場合は、アロー関数ではなく通常のメソッドを使用してください。

javascript
// Arrow functions: concise syntax, lexically bound 'this'
const add = (a, b) => a + b;
const square = x => x * x;          // single param, no parens
const greet = name => `Hello, ${name}`;  // template literal
const noop = () => {};              // no params, empty body

// Returning an object literal needs parens
const makeUser = (name, age) => ({ name, age });

// Arrow functions DON'T have their own 'this' — they inherit it
function Counter() {
  this.count = 0;
  setInterval(() => {
    this.count++;  // 'this' is the Counter instance (lexical)
    console.log(this.count);
  }, 1000);
}
// Regular function would lose 'this' (it'd be window/undefined)

分割代入

分割代入はオブジェクト/配列から値を変数に1行で抽出します — 手動プロパティアクセスよりクリーンです。オブジェクトの分割代入は { key }、配列の分割代入は [index] を使用します。リネーム(key: alias)、デフォルト(key = default)、rest(...rest)、ネストパターンをサポートします。React の props や関数パラメータで多用されます。

javascript
// Object destructuring
const user = { name: "Alice", age: 30, city: "NYC" };
const { name, age } = user;
console.log(name, age);  // Alice 30

// Rename and default values
const { name: fullName, country = "USA" } = user;
console.log(fullName, country);  // Alice USA

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest);  // 1 2 [3,4,5]

// Swap variables (no temp needed!)
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b);  // 2 1

// Destructuring in function parameters
function greet({ name, greeting = "Hello" }) {
  console.log(`${greeting}, ${name}`);
}
greet({ name: "Bob" });  // Hello, Bob

// Nested destructuring
const { data: { results } } = response;

Spread と Rest 演算子

... 演算子は展開時(配列/オブジェクト/呼び出し)は 'spread'、集約時(パラメータ/分割代入)は 'rest' です。Spread はシャローコピーを作成し、オブジェクトをマージします(後のキーが前を上書き)。Rest パラメータは古い 'arguments' オブジェクトを置き換え、実際の配列です。どちらもモダン JS の不可欠なイディオムです。

javascript
// Spread (...) expands iterables into individual elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1, 2, 3, 4, 5] (copy + append)
const merged = [...arr1, ...arr2];

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };  // { a: 1, b: 2, c: 3 } (shallow copy)
const updated = { ...obj1, b: 99 };  // override b: { a: 1, b: 99 }

// Rest (...) collects multiple elements into an array
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // 10

// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail);  // 1 [2, 3, 4]

// Spread in function calls
Math.max(...[1, 5, 3]);  // 5 (same as Math.max(1, 5, 3))

テンプレートリテラルとタグ付きテンプレート

テンプレートリテラル(バッククォート)は複数行文字列と ${} 補間をサポートします — 文字列連結よりはるかにクリーンです。タグ付きテンプレートは関数がリテラル部分と補間値を処理できるようにし、カスタムフォーマット、サニタイズ(HTML エスケープなど)、i18n を可能にします。styled-components や graphql-tag で人気があります。

javascript
// Template literals: backticks, multi-line, interpolation
const name = "Alice";
const msg = `Hello ${name},
  this spans
  multiple lines`;

// Expressions inside ${}
const price = 19.99;
const tax = 0.08;
console.log(`Total: ${(price * (1 + tax)).toFixed(2)}`);  // Total: 21.59

// Nested template literals
const items = ["apple", "banana"];
const html = `
  <ul>
    ${items.map(i => `<li>${i}</li>`).join("")}
  </ul>
`;

// Tagged templates: function processes the literal
function highlight(strings, ...values) {
  return strings.reduce((acc, str, i) =>
    acc + str + (values[i] ? `**${values[i]}**` : ""), "");
}
const result = highlight`Name: ${name}, Age: ${30}`;
// "Name: **Alice**, Age: **30**"

オプショナルチェーンと null 合体

オプショナルチェーン(?.)はチェーンの一部が null/undefined の場合に undefined に短絡します — 冗長な手動チェックを排除します。null 合体(??)は null/undefined のみにデフォルトを提供し、0、''、false も上書きする || とは異なります。この2つを組み合わせて最も一般的な「データ欠落」パターンを安全に処理できます。ES2020 以降で利用可能です。

javascript
// Optional chaining (?.): safely access nested properties
const user = { profile: { name: "Alice" } };
console.log(user?.profile?.name);     // "Alice"
console.log(user?.settings?.theme);   // undefined (no error!)
console.log(user?.profile?.address?.city);  // undefined

// Without ?., you'd write:
// user && user.profile && user.profile.name

// Optional method calls
const result = obj?.method?.();
const firstChar = str?.[0];

// Nullish coalescing (??): default only for null/undefined
const theme = user.settings?.theme ?? "light";  // "light"
const count = 0 ?? 10;  // 0 (NOT 10 — ?? keeps falsy but valid values)
const name = "" ?? "Anonymous";  // "" (empty string is kept!)

// vs || which treats all falsy values (0, "", false) as missing
const count2 = 0 || 10;  // 10 (probably not what you want!)
13

イベントとイベント処理

addEventListener の基礎

addEventListener はイベントをバインドするモダンな方法です — 複数のハンドラを許可し、イベント委任をサポートし、once、passive、capture などのオプションを提供します。後で削除する必要がある場合はハンドラへの参照を保持してください(匿名関数は削除できません)。イベントオブジェクトは target、currentTarget、preventDefault()、stopPropagation() を運びます。

javascript
// Modern event binding (preferred over onclick=)
const button = document.querySelector("#myBtn");

button.addEventListener("click", function(event) {
  console.log("Clicked!", event.target);
  event.preventDefault();  // stop default action (e.g., form submit)
});

// Multiple listeners can be attached
button.addEventListener("click", handler1);
button.addEventListener("click", handler2);

// Remove a specific listener (must be same reference)
button.removeEventListener("click", handler1);

// Common event types
// "click", "dblclick", "mousedown", "mouseup", "mousemove"
// "keydown", "keyup", "keypress"
// "submit", "change", "input", "focus", "blur"
// "load", "DOMContentLoaded", "resize", "scroll"

// Once option: auto-remove after first trigger
button.addEventListener("click", handler, { once: true });

イベント委任

イベント委任は親に1つのリスナーをアタッチし、イベントバブリングを介してすべての子からのイベントを処理します。event.target.matches(selector) でフィルタします。各子にバインドするよりはるかに効率的で、動的に追加された要素を自動的に処理します。トレードオフ:親は常に存在する共通の祖先でなければなりません。

javascript
// Delegate events to a parent instead of each child
// Efficient for dynamically added elements
const list = document.querySelector("#item-list");

list.addEventListener("click", (event) => {
  // event.target is the actual element clicked
  if (event.target.matches("li.item")) {
    console.log("Clicked:", event.target.textContent);
    event.target.classList.toggle("selected");
  }
});

// Now dynamically added items automatically work
list.insertAdjacentHTML("beforeend", "<li class='item'>New Item</li>");

// Benefits:
// 1. One listener instead of many (memory efficient)
// 2. Works for elements added after binding
// 3. No need to re-bind when DOM changes

イベント伝播(バブリングとキャプチャリング)

イベントは 3 つのフェーズで伝播します:キャプチャリング(トップダウン)、ターゲット、バブリング(ボトムアップ、デフォルト)。ほとんどのハンドラはバブリングフェーズで実行されます。stopPropagation() は親要素へのイベント伝播を防ぎ、stopImmediatePropagation() は同じ要素の他のハンドラも停止します。子ハンドラの前に実行する必要があるハンドラにはキャプチャリング(第3引数 true)を使用してください。

javascript
<div id="outer">
  <div id="inner">
    <button id="btn">Click</button>
  </div>
</div>

// Events flow in three phases:
// 1. Capturing: top -> target (window -> document -> ... -> target)
// 2. Target: at the target element
// 3. Bubbling: target -> top (default phase most handlers run in)

// Bubbling (default): child fires first, then parents
btn.addEventListener("click", () => console.log("button"));
inner.addEventListener("click", () => console.log("inner"));
outer.addEventListener("click", () => console.log("outer"));
// Click button logs: button -> inner -> outer

// stopPropagation: prevent bubbling to parents
btn.addEventListener("click", (e) => {
  e.stopPropagation();
  console.log("only button");
});

// Capturing phase (third arg = true)
outer.addEventListener("click", handler, true);  // runs during capture

// stopImmediatePropagation: stop other handlers on SAME element too

カスタムイベント

CustomEvent は 'detail' プロパティにペイロードデータを持つアプリケーション固有のイベントを作成できます。dispatchEvent と組み合わせて、コンポーネントを疎結合する pub/sub パターンを実現します — モジュールが直接参照なしで通信できます。'namespace:action' のような命名規則を使用して衝突を避けてください。多くのカスタム要素フレームワークの基盤です。

javascript
// Create and dispatch custom events
const event = new CustomEvent("userLoggedIn", {
  detail: { userId: 42, name: "Alice" },
  bubbles: true,    // allow bubbling
});

document.dispatchEvent(event);

// Listen for the custom event
document.addEventListener("userLoggedIn", (e) => {
  console.log("User logged in:", e.detail.userId, e.detail.name);
});

// Practical: decoupled communication between components
class Cart {
  constructor() {
    this.items = [];
  }
  add(item) {
    this.items.push(item);
    window.dispatchEvent(new CustomEvent("cart:updated", {
      detail: { count: this.items.length }
    }));
  }
}

// Any module can listen without knowing about Cart internals
window.addEventListener("cart:updated", (e) => {
  updateBadge(e.detail.count);
});

キーボードとフォームイベント

キーボードイベントは e.key('a'、'Enter' のような論理キー)と e.code('KeyA' のような物理キー)を提供します。ほとんどのロジックには e.key を使用してください。フォーム送信にはページリロードを停止するために常に preventDefault() が必要です。FormData + Object.fromEntries でフォームデータを簡単に収集できます。'input' は連続的に発火し、'change' はフォーカスを失った時に発火します — 検証のタイミングに基づいて選択してください。

javascript
// Keyboard events
document.addEventListener("keydown", (e) => {
  console.log(e.key, e.code);  // e.key="a", e.code="KeyA"
  if (e.key === "Escape") closeModal();
  if (e.ctrlKey && e.key === "s") { e.preventDefault(); save(); }
});

// Form events
const form = document.querySelector("#myForm");
form.addEventListener("submit", (e) => {
  e.preventDefault();  // stop page reload
  const formData = new FormData(form);
  const data = Object.fromEntries(formData);
  console.log(data);  // { username: "...", email: "..." }
});

// Input validation on change/blur
const emailInput = document.querySelector("#email");
emailInput.addEventListener("blur", () => {
  if (!emailInput.value.includes("@")) {
    emailInput.setCustomValidity("Enter a valid email");
  } else {
    emailInput.setCustomValidity("");
  }
});

// Change vs input:
// 'input' fires on every keystroke; 'change' fires on blur
14

Fetch API と AJAX

基本的な fetch(GET)

fetch() は XMLHttpRequest のモダンな代替です — Promise ベースでクリーンです。重要なことに、fetch はネットワークエラーでのみ拒否され、HTTP エラーステータス(404、500)では拒否されません。解析前に常に response.ok(ステータス 200-299)をチェックしてください。読みやすい逐次コードには async/await を使用してください。response.json() はボディストリームを読み取るため非同期です。

javascript
// fetch() returns a Promise; .json() returns another Promise
fetch("https://api.example.com/users")
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return response.json();  // or .text(), .blob(), .arrayBuffer()
  })
  .then(data => console.log(data))
  .catch(error => console.error("Fetch failed:", error));

// fetch does NOT reject on HTTP errors (404, 500) — only on network
// failures. You MUST check response.ok manually.

// Same with async/await (preferred)
async function getUsers() {
  try {
    const res = await fetch("https://api.example.com/users");
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error(err);
  }
}

POST、PUT、DELETE with fetch

fetch のオプションオブジェクトで method、headers、body を設定します。JSON の場合は Content-Type: application/json を設定し、body を JSON.stringify します。ファイルアップロードには FormData を使用してください(Content-Type を手動設定しないでください — ブラウザが multipart boundary を追加します)。PUT はリソース全体を置換し、PATCH は部分的に更新します。

javascript
// POST: create a resource
async function createUser(data) {
  const res = await fetch("/api/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  return res.json();
}
createUser({ name: "Alice", email: "[email protected]" });

// PUT: update a resource (full replace)
await fetch("/api/users/1", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Alice Updated" }),
});

// DELETE
await fetch("/api/users/1", { method: "DELETE" });

// Sending form data (file uploads)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: formData });
// Don't set Content-Type for FormData — browser sets it with boundary

リクエストヘッダーと認証

ヘッダーはメタデータと認証トークンを運びます。Bearer トークン(JWT)は Authorization ヘッダーに入ります。Host や Cookie のような一部のヘッダーは「禁止」(ブラウザ制御)です。CORS はサーバーではなくブラウザによって強制されます — クライアント JS からバイパスできません。サーバーが Access-Control-Allow-Origin を送信する必要があります。非単純リクエストではプリフライト OPTIONS リクエストが発生します。

javascript
// Custom headers (e.g., Bearer token auth)
async function fetchWithAuth(url, options = {}) {
  const token = localStorage.getItem("authToken");
  const res = await fetch(url, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
      "Accept": "application/json",
      ...options.headers,  // allow overrides
    },
  });
  return res;
}

// Common headers:
// Content-Type: application/json | application/x-www-form-urlencoded
// Authorization: Bearer <token> | Basic <base64>
// Accept: application/json
// X-Requested-With: XMLHttpRequest (anti-CSRF)

// CORS: browser blocks cross-origin requests unless the server
// returns Access-Control-Allow-Origin. fetch can't bypass CORS.

AbortController(リクエストのキャンセル)

AbortController は fetch リクエストをキャンセルします — 検索入力、ナビゲーション離脱、タイムアウトに不可欠です。signal を fetch に渡し、controller.abort() を呼び出すと AbortError が発生します。これがないと、古いリクエストが UI を順不同で更新する可能性があります。AbortController は他の非同期 API でも動作し、モダン JS の標準キャンセル機構です。

javascript
// AbortController lets you cancel in-flight fetch requests
const controller = new AbortController();

async function fetchWithTimeout(url, ms = 5000) {
  const timeout = setTimeout(() => controller.abort(), ms);
  try {
    const res = await fetch(url, { signal: controller.signal });
    return await res.json();
  } catch (err) {
    if (err.name === "AbortError") {
      console.log("Request was aborted (timeout or cancel)");
    } else {
      throw err;
    }
  } finally {
    clearTimeout(timeout);
  }
}

// Cancel manually (e.g., user navigated away)
// controller.abort();

// Practical: cancel on new search input
searchInput.addEventListener("input", (e) => {
  controller.abort();  // cancel previous request
  fetchResults(e.target.value);
});

ストリーミングレスポンス

response.body は ReadableStream です — 応答全体をメモリにバッファする代わりに、データが到着したらチャンクで処理できます。大きなファイル、ストリーミングログ、リアルタイムデータに不可欠です。テキストストリームには TextDecoder を使用してください。reader.read() ループは done が true になるまで続きます。ストリーミングは大きなペイロードでのメモリスパイクを回避します。

javascript
// Read a large response in chunks (streaming)
async function streamJson(url) {
  const res = await fetch(url);
  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    // Process complete lines
    const lines = buffer.split("\n");
    buffer = lines.pop();  // keep incomplete line
    for (const line of lines) {
      if (line.trim()) console.log(JSON.parse(line));
    }
  }
}

// Useful for: large files, NDJSON logs, real-time data feeds
// Processes data as it arrives instead of waiting for the full response
15

Web ストレージ(LocalStorage と SessionStorage)

localStorage と sessionStorage の基礎

localStorage は無期限で永続し、sessionStorage はタブを閉じるとクリアされます。両方とも文字列のみを格納します — オブジェクトには JSON.stringify/parse を使用してください。ストレージは同期的でメインスレッドをブロックするため、大きなデータの格納は避けてください。すべてのモダンブラウザで利用可能ですが、プライベートブラウジングモードでは無効な場合があります。容量はオリジンごとに約 5-10MB です。

javascript
// Both store strings only — JSON.stringify objects
// localStorage: persists until cleared (survives browser restart)
// sessionStorage: cleared when the tab closes

// localStorage API
localStorage.setItem("username", "Alice");
const name = localStorage.getItem("username");  // "Alice"
localStorage.removeItem("username");
localStorage.clear();  // remove ALL items

// sessionStorage API (same interface, tab-scoped)
sessionStorage.setItem("tempToken", "abc123");

// Storing objects (must serialize)
const prefs = { theme: "dark", lang: "en" };
localStorage.setItem("prefs", JSON.stringify(prefs));
const loaded = JSON.parse(localStorage.getItem("prefs"));
console.log(loaded.theme);  // "dark"

// Check if storage exists (private mode may disable it)
if (typeof Storage !== "undefined") {
  // localStorage/sessionStorage available
}

TTL と JSON を備えたストレージヘルパー

Web ストレージには組み込みの有効期限がありません — このラッパーは値と共に有期限タイムスタンプを格納して TTL(有効期間)を追加します。これは API レスポンスや期限切れすべきセッションデータをキャッシュする標準パターンです。JSON.parse は破損したデータで例外をスローし、クォータを超える可能性があるため、本番では常にストレージアクセスを try/catch で囲んでください。

javascript
// A robust storage wrapper with expiration (TTL)
const store = {
  set(key, value, ttlMs) {
    const item = {
      value: value,
      expiry: ttlMs ? Date.now() + ttlMs : null,
    };
    localStorage.setItem(key, JSON.stringify(item));
  },
  get(key) {
    const raw = localStorage.getItem(key);
    if (!raw) return null;
    const item = JSON.parse(raw);
    if (item.expiry && Date.now() > item.expiry) {
      localStorage.removeItem(key);
      return null;  // expired
    }
    return item.value;
  },
  remove(key) { localStorage.removeItem(key); },
};

// Usage: cache data for 5 minutes
store.set("userData", { name: "Alice" }, 5 * 60 * 1000);
console.log(store.get("userData"));  // { name: "Alice" }
// After 5 min: returns null and cleans up

ストレージイベント(クロスタブ同期)

ストレージイベントは組み込みのクロスタブ通信チャネルです — 1 つのタブが localStorage を変更すると、同じオリジンの他のすべてのタブがイベントを受け取ります(元のタブを除く)。これにより WebSockets なしでログイン/ログアウト、カート更新、テーマ変更などの状態をタブ間で同期できます。イベントには key、oldValue、newValue、url が含まれます。

javascript
// The 'storage' event fires in OTHER tabs when storage changes
// (not in the tab that made the change)
window.addEventListener("storage", (event) => {
  console.log("Key changed:", event.key);
  console.log("Old value:", event.oldValue);
  console.log("New value:", event.newValue);
  console.log("URL:", event.url);

  if (event.key === "cart") {
    updateCartDisplay(JSON.parse(event.newValue));
  }
});

// Practical: sync logout across tabs
// Tab A: localStorage.setItem("logout", Date.now());
// Tab B: receives storage event -> redirects to login

// Practical: broadcast a message to all tabs
function broadcast(type, data) {
  localStorage.setItem("broadcast", JSON.stringify({ type, data, t: Date.now() }));
}
window.addEventListener("storage", (e) => {
  if (e.key === "broadcast") {
    const msg = JSON.parse(e.newValue);
    handleMessage(msg);
  }
});

IndexedDB(大きな構造化ストレージ)

IndexedDB はブラウザ内の強力な NoSQL データベースです — 非同期、トランザクション、localStorage よりはるかに多くのデータ(数百 MB)を格納できます。インデックス、カーソル、トランザクションをサポートします。生の API はコールバックベースで冗長です。'idb' npm パッケージがクリーンな Promise ベースのラッパーを提供します。オフラインファーストアプリ、大きなキャッシュ、複雑なクライアント側データに使用してください。

javascript
// IndexedDB: async, transactional NoSQL store for large data
// Capacity: hundreds of MB to GB (far more than localStorage's 5MB)

// Open a database
const request = indexedDB.open("MyDatabase", 1);

request.onupgradeneeded = (e) => {
  const db = e.target.result;
  // Create an object store (like a table) with a key path
  if (!db.objectStoreNames.contains("users")) {
    db.createObjectStore("users", { keyPath: "id" });
  }
};

request.onsuccess = (e) => {
  const db = e.target.result;
  // Add data in a transaction
  const tx = db.transaction("users", "readwrite");
  const store = tx.objectStore("users");
  store.add({ id: 1, name: "Alice", age: 30 });
  store.add({ id: 2, name: "Bob", age: 25 });

  // Query
  store.get(1).onsuccess = (e) => {
    console.log(e.target.result);  // { id: 1, name: "Alice", age: 30 }
  };
};

// For easier use, consider the 'idb' library (Promise-based wrapper)

Cookie とストレージの比較

Cookie はすべての HTTP リクエストで送信され(帯域幅を追加)、サーバー側認証の標準です(セッション ID、CSRF トークン)。localStorage/sessionStorage はクライアントのみで、より多くのデータを保持します。IndexedDB は大きな構造化データ用です。選択基準:サーバーが必要か?(Cookie)データ量は?(ストレージ vs IndexedDB)期間は?(セッション vs ローカル)。セキュリティに敏感な Cookie には Secure、HttpOnly、SameSite を設定してください。

javascript
// COOKIES: sent with every HTTP request, ~4KB limit
document.cookie = "session=abc123; max-age=3600; path=/; Secure; SameSite=Strict";
console.log(document.cookie);  // "session=abc123; theme=dark"

// Set cookie with attributes
document.cookie = "token=xyz; max-age=86400; Secure; HttpOnly; SameSite=Lax";
// Note: HttpOnly can't be set via JS (server-only for security)

// LOCAL STORAGE: ~5-10MB, NOT sent to server, synchronous
localStorage.setItem("theme", "dark");

// SESSION STORAGE: ~5MB, cleared on tab close
sessionStorage.setItem("draft", "work in progress");

// WHEN TO USE WHAT:
// Cookies   -> auth tokens that the server needs to read, server-side sessions
// localStorage -> user preferences, cached data that persists
// sessionStorage -> temporary per-tab state (form drafts, wizard steps)
// IndexedDB -> large datasets, offline data, complex queries
16

タイマー(setTimeout、setInterval)

setTimeout と setInterval

setTimeout は遅延後にコールバックを一度実行し、setInterval は繰り返し実行します。どちらも clearTimeout/clearInterval でキャンセルするための ID を返します。タイマーは正確ではありません — イベントループ、タブの可視性(バックグラウンドタブでスロットル)、メインスレッドの可用性に影響される最小遅延です。ネストされたタイマーで 4ms 未満の遅延は 4ms に切り詰められる場合があります。

javascript
// setTimeout: run once after a delay (milliseconds)
const timeoutId = setTimeout(() => {
  console.log("Runs after 2 seconds");
}, 2000);

// Cancel before it fires
clearTimeout(timeoutId);

// setInterval: run repeatedly every interval
const intervalId = setInterval(() => {
  console.log("Runs every 1 second");
}, 1000);

// Stop the interval
clearInterval(intervalId);

// Pass arguments to the callback
setTimeout((greeting, name) => {
  console.log(`${greeting}, ${name}`);
}, 1000, "Hello", "Alice");

// NOTE: timers don't guarantee exact timing — they run after the
// minimum delay, but only when the call stack is empty (event loop)

再帰的 setTimeout(setInterval より優れている)

再帰的 setTimeout は、前の呼び出しが終了してから次が開始されることを保証するため、繰り返し非同期タスクに setInterval より推奨されます — 重複実行がありません。動的インターバル(エラー時に長いバックオフなど)も可能です。setInterval はハンドラがインターバルより長い場合に呼び出しがスタックし、パフォーマンス問題を引き起こす可能性があります。

javascript
// setInterval problems: doesn't wait for the previous call to finish
// If a call takes longer than the interval, they can stack up.

// Better: recursive setTimeout — guarantees the gap between completions
function poll() {
  fetch("/api/status")
    .then(r => r.json())
    .then(data => {
      console.log(data);
      setTimeout(poll, 1000);  // schedule next AFTER this finishes
    })
    .catch(err => {
      console.error(err);
      setTimeout(poll, 5000);  // retry with backoff on error
    });
}
poll();  // start the loop

// This pattern: always waits for the previous call to complete
// before scheduling the next, avoiding overlap/stacking.

requestAnimationFrame(スムーズなアニメーション)

requestAnimationFrame(rAF)は視覚アニメーションの正しい方法です — ブラウザの再描画サイクル(約60fps)と同期し、タブ非表示時に不要なフレームを回避し、setInterval よりスムーズなアニメーションを生成します。異なるリフレッシュレートでアニメーション速度を一定に保つため、タイムスタンプパラメータでデルタタイム計算を使用してください。完了時に cancelAnimationFrame でキャンセルしてください。

javascript
// requestAnimationFrame: syncs with the display refresh (~60fps)
// More efficient than setInterval for visual animations
function animate() {
  element.style.left = parseInt(element.style.left || 0) + 2 + "px";
  if (parseInt(element.style.left) < 300) {
    requestAnimationFrame(animate);  // schedule next frame
  }
}
requestAnimationFrame(animate);

// Cancel if needed
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);

// Timestamp for delta-time calculations
let lastTime = 0;
function loop(timestamp) {
  const delta = timestamp - lastTime;
  lastTime = timestamp;
  update(delta);  // frame-rate independent movement
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

// rAF pauses in background tabs (saves CPU/battery)

デバウンスとスロットル

デバウンスは呼び出しの停止を待ってから実行します(例:ユーザーが入力を止めてから 300ms 後にのみ検索)。スロットルはインターバルごとに1回の実行に制限します(例:スクロール位置を最大 200ms ごとに更新)。どちらも高頻度イベントによるパフォーマンス問題を防ぎます。デバウンス = 「急速な呼び出しを1つにまとめる」、スロットル = 「呼び出しレートを制限する」。

javascript
// Debounce: delay execution until calls stop for N ms
// Use for: search input, window resize, button spam
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);  // reset the countdown
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const search = debounce((query) => {
  fetchResults(query);
}, 300);
input.addEventListener("input", (e) => search(e.target.value));

// Throttle: execute at most once per N ms
// Use for: scroll, mousemove, drag handlers
function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

const onScroll = throttle(() => {
  console.log("Scroll position:", window.scrollY);
}, 200);
window.addEventListener("scroll", onScroll);

Promise 化された setTimeout(delay)

setTimeout を Promise でラップすると、async/await 用のクリーンな 'delay' 関数が作成されます — コールバックチェーンよりはるかに読みやすいです。このパターンは指数バックオフでのリトライロジック(リトライ間に 1s、2s、4s 待機)、逐次アニメーション、レート制限を支えます。delay ヘルパーはモダンな非同期 JS で最も有用な小さなユーティリティの1つです。

javascript
// A promise-based delay for use with async/await
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function countdown() {
  console.log("3...");
  await delay(1000);
  console.log("2...");
  await delay(1000);
  console.log("1...");
  await delay(1000);
  console.log("Go!");
}
countdown();

// Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url);
      if (res.ok) return await res.json();
      throw new Error(`HTTP ${res.status}`);
    } catch (err) {
      if (i === retries - 1) throw err;
      const wait = Math.pow(2, i) * 1000;  // 1s, 2s, 4s
      console.log(`Retry ${i + 1} in ${wait}ms`);
      await delay(wait);
    }
  }
}
17

Map、Set と WeakMap

Map(任意のキー型のキーと値)

Map は適切なキーと値コレクションで、キーは任意の型(オブジェクト、関数、数値)になります — キーを文字列に強制変換する Object とは異なります。Map は挿入順序を保持し、.size プロパティを持ち、直接反復可能です。非文字列キー、頻繁な追加/削除、またはコレクションがレコード/DTO でない場合に Map を使用してください。固定形状のデータには Object を使用してください。

javascript
// Map: like an Object but keys can be ANY type (not just strings)
const map = new Map();

// Keys can be objects, functions, primitives
const objKey = { id: 1 };
map.set("name", "Alice");
map.set(objKey, "data for this object");
map.set(42, "numeric key");
map.set(true, "boolean key");

console.log(map.get("name"));   // "Alice"
console.log(map.get(objKey));   // "data for this object"
console.log(map.size);          // 4
console.log(map.has(42));       // true

// Iteration preserves insertion order
for (const [key, value] of map) {
  console.log(key, value);
}

// Map vs Object:
// - Map keys can be any type; Object keys are strings/symbols
// - Map has .size; Object needs Object.keys().length
// - Map is iterable by default; Object needs Object.entries()
// - Map performs better for frequent add/remove

Set(一意の値)

Set は一意の値を格納します — 重複排除とメンバーシップテストに最適です。配列を Set に変換して戻す([...new Set(arr)])のが重複を削除する慣用的な方法です。Set.has() は O(1) で、Array.includes() の O(n) に対して高速です。頻繁にチェックする大きなコレクションには Set を使用してください。Set には map/filter がありません — まず配列に展開してください。

javascript
// Set: collection of unique values (no duplicates)
const set = new Set([1, 2, 3, 2, 1]);
console.log(set);        // Set(3) { 1, 2, 3 }
console.log(set.size);   // 3

set.add(4);
set.add(4);  // ignored (already exists)
console.log(set.has(3));  // true
set.delete(2);

// Iterate (preserves insertion order)
for (const item of set) {
  console.log(item);
}

// Common use cases:
// 1. Remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3, 3, 3])];  // [1, 2, 3]

// 2. Check for membership (faster than Array.includes for large sets)
const validIds = new Set([101, 102, 103]);
if (validIds.has(userId)) { /* ... */ }

// 3. Set operations (union, intersection)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]);        // {1,2,3,4}
const intersect = new Set([...a].filter(x => b.has(x)));  // {2,3}

WeakMap と WeakSet(メモリセーフな参照)

WeakMap と WeakSet はオブジェクトへの弱参照を保持します — 他に参照がない場合、エントリは自動的にガベージコレクションされます。これにより DOM 要素や他のオブジェクトにデータを関連付ける際のメモリリークを防ぎます。キーはオブジェクトでなければなりません。WeakMap/WeakSet は反復不可能で .size を持ちません(GC 中にいつでもエントリが消えるため)。オブジェクトのライフタイムに結び付けられたキャッシュ/メタデータに使用してください。

javascript
// WeakMap: keys must be objects; references are "weak"
// (doesn't prevent garbage collection of the key object)
const weakMap = new WeakMap();

let user = { name: "Alice" };
weakMap.set(user, "metadata");

console.log(weakMap.get(user));  // "metadata"

user = null;  // remove the only strong reference
// Now the object can be GC'd, and WeakMap entry disappears automatically

// Practical: attach data to DOM elements without memory leaks
const elementData = new WeakMap();
function cacheData(element, data) {
  elementData.set(element, data);
}
// When the DOM element is removed, its data is auto-cleaned

// WeakSet: collection of objects, weakly held
const processed = new WeakSet();
function processOnce(obj) {
  if (processed.has(obj)) return;  // already done
  processed.add(obj);
  doWork(obj);
}

// WeakMap/WeakSet are NOT iterable (no .size, no for...of)

Map の反復と変換

Map は for...of(デフォルトでエントリ)、.keys()、.values()、.forEach() で挿入順序で反復可能です。Object.entries() と Object.fromEntries() で Map と Object を相互変換できます。API がプレーンオブジェクトを期待するが内部的に Map の機能を使いたい場合に、この双方向変換が便利です。注意:変換で Object のキーは文字列型になります。

javascript
const map = new Map([
  ["name", "Alice"],
  ["age", 30],
  ["city", "NYC"],
]);

// Iterate entries (default)
for (const [key, value] of map) {
  console.log(`${key} = ${value}`);
}

// Iterate keys only
for (const key of map.keys()) {
  console.log(key);
}

// Iterate values only
for (const value of map.values()) {
  console.log(value);
}

// forEach (callback style)
map.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});

// Convert to Array
const entries = [...map.entries()];  // [["name","Alice"], ["age",30], ...]
const keys = [...map.keys()];        // ["name", "age", "city"]
const values = [...map.values()];    // ["Alice", 30, "NYC"]

// Convert Object to Map and back
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));
const objFromMap = Object.fromEntries(map);

Map、Set、Object、Array の選択

適切なコレクションを選択してください:配列は重複とインデックスアクセスのある順序付きリスト、Object は固定形状のレコードと JSON、Map は任意のキー型の動的キーと値コレクション、Set は一意性と高速ルックアップ。間違った構造を使用すると冗長なコードとパフォーマンス問題につながります — 例:ループ内で Array.includes() をチェックする vs Set.has()。

javascript
// Decision guide:

// Use ARRAY when:
// - You need ordered data with duplicates
// - You need index-based access (arr[5])
// - You'll map/filter/reduce frequently
const todoList = ["buy milk", "walk dog"];

// Use OBJECT when:
// - You have a fixed known shape (like a user record)
// - You need JSON serialization (JSON.stringify)
// - Keys are strings and known at write time
const user = { name: "Alice", age: 30 };

// Use MAP when:
// - Keys are not strings (objects, functions, numbers)
// - You frequently add/remove key-value pairs
// - You need to iterate in insertion order
// - The collection size changes dynamically
const handlers = new Map();
handlers.set(buttonElement, () => onClick());

// Use SET when:
// - You need unique values (no duplicates)
// - You need fast membership testing (.has is O(1))
// - Order doesn't matter much
const seen = new Set();
if (!seen.has(url)) { seen.add(url); visit(url); }
18

ジェネレータとイテレータ

イテレータと Symbol.iterator

イテレータは {value, done} を返す next() メソッドを持ちます。イテラブルは Symbol.iterator を実装し、イテレータを返します。組み込みイテラブル(配列、文字列、Map、Set)は for...of、spread(...)、分割代入、Array.from() で動作します。Symbol.iterator を実装すると、カスタムオブジェクトがこれらの言語機能すべてでシームレスに動作します。

javascript
// An iterator is an object with a next() method returning {value, done}
function makeIterator(arr) {
  let i = 0;
  return {
    next() {
      return i < arr.length
        ? { value: arr[i++], done: false }
        : { value: undefined, done: true };
    },
  };
}

const it = makeIterator(["a", "b", "c"]);
console.log(it.next());  // { value: "a", done: false }
console.log(it.next());  // { value: "b", done: false }
console.log(it.next());  // { value: "c", done: false }
console.log(it.next());  // { value: undefined, done: true }

// An iterable implements Symbol.iterator (returns an iterator)
const range = {
  from: 1, to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { done: true };
      },
    };
  },
};

// Now range works with for...of, spread, etc.
console.log([...range]);  // [1, 2, 3]

ジェネレータ関数(function*)

ジェネレータ関数(function*)は yield を使用して実行を一時停止・再開します — 値を遅延生成し、一度に1つずつ生成します。これにより無限シーケンス、遅延評価、メモリ効率の良いパイプラインが可能になります。ジェネレータはイテレータでもありイテラブルでもあります。next() の各呼び出しは次の yield(または return)まで実行します。非同期ジェネレータと JS コルーチンパターンの基盤です。

javascript
// Generators: pause execution with 'yield', resume with next()
function* idGenerator() {
  let id = 1;
  while (true) {
    yield id++;  // pause here, return value, resume on next()
  }
}

const gen = idGenerator();
console.log(gen.next());  // { value: 1, done: false }
console.log(gen.next());  // { value: 2, done: false }
console.log(gen.next());  // { value: 3, done: false }
// Infinite sequence — only computes values on demand

// Generators are iterables (work with for...of)
function* take(n, iterable) {
  for (const item of iterable) {
    if (n-- <= 0) return;
    yield item;
  }
}

function* naturalNumbers() {
  let n = 1;
  while (true) yield n++;
}

const firstFive = [...take(5, naturalNumbers())];
console.log(firstFive);  // [1, 2, 3, 4, 5]

yield*(別のジェネレータへの委譲)

yield* はすべての yield を別のイテラブルやジェネレータに委譲します — ネストされた構造を平坦化し、ジェネレータをクリーンに構成します。Python の 'yield from' に相当します。委譲されたジェネレータの値が、外側のジェネレータの一部であるかのように1つずつ yield されます。ジェネレータを再帰的に構成する標準的な方法です。

javascript
// yield* delegates to another iterable/generator
function* inner() {
  yield "a";
  yield "b";
}

function* outer() {
  yield 1;
  yield* inner();     // delegates: yields "a", then "b"
  yield* [10, 20];    // also works with arrays
  yield 2;
}

console.log([...outer()]);  // [1, "a", "b", 10, 20, 2]

// Practical: flatten nested structures
function* flatten(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      yield* flatten(item);  // recursive delegation
    } else {
      yield item;
    }
  }
}

const nested = [1, [2, [3, 4]], 5];
console.log([...flatten(nested)]);  // [1, 2, 3, 4, 5]

next(value) によるジェネレータ send()

ジェネレータは双方向通信をサポートします:next(value) は値を渡し(最後の yield 式の結果になる)、throw(error) は yield ポイントに例外を注入します。これによりコルーチン、状態機械、初期の async/await 実装を支えたジェネレータベースの非同期パターンが可能になります。最初の next() 呼び出しでは値を渡せません(受け取るものがまだないため)。

javascript
// next(value) passes a value INTO the generator (becomes yield's result)
function* conversation() {
  const name = yield "What's your name?";  // receives via next()
  const age = yield `Hello ${name}, how old are you?`;
  return `${name} is ${age} years old`;
}

const talk = conversation();
console.log(talk.next().value);          // "What's your name?"
console.log(talk.next("Alice").value);   // "Hello Alice, how old are you?"
console.log(talk.next(30).value);        // "Alice is 30 years old"

// throw(error) injects an exception at the yield point
function* safeGen() {
  try {
    yield "step 1";
  } catch (e) {
    console.log("Caught:", e.message);
    yield "recovered";
  }
}
const g = safeGen();
g.next();           // "step 1"
g.throw(new Error("oops"));  // "Caught: oops" -> "recovered"

非同期ジェネレータ

非同期ジェネレータ(async function*)はジェネレータと async/await を組み合わせます — 各 yield は await 後に値を生成できます。for await...of で消費します。ページネーションされた API、ストリーミングデータ、非同期で値を生成するあらゆるシナリオに理想的です。コールバックや手動の Promise チェーンなしで JS でストリーミングデータを処理するモダンな方法です。

javascript
// async function* : yields promises, can use await
async function* fetchPages(url) {
  let page = 1;
  while (true) {
    const res = await fetch(`${url}?page=${page}`);
    const data = await res.json();
    if (data.items.length === 0) break;  // no more pages
    yield data.items;
    page++;
  }
}

// Consume with for await...of
(async () => {
  for await (const items of fetchPages("/api/products")) {
    console.log(`Got ${items.length} items`);
    renderItems(items);
  }
  console.log("All pages loaded");
})();

// Practical: stream data as it arrives
async function* streamLines(response) {
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop();
    for (const line of lines) yield line;
  }
}
19

Proxy と Reflect

Proxy の基礎(操作のインターセプト)

Proxy はオブジェクトの基本操作(get、set、has、deleteProperty、ownKeys など)をインターセプトしてカスタマイズできます。ハンドラオブジェクトは「トラップ」を定義します(すべてのプロパティに対するゲッター/セッターのようなもの)。ユースケース:バリデーション、ロギング、デフォルト値、アクセス制御、リアクティブシステム(Vue 3 はリアクティビティに Proxy を使用)。strict モードで成功を示すには set から true を返してください。

javascript
// Proxy wraps an object and intercepts fundamental operations
const target = { name: "Alice", age: 30 };

const handler = {
  get(obj, prop) {
    console.log(`Reading ${prop}`);
    return prop in obj ? obj[prop] : "N/A";  // default value
  },
  set(obj, prop, value) {
    console.log(`Setting ${prop} = ${value}`);
    if (prop === "age" && value < 0) return false;  // validation
    obj[prop] = value;
    return true;  // indicate success
  },
};

const proxy = new Proxy(target, handler);
console.log(proxy.name);   // logs "Reading name", returns "Alice"
proxy.age = 25;            // logs "Setting age = 25"
// proxy.age = -5;         // logs "Setting age = -5", rejected
console.log(proxy.unknown); // logs "Reading unknown", returns "N/A"

一般的な Proxy のユースケース

Proxy は強力なメタプログラミングを可能にします:負のインデックス、遅延プロパティ計算、読み取り専用ビュー、バリデーション、ロギング、リアクティブデータバインディング。Vue 3 のリアクティビティシステムは Proxy を使用してプロパティアクセスを追跡し、自動的に再レンダリングをトリガーします。トレードオフはわずかなパフォーマンスオーバーヘッドです。抽象化が価値ある場合にのみ Proxy を使用してください。

javascript
// 1. Negative array indices (like Python)
function negativeArray(arr) {
  return new Proxy(arr, {
    get(target, prop) {
      const idx = Number(prop);
      if (Number.isInteger(idx)) {
        return target[idx < 0 ? target.length + idx : idx];
      }
      return target[prop];
    },
  });
}
const arr = negativeArray(["a", "b", "c"]);
console.log(arr[-1]);  // "c"

// 2. Auto-populating / lazy properties
const lazy = new Proxy({}, {
  get(target, prop) {
    if (!(prop in target)) {
      target[prop] = expensiveCompute(prop);  // compute on first access
    }
    return target[prop];
  },
});

// 3. Read-only object
const readOnly = new Proxy(data, {
  set() { throw new Error("This object is read-only"); },
  deleteProperty() { throw new Error("Cannot delete"); },
});

Reflect API

Reflect は Proxy トラップがインターセプトするのと同じ操作を提供します — トラップ内でデフォルト動作に転送するために使用します。Reflect メソッドは例外をスローする代わりにブール値(成功/失敗)を返し、条件ロジックに安全です。Reflect.ownKeys はすべてのキー(文字列とシンボル)を返します。列挙可能な文字列キーのみを返す Object.keys とは異なります。Proxy と Reflect で JS のメタプログラミングツールキットを形成します。

javascript
// Reflect provides default behavior for proxy traps and
// functional equivalents of object operations

const obj = { x: 1, y: 2 };

// Reflect.get / Reflect.set (instead of obj[prop])
console.log(Reflect.get(obj, "x"));    // 1
Reflect.set(obj, "z", 3);              // obj.z = 3
console.log(Reflect.has(obj, "x"));    // true (like "x" in obj)
Reflect.deleteProperty(obj, "y");      // delete obj.y
console.log(Reflect.ownKeys(obj));     // ["x", "z"]

// In a Proxy, use Reflect to forward to the default behavior
const proxy = new Proxy({}, {
  get(target, prop, receiver) {
    console.log(`get ${prop}`);
    return Reflect.get(target, prop, receiver);  // default behavior
  },
  set(target, prop, value, receiver) {
    console.log(`set ${prop}`);
    return Reflect.set(target, prop, value, receiver);
  },
});

// Reflect.construct: call a constructor with an array of args
const instance = Reflect.construct(Array, [1, 2, 3]);

Proxy によるリアクティブオブジェクト(Vue スタイル)

これが Vue 3 のリアクティビティの核となるアイデアです — Proxy が get(どのエフェクトがプロパティに依存するかを追跡)と set(プロパティ変更時にそれらのエフェクトをトリガー)をインターセプトします。これにより宣言的 UI 更新が可能になります:状態を変更すると、フレームワークが自動的に再レンダリングします。Proxy により ES6 でこれが可能になりました。以前のフレームワーク(Vue 2)は制限のある Object.defineProperty を使用していました。

javascript
// A minimal reactive system using Proxy
function reactive(target) {
  const subscribers = new Set();

  const handler = {
    get(obj, prop) {
      track(prop);  // record who reads this property
      const value = Reflect.get(obj, prop);
      return typeof value === "object" && value !== null
        ? reactive(value)  // deeply reactive
        : value;
    },
    set(obj, prop, value) {
      const result = Reflect.set(obj, prop, value);
      trigger(prop);  // notify subscribers
      return result;
    },
  };

  let currentEffect = null;
  function track(prop) {
    if (currentEffect) subscribers.add(currentEffect);
  }
  function trigger(prop) {
    subscribers.forEach(fn => fn());
  }

  return new Proxy(target, handler);
}

const state = reactive({ count: 0 });
// When state.count changes, effects re-run automatically
20

Web Workers

Web Worker の作成

Web Worker は別のバックグラウンドスレッドで JavaScript を実行し、UI をブロックせずに真の並行処理を可能にします。メインスレッドとワーカーは postMessage で通信します(データは共有ではなくコピー/構造的クローンされます)。ワーカーは DOM や window オブジェクトにアクセスできません — 隔離されています。画像処理、大きなファイルの解析、複雑な計算などの CPU 集約的タスクにワーカーを使用してください。

javascript
// main.js — workers run JS in a background thread
const worker = new Worker("worker.js");

// Send data to the worker
worker.postMessage({ command: "calculate", data: [1, 2, 3, 4, 5] });

// Receive results from the worker
worker.onmessage = (event) => {
  console.log("Result from worker:", event.data);
};

worker.onerror = (error) => {
  console.error("Worker error:", error.message);
};

// Terminate when done (frees resources)
// worker.terminate();

// --- worker.js (separate file) ---
// self.onmessage = (event) => {
//   const { command, data } = event.data;
//   const result = heavyComputation(data);
//   self.postMessage(result);
// };

インラインワーカー(Blob URL)

インラインワーカーは Blob URL を介してコード文字列から Worker を作成します — 個別ファイル不要です。デモ、小さなユーティリティ、ビルドシステムが個別のワーカーファイルを簡単に処理できない場合に便利です。メモリリークを避けるためオブジェクト URL を取り消してください。ワーカーコードは文字列なので、エディタのシンタックスハイライトと型チェックが失われます — 本番では注意して使用してください。

javascript
// Create a worker from a string (no separate file needed)
const workerCode = `
  self.onmessage = function(e) {
    const result = e.data.map(x => x * x);
    self.postMessage(result);
  };
`;

const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

worker.postMessage([1, 2, 3, 4]);
worker.onmessage = (e) => console.log(e.data);  // [1, 4, 9, 16]

// Clean up the blob URL when done
// URL.revokeObjectURL(blob URL);

// Useful for: single-file demos, bundlers that inline workers,
// or when you can't serve a separate .js file

転送可能オブジェクト(ゼロコピー)

通常、postMessage はデータをコピーします(構造的クローン)。これは大きなバッファには遅いです。転送リスト(第2引数)は ArrayBuffer/MessagePort/ImageBitmap の所有権をゼロコピーでワーカーに移動します — サイズに関わらずほぼ瞬時です。元のバッファはデタッチされます(使用不可)。大きなデータセット、画像処理、オーディオでコピーのオーバーヘッドを回避するために使用してください。

javascript
// Transferable objects move data to a worker WITHOUT copying
// (the original becomes unusable) — much faster for large ArrayBuffers

// Create a large buffer
const buffer = new ArrayBuffer(1024 * 1024 * 10);  // 10MB
const view = new Float64Array(buffer);
view[0] = 3.14;

// Transfer the buffer (second arg = transfer list)
worker.postMessage({ data: buffer }, [buffer]);

// After transfer, 'buffer' is detached (length becomes 0)
console.log(buffer.byteLength);  // 0 — ownership moved to worker

// Worker receives it normally:
// self.onmessage = (e) => {
//   const buf = e.data.data;  // full 10MB, no copy
// };

// Transferable types: ArrayBuffer, MessagePort, ImageBitmap
// Use for: large datasets, image data, audio buffers

SharedArrayBuffer と Atomics

SharedArrayBuffer はスレッド間で真の共有メモリを可能にし(コピーなし)、Atomics はその上でスレッドセーフな操作(add、load、store、compareExchange、wait/notify)を提供します。これにより JS で高性能な並行アルゴリズムが可能になります。Spectre セキュリティの懸念により、SharedArrayBuffer はクロスオリジン分離 HTTP ヘッダーが必要です — ない場合はモダンブラウザで無効化されます。WASM 相互運用と重い並行計算に使用してください。

javascript
// SharedArrayBuffer: memory shared between main thread and workers
// (both can read/write simultaneously) — true shared memory

// const sharedBuffer = new SharedArrayBuffer(1024);
// const view = new Int32Array(sharedBuffer);
// worker.postMessage({ buffer: sharedBuffer });

// Atomics: thread-safe operations on SharedArrayBuffer
// const sharedArray = new Int32Array(sharedBuffer);
// Atomics.add(sharedArray, 0, 1);     // atomic increment
// Atomics.load(sharedArray, 0);       // atomic read
// Atomics.store(sharedArray, 0, 42);  // atomic write
// Atomics.compareExchange(sharedArray, 0, 42, 99);  // CAS

// Atomics.wait / notify: block and wake threads
// Worker:
//   Atomics.wait(sharedArray, 0, 0);  // block until index 0 != 0
// Main:
//   Atomics.store(sharedArray, 0, 1);
//   Atomics.notify(sharedArray, 0);   // wake waiting workers

// NOTE: Requires cross-origin isolation headers:
//   Cross-Origin-Opener-Policy: same-origin
//   Cross-Origin-Embedder-Policy: require-corp

ワーカープールパターン

ワーカー作成にはオーバーヘッドがあるため、ワーカープールは固定数のワーカーを再利用し、タスクをキューイングします — 他の言語のスレッドプールのようなものです。このパターンは CPU 使用率を最大化し(コアごとに1ワーカー)、タスクごとのワーカー生成コストを回避します。プールは空きワーカーにタスクをディスパッチし、すべてビジーの時はキューイングします。多くの独立した作業チャンクの処理に不可欠です。

javascript
// A worker pool reuses workers to avoid creation overhead
class WorkerPool {
  constructor(workerUrl, size = 4) {
    this.workers = [];
    this.queue = [];
    for (let i = 0; i < size; i++) {
      const worker = new Worker(workerUrl);
      worker.busy = false;
      this.workers.push(worker);
    }
  }

  run(data) {
    return new Promise((resolve, reject) => {
      const task = { data, resolve, reject };
      const worker = this.workers.find(w => !w.busy);
      if (worker) {
        this.execute(worker, task);
      } else {
        this.queue.push(task);  // wait for a free worker
      }
    });
  }

  execute(worker, task) {
    worker.busy = true;
    worker.onmessage = (e) => {
      worker.busy = false;
      task.resolve(e.data);
      this.next();
    };
    worker.postMessage(task.data);
  }

  next() {
    const task = this.queue.shift();
    const worker = this.workers.find(w => !w.busy);
    if (task && worker) this.execute(worker, task);
  }
}
21

DOM 操作の詳細

querySelector

querySelector は最初のマッチを返し、querySelectorAll は静的な NodeList を返します。getElementsByClassName はライブな HTMLCollection を返します。NodeList は forEach をサポートしますが、HTMLCollection はサポートしません。

javascript
const el = document.querySelector('.my-class');
const all = document.querySelectorAll('div.item');
all.forEach(el => console.log(el.textContent));
const live = document.getElementsByClassName('item');  // live
const static = document.querySelectorAll('.item');     // static

作成と挿入

createElement は新しい要素を作成します。appendChild は最後の子として追加し、prepend は最初として追加します。textContent は XSS を防ぐため innerHTML より安全です。

javascript
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello';
div.setAttribute('data-id', '42');
const parent = document.querySelector('#container');
parent.appendChild(div);
parent.prepend(div);

イベント委任

イベント委任は複数の子ではなく親に1つのリスナーをアタッチします。closest はセレクタにマッチする最も近い祖先を見つけます。動的に追加された要素を処理します。

javascript
document.querySelector('#list').addEventListener('click', (e) => {
    const item = e.target.closest('.list-item');
    if (!item) return;
    console.log('Clicked:', item.dataset.id);
});

classList API

classList は CSS クラスを安全に操作するメソッドを提供します。toggle は追加された場合 true を、削除された場合 false を返します。className 文字列の操作よりクリーンです。

javascript
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('dark-mode');
el.classList.replace('old', 'new');
if (el.classList.contains('active')) { /* ... */ }

Dataset 属性

data-* 属性はカスタムデータを格納します。dataset は camelCase アクセスを提供します:data-user-id は dataset.userId になります。値は常に文字列です。

javascript
// HTML: <div data-user-id="42" data-role="admin"></div>
const el = document.querySelector('[data-user-id]');
console.log(el.dataset.userId);  // "42"
console.log(el.dataset.role);    // "admin"
el.dataset.userId = '99';
22

Canvas API

基本的な描画

getContext("2d") は 2D レンダリングコンテキストを返します。fillRect は塗りつぶされた矩形を描画し、strokeRect はアウトラインを描画します。描画前に fillStyle/strokeStyle を設定してください。

javascript
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 50);

パスとライン

moveTo は描画せずにカーソルを移動します。lineTo はラインを描画します。closePath は開始点に戻ります。arc は円/円弧を描画します。

javascript
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 35, 25, 0, Math.PI * 2);
ctx.fill();

テキストとグラデーション

createLinearGradient はグラデーションを作成します。addColorStop は位置(0-1)で色を定義します。テキスト描画前に font と textAlign を設定してください。

javascript
const grad = ctx.createLinearGradient(0, 0, 200, 0);
grad.addColorStop(0, 'red');
grad.addColorStop(1, 'blue');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 100);
ctx.font = '24px Arial';
ctx.fillText('Hello Canvas', 100, 50);

アニメーションループ

requestAnimationFrame はディスプレイのリフレッシュと同期します(約60fps)。各フレームの前に clearRect でクリアします。cancelAnimationFrame でキャンセルします。

javascript
let x = 0;
function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'green';
    ctx.fillRect(x, 50, 30, 30);
    x += 2;
    if (x > canvas.width) x = 0;
    requestAnimationFrame(animate);
}
animate();

画像操作

drawImage は画像をレンダリングします。getImageData はピクセルデータを RGBA 配列として返します。フィルターのためにピクセルを操作します。putImageData は変更したピクセルを書き戻します。

javascript
const img = new Image();
img.onload = () => {
    ctx.drawImage(img, 0, 0, 200, 150);
    const data = ctx.getImageData(0, 0, 200, 150);
    for (let i = 0; i < data.data.length; i += 4)
        data.data[i] = 255 - data.data[i];
    ctx.putImageData(data, 0, 0);
};
img.src = 'photo.jpg';
23

WebSockets

基本的な WebSocket

WebSocket は単一の TCP 接続上で全二重通信を提供します。onopen は接続時に、onmessage はデータ到着時に発火します。常に onerror を処理してください。

javascript
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => { ws.send('Hello Server'); };
ws.onmessage = (e) => { console.log('Received:', e.data); };
ws.onclose = () => console.log('Disconnected');
ws.onerror = (err) => console.error('Error:', err);

JSON の送信と受信

WebSocket データは文字列またはバイナリとして送信されます。JSON.stringify/parse で構造化データ交換を可能にします。type フィールドでメッセージルーティングを可能にします。

javascript
ws.onopen = () => {
    ws.send(JSON.stringify({ type: 'message', text: 'Hello' }));
};
ws.onmessage = (e) => {
    const data = JSON.parse(e.data);
    switch (data.type) {
        case 'message': console.log(data.text); break;
    }
};

再接続

WebSocket 接続は切断される可能性があります。指数バックオフ(2^retries)でサーバーへの過負荷を防ぎます。遅延を 30秒でキャップします。成功時にリトライカウントをリセットします。

javascript
class ReconnectingWS {
    constructor(url) { this.url = url; this.retries = 0; this.connect(); }
    connect() {
        this.ws = new WebSocket(this.url);
        this.ws.onopen = () => { this.retries = 0; };
        this.ws.onclose = () => {
            const delay = Math.min(1000 * 2 ** this.retries++, 30000);
            setTimeout(() => this.connect(), delay);
        };
    }
}

バイナリデータ

WebSocket は ArrayBuffer でバイナリデータをサポートします。binaryType を arraybuffer に設定します。DataView で型付きアクセスを提供します。数値データにはバイナリの方が効率的です。

javascript
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 42);
ws.send(buffer);
ws.binaryType = 'arraybuffer';
ws.onmessage = (e) => {
    if (e.data instanceof ArrayBuffer) {
        const v = new DataView(e.data);
        console.log(v.getInt32(0));
    }
};

ハートビート

ハートビートは失効した接続を検出します。ping を送信し pong を期待します。タイムアウト内に pong がない場合、閉じて再接続します。readyState で接続状態をチェックします。

javascript
setInterval(() => {
    if (ws.readyState === WebSocket.OPEN)
        ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
setInterval(() => {
    if (Date.now() - lastPong > 60000) ws.close();
}, 10000);
24

Service Workers

登録

Service Worker は別スレッドで実行され、ネットワークリクエストをインターセプトします。登録は HTTPS または localhost で行う必要があります。SW ファイルの場所がスコープを決定します。

javascript
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(reg => console.log('Registered:', reg.scope))
        .catch(err => console.error('Failed:', err));
}

キャッシュ

キャッシュファースト戦略:キャッシュから配信し、ネットワークにフォールバック。install でアセットをプリキャッシュ。fetch でリクエストをインターセプト。その他の戦略:ネットワークファースト、stale-while-revalidate。

javascript
const CACHE = 'app-v1';
self.addEventListener('install', (e) => {
    e.waitUntil(caches.open(CACHE)
        .then(c => c.addAll(['/', '/index.html', '/style.css'])));
});
self.addEventListener('fetch', (e) => {
    e.respondWith(caches.match(e.request)
        .then(cached => cached || fetch(e.request)));
});

バックグラウンド同期

バックグラウンド同期は接続が復帰するまでアクションを延期します。ネットワークが利用可能になると sync イベントが発火します。保留中のアクションを IndexedDB に格納します。

javascript
navigator.serviceWorker.ready.then(reg =>
    reg.sync.register('send-messages'));
self.addEventListener('sync', (e) => {
    if (e.tag === 'send-messages')
        e.waitUntil(sendPendingMessages());
});

プッシュ通知

プッシュ通知はアプリが閉じていても機能します。subscribe は VAPID キーを使用してプッシュサービスに登録します。userVisibleOnly は通知の表示を要求します。

javascript
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: vapidPublicKey
});
self.addEventListener('push', (e) => {
    const data = e.data.json();
    e.waitUntil(self.registration.showNotification(data.title, {
        body: data.body
    }));
});

更新とアクティベート

skipWaiting は新しい SW を即座にアクティベートします。activate は古いキャッシュをクリーンアップします。clients.claim は即座に制御を取得します。キャッシュ名をバージョン管理して更新をトリガーします。

javascript
self.addEventListener('install', (e) => {
    self.skipWaiting();
});
self.addEventListener('activate', (e) => {
    e.waitUntil(caches.keys().then(keys =>
        Promise.all(keys.filter(k => k !== CACHE)
            .map(k => caches.delete(k)))));
    self.clients.claim();
});
25

IndexedDB

データベースを開く

IndexedDB はブラウザ内の NoSQL データベースです。onupgradeneeded はバージョン変更時に発火し、オブジェクトストアの作成に使用します。keyPath は主キーを定義します。

javascript
const req = indexedDB.open('MyDatabase', 1);
req.onupgradeneeded = (e) => {
    const db = e.target.result;
    if (!db.objectStoreNames.contains('users'))
        db.createObjectStore('users', { keyPath: 'id' });
};
req.onsuccess = (e) => { const db = e.target.result; };

追加と Put

トランザクションは操作を原子的にグループ化します。add は重複キーで失敗し、put は上書きします。readwrite が変更を許可します。oncomplete は成功時に発火します。

javascript
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ id: 1, name: 'Alice' });  // Fails if key exists
store.put({ id: 1, name: 'Bob' });   // Overwrites
tx.oncomplete = () => console.log('Saved');

データの照会

get はキーで取得します。openCursor はレコードを反復します。cursor.continue で次に移動します。async/await のために Promise でラップしてください。

javascript
const tx = db.transaction('users', 'readonly');
const req = tx.objectStore('users').get(1);
req.onsuccess = () => console.log(req.result);
const cursorReq = tx.objectStore('users').openCursor();
cursorReq.onsuccess = (e) => {
    const cursor = e.target.result;
    if (cursor) { console.log(cursor.value); cursor.continue(); }
};

インデックスクエリ

インデックスは非キーフィールドでのクエリを可能にします。IDBKeyRange は範囲を作成します。インデックスは onupgradeneeded で作成する必要があります。

javascript
const tx = db.transaction('users', 'readonly');
const index = tx.objectStore('users').index('name');
index.get('Alice').onsuccess = (e) =>
    console.log(e.target.result);
const range = IDBKeyRange.bound('A', 'M');
index.openCursor(range).onsuccess = (e) => {
    const cursor = e.target.result;
    if (cursor) { cursor.continue(); }
};

削除とクリア

delete は単一レコードを削除します。clear はすべてのレコードを削除します。deleteDatabase はデータベース全体を削除します。すべての変更には readwrite トランザクションが必要です。

javascript
const tx = db.transaction('users', 'readwrite');
tx.objectStore('users').delete(1);  // Delete by key
tx.objectStore('users').clear();   // Clear all
indexedDB.deleteDatabase('MyDatabase');  // Delete DB
tx.oncomplete = () => console.log('Done');
26

WebRTC

ユーザーメディアの取得

getUserMedia はカメラとマイクへのアクセスを要求します。MediaStream を返します。srcObject でストリームを video 要素に割り当てます。HTTPS とユーザー許可が必要です。

javascript
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream => {
        document.querySelector('video').srcObject = stream;
    });

ピア接続

RTCPeerConnection は P2P 接続を確立します。addTrack はメディアを追加します。ontrack はリモートストリームを受信します。ICE 候補はシグナリング経由で交換されるネットワークパスです。

javascript
const pc = new RTCPeerConnection(config);
stream.getTracks().forEach(t => pc.addTrack(t, stream));
pc.ontrack = (e) => { remoteVideo.srcObject = e.streams[0]; };
pc.onicecandidate = (e) => {
    if (e.candidate) sendToPeer(e.candidate);
};

Offer と Answer

SDP ネゴシエーション:offer/answer 交換でメディアフォーマットを記述します。setLocalDescription はローカル SDP を設定し、setRemoteDescription はリモート SDP を設定します。

javascript
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendToPeer(offer);
// Callee:
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendToPeer(answer);

データチャネル

データチャネルは WebRTC 上で低遅延の任意のデータ転送を可能にします。createDataChannel はオファー側で作成します。ondatachannel はアンサー側で受信します。

javascript
const channel = pc.createDataChannel('chat');
channel.onopen = () => console.log('Connected');
channel.onmessage = (e) => console.log('Received:', e.data);
channel.send('Hello peer!');
pc.ondatachannel = (e) => {
    e.channel.onmessage = (ev) => console.log(ev.data);
};

画面共有

getDisplayMedia は画面、ウィンドウ、タブをキャプチャします。ブラウザがピッカーを表示します。ユーザーが共有を停止すると onended が発火します。常にクリーンアップを処理してください。

javascript
const stream = await navigator.mediaDevices.getDisplayMedia({
    video: { frameRate: 30 }, audio: true
});
video.srcObject = stream;
stream.getVideoTracks()[0].onended = () =>
    console.log('Stopped');
27

パフォーマンス最適化

デバウンスとスロットル

デバウンスは呼び出しが止まるまで実行を遅延します(検索に適しています)。スロットルはインターバルごとに1回に制限します(スクロールに適しています)。どちらも過剰な呼び出しを防ぎます。

javascript
function debounce(fn, delay) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    };
}
function throttle(fn, limit) {
    let inThrottle;
    return (...args) => {
        if (!inThrottle) { fn(...args); inThrottle = true;
            setTimeout(() => inThrottle = false, limit); }
    };
}

Web Workers

Web Worker は CPU 集約的タスクのために別スレッドで JavaScript を実行します。データは postMessage で渡されます。ワーカーは DOM にアクセスできません。

javascript
const worker = new Worker('worker.js');
worker.postMessage({ data: [1, 2, 3] });
worker.onmessage = (e) => console.log('Result:', e.data);
// worker.js
self.onmessage = (e) => {
    self.postMessage(e.data.data.map(x => x ** 2));
};

遅延読み込み

IntersectionObserver は要素がビューポートに入った時に発火します。data-src が実際の URL を保持し、可視時に src を設定します。画像の多いページの初期読み込みを削減します。

javascript
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            observer.unobserve(img);
        }
    });
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));

DOM 更新のバッチ化

DocumentFragment は DOM 挿入を1回のリフローにバッチ化し、1つずつ追加するよりはるかに高速です。レイアウトスラッシングを最小化してください。

javascript
const fragment = document.createDocumentFragment();
items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    fragment.appendChild(li);
});
list.appendChild(fragment);  // Single reflow

メモリ管理

WeakMap はキーの GC を許可し、リークを防ぎます。要素削除時に常にイベントリスナーを削除してください。GC のために大きなオブジェクトを null に設定します。

javascript
const cache = new WeakMap();
cache.set(element, data);
// When element is GC'd, entry is removed
element.removeEventListener('click', handler);
bigArray = null;  // Allow GC
28

セキュリティ (XSS/CSRF)

XSS 防止

XSS はユーザー入力を介して悪意のあるスクリプトを注入します。信頼できないデータで innerHTML を使用しないでください。textContent は安全です。CSP ヘッダーがスクリプトソースを制限します。

javascript
// BAD: vulnerable to XSS
element.innerHTML = userInput;
// GOOD: safe alternatives
element.textContent = userInput;
function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
}

CSRF 防止

CSRF はユーザーをだまして意図しないアクションを実行させます。トークンでリクエストがアプリから来たことを確認します。SameSite=Strict Cookie はクロスサイトで送信されません。状態変更操作には CSRF トークンを使用してください。

javascript
const token = document.cookie.match(/csrf_token=([^;]+)/)?.[1];
fetch('/api/data', {
    method: 'POST',
    headers: { 'X-CSRF-Token': token }
});
// SameSite cookie: Set-Cookie: session=abc; SameSite=Strict

Content Security Policy

CSP はどのリソースが読み込み可能かを制限します。default-src はフォールバックです。script-src が JavaScript を制御します。強制前に report-only モードから始めてください。

javascript
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' https://cdn.example.com;
               style-src 'self' 'unsafe-inline';
               img-src 'self' data:">

セキュアな Cookie

HttpOnly は JavaScript の Cookie アクセスを防ぎ、XSS を軽減します。Secure は HTTPS のみを保証します。SameSite=Strict は CSRF を防ぎます。セッション Cookie には常に両方を使用してください。

javascript
// Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
// HttpOnly: not accessible via JavaScript
// Secure: only sent over HTTPS
// SameSite: Strict | Lax | None
document.cookie  // Cannot see HttpOnly cookies

入力バリデーション

クライアント側のバリデーションは UX を向上させますがセキュリティではありません。常にサーバーでバリデーションしてください。HTML サニタイズには DOMPurify を使用します。許可するタグをホワイトリスト化します。

javascript
function validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function sanitize(html) {
    return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'br', 'strong'] });
}
// Never trust client input - always validate on server too
29

デザインパターン

Singleton

Singleton は1つのインスタンスのみが存在することを保証します。コンストラクタは既存のインスタンスを返します。設定、ロギング、データベース接続に有用です。

javascript
class Config {
    constructor() {
        if (Config.instance) return Config.instance;
        this.settings = {};
        Config.instance = this;
    }
}
const c1 = new Config();
const c2 = new Config();
console.log(c1 === c2);  // true

Observer/Pub-Sub

Observer パターンはオブジェクトがイベントをサブスクライブできるようにします。on で登録、emit でトリガー、off で登録解除します。イベント駆動アーキテクチャの基盤です。

javascript
class EventEmitter {
    constructor() { this.events = {}; }
    on(event, cb) { (this.events[event] ||= []).push(cb); }
    emit(event, data) { (this.events[event] || []).forEach(cb => cb(data)); }
    off(event, cb) { this.events[event] = this.events[event]?.filter(c => c !== cb); }
}

Factory

Factory はインスタンス化ロジックを隠してオブジェクトを作成します。呼び出し側が型を指定し、ファクトリがどのクラスをインスタンス化するかを決定します。

javascript
class Dog { speak() { return 'Woof'; } }
class Cat { speak() { return 'Meow'; } }
function AnimalFactory(type) {
    switch (type) {
        case 'dog': return new Dog();
        case 'cat': return new Cat();
    }
}

Module パターン

Module パターンはクロージャを使用してプライベート状態をカプセル化します。IIFE がプライベートスコープを作成します。返されたメソッドのみが公開されます。

javascript
const counter = (() => {
    let count = 0;  // Private
    return {
        increment: () => ++count,
        getCount: () => count
    };
})();
counter.increment();
console.log(counter.getCount());  // 1

Strategy

Strategy パターンは交換可能なアルゴリズムをカプセル化します。コンテキストが選択されたストラテジに委譲します。大きな if/else チェーンを回避します。

javascript
const strategies = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b,
    multiply: (a, b) => a * b,
};
function calculate(strategy, a, b) {
    return strategies[strategy]?.(a, b);
}
30

よくある落とし穴

this のバインディング

this は関数の呼び出され方で決まります。アロー関数は囲みスコープから this を継承します。デタッチされたメソッドは this を失います。bind を使用してください。

javascript
const obj = {
    name: 'Alice',
    greet: function() { console.log(this.name); },
    arrow: () => console.log(this.name)
};
obj.greet();   // Alice
obj.arrow();   // undefined
const fn = obj.greet;
fn();  // undefined (lost binding)

浮動小数点

JavaScript は IEEE 754 浮動小数点を使用します。比較には Number.EPSILON を使用するか、整数で操作するために 10 のべき乗を掛けてください。

javascript
console.log(0.1 + 0.2);        // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
const eps = Number.EPSILON;
console.log(Math.abs(0.1 + 0.2 - 0.3) < eps);  // true

== と ===

== は型強制変換を行い、予期しない結果をもたらします。=== は強制変換なしで型と値の両方をチェックします。常に === と !== を使用してください。

javascript
console.log(0 == false);      // true
console.log('' == false);     // true
console.log(null == undefined); // true
console.log(0 === false);     // false
console.log('' === false);    // false
// Always use ===

ループ内のクロージャ

var は関数スコープのため、すべてのクロージャが同じ変数を共有します。let はブロックスコープで、反復ごとに新しいバインディングを作成します。常に let/const を使用してください。

javascript
// BUG: all print 3
for (var i = 0; i < 3; i++)
    setTimeout(() => console.log(i), 100);
// FIX: let
for (let i = 0; i < 3; i++)
    setTimeout(() => console.log(i), 100);

非同期エラー処理

未処理の Promise リジェクションは Node.js をクラッシュさせる可能性があります。常に await を try/catch で囲むか、.catch() を使用してください。unhandledRejection イベントをリッスンしてください。

javascript
// BUG: unhandled rejection
async function fetchData() {
    const data = await fetch('/api');
    return data.json();
}
// FIX: try/catch
async function fetchDataSafe() {
    try { return await (await fetch('/api')).json(); }
    catch (err) { console.error('Failed:', err); return null; }
}

関連する JavaScript スニペット

Copy-paste ready code for common tasks.

配列の Map Filter Reduce

配列に対する map、filter、reduce、find、some、every のチェーン。

配列の重複排除

Set を使用して配列を重複排除。

ディープクローン

オブジェクトのディープクローン、一般的なデータ型をサポート。

デバウンス関数

イベント発生後に一定時間待ってから実行。待機中に再度発生した場合はタイマーをリセット。

スロットル関数

一定時間間隔内に最大 1 回のみ関数を実行。

Promise.all 並行制御

並行数制限付きの Promise 実行関数。

async/await エラーハンドリング

async 関数をラップして例外を一元的にキャッチ。

Fetch ラッパー

タイムアウト、エラーハンドリング、JSON 解析付きの fetch ラッパー。

localStorage 操作

有効期限と JSON サポート付きの localStorage ラッパー。

Cookie 操作

Cookie の読み取り、書き込み、削除操作のラッパー。

URL パラメータ解析

URL クエリ文字列をオブジェクトに解析。

日付フォーマット

日付を指定した文字列にフォーマット。

金額フォーマット

数値を桁区切りされた金額文字列にフォーマット。

乱数生成

範囲内の乱数とランダム文字列を生成。

色変換

RGB と HEX 色の相互変換。

UUID 生成

UUID v4 準拠の一意識別子を生成。

文字列切り詰め

文字列を切り詰め、省略記号を追加。

配列のフラット化

多次元配列を 1 次元にフラット化。

オブジェクトのマージ

複数のオブジェクトをディープマージ。

型チェック

JavaScript のデータ型を正確に判定。

イベント委譲

イベントバブリングでイベント委譲を実装。

DOM 操作

DOM 要素を動的に作成して操作。

フォームバリデーション

一般的なフォームバリデーションルール集。

ファイルアップロード

プログレスとチャンク化をサポートするファイルアップロードラッパー。

画像の遅延読み込み

IntersectionObserver で画像の遅延読み込みを実装。

クリップボードにコピー

クロスブラウザのクリップボードコピーメソッド。

Fullscreen API

ブラウザのフルスクリーン操作のラッパー。

Geolocation

ユーザーの位置情報を取得。

Web Worker

時間のかかるタスクを実行する Web Worker を作成。

Service Worker

オフラインキャッシュ用の Service Worker を登録。

IndexedDB 操作

IndexedDB の CRUD 操作のラッパー。

Canvas 描画

基本的な Canvas 描画の例。

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.