はじめに
Hello World とコメント
JavaScript はブラウザと Node.js で実行されます。console.log() は主要なデバッグ出力です。JSDoc コメント(/** */)は IDE の型情報とドキュメントを提供します。より安全なパースのために 'use strict' または ES モジュールを使用してください。コメントは実行時に無視されます。
// 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'> をサポートしています。
"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 は宣言前の変数使用を防ぎます。
// 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 を使用してください。
// 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)。
// 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文字列
文字列メソッド
文字列は不変です — メソッドは新しい文字列を返します。slice() は負のインデックスをサポートしますが、substring() はサポートしません。replace() は最初のマッチのみ置換します。すべて置換するには replaceAll()(ES2021)を使用してください。at()(ES2022)は負のインデックスを許可します。split() + join() は文字列内の文字を置換する慣用的な方法です。
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 などで使用されています。
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() を使用してください。
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 コード単位の長さです。
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数値と数学
数値と演算子
JavaScript には単一の数値型(64ビット浮動小数点)しかありません — int/float の区別はありません。BigInt(n サフィックス)は 2^53 を超える整数を扱います。浮動小数点演算には精度問題があります(0.1 + 0.2 !== 0.3)— 比較には Number.EPSILON を使用してください。** はべき乗演算子(ES2016)です。
// 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); // trueMath オブジェクト
Math オブジェクトは定数と関数を提供します。すべての三角関数はラジアンを使用します。Math.random() は [0, 1) を返します — 整数範囲には乗算して floor してください。暗号論的な乱数には crypto.getRandomValues() を使用してください。Math.max/min は配列を直接受け入れません — ... で展開してください。
// 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 を使用してください。
// 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データ構造
配列
配列は動的、順序付き、異種混合型を保持できます。push/pop は O(1)、shift/unshift/splice は O(n) です。find()/findIndex() は述語関数を受け取ります。forEach() は何も返しません。変換には map() を使用してください。配列はオブジェクトです — typeof [] は 'object' です。チェックには Array.isArray() を使用してください。
// 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() はネストされた配列を平坦化します。
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 はキーを反復します(継承されたものを含む)。
// 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 は重複キーを上書きします(後勝ち)。関数パラメータの分割代入はオプション設定に強力です。
// 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 copyMap、Set、WeakMap
Map は任意のキー型を許可し、挿入順序を維持します。Set は一意の値を格納します — 重複排除に最適です。WeakMap/WeakSet のキーは弱参照され(ガベージコレクション可能)、メモリリークを防ぎます。非文字列キーや頻繁な追加/削除が必要な場合は Map を使用してください。
// 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制御フロー
If / Else と三項演算子
複雑なロジックには if/else を、単純な値の選択には三項演算子を使用してください。&& と || は短絡評価します(デフォルト値/条件付きに便利)。??(null 合体)は || とは異なり null/undefined のみをチェックします。?.(オプショナルチェーン)はエラーなしでネストされたプロパティに安全にアクセスします。
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 nullSwitch 文
switch は厳密等価(===)で比較するため、型が重要です。break を忘れないでください — ないと次の case にフォールスルーします。case を積み重ねてグループ化します(case 6: case 7:)。離散値には if/else の長いチェーンより switch がクリーンです。モダンなコードではオブジェクトルックアップテーブルを好む場合もあります。
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() を使用してください。
// 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() で再開します。ジェネレータは遅延評価で、無限にできます。カスタムイテラブル、シーケンス、非同期フローに使用してください。
// 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;
}関数
関数宣言と式
関数宣言は巻き上げられます(定義前に呼び出し可能)。式は巻き上げられません。アロー関数は簡潔で、独自の 'this' を持ちません(囲みスコープから継承)。IIFE はプライベートスコープを作成します(モジュールでは不要)。関数は第一級オブジェクトです — 引数として渡し、返し、変数に格納できます。
// 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' が必要なメソッドには使用できません。オブジェクトメソッドには通常の関数を使用してください。
// 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 のすべての関数はクロージャです。内部関数は外側の変数のコピーではなく参照を保持します。
// 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 パターンです。
// 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)OOP とクラス
クラスとコンストラクタ
ES6 クラスはプロトタイプの糖衣構文です。プライベートフィールド(#name)は ES2022 で真にプライベートです(_name 慣習とは異なります)。ゲッター/セッターは計算プロパティを可能にします。静的メンバーはインスタンスではなくクラスに属します。クラスフィールド(name = value)はコンストラクタなしでインスタンスプロパティを初期化します。
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 はプロトタイプチェーンをチェックします。ポリモーフィズムはメソッドオーバーライドで機能します。
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 を使用してください。クラスはこのシステムの糖衣構文です。組み込みプロトタイプの変更は危険です — コードを壊す可能性があります。深い継承よりコンポジションを優先してください。
// 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);
}エラー処理
Try / Catch / Finally
try/catch/finally は例外を処理します。catch はエラーオブジェクト(.message と .stack を持つ)をバインドします。instanceof で特定のエラー型を異なる方法で処理してください。予期したエラーを処理した後、未知のエラーは再スローしてください。アプリケーション固有のエラー処理のために Error を拡張してカスタムエラーを作成してください。
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 })はデバッグのために元のエラーを保持します。良いエラー階層によりエラー処理が精密になります。
// 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 });
}非同期と Promises
コールバック
コールバックは後で呼び出されるために渡される関数です。エラーファースト規約(err, data)は Node.js で標準です。ネストされたコールバックは「コールバック地獄」を作ります — 深くネストされ、読みにくいコード。Promises と async/await がこれを解決します。setTimeout/setInterval は一般的なコールバックベースの API です。
// 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); // stopPromises
Promises は 3 つの状態を持つ将来の値を表します:pending、fulfilled、rejected。.then() は成功を処理し、.catch() はエラーを処理し、.finally() は常に実行されます。Promise.all() はすべてを待機し(フェイルファスト)、allSettled() はすべてを待機し(失敗しない)、race() は最初に確定したものを返し、any() は最初に成功したものを返します。
// 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 モジュールで動作します。
// 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)));
}DOM 操作
要素の選択と変更
querySelector/querySelectorAll(CSS セレクタ)が要素を選択するモダンな方法です。textContent は innerHTML より安全です(XSS を防ぐ)。classList はクラスの add/remove/toggle/contains を提供します。dataset は data-* 属性にアクセスします。innerHTML を設定する前に常にユーザー入力をサニタイズしてください。
// 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() はバブリングを停止します。
// 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);モジュールと JSON
ES モジュール
ES モジュール(import/export)はモダンな標準で、ブラウザと Node.js でサポートされています。デフォルトエクスポート(モジュールごとに1つ)と名前付きエクスポート(複数)があります。動的 import() は遅延読み込みを可能にします。モジュールは常に strict モードで、独自のスコープを持ちます。HTML の script タグで type='module' を使用してください。
// 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 defaultJSON
JSON.stringify() は JS の値を JSON 文字列に変換し、JSON.parse() は逆変換します。変換中にフィルタや変換を行うには replacers/revivers を使用してください。JSON は関数、undefined、Date(文字列になる)、循環参照をサポートしません。fetch().json() は JSON レスポンスを自動的に解析します。信頼できない入力の JSON.parse は常に try/catch で囲んでください。
// 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 はロケール対応のフォーマットを提供します。
// 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日"ES6+ の機能
let、const とブロックスコープ
デフォルトで const を優先し、再代入が必要な場合は let、var は完全に避けてください。const は再代入を防ぎますが、オブジェクト/配列は変更可能です。let と const はブロックスコープで、宣言前は TDZ(一時的デッドゾーン)にあります(undefined として巻き上げられる var とは異なります)。これにより多くの微妙なバグを防げます。
// 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' がオブジェクトを指す必要がある場合は、アロー関数ではなく通常のメソッドを使用してください。
// 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 や関数パラメータで多用されます。
// 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 の不可欠なイディオムです。
// 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 で人気があります。
// 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 以降で利用可能です。
// 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!)イベントとイベント処理
addEventListener の基礎
addEventListener はイベントをバインドするモダンな方法です — 複数のハンドラを許可し、イベント委任をサポートし、once、passive、capture などのオプションを提供します。後で削除する必要がある場合はハンドラへの参照を保持してください(匿名関数は削除できません)。イベントオブジェクトは target、currentTarget、preventDefault()、stopPropagation() を運びます。
// 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) でフィルタします。各子にバインドするよりはるかに効率的で、動的に追加された要素を自動的に処理します。トレードオフ:親は常に存在する共通の祖先でなければなりません。
// 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)を使用してください。
<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' のような命名規則を使用して衝突を避けてください。多くのカスタム要素フレームワークの基盤です。
// 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' はフォーカスを失った時に発火します — 検証のタイミングに基づいて選択してください。
// 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 blurFetch API と AJAX
基本的な fetch(GET)
fetch() は XMLHttpRequest のモダンな代替です — Promise ベースでクリーンです。重要なことに、fetch はネットワークエラーでのみ拒否され、HTTP エラーステータス(404、500)では拒否されません。解析前に常に response.ok(ステータス 200-299)をチェックしてください。読みやすい逐次コードには async/await を使用してください。response.json() はボディストリームを読み取るため非同期です。
// 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 は部分的に更新します。
// 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 リクエストが発生します。
// 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 の標準キャンセル機構です。
// 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 になるまで続きます。ストリーミングは大きなペイロードでのメモリスパイクを回避します。
// 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 responseWeb ストレージ(LocalStorage と SessionStorage)
localStorage と sessionStorage の基礎
localStorage は無期限で永続し、sessionStorage はタブを閉じるとクリアされます。両方とも文字列のみを格納します — オブジェクトには JSON.stringify/parse を使用してください。ストレージは同期的でメインスレッドをブロックするため、大きなデータの格納は避けてください。すべてのモダンブラウザで利用可能ですが、プライベートブラウジングモードでは無効な場合があります。容量はオリジンごとに約 5-10MB です。
// 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 で囲んでください。
// 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 が含まれます。
// 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 ベースのラッパーを提供します。オフラインファーストアプリ、大きなキャッシュ、複雑なクライアント側データに使用してください。
// 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 を設定してください。
// 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タイマー(setTimeout、setInterval)
setTimeout と setInterval
setTimeout は遅延後にコールバックを一度実行し、setInterval は繰り返し実行します。どちらも clearTimeout/clearInterval でキャンセルするための ID を返します。タイマーは正確ではありません — イベントループ、タブの可視性(バックグラウンドタブでスロットル)、メインスレッドの可用性に影響される最小遅延です。ネストされたタイマーで 4ms 未満の遅延は 4ms に切り詰められる場合があります。
// 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 はハンドラがインターバルより長い場合に呼び出しがスタックし、パフォーマンス問題を引き起こす可能性があります。
// 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 でキャンセルしてください。
// 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つにまとめる」、スロットル = 「呼び出しレートを制限する」。
// 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つです。
// 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);
}
}
}Map、Set と WeakMap
Map(任意のキー型のキーと値)
Map は適切なキーと値コレクションで、キーは任意の型(オブジェクト、関数、数値)になります — キーを文字列に強制変換する Object とは異なります。Map は挿入順序を保持し、.size プロパティを持ち、直接反復可能です。非文字列キー、頻繁な追加/削除、またはコレクションがレコード/DTO でない場合に Map を使用してください。固定形状のデータには Object を使用してください。
// 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/removeSet(一意の値)
Set は一意の値を格納します — 重複排除とメンバーシップテストに最適です。配列を Set に変換して戻す([...new Set(arr)])のが重複を削除する慣用的な方法です。Set.has() は O(1) で、Array.includes() の O(n) に対して高速です。頻繁にチェックする大きなコレクションには Set を使用してください。Set には map/filter がありません — まず配列に展開してください。
// 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 中にいつでもエントリが消えるため)。オブジェクトのライフタイムに結び付けられたキャッシュ/メタデータに使用してください。
// 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 のキーは文字列型になります。
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()。
// 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); }ジェネレータとイテレータ
イテレータと Symbol.iterator
イテレータは {value, done} を返す next() メソッドを持ちます。イテラブルは Symbol.iterator を実装し、イテレータを返します。組み込みイテラブル(配列、文字列、Map、Set)は for...of、spread(...)、分割代入、Array.from() で動作します。Symbol.iterator を実装すると、カスタムオブジェクトがこれらの言語機能すべてでシームレスに動作します。
// 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 コルーチンパターンの基盤です。
// 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 されます。ジェネレータを再帰的に構成する標準的な方法です。
// 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() 呼び出しでは値を渡せません(受け取るものがまだないため)。
// 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 でストリーミングデータを処理するモダンな方法です。
// 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;
}
}Proxy と Reflect
Proxy の基礎(操作のインターセプト)
Proxy はオブジェクトの基本操作(get、set、has、deleteProperty、ownKeys など)をインターセプトしてカスタマイズできます。ハンドラオブジェクトは「トラップ」を定義します(すべてのプロパティに対するゲッター/セッターのようなもの)。ユースケース:バリデーション、ロギング、デフォルト値、アクセス制御、リアクティブシステム(Vue 3 はリアクティビティに Proxy を使用)。strict モードで成功を示すには set か ら true を返してください。
// 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 を使用してください。
// 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 のメタプログラミングツールキットを形成します。
// 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 を使用していました。
// 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 automaticallyWeb Workers
Web Worker の作成
Web Worker は別のバックグラウンドスレッドで JavaScript を実行し、UI をブロックせずに真の並行処理を可能にします。メインスレッドとワーカーは postMessage で通信します(データは共有ではなくコピー/構造的クローンされます)。ワーカーは DOM や window オブジェクトにアクセスできません — 隔離されています。画像処理、大きなファイルの解析、複雑な計算などの CPU 集約的タスクにワーカーを使用してください。
// 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 を取り消してください。ワーカーコードは文字列なので、エディタのシンタックスハイライトと型チェックが失われます — 本番では注意して使用してください。
// 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 の所有権をゼロコピーでワーカーに移動します — サイズに関わらずほぼ瞬時です。元のバッファはデタッチされます(使用不可)。大きなデータセット、画像処理、オーディオでコピーのオーバーヘッドを回避するために使用してください。
// 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 buffersSharedArrayBuffer と Atomics
SharedArrayBuffer はスレッド間で真の共有メモリを可能にし(コピーなし)、Atomics はその上でスレッドセーフな操作(add、load、store、compareExchange、wait/notify)を提供します。これにより JS で高性能な並行アルゴリズムが可能になります。Spectre セキュリティの懸念により、SharedArrayBuffer はクロスオリジン分離 HTTP ヘッダーが必要です — ない場合はモダンブラウザで無効化されます。WASM 相互運用と重い並行計算に使用してください。
// 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ワーカー)、タスクごとのワーカー生成コストを回避します。プールは空きワーカーにタスクをディスパッチし、すべてビジーの時はキューイングします。多くの独立した作業チャンクの処理に不可欠です。
// 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);
}
}DOM 操作の詳細
querySelector
querySelector は最初のマッチを返し、querySelectorAll は静的な NodeList を返します。getElementsByClassName はライブな HTMLCollection を返します。NodeList は forEach をサポートしますが、HTMLCollection はサポートしません。
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 より安全です。
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 はセレクタにマッチする最も近い祖先を見つけます。動的に追加された要素を処理します。
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 文字列の操作よりクリーンです。
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 になります。値は常に文字列です。
// 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';Canvas API
基本的な描画
getContext("2d") は 2D レンダリングコンテキストを返します。fillRect は塗りつぶされた矩形を描画し、strokeRect はアウトラインを描画します。描画前に fillStyle/strokeStyle を設定してください。
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 は円/円弧を描画します。
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 を設定してください。
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 でキャンセルします。