本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

JavaScript チートシート — クイックリファレンス

モダン JavaScript(ES2022)の構文、標準ライブラリ、よく使う DOM / Node ヘルパーをまとめたチートシート。日常の約 80% をカバーします。

JS

JavaScript ES2022

ECMAScript · マルチパラダイム · 動的・弱い型付け

おすすめの学習パス

まず「Hello World と実行環境」(Node.js またはブラウザコンソール)を通す → 変数・型・制御フローに慣れる → 配列とオブジェクトの高階メソッドを習得 → 参照とコピー、関数とクロージャを理解 → 非同期(Promise / async / await)を重点学習 → 最後に必要に応じて DOM、ネットワーク fetch、Node モジュール、ビルドとデバッグを参照。FAQ は落とし穴回避の振り返りに最適です。

1.Hello World と実行環境

Node.js またはブラウザで JavaScript を実行し、スクリプト・モジュール・npm プロジェクトの構造を理解します。

console 出力

console.log でコンソールに出力します。console.error はエラー出力、console.table は表形式表示。ブラウザと Node の両方で使えます。

1
2
3
4
5
console.log('Hello, world!');
console.error('something failed'); // 赤色のエラー出力
console.log('values:', 42, true); // 複数引数
console.table([{ id: 1 }, { id: 2 }]); // テーブル
// Node:$ node script.js

Node で実行

node で .js ファイルを直接実行します。node -e でインライン式を評価、node -p で結果を表示。ブラウザ依存がない場合に首选です。

1
2
3
4
5
// $ node script.js # スクリプトを実行
// $ node -e "console.log(1)" # 直接実行
// $ node -p 1 + 2 # 結果を出力:3
// 対話式 REPL:$ node
// 構文チェック:$ node --check script.js

ブラウザの script

HTML は <script> で JS を読み込みます。defer は DOM 解析後に遅延実行、async は非同期読み込み。type="module" で ESM が有効になります。

1
2
3
4
5
6
<!-- 普通脚本(阻塞渲染) -->
<script src="app.js"></script>
<!-- 延迟执行:DOM 解析完后运行 -->
<script src="app.js" defer></script>
<!-- ES 模块 -->
<script type="module" src="app.js"></script>

コマンドライン引数

Node では process.argv に完全なコマンドラインが入ります:argv[0] が node、argv[1] がスクリプトパス、argv[2] 以降が引数です。

1
2
3
4
5
6
// $ node app.js --name Nick
const args = process.argv.slice(2);
console.log(args); // ['--name', 'Nick']
// 簡易パース:
const name = process.argv[process.argv.indexOf('--name') + 1];
console.log('Hello, ' + name);

npm init

npm init で package.json を作成します。npm install で依存をインストールし、node_modules とロックファイルを生成します。

1
2
3
4
5
// $ npm init -y # すばやく初期化
// $ npm install express # 依存をインストールし dependencies に書込
// $ npm install -D vitest # 開発用依存
// $ npm run build # scripts.build を実行
// $ npm ls # インストール済み依存を確認

package.json

package.json はプロジェクトを記述します:name / version、main エントリ、scripts コマンド、dependencies 依存関係。

1
2
3
4
5
6
7
8
9
10
11
{
"name": "my-app",
"version": "1.0.0",
"type": "module", // ESM モジュール
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"test": "vitest run"
},
"dependencies": { }
}

ES Modules

import / export でモジュール化します:export で公開、import で取り込み。package.json に type: module を設定するか .mjs 拡張子を使います。

1
2
3
4
5
6
7
// math.js
export const PI = 3.14;
export function add(a, b) { return a + b; }
export default function main() { }
// main.js
import main, { add, PI as pi } from './math.js';
console.log(add(1, 2), pi);

strict モード

'use strict' で strict モードを有効化します:暗黙のグローバルを禁止し、無音エラーを例外化します。ESM は自動で strict モードです。

1
2
3
4
5
6
'use strict';
// 宣言なしの代入を禁止:
x = 10; // ReferenceError
// 厳格モードは過去の癖を修正
// ESM / class / モダンツールは既定で有効
// 推奨:コードファイルの先頭で宣言

2.変数と定数

let / const 宣言、スコープ、動的型付け、分割代入、テンプレート文字列。

let と const

let は再代入可能な変数、const は再代入不可能な定数(オブジェクト自体は可変)を宣言します。どちらもブロックスコープです。const を優先し、再代入が必要なら let を使います。

1
2
3
4
5
6
7
8
let count = 42;
count = 43; // OK:let は再代入可
const name = 'Nick';
// name = 'X'; // エラー:const は再代入不可
const obj = { a: 1 };
obj.a = 2; // OK:オブジェクトのプロパティは変更可
const arr = [];
arr.push(1); // OK:配列は変更可

var と let の違い

var は関数スコープ+ホイスト、let / const はブロックスコープです。モダンコードでは let / const を使い、var の罠を避けます。

1
2
3
4
5
6
7
8
9
if (true) {
var x = 1; // var は関数スコープに巻き上げ
let y = 2; // let はブロック内のみ
}
console.log(x); // 1
// console.log(y); // ReferenceError
// var は再宣言可、巻き上げあり:
console.log(z); // undefined(巻き上げ)
var z = 3;

スコープ

ブロックスコープ {}、関数スコープ、グローバルスコープ。内側のスコープは外側の変数を参照できますが、その逆はできません。

1
2
3
4
5
6
7
8
9
10
let global = 'g';
function outer() {
let outerVar = 'o';
function inner() {
let innerVar = 'i';
console.log(outerVar); // 外側にアクセス可
console.log(global);
}
// innerVar はここでは見えない
}

動的型付け

JS の変数には型の制約がなく、同一変数に任意の値を代入できます。typeof で実行時の型を判定します。

1
2
3
4
5
let value = 42; // number
value = 'hello'; // string(許容)
value = { a: 1 }; // object
console.log(typeof value); // 'object'
// 弱い型付け:'5' + 3 = '53'、'5' - 3 = 2(暗黙変換)

分割代入

配列やオブジェクトから値を変数に取り出します。オブジェクトは名前で、配列は位置で対応付け。デフォルト値やネストも可能です。

1
2
3
4
5
6
7
const { name, age = 0 } = person; // オブジェクト分割代入
const [first, , third] = arr; // 配列は順番に
const { config: { host = 'localhost' } } = app;
// リネーム:
const { name: userName } = person;
// 交換:
[a, b] = [b, a];

命名規則

変数・関数は lowerCamelCase、定数は UPPER_SNAKE_CASE、クラス・コンポーネントは UpperCamelCase を使う。真偽値変数には is / has 接頭辞を付ける。

1
2
3
4
5
6
7
let userId = 1;
const MAX_RETRY = 3;
class UserProfile { }
function fetchData() { }
let isValid = false; // 真偽値プレフィックス
let hasPermission = true;
// ファイルは kebab-case が一般的:user-profile.js

ホイスト

var 宣言と関数宣言はスコープの先頭にホイストされます。let / const には TDZ(一時的デッドゾーン)があり、宣言前に参照するとエラーになります。

1
2
3
4
5
6
7
console.log(foo()); // 関数宣言は巻き上げられ利用可
function foo() { return 1; }
// console.log(a); // TDZ エラー
let a = 1;
// var は undefined に巻き上げ:
console.log(b); // undefined
var b = 2;

テンプレート文字列

バッククォート文字列は ${} の埋め込みと複数行に対応します。文字列連結や複数行テキストの首选で、+ 連結を避けられます。

1
2
3
4
5
6
7
8
const name = 'Nick';
const age = 30;
const msg = `${name} is ${age} years old`;
const multi = `第一行
第二行
缩进保留`;
const expr = `${1 + 2}`; // '3'
// タグ付きテンプレート:tag`...` 高度な使い方

3.データ型

プリミティブ型、オブジェクト、typeof 判定、型変換。

プリミティブ型

プリミティブ型は 7 種類:string、number、boolean、null、undefined、symbol、bigint。それ以外はすべて object です。

1
2
3
4
5
6
7
8
const s = 'text'; // string
const n = 42; // number
const b = true; // boolean
const nl = null; // null
const u = undefined; // undefined
const sym = Symbol('id'); // symbol
const big = 10n; // bigint
console.log(typeof s, typeof n, typeof nl);

数値と NaN

number は整数と浮動小数を兼用します。NaN は非数、Infinity は無限大。NaN は自分自身を含め、どの値とも等しくありません。

1
2
3
4
5
6
7
const n = 3.14;
const int = 42;
Number.isNaN(NaN); // true
Number.isFinite(1 / 0); // false(Infinity)
parseInt('42px', 10); // 42
parseFloat('3.14'); // 3.14
// 浮動小数点は不正確:0.1 + 0.2 !== 0.3

文字列

文字列は不変です。長さ length、インデックス [i]、メソッド toUpperCase / slice / split などを使います。文字エンコーディングは UTF-16 です。

1
2
3
4
5
6
7
8
const s = 'hello world';
s.length; // 11
s[0]; // 'h'
s.toUpperCase(); // 'HELLO WORLD'
s.slice(0, 5); // 'hello'
s.split(' '); // ['hello', 'world']
s.includes('lo'); // true
s.startsWith('he'); // true

真値と偽値

偽値:false、0、''、null、undefined、NaN。それ以外はすべて真値です。if の判定は真偽値への暗黙変換で行われます。

1
2
3
4
5
6
7
8
9
// 偽値は 6 つだけ:
!!false === false;
!!0 === false;
!!'' === false;
!!null === false;
!!undefined === false;
!!NaN === false;
// それ以外は真値:[] 真、{} 真、'0' 真
// 空配列は真値!if (arr.length) が正確

null と undefined

undefined は未定義 / 未代入、null は明示的な空値を意味します。存在しないプロパティへアクセスすると undefined が返ります。

1
2
3
4
5
6
7
let a; // undefined
let b = null; // 明示的な空
const obj = {};
console.log(obj.x); // undefined
console.log(obj?.x?.y); // undefined(オプショナルチェーン)
// 判定:value == null で null と undefined の両方をカバー
if (a == null) { /* 空 */ }

Symbol と BigInt

Symbol は一意な識別子(オブジェクトのプライベートキーに最適)、BigInt は末尾に n を付けた任意精度整数です。typeof はそれぞれを区別します。

1
2
3
4
5
6
7
const key = Symbol('id');
obj[key] = 1; // 一意なプロパティキー
Symbol('a') === Symbol('a'); // false(一意)
const big = 12345678901234567890n;
big + 10n; // BigInt 演算
// BigInt は number と混算できない:
// 10n + 1 // エラー

typeof 判定

typeof はプリミティブ型を判定できます。配列・Date・null はすべて 'object' を返すため、別の方法で区別する必要があります。

1
2
3
4
5
6
7
8
typeof 'a'; // 'string'
typeof 42; // 'number'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof null; // 'object'(歴史的なバグ)
Array.isArray([]); // true
// オブジェクトの正確な判定:
Object.prototype.toString.call([]); // '[object Array]'

型変換

明示的な変換:String() / Number() / Boolean()。暗黙の変換はバグの温床です:+ は連結、- は数値変換。

1
2
3
4
5
6
7
8
9
String(42); // '42'
Number('3.14'); // 3.14
Number('abc'); // NaN
Boolean(''); // false
// 暗黙変換:
'5' + 3; // '53'(+ は連結)
'5' - 3; // 2(- は数値に変換)
!!'text'; // true
// 数値変換は安全:Number(null) は 0

オブジェクト型

プリミティブ以外はすべてオブジェクト:通常のオブジェクト、配列、関数、Date、Map など。オブジェクトは参照型です。

1
2
3
4
5
6
7
const obj = { a: 1, b: 'x' }; // 通常のオブジェクト
const arr = [1, 2]; // 配列
const fn = () => {}; // 関数
const date = new Date(); // 日付
const map = new Map(); // Map
Object.keys(obj); // ['a', 'b']
// typeof はどれも 'object' か 'function'

4.参照とコピー

値渡し vs 参照渡し、浅いコピーと深いコピー、配列とオブジェクトの参照、GC。

値渡し vs 参照渡し

プリミティブは値渡し(コピー)、オブジェクト・配列・関数は参照渡し(共有)です。これは JS の最も重要なメンタルモデルです。

1
2
3
4
5
6
7
let a = 5, b = a; // b は a のコピー
b = 10;
console.log(a); // 5(値コピー)
const objA = { x: 1 };
const objB = objA; // 参照を共有
objB.x = 99;
console.log(objA.x); // 99(同じオブジェクトを参照)

オブジェクト参照

関数の引数:オブジェクト引数は同じオブジェクトを共有するため、関数内で変更すると外側も変わります。プリミティブは影響を受けません。

1
2
3
4
5
6
7
function mutate(o) { o.x = 99; }
const obj = { x: 1 };
mutate(obj);
console.log(obj.x); // 99(関数に変更された)
function bad(o) { o = {}; } // 再代入は外に影響しない
// コピーが必要なら明示的に:
// const copy = { ...obj };

浅いコピー

スプレッド構文 {...obj} / [...arr] や Object.assign で 1 階層のコピーが作れます。ネストしたオブジェクトはまだ参照を共有します。

1
2
3
4
5
6
const obj = { a: 1, nested: { b: 2 } };
const copy = { ...obj };
copy.a = 99; // 元オブジェクトには影響なし
copy.nested.b = 99; // 元オブジェクトも変わる(浅いコピー)
const arrCopy = [...arr];
// Object.assign({}, obj) は浅いコピーと等価

深いコピー

深いコピーは全階層を再帰的に複製します。structuredClone(モダン)か自前の再帰コピーを使用。関数や循環参照は特別な対応が必要です。

1
2
3
4
5
6
const deep = structuredClone(obj); // ネイティブの深いコピー
// ネストを変更しても元に影響しない:
deep.nested.b = 1;
console.log(obj.nested.b); // 2
// 古い環境:
// JSON.parse(JSON.stringify(obj)) は関数/undefined を失う

配列のコピー

[...arr] は浅いコピー、slice() もコピー、Array.from は変換。ネストした配列(多次元)はまだ浅いコピーです。

1
2
3
4
5
6
const arr = [1, 2, [3]];
const copy = [...arr]; // 浅いコピー
const copy2 = arr.slice(); // 同上
copy[0] = 99; // 元の配列は変わらない
copy[2][0] = 99; // 元の配列も変わる!
// 多次元の深いコピー:structuredClone(arr)

JSON シリアライズによるコピー

JSON.stringify / parse は簡単なディープコピーですが、undefined・関数・Symbol を落とし、循環参照も扱えません。

1
2
3
4
5
6
const obj = { a: 1, b: { c: 2 }, d: undefined };
const copy = JSON.parse(JSON.stringify(obj));
// copy.d は失われる(undefined はシリアライズされない)
// 循環参照はエラー:
// const o = {}; o.self = o;
// JSON.stringify(o) // エラーを投げる

ガベージコレクション

V8 は到達不能なオブジェクトを自動で回収します。ローカル変数は関数終了時に解放されます。グローバルやクロージャが捕捉した変数は寿命を延ばします。

1
2
3
4
5
6
7
function create() {
const big = new Array(1000); // ヒープ割当
return () => big.length; // クロージャが big を保持
}
const fn = create();
// fn を使わなくなったら null にして解放:
// fn = null; GC がクロージャチェーンを回収

オブジェクトの等価比較

=== はオブジェクトに対しては参照を比較するため、内容が同じ 2 つのオブジェクトでも等しくありません。フィールド単位の比較は手動のディープイコールが必要です。

1
2
3
4
5
6
7
8
const a = { x: 1 };
const b = { x: 1 };
a === b; // false(参照が異なる)
const c = a;
c === a; // true(同一参照)
// 内容比較は手動で:
JSON.stringify(a) === JSON.stringify(b); // true
// またはフィールドごとに比較(ネストは再帰/ライブラリで)

5.制御フロー

if / else、ループ、switch、三項演算子、論理短絡。

if / else

if / else は条件に応じて分岐します。条件は真偽値に暗黙変換されます。else-if チェーンで複数分岐できます。

1
2
3
4
5
6
7
8
9
10
const score = 85;
let grade;
if (score >= 90) {
grade = 'A';
} else if (score >= 60) {
grade = 'B';
} else {
grade = 'F';
}
console.log(grade);

三項演算子

cond ? a : b でワンライナー条件代入。ネスト三項は可読性が落ちるため、複雑なロジックでは if / switch を使います。

1
2
3
4
5
6
const age = 20;
const type = age >= 18 ? 'adult' : 'minor';
// 戻り値にも使える:
return ok ? data : null;
// 連結も可:
const v = n > 0 ? 'pos' : n < 0 ? 'neg' : 'zero';

for ループ

古典的な for はインデックスを使います。コレクションの多くは for...of や配列メソッドの方が簡潔です。

1
2
3
4
5
6
for (let i = 0; i < 10; i++) {
console.log(i); // 0..9
}
for (let i = arr.length - 1; i >= 0; i--) {
// 逆順走査(インデックスが必要なとき)
}

for...of

for...of はイテラブル(配列、文字列、Map / Set、NodeList)を反復します。インデックスは不要です。

1
2
3
4
5
6
7
8
9
for (const item of items) {
console.log(item);
}
for (const ch of 'abc') { console.log(ch); }
for (const [key, val] of map) {
console.log(key, val);
}
// インデックスが必要:
// for (const [i, v] of arr.entries())

for...in(オブジェクト)

for...in はオブジェクトの列挙可能キー(継承含む)を反復します。配列は for...of、オブジェクトキーは Object.keys を使います。

1
2
3
4
5
6
7
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key, obj[key]);
}
// より安全:
Object.keys(obj).forEach(k => console.log(k));
// 配列に for...in は使わない(インデックス文字列を走査)

while / do-while

while は判定後に実行、do-while は最低 1 回実行します。反復回数が未定の場合に使います。

1
2
3
4
5
6
7
let i = 0;
while (i < 5) { i++; }
let x = 0;
do {
x++;
} while (x < 3); // 少なくとも 1 回は実行
// ファイルストリーム/ポーリングでは while (true) + break が一般的

switch

switch は厳密等価(===)で比較します。case 一致後、break まで実行が流れ落ちます(フォールスルー)に注意してください。

1
2
3
4
5
6
7
8
9
10
11
12
const day = 3;
switch (day) {
case 1:
console.log('Monday');
break;
case 2:
case 3: // 複数の値の合併
console.log('Tue/Wed');
break;
default:
console.log('other');
}

論理短絡とオプショナル連鎖

&&、||、?? は短絡評価です。?. は安全なオプショナル連鎖、?? は null / undefined のときだけ既定値を使います。

1
2
3
4
5
6
7
const name = user?.name ?? 'guest'; // 安全な取得+フォールバック
const ok = value && doSomething(); // 真値のときだけ実行
const fallback = a || 'default'; // 偽値のフォールバック
// 違いに注意:
// a ?? 'd' null/undefined にだけフォールバック
// a || 'd' すべての偽値にフォールバック
// ?. 連結:obj?.a?.b?.c

break と continue

continue で現在の反復をスキップ、break でループを抜け、ラベル付き break / continue でネストしたループを制御します。

1
2
3
4
5
6
7
8
9
10
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) continue; // 偶数をスキップ
if (i > 7) break; // 途中で抜ける
}
outer:
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 2) continue outer; // 外側の次の周回へ
}
}

6.関数とクロージャ

関数宣言、アロー関数、引数、クロージャ、this、コールバック。

関数宣言と関数式

関数宣言はホイストされ、関数式はされません。アロー関数は関数式でより簡潔。三者の構文は異なります。

1
2
3
4
function add(a, b) { return a + b; } // 宣言(巻き上げ)
const sub = function (a, b) { return a - b; }; // 式
const mul = (a, b) => a * b; // アロー関数
// 呼び出しは同じ:add(1, 2) / sub(5, 3) / mul(2, 4)

アロー関数

アロー関数は簡潔で、自前の this / arguments を持ちません。コンストラクタにはなれません。単一式は暗黙的に return されます。

1
2
3
4
5
6
7
8
const square = x => x * x; // 引数 1 つなら括弧省略可
const add = (a, b) => a + b;
const nothing = () => console.log('hi');
// 複数行の本体は波括弧 + return:
const calc = (a, b) => {
const sum = a + b;
return sum * 2;
};

引数とデフォルト値

関数の引数にはデフォルト値を設定でき、rest で残りを集められます。arguments は配列風でアロー関数には存在しません。

1
2
3
4
5
6
7
8
9
10
function greet(name = 'guest') {
return 'Hello, ' + name;
}
greet(); // 'Hello, guest'
function sum(...nums) { // rest パラメータ
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
// 分割代入パラメータ:
function f({ a, b }) { return a + b; }

クロージャ

クロージャは生成時のスコープを覚えます。内側の関数は外側の変数を参照でき、外側の関数が戻った後も有効です。

1
2
3
4
5
6
7
8
function counter() {
let count = 0; // クロージャが捕捉
return () => ++count; // 呼び出しごとに count を共有
}
const c = counter();
c(); // 1
c(); // 2
// クロージャは外側の変数参照を保持、メモリ消費に注意

this の束縛

this は呼び出し時に決まります:オブジェクトのメソッドはそのオブジェクト、通常関数の非 strict モードではグローバル、アロー関数は外側の this を継承します。

1
2
3
4
5
6
7
8
9
const obj = {
name: 'Nick',
greet() { return 'Hi ' + this.name; }, // メソッド
arrow: () => this, // アロー:外側の this を継承
};
obj.greet(); // 'Hi Nick'
// this が失われる:
const g = obj.greet; // g() のとき this はグローバル
// 修正:g.bind(obj) かアローで包む () => obj.greet()

call / apply / bind

call / apply は即時呼び出しで this を指定し、bind は this を束縛した新しい関数を返します。apply は配列で引数を渡します。

1
2
3
4
5
6
7
8
function info(a, b) { return this.name + a + b; }
const ctx = { name: 'Nick' };
info.call(ctx, 1, 2); // 引数を個別に渡す
info.apply(ctx, [1, 2]); // 配列で渡す
const bound = info.bind(ctx, 1); // this + 先頭引数をバインド
bound(2);
// 配列メソッドの再利用:
Array.prototype.slice.call(arguments)

コールバック関数

コールバックは関数を非同期処理の引数として渡すことです。モダンコードでは Promise / async が首选です。多重ネストはコールバック地獄になります。

1
2
3
4
5
6
7
8
9
function load(cb) {
setTimeout(() => cb('data'), 1000);
}
load(result => console.log(result));
// コールバック地獄:
load(a => load(b => load(c => {
// ネストが深くて保守しづらい
})));
// Promise に書き換えると明快

IIFE(即時実行)

IIFE(function(){})() は即時実行され、スコープを隔離します。モダンコードではブロックスコープ {} やモジュールで代替します。

1
2
3
4
5
6
7
8
9
(function () {
const secret = 'private'; // 外部からアクセス不可
})();
// 現代の代替:
{
const scoped = 'block scope';
}
// アロー IIFE:
(() => console.log('run once'))();

再帰

関数が自身を呼ぶには基底条件が必要です。なければスタックオーバーフローします。末尾呼び出し最適化は多くの JS エンジンで完全には実装されていません。

1
2
3
4
5
6
function factorial(n) {
if (n <= 1) return 1; // ベースケース
return n * factorial(n - 1);
}
factorial(5); // 120
// 深い再帰は RangeError: Maximum call stack に注意

7.文字列処理

文字列メソッド、テンプレート文字列、正規表現マッチ、分割と文字処理。

よく使うメソッド

よく使うメソッド:length で長さ、slice / substring で切り出し、indexOf で検索、includes で包含判定、replace で置換、split で分割、trim で空白除去。

1
2
3
4
5
6
7
8
const s = ' Hello, World ';
s.trim(); // 'Hello, World'
s.indexOf('World'); // 8
s.includes('lo'); // true
s.slice(0, 5); // ' Hell'
s.replace('World', 'JS'); // ' Hello, JS '
s.split(','); // [' Hello', ' World ']
'5'.padStart(3, '0'); // '005'

大小文字とエンコード

toUpperCase / toLowerCase で大小文字を変換します。charCodeAt はコードユニット取得、fromCharCode は逆変換、localeCompare はロケール順で比較します。

1
2
3
4
5
6
'hello'.toUpperCase(); // 'HELLO'
'HELLO'.toLowerCase(); // 'hello'
'A'.charCodeAt(0); // 65
String.fromCharCode(65); // 'A'
'a'.localeCompare('b'); // -1(辞書順)
// 中文字符注意码点:'中'.codePointAt(0)

Unicode 処理

文字列は UTF-16 コードユニットの列で、絵文字など補助平面の文字は 2 ユニットを占めます。反復は for...of か Array.from を使います。

1
2
3
4
5
6
const emoji = '😀';
emoji.length; // 2(コードユニット)
[...emoji].length; // 1(完全な文字)
for (const ch of 'ab😀') { console.log(ch); }
// 文字列反転(emoji を正しく処理):
const rev = [...'abc😀'].reverse().join('');

テンプレート文字列で連結

テンプレート文字列の ${} 埋め込みと複数行が最も推奨されます。式やメソッド呼び出しもネストできます。

1
2
3
4
5
6
7
const user = { name: 'Nick', age: 30 };
const msg = `${user.name} 今年 ${user.age} 岁`;
const html = `<div>
<p>${user.name}</p>
</div>`; // 複数行
// 条件付き挿入:
const tag = `${user.age >= 18 ? '成年' : '未成年'}`;

正規表現の基本

正規表現リテラル /pattern/ または new RegExp を使います。test で判定、exec で取得。g はグローバル、i は大小無視。

1
2
3
4
5
6
7
const re = /\d{3}-\d{4}/;
re.test('123-4567'); // true
re.exec('call 123-4567'); // マッチ結果
const m = 'abc123'.match(/(\d+)/);
m[0]; // '123'
m[1]; // '123'(グループ)
// グローバル走査:'a1b2'.matchAll(/\d/g)

正規表現による置換

replace は正規表現で置換し、$1 でグループを参照できます。コールバックで複雑な処理も可能。replaceAll はリテラルをすべて置換します。

1
2
3
4
5
6
'2024-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$1/$2/$3');
// マスキング:
'13812345678'.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
// コールバック:
'abc'.replace(/\w/g, ch => ch.toUpperCase()); // 'ABC'
// 全置換:'a-b-c'.replaceAll('-', '+')

分割と連結

split は区切りで配列に分割し、join は配列を文字列に連結します。split('') は文字に分解します。空文字列の挙動に注意してください。

1
2
3
4
5
6
7
'a,b,c'.split(','); // ['a','b','c']
['a','b','c'].join('-'); // 'a-b-c'
'hello'.split(''); // ['h','e','l','l','o']
// 個数制限:
'a,b,c'.split(',', 2); // ['a','b']
// 空白で分割:
' a b '.trim().split(/\s+/); // ['a','b']

数値を文字列に変換

toString / テンプレートで文字列化、toFixed で小数桁、toLocaleString で桁区切り、padStart でゼロパディング。

1
2
3
4
5
6
(255).toString(16); // 'ff'
(3.14159).toFixed(2); // '3.14'
(1234567).toLocaleString('en-US'); // '1,234,567'
(42).toString().padStart(5, '0'); // '00042'
// 安全な浮動小数点:
(0.1 + 0.2).toFixed(1); // '0.3'

8.配列とオブジェクト

配列の高階関数、Map / Set、オブジェクト操作、分割代入。

配列の高階メソッド

map で写像、filter で抽出、reduce で畳み込み、find で検索、some / every で判定。元の配列は変更しません。

1
2
3
4
5
6
7
const nums = [1, 2, 3, 4];
nums.map(n => n * 2); // [2, 4, 6, 8]
nums.filter(n => n % 2 === 0); // [2, 4]
nums.reduce((a, b) => a + b, 0); // 10
nums.find(n => n > 2); // 3
nums.some(n => n > 3); // true
nums.every(n => n > 0); // true

Map

Map は任意の値をキーにできるマップ:get / set / has / delete / size。キーはオブジェクトでもよく、反復は挿入順を保ちます。

1
2
3
4
5
6
7
8
9
const m = new Map();
m.set('name', 'Nick');
m.set(obj, 'value'); // オブジェクトをキーに
m.get('name'); // 'Nick'
m.has('name'); // true
m.delete('name');
m.size; // 1
for (const [k, v] of m) { console.log(k, v); }
// 初期化:new Map([['a',1],['b',2]])

Set

Set は重複を許さないコレクション:add / has / delete / size。配列の重複除去やメンバー判定に首选です。

1
2
3
4
5
6
7
8
const set = new Set([1, 2, 2, 3]);
set.size; // 3(自動で重複排除)
set.has(2); // true
set.add(4);
set.delete(1);
// 配列の重複排除:
const uniq = [...new Set(arr)];
// 走査:for (const v of set) / set.forEach

オブジェクト操作

Object.keys / values / entries で反復、Object.assign でマージ、Object.freeze で凍結、in でプロパティ判定。

1
2
3
4
5
6
7
const obj = { a: 1, b: 2 };
Object.keys(obj); // ['a', 'b']
Object.values(obj); // [1, 2]
Object.entries(obj); // [['a',1],['b',2]]
Object.assign({}, obj, { c: 3 }); // マージ
'a' in obj; // true
Object.freeze(obj); // 変更不可

reduce

reduce は配列を単一の値に畳み込みます:集計、グループ化、フラット化。初期値は任意ですが、指定が推奨です。

1
2
3
4
5
6
7
8
9
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
// グルーピング:
const byType = items.reduce((acc, it) => {
(acc[it.type] ||= []).push(it);
return acc;
}, {});
// 平坦化:
[[1], [2, 3]].reduce((a, b) => a.concat(b), []);

ソート

sort はデフォルトで文字列順(罠)です。数値ソートは比較関数が必要です。sort は配列をインプレースに変更します。

1
2
3
4
5
6
[3, 10, 1].sort(); // [1, 10, 3](文字列順!)
[3, 10, 1].sort((a, b) => a - b); // [1, 3, 10] 昇順
[3, 10, 1].sort((a, b) => b - a); // 降順
// オブジェクトをフィールドでソート:
people.sort((a, b) => a.age - b.age);
// コピーしてソート:[...arr].sort(...) は元配列を汚さない

スプレッドとマージ

スプレッド演算子 ... は配列 / オブジェクトのマージ、コピー、引数の展開に使います。rest で収集、spread で展開はミラーです。

1
2
3
4
5
6
const a = [1, 2], b = [3, 4];
const merged = [...a, ...b]; // [1,2,3,4]
const copy = [...a]; // 浅いコピー
const obj = { ...o1, ...o2 }; // オブジェクトのマージ
const max = Math.max(...a); // 引数展開
function f(...rest) { } // 引数収集

メソッドチェーン

map / filter などは新しい配列を返すためチェーンできます。長いチェーンでは中間配列の割り当てに注意してください。

1
2
3
4
5
6
7
const result = people
.filter(p => p.age >= 18)
.map(p => p.name)
.sort()
.join(', ');
// 避ける:チェーン内での大きな配列の重複操作
// 性能:大量データは for ループで代替可

配列の穴と疎配列

配列の穴(hole)は undefined と異なります。map は穴をスキップします。密な配列は Array.from または fill で作ります。

1
2
3
4
5
6
const holes = [1, , 3]; // 途中がホール
holes.map(n => n * 2); // [2, <empty>, 6]
// 密な配列を作る:
Array.from({ length: 5 }, (_, i) => i); // [0..4]
new Array(5).fill(0); // 全部 0 の密配列
// 確認:0 in holes は false

9.メモリとパフォーマンス

クロージャとメモリリーク、WeakMap / WeakSet、大量データ処理と最適化。

クロージャとメモリ

クロージャは不要になった外部変数も参照し続けます。長寿命の参照は GC を妨げます。

1
2
3
4
5
6
7
function heavy() {
const big = new Array(1000000);
return function () { return big.length; };
}
const keep = heavy(); // big はクロージャに保持される
// keep を使わなくなったら null にすべき:
// keep = null; しないと big を回収できない

イベントリスナーのリーク

DOM 要素を除去してもリスナーが残ったり、リスナーが外部オブジェクトを保持することでリークします。要素除去前にデタッチするか AbortController を使います。

1
2
3
4
5
6
7
8
9
const btn = document.getElementById('btn');
const handler = () => { /* ... */ };
btn.addEventListener('click', handler);
// 取り除く前に解除:
// btn.removeEventListener('click', handler);
// または btn.onclick = null;
// AbortController で一括解除:
// const ac = new AbortController();
// el.addEventListener('click', fn, { signal: ac.signal })

WeakMap / WeakSet

WeakMap のキーは弱参照で、他に参照がなければ回収されます。関連データ、キャッシュ、プライベートフィールドに適しています。

1
2
3
4
5
6
7
8
const wm = new WeakMap();
let obj = {};
wm.set(obj, 'private data'); // 関連付けるが回収は妨げない
obj = null; // キーが回収され、エントリも自動で消える
// WeakMap は反復不可、size なし
// プライベートフィールドの実装:
// const priv = new WeakMap();
// class A { m() { return priv.get(this); } }

パフォーマンス最適化

不要なコピーや再計算を避けます。length をローカルへキャッシュ、DOM 操作をバッチ化、高頻度イベントは debounce / throttle。

1
2
3
4
5
6
7
8
9
10
11
// length をキャッシュ:
for (let i = 0, len = arr.length; i < len; i++) { }
// デバウンス(高頻度発火で最後だけ実行):
function debounce(fn, ms) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), ms);
};
}
const onInput = debounce(save, 300);

スタックオーバーフロー

無限再帰や深すぎる再帰は RangeError: Maximum call stack を引き起こします。ループに変えるか深さを制限します。

1
2
3
4
5
6
7
8
9
10
// スタックオーバーフローする:
// function loop() { loop(); }
// loop(); // RangeError
// 深い木の走査は明示的スタックで:
const stack = [root];
while (stack.length) {
const node = stack.pop();
stack.push(...node.children);
}
// または末尾再帰 / ジェネレータ

大量データの処理

大きな配列に reduce / map を全適用するとメモリを消費します。バッチ処理、ジェネレータでの遅延評価、TypedArray でのバイナリ最適化を使います。

1
2
3
4
5
6
7
8
9
// ジェネレータで遅延処理:
function* range(n) {
for (let i = 0; i < n; i++) yield i;
}
for (const x of range(1e7)) { /* 必要な分だけ */ }
// バイナリデータ:
const buf = new Uint8Array(1024);
// 大きなファイルはストリーム:
// for await (const chunk of stream) { }

GC とグローバルオブジェクト

グローバル変数やキャッシュは解放されません。無制限なキャッシュを避け、適切に掃除します。V8 のマーク&スイープが処理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// グローバルキャッシュは生き続ける:
globalThis.cache = {}; // 決して回収されない
// 有限キャッシュ:
function lruCache(limit) {
const map = new Map();
return {
get(k) { return map.get(k); },
set(k, v) {
map.set(k, v);
if (map.size > limit) {
map.delete(map.keys().next().value);
}
},
};
}

メモ化キャッシュ

Map で純関数の結果をキャッシュし、同じ入力ならキャッシュを返して高コストな再計算を避けます。キャッシュのサイズに注意してください。

1
2
3
4
5
6
7
8
9
10
11
12
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const value = fn(...args);
cache.set(key, value);
return value;
};
}
const fib = memoize(n => (n <= 1 ? n : fib(n - 1) + fib(n - 2)));
fib(40); // 一瞬(キャッシュで枝刈り)

10.オブジェクト指向

class 構文、継承、カプセル化、静的メンバー、プロトタイプチェーン。

class 定義

class はオブジェクトの雛形を定義します:コンストラクタ、メソッド、プロパティ。new でインスタンスを生成します。

1
2
3
4
5
6
7
8
9
10
11
class Person {
constructor(name, age) {
this.name = name; // インスタンスプロパティ
this.age = age;
}
greet() { // インスタンスメソッド
return 'Hi, ' + this.name;
}
}
const p = new Person('Nick', 30);
p.greet(); // 'Hi, Nick'

継承(extends)

extends で継承し、super() で親コンストラクタ、super.method() で親メソッドを呼び出します。

1
2
3
4
5
6
7
8
9
10
11
class Animal {
constructor(name) { this.name = name; }
speak() { return '...'; }
}
class Dog extends Animal {
constructor(name) { super(name); }
speak() { return 'Woof'; } // オーバーライド
}
const d = new Dog('Rex');
d.speak(); // 'Woof'
d.name; // 'Rex'(継承したプロパティ)

getter / setter

get / set はアクセサプロパティを定義し、読み書き時にロジック(バリデーションなど)を実行します。メソッドではなくプロパティとしてアクセスします。

1
2
3
4
5
6
7
8
9
10
11
class Account {
constructor() { this._balance = 0; }
get balance() { return this._balance; }
set balance(v) {
if (v < 0) throw new Error('负余额');
this._balance = v;
}
}
const a = new Account();
a.balance = 100; // setter が発動
console.log(a.balance); // 100(getter が発動)

静的メンバー

static メンバーはクラスに属し、インスタンスには属しません:静的メソッドはユーティリティ、静的フィールドは定数。static {} は静的初期化ブロックです。

1
2
3
4
5
6
7
8
class MathUtils {
static PI = 3.14; // 静的プロパティ
static add(a, b) { return a + b; } // 静的メソッド
static { console.log('init'); } // 静的ブロック
}
MathUtils.add(1, 2); // クラス経由で呼ぶ
MathUtils.PI; // 3.14
// 静的メソッド内の this はクラスを指す

プライベートフィールド

# 接頭辞は真のプライベートフィールド / メソッドを定義し、クラス外からアクセスするとエラーになります。アンダースコア命名ではなく本物のカプセル化です。

1
2
3
4
5
6
7
8
9
10
class Counter {
#count = 0; // プライベートフィールド
#increment() { // プライベートメソッド
this.#count++;
}
up() { this.#increment(); return this.#count; }
}
const c = new Counter();
c.up(); // 1
// c.#count // エラー:プライベートフィールド

instanceof 判定

instanceof はオブジェクトがクラス(またはその親)のプロトタイプチェーン上にあるか判定します。Object.getPrototypeOf でプロトタイプを確認できます。

1
2
3
4
5
6
7
8
class Dog extends Animal { }
const d = new Dog();
d instanceof Dog; // true
d instanceof Animal; // true(親クラスチェーン)
[] instanceof Array; // true
// プロトタイプチェーン:
Object.getPrototypeOf(d) === Dog.prototype; // true
// 別 iframe の配列は instanceof が失敗、Array.isArray を使う

プロトタイプチェーン

オブジェクトはプロトタイプチェーンでメソッドを共有します。class はプロトタイプ継承のシンタックスシュガーです。prototype を変更すると全インスタンスに影響します。

1
2
3
4
5
6
7
8
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () {
return this.name + ' speaks';
};
const a = new Animal('cat');
a.speak();
// クラスメソッドは実際には prototype に載っている:
Person.prototype.greet === p.greet; // true(メソッド共有)

継承より合成

深い継承階層は保守が困難です。オブジェクト合成を優先:コンストラクタで依存を注入、mixin で機能をブレンド。

1
2
3
4
5
6
7
8
9
10
11
class Logger {
constructor(logger) { this.logger = logger; }
save() { this.logger.log('saved'); }
}
// mixin:
const withLogging = (cls) => class extends cls {
log(msg) { console.log(msg); }
};
class User { }
const LoggedUser = withLogging(User);
// 組み合わせ:コンストラクタ注入で依存をテストしやすく

11.例外処理

try / catch / finally、エラー型、throw、非同期エラー処理。

try / catch / finally

try には失敗しうるコードを入れ、catch で受け取り、finally は成否に関わらず実行されます。例外は呼び出しスタックを遡って伝播します。

1
2
3
4
5
6
7
8
try {
const data = JSON.parse(json);
} catch (e) {
console.error('parse failed:', e.message);
} finally {
console.log('cleanup'); // 必ず実行される
}
// catch の束縛省略:catch { ... }

エラー型

Error が基底で、サブクラスに TypeError、ReferenceError、RangeError、SyntaxError があります。instanceof で区別します。

1
2
3
4
5
6
7
8
9
try {
null.prop; // TypeError
undefinedVar; // ReferenceError
} catch (e) {
if (e instanceof TypeError) { }
if (e instanceof ReferenceError) { }
}
// ユーザー定義エラーは通常 Error のサブクラス
// 非同期エラーは同期 try/catch に入らない

エラーを投げる

throw new Error('msg') で例外を投げます。何でも投げられますが、慣例ではスタックを持つ Error オブジェクトを使います。

1
2
3
4
5
6
7
8
9
10
11
function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为 0');
}
return a / b;
}
try {
divide(1, 0);
} catch (e) {
console.log(e.message); // '除数不能为 0'
}

カスタムエラー

extends Error で業務エラーを定義します。name を設定して識別しやすくし、message とスタックを保持します。

1
2
3
4
5
6
7
8
9
10
11
12
13
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
try {
throw new ValidationError('邮箱格式错误');
} catch (e) {
if (e instanceof ValidationError) {
// 業務エラーの分岐
}
}

非同期エラー

Promise / async のエラーは .catch または try / catch(await 内)で処理します。同期 try / catch では非同期の throw を捕捉できません。

1
2
3
4
5
6
7
8
9
10
11
12
13
async function load() {
try {
const res = await fetch(url); // 非同期
return await res.json();
} catch (e) {
console.error('load failed', e);
return null;
}
}
// または Promise スタイル:
fetch(url)
.then(r => r.json())
.catch(e => console.error(e));

未処理エラー

.catch がない Promise は unhandledrejection を引き起こします。最上位で uncaughtException / unhandledrejection をグローバル監視します。

1
2
3
4
5
6
7
8
9
10
// Node グローバル:
process.on('uncaughtException', e => {
console.error('uncaught', e);
});
process.on('unhandledRejection', reason => {
console.error('unhandled rejection', reason);
});
// ブラウザ:
window.addEventListener('unhandledrejection', e => { });
// 通常の開発:すべての async で try/catch か .catch を繋ぐ

エラーの分離

バッチ処理では 1 件の失敗で全体を止めてはいけません。各要素を try / catch で包み、ログを残して継続します。

1
2
3
4
5
6
7
8
9
10
11
const results = [];
for (const item of items) {
try {
results.push(process(item));
} catch (e) {
console.error('skip item:', item, e);
results.push(null); // 失敗をマークして続行
}
}
// 素の catch でエラーを飲み込まない:最低限ログ
// Promise 一括は Promise.allSettled で失敗を隔離

エラーの原因 cause

ES2022 で追加された Error の cause オプションは、エラーをラップしても原因とスタックを保持し、層ごとの追跡を容易にします。

1
2
3
4
5
6
7
8
9
10
11
12
13
function parse(data) {
try {
return JSON.parse(data);
} catch (e) {
throw new Error('解析失败', { cause: e });
}
}
try {
parse('not-json');
} catch (e) {
console.log(e.message); // '解析失败'
console.log(e.cause); // 元の解析エラー
}

12.ファイルと IO

Node のファイルシステム、コンソール入力、JSON 読み書き、ブラウザ DOM の IO。

fs でファイルを読む

Node の fs/promises でファイルを読みます:readFile は一括読み込み、readFileSync は同期版。モダンでは Promise 版を推奨します。

1
2
3
4
5
6
7
import { readFile, writeFile } from 'node:fs/promises';
const data = await readFile('in.txt', 'utf8');
await writeFile('out.txt', data);
// 同期版(簡単なスクリプト用):
import fs from 'node:fs';
const s = fs.readFileSync('in.txt', 'utf8');
// 行ごと:data.split('\n')

fs で書き込みと追記

writeFile は上書き、appendFile は追記、mkdir はディレクトリ作成、access は存在確認。rename / cp / rm でファイル操作を行います。

1
2
3
4
5
6
7
import { writeFile, appendFile, mkdir } from 'node:fs/promises';
await writeFile('a.txt', 'content');
await appendFile('a.txt', '\nmore');
await mkdir('data', { recursive: true });
// 存在確認:
import { access } from 'node:fs/promises';
try { await access('a.txt'); } catch { /* 存在しない */ }

JSON ファイルの読み書き

JSON ファイルの読み込みは parse、書き出しは stringify。JSON は設定やデータの保存によく使われます。

1
2
3
4
5
6
7
import { readFile, writeFile } from 'node:fs/promises';
const data = JSON.parse(await readFile('config.json', 'utf8'));
data.updated = true;
await writeFile('config.json', JSON.stringify(data, null, 2));
// 耐障害な解析:
try { JSON.parse(s) } catch { /* 壊れた JSON を処理 */ }
// 見やすく:JSON.stringify(obj, null, 2) でインデント

標準入出力

process.stdin で入力(ストリーム)を読み、readline で対話的に 1 行ずつ処理、process.stdout.write で出力します。

1
2
3
4
5
6
7
8
9
10
11
import readline from 'node:readline';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('你的名字?', name => {
console.log('你好', name);
rl.close();
});
// パイプ入力を 1 行ずつ処理:
// for await (const line of rl) { }

fetch ネットワーク IO

fetch でリモートリソースを取得します。async / await でレスポンスを扱い、res.json() / res.text() で解析。Node 18+ に内蔵。

1
2
3
4
5
6
7
8
9
const res = await fetch('https://api.example.com/data');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
// 書き込みリクエスト:
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Nick' }),
});

ブラウザ DOM の IO

DOM はページ内容を読み書きします:getElementById で取得、textContent はテキスト、innerHTML はマークアップ。ブラウザ環境専用です。

1
2
3
4
5
6
7
const el = document.getElementById('output');
el.textContent = 'Hello'; // 安全なテキスト
// el.innerHTML = '<p>Hi</p>'; // HTML(XSS に注意)
const input = document.querySelector('input');
const val = input.value; // 入力を読む
// イベント監視:
btn.addEventListener('click', () => { });

ストリーム IO

大きなファイルはストリームでメモリ全体を一度に読み込まないようにします。createReadStream / createWriteStream、pipe または for await を使います。

1
2
3
4
5
6
7
8
9
import { createReadStream } from 'node:fs';
const stream = createReadStream('big.log', 'utf8');
for await (const chunk of stream) {
processChunk(chunk); // チャンク単位で処理
}
// パイプ:
// createReadStream('a').pipe(createWriteStream('b'))
// ブラウザのレスポンスストリーム:
// for await (const c of res.body)

path のパス処理

node:path はファイルパスを扱います:join で連結、resolve で絶対パス化、extname で拡張子、basename でファイル名を取得。

1
2
3
4
5
6
7
import path from 'node:path';
path.join('data', 'sub', 'a.txt'); // 'data/sub/a.txt'(クロスプラットフォーム)
path.resolve('data'); // 絶対パス
path.extname('a/b.json'); // '.json'
path.basename('/a/b/file.txt'); // 'file.txt'
path.parse('/a/b/file.txt'); // 各セグメントのオブジェクト
// 相対:path.relative('/a', '/a/b') // 'b'

13.よくある落とし穴(FAQ)

JS 開発者が最もハマる罠:== vs ===、this、非同期、コピー、真値判定。

== と ===

== は暗黙変換を行い(バグのもと)、=== は厳密等価です。原則として === を使います。null == undefined のみ例外です。

1
2
3
4
5
6
7
8
9
// BAD:暗黙変換で想定外
1 == '1'; // true
null == 0; // false
[] == false; // true(奇妙)
// GOOD:厳密等価
1 === '1'; // false
// 空判定:value === null || value === undefined
// または value == null(等価)

this の喪失

メソッドを変数に代入したりコールバックに渡すと this が失われます。アロー関数で継承するか bind で明示的に束縛します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:コールバックで this が失われる
class A {
init() {
btn.onclick = function () {
this.doSomething(); // this はボタン!
};
}
}
// GOOD:アロー関数
class B {
init() {
btn.onclick = () => this.doSomething(); // this はインスタンス
}
}

浮動小数点の精度

0.1 + 0.2 !== 0.3(二進表現で不正確)。比較は許容誤差で行い、金額は整数セントか専用ライブラリで扱います。

1
2
3
4
5
6
7
// BAD:浮動小数点の直接等価
0.1 + 0.2 === 0.3; // false
// GOOD:許容差比較
Math.abs((0.1 + 0.2) - 0.3) < 1e-9; // true
// 金額:分で整数保存 1990 を使い 19.9 にしない
// または toFixed で表示:(0.1+0.2).toFixed(2) === '0.30'

非同期の実行順序

setTimeout やネットワークリクエストは非同期です。ソース順に書いても実行順は保証されません。await / Promise を使います。

1
2
3
4
5
6
7
8
9
// BAD:同期実行と思い込む
let data;
fetch(url).then(r => data = r);
console.log(data); // undefined!
// GOOD:await で待つ
const res = await fetch(url);
console.log(res); // データ準備完了
// または .then チェーン / async 関数

浅いコピーと深いコピー

スプレッドは 1 階層しかコピーしません。ネストしたオブジェクトは共有されるため、変更すると元にも影響します。

1
2
3
4
5
6
7
8
// BAD:スプレッドを深いコピーだと思う
const b = { ...a };
b.nested.x = 1; // a.nested.x も変わる
// GOOD:深いコピー
const c = structuredClone(a);
c.nested.x = 1; // a に影響なし
// structuredClone がない環境は JSON か手書き再帰

真値判定

空配列 / 空オブジェクトは真値です。配列の長さは .length で、オブジェクトは Object.keys(obj).length で確認します。

1
2
3
4
5
6
7
// BAD:if (arr) は常に真
if ([]) { console.log('runs'); } // 空配列は真値
// GOOD:長さをチェック
if (arr.length > 0) { }
if (Object.keys(obj).length === 0) { /* 空オブジェクト */ }
// 文字列:if (str) で非空文字列をチェックするのは安全

var のスコープ漏れ

var にはブロックスコープがないため、ループ / if 内の宣言は関数スコープに漏れます。ループカウンタには let を使います。

1
2
3
4
5
6
7
8
9
// BAD:var のリーク + クロージャが古い値を捕捉
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 全部 3 を出力
}
// GOOD:let のブロックスコープ
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 0, 1, 2
}

parseInt の落とし穴

古いエンジンでは parseInt('08') が 8 進扱いになることも、parseInt('3px') は 3 を返します。厳密には Number か + を使います。

1
2
3
4
5
6
7
8
9
// BAD:parseInt の緩い解析
parseInt('42px'); // 42(意図と違うかも)
// 古いエンジンの八進数:
// parseInt('010') 8 になる可能性
// GOOD:明示的な基数 + 厳格
parseInt('010', 10); // 10
Number('42px'); // NaN(厳格)
+'42'; // 42

ネスト三項演算子

ネストした三項演算子は読みにくいです。複雑な分岐は if / else かルックアップマップで書きましょう。

1
2
3
4
5
6
7
8
9
10
// BAD:ネストした三項は読みにくい
const v = a ? b ? 'x' : 'y' : 'z';
// GOOD:if/else かマッピング
let v;
if (a) v = b ? 'x' : 'y';
else v = 'z';
// またはマップ表:
const map = { x: 'X', y: 'Y' };
const result = map[key] ?? 'default';

オブジェクトプロパティの変更

分割代入は浅いコピーで、ネストしたプロパティは参照のままです。元オブジェクトを変更から守るにはディープコピーが必要です。

1
2
3
4
5
6
7
8
// BAD:分割代入後にネストを変更
const { nested } = obj;
nested.x = 99; // obj.nested.x も変わる
// GOOD:深いコピー後に変更
const copy = structuredClone(obj);
copy.nested.x = 99; // 元オブジェクトは不変
// または共有を避ける:copy 前に完全な値を分割

14.非同期とイベントループ

イベントループ、Promise、async / await、タイマー、並行処理。

イベントループ

JS はシングルスレッド + イベントループです。同期コードが先に実行され、非同期コールバック(タイマー / IO)はタスクキューに入ります。

1
2
3
4
5
console.log('1'); // 同期
setTimeout(() => console.log('2')); // マクロタスク
Promise.resolve().then(() => console.log('3')); // マイクロタスク
// 出力順:1, 3, 2
// マイクロタスクがマクロタスクに先行;IO 完了後コールバックが入隊

Promise

Promise は非同期の結果を表します。then が成功、catch が失敗、finally が後処理。チェーンでコールバック地獄を避けられます。

1
2
3
4
5
6
7
8
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve('data'), 1000);
});
p.then(data => console.log(data))
.catch(err => console.error(err))
.finally(() => console.log('done'));
// resolve 成功、reject 失敗
// then は新 Promise を返し連結できる

async / await

async 関数は Promise を返し、await で結果を待ちます。同期的に書けて、try / catch で非同期エラーを捕捉できます。

1
2
3
4
5
6
7
8
9
10
11
12
async function load() {
try {
const res = await fetch(url); // 待機
return await res.json();
} catch (e) {
console.error(e);
return null;
}
}
const data = await load();
// await は async 関数またはトップレベルモジュールのみ
// async 関数は常に Promise を返す

タイマー

setTimeout は遅延実行、setInterval は周期実行、clearTimeout / clearInterval でキャンセルします。単位はミリ秒です。

1
2
3
4
5
6
7
const t = setTimeout(() => console.log('once'), 1000);
clearTimeout(t); // キャンセル
const id = setInterval(() => tick(), 500);
clearInterval(id); // 停止
// 遅延 0 でも非同期:
setTimeout(() => console.log('async'), 0);
// Node:setImmediate はマクロタスク、process.nextTick はマイクロタスク

Promise の組み合わせ

Promise.all は全部成功で成功、allSettled は全完了後に各状態を報告、race は最初に完了したもので決着します。

1
2
3
4
5
6
7
8
const p1 = fetch('/a');
const p2 = fetch('/b');
// 並行で全部待つ(どれか失敗すれば即失敗):
const [a, b] = await Promise.all([p1, p2]);
// 単一の失敗で中断しない:
const results = await Promise.allSettled([p1, p2]);
// results[i].status === 'fulfilled' | 'rejected'
// 競走:Promise.race([p, timeout])

Web Worker

Worker は独立スレッドで重い処理を実行し UI をブロックしません。postMessage で通信。Node では worker_threads を使います。

1
2
3
4
5
6
7
8
9
10
11
// メインスレッド:
const w = new Worker('worker.js');
w.postMessage('start');
w.onmessage = e => console.log('result', e.data);
// worker.js:
onmessage = e => {
const result = heavyCompute(e.data);
postMessage(result);
};
// Node:
// import { Worker } from 'node:worker_threads'

並行数の制御

バッチ非同期タスクでは並行数を制限し、リソース枯渇を防ぎます。バッチ処理やセマフォを使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function mapLimit(items, limit, fn) {
const results = [];
const running = [];
for (const item of items) {
const p = fn(item).then(r => {
results.push(r);
running.splice(running.indexOf(p), 1);
});
running.push(p);
if (running.length >= limit) {
await Promise.race(running);
}
}
await Promise.all(running);
return results;
}
// 分割:for (let i = 0; i < items.length; i += limit)

マイクロタスクとマクロタスク

マイクロタスク(Promise.then、queueMicrotask)はマクロタスク(タイマー)より優先されます。各ターンで先にすべて処理されます。

1
2
3
4
5
queueMicrotask(() => console.log('micro'));
setTimeout(() => console.log('macro'), 0);
// 出力:micro, macro
// 順序:同期 → マイクロタスクを空にする → マクロタスク
// マクロタスクが 1 つ終わるごとにマイクロタスクを確認・清掃

15.ネットワークと HTTP

fetch リクエスト、HTTP メソッド、エラー処理、JSON、WebSocket、URL。

fetch の基本

fetch は HTTP リクエストを送り Promise を返します。res.ok で成功判定、res.json() / text() で読み取り。Node 18+ とブラウザ内蔵です。

1
2
3
4
5
6
const res = await fetch('https://api.example.com/users');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const users = await res.json();
// またはテキスト:await res.text()
// ステータス:res.status(200/404...)
// ヘッダー:res.headers.get('content-type')

POST とリクエストボディ

fetch の第 2 引数で method、headers、body を設定します。JSON ボディは JSON.stringify が必要です。

1
2
3
4
5
6
7
8
9
10
const res = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token',
},
body: JSON.stringify({ name: 'Nick', age: 30 }),
});
if (!res.ok) throw new Error('create failed');
const created = await res.json();

クエリパラメータ

URLSearchParams でクエリ文字列を構築、encodeURIComponent で特殊文字をエンコード、URL で URL を解析します。

1
2
3
4
5
6
7
8
9
10
11
12
const params = new URLSearchParams({
q: '速查', page: '2',
});
const url = `/search?${params}`; // q=%E9%80%9F%E6%9F%A5&page=2
// 追加:
params.append('sort', 'asc');
params.get('q'); // '速查'
// 単一値のエンコード:
const q = encodeURIComponent('a&b');
// URL を解析:
const u = new URL('https://a.com/x?y=1');
u.searchParams.get('y'); // '1'

ネットワークエラー処理

fetch はネットワーク層失敗でのみ reject します(HTTP 4xx / 5xx は例外を投げない)ので res.ok を確認してください。タイムアウトは AbortController を使います。

1
2
3
4
5
6
7
8
9
10
11
12
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10000);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
} catch (e) {
if (e.name === 'AbortError') console.error('超时');
else console.error('网络错误', e);
} finally {
clearTimeout(timer);
}

WebSocket

WebSocket は全二重の長時間接続で、onopen / onmessage / onclose イベントと send() で送信します。リアルタイム推送に適しています。

1
2
3
4
5
6
7
const ws = new WebSocket('wss://example.com/ws');
ws.onopen = () => ws.send('hello');
ws.onmessage = e => console.log('收到', e.data);
ws.onclose = () => console.log('连接关闭');
ws.onerror = e => console.error('错误', e);
// 閉じる:ws.close()
// ハートビート:定期 ping で接続の健全性を確認

axios との比較

axios はインターセプタ、自動 JSON、豊富なエラーオブジェクトを提供します。fetch はネイティブ、axios はサードパーティです。

1
2
3
4
5
6
7
8
9
10
11
import axios from 'axios';
// GET:
const { data } = await axios.get('/api/users');
// POST:
await axios.post('/api/users', { name: 'Nick' });
// インターセプタ:
axios.interceptors.request.use(cfg => {
cfg.headers.Authorization = 'Bearer ' + token;
return cfg;
});
// エラー:err.response.status / err.response.data

HTTP ステータスコード

2xx 成功、3xx リダイレクト、4xx クライアントエラー、5xx サーバーエラー。よく見るのは 200 / 201 / 400 / 401 / 404 / 500。

1
2
3
4
5
6
7
8
9
10
11
const map = {
200: 'OK 成功',
201: 'Created 已创建',
204: 'No Content 无内容',
400: 'Bad Request 参数错误',
401: 'Unauthorized 未认证',
403: 'Forbidden 无权限',
404: 'Not Found 未找到',
500: 'Internal Server Error 服务器错误',
};
console.log(map[res.status] ?? '其他');

FormData でファイルアップロード

FormData でマルチパートフォームを作り、fetch で POST してファイルとフィールドをアップロードします。進捗は XMLHttpRequest の方が扱いやすいです。

1
2
3
4
5
6
7
8
9
10
11
const file = document.querySelector('input[type=file]').files[0];
const form = new FormData();
form.append('file', file);
form.append('desc', '示例文件');
await fetch('/api/upload', {
method: 'POST',
body: form, // 自動 multipart、Content-Type は手動設定しない
});
// 進捗:
// xhr.upload.onprogress = e => pct = e.loaded / e.total
// 複数ファイル:for (const f of files) form.append('file', f)

16.日付と時刻

Date、タイムスタンプ、フォーマット、タイムゾーン、タイマー。

Date の基本

new Date() で現在時刻。getFullYear / getMonth(0 始まり!)/ getDate で各要素を取得します。月が 0 始まりに注意。

1
2
3
4
5
6
7
const now = new Date();
now.getFullYear(); // 2024
now.getMonth(); // 0-11(1 月は 0!)
now.getDate(); // 当月の日
now.getDay(); // 曜日 0-6(日曜が 0)
now.getHours();
now.toISOString(); // UTC ISO 文字列

タイムスタンプ

Date.now() は UTC エポックからのミリ秒タイムスタンプ。getTime() も同じです。タイムゾーン横断の保存はタイムスタンプか ISO 文字列で。

1
2
3
4
5
6
7
Date.now(); // ミリ秒タイムスタンプ
new Date().getTime(); // 同上
new Date(1700000000000); // ミリ秒から構築
// 秒単位:Math.floor(Date.now() / 1000)
// 文字列を解析:
new Date('2024-01-01T12:00:00Z');
// 解析形式は統一、曖昧さを避ける(例 '01/02')

フォーマット出力

toLocaleDateString / toLocaleString はローカライズ形式、Intl.DateTimeFormat で細かく制御します。

1
2
3
4
5
6
7
8
9
const d = new Date();
d.toISOString(); // '2024-01-01T04:00:00.000Z'
d.toLocaleDateString('zh-CN'); // '2024/1/1'
d.toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
});
// yyyy-MM-dd を手組み:
const pad = n => String(n).padStart(2, '0');
const s = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;

タイムゾーン

getTime は UTC のある瞬間、ローカルのメソッド(getHours など)は実行タイムゾーンで表示します。保存は ISO / タイムスタンプで。

1
2
3
4
5
6
7
8
9
const d = new Date();
d.getTimezoneOffset(); // ローカルと UTC のオフセット(分)
// UTC メソッド:
d.getUTCHours();
// 指定タイムゾーンでフォーマット:
new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
}).format(d);
// サーバーは UTC 保存、フロントはローカル表示

経過時間

2 つの Date を引くとミリ秒差が得られます。秒 / 分 / 時に変換。より高精度な計測は performance.now を使います。

1
2
3
4
5
6
7
8
9
10
const start = Date.now();
doWork();
const ms = Date.now() - start;
// 換算:
const secs = Math.floor(ms / 1000);
const mins = Math.floor(ms / 60000);
// 正確な計時(ミリ秒の高精度):
const t0 = performance.now();
doWork();
const precise = performance.now() - t0;

日付の演算

日 / 月の加算は setDate / setMonth かミリ秒演算で。注意点は 0 始まりの月と月の境界です。

1
2
3
4
5
6
7
8
9
const d = new Date();
d.setDate(d.getDate() + 7); // 7 日加算
// 月を加算:
d.setMonth(d.getMonth() + 1);
// 時間を加算:
const later = new Date(Date.now() + 3 * 3600_000);
// 時間部分をクリア:
d.setHours(0, 0, 0, 0); // 当日 0 時
// DST 時制では丸一日加算に setDate が安全

日付の解析

new Date(string) で日付文字列を解析します。ISO 形式が最も信頼でき、他形式は互換性の差があります。

1
2
3
4
5
6
7
8
9
new Date('2024-01-01'); // ローカル時制 0 時
new Date('2024-01-01T12:00:00Z'); // UTC を明示
new Date('2024/01/01'); // ブラウザ依存、頼らない
// 解析検証:
const d = new Date(str);
if (isNaN(d.getTime())) { /* 不正な日付 */ }
// 手動解析(制御可能):
const [y, m, day] = '2024-01-01'.split('-').map(Number);
new Date(y, m - 1, day); // month は 0 基点

Intl フォーマット

Intl.NumberFormat は数値 / 通貨、Intl.DateTimeFormat は日付。地域ごとにローカライズ出力します。

1
2
3
4
5
6
7
8
9
10
const n = 1234567.89;
new Intl.NumberFormat('zh-CN').format(n); // '1,234,567.89'
new Intl.NumberFormat('zh-CN', {
style: 'currency', currency: 'CNY',
}).format(n); // '¥1,234,567.89'
new Intl.DateTimeFormat('zh-CN', {
dateStyle: 'medium', timeStyle: 'short',
}).format(new Date());
// 相対時間:
// new Intl.RelativeTimeFormat('zh-CN').format(-2, 'day')

17.Node プロセス

process の環境変数、引数、終了コード、カレントディレクトリ、プロセス情報。

環境変数

process.env で環境変数を読みます(Node)。process.env.NODE_ENV がよく使われます。クロスプラットフォーム設定は .env ファイルで。

1
2
3
4
5
6
7
8
process.env.NODE_ENV; // 'production' | 'development'
process.env.PORT;
// 設定:
process.env.MY_VAR = 'value';
// dotenv を使う:
// import 'dotenv/config';
// .env ファイルの変数を読み込む
// 本番設定に秘密鍵をハードコードしない

コマンドライン引数

process.argv は生の引数です。オプション解析は commander / yargs を、簡単な場合は手書きでも構いません。

1
2
3
4
5
6
7
8
9
10
11
// $ node app.js --name Nick
const args = process.argv.slice(2);
// commander を使う:
import { Command } from 'commander';
const program = new Command();
program
.option('-n, --name <name>', '名字')
.parse(process.argv);
console.log(program.opts().name);
// 引数不足の検証:
if (!args.includes('--name')) process.exit(1);

終了コード

process.exit(code) で終了コードを指定して終了します(0 成功、非 0 失敗)。未捕捉例外はデフォルトでコード 1 です。

1
2
3
4
5
6
7
8
9
process.exit(0); // 成功
process.exit(1); // 一般エラー
// 未捕捉例外の終了コード:
process.on('uncaughtException', e => {
console.error(e);
process.exit(1);
});
// async では process.exitCode がより綺麗:
process.exitCode = 1; // イベントループ排出後に終了

カレントディレクトリとパス

process.cwd() は作業ディレクトリ、__dirname はモジュールのディレクトリ、__filename はモジュールのファイル(CJS)。

1
2
3
4
5
6
7
process.cwd(); // 起動時の cwd
// ESM では:
import { fileURLToPath } from 'node:url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
// 設定ファイルは cwd 基準かモジュール基準か?
// 実行ディレクトリに依存するなら process.cwd()
// スクリプト自体を基準にするなら __dirname

プロセスシグナル

SIGINT(Ctrl+C)、SIGTERM(kill のデフォルト)を処理して、Graceful にシャットダウン(データ保存、接続クローズ)。

1
2
3
4
5
6
7
8
9
10
process.on('SIGINT', () => {
console.log('收到 Ctrl+C,清理中...');
cleanup();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('收到终止信号,优雅退出');
server.close();
});
// 二重発火防止:フラグでクリーンアップ中をマーク

プロセス情報

process.pid はプロセス ID、process.uptime() は稼働秒数、process.memoryUsage() はメモリ、process.platform は OS。

1
2
3
4
5
6
7
8
process.pid; // プロセス ID
process.platform; // 'win32' | 'linux' | 'darwin'
process.arch; // 'x64'
process.uptime(); // 実行秒数
process.memoryUsage(); // { rss, heapUsed, ... }
process.version; // Node バージョン
// よく使うデバッグ:
console.log(process.memoryUsage().heapUsed / 1024 / 1024);

標準ストリーム

process.stdin / stdout / stderr の 3 つの標準ストリーム。パイプライン、リダイレクト、シェル連携に使います。

1
2
3
4
5
6
7
8
9
10
11
// 出力:
process.stdout.write('text'); // 改行なし
process.stderr.write('error'); // エラーストリーム
// stdin を 1 行ずつ読む(パイプ):
import readline from 'node:readline';
const rl = readline.createInterface({ input: process.stdin });
for await (const line of rl) {
console.log(line.toUpperCase()); // 行ごとに処理
}
// $ cat file.txt | node app.js > out.txt
// ストリームが TTY か:process.stdout.isTTY

子プロセス child_process

child_process は外部コマンド起動:exec は出力取得、spawn はストリーム、fork は JS スクリプト。シェルインジェクションに注意。

1
2
3
4
5
6
7
8
9
10
import { exec, spawn } from 'node:child_process';
// exec:コマンド実行し完全な出力を取得
exec('ls -la', (err, stdout) => {
if (err) console.error(err.message);
else console.log(stdout);
});
// spawn:大出力をストリーム処理
const child = spawn('node', ['worker.js'], { stdio: 'inherit' });
child.on('exit', code => console.log('退出码', code));
// execFile/spawn で配列引数を渡すのを優先、シェルインジェクション回避

18.正規表現

正規表現リテラル、マッチ、キャプチャグループ、置換、よく使うパターン。

正規表現の構文

リテラル /pattern/ または new RegExp。\d 数字、\w 単語文字、\s 空白、[] 文字クラス、{} 量指定子。

1
2
3
4
5
6
7
const email = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
const phone = /^1[3-9]\d{9}$/;
/\d{4}-\d{2}-\d{2}/; // 日付
/colou?r/; // color/colour
/\bword\b/; // 単語境界
// 文字列から正規表現を作るには二重バックスラッシュ:
new RegExp('\\d+'); // /\d+/ と同じ

正規表現のフラグ

g はグローバル、i は大小無視、m はマルチライン(^$ が行頭 / 末)、s は dotAll(. が改行に一致)、u は Unicode。

1
2
3
4
5
6
7
const g = /\b\w+\b/g; // 全体マッチ
const i = /hello/i; // 大文字小文字を無視
const m = /^start/m; // 各行の先頭
const s = /a.b/s; // . は改行にもマッチ
// インライン:
/(?i)hello/; // 大文字小文字を無視
// 組み合わせ:/ab/gi

test と match

test は boolean、match は結果、matchAll はすべてのマッチを反復、exec は単発マッチで lastIndex を持ちます。

1
2
3
4
5
6
7
8
const re = /\d+/g;
re.test('abc123'); // true
'abc123'.match(/\d+/); // ['123', index, input]
// 全マッチ:
'a1b2'.matchAll(/\d/g);
// [...str.matchAll(/\d/g)].map(m => m[0]);
// 置換のカウント:
('a1b2'.match(/\d/g) || []).length; // 2

キャプチャグループ

丸括弧はキャプチャ、match[1] で取得、名前付きグループ (?<name>...) は match.groups.name。 非キャプチャは (?:...)。

1
2
3
4
5
6
7
const m = '2024-01-01'.match(/(\d{4})-(\d{2})-(\d{2})/);
m[1]; // '2024'
m[2]; // '01'
// 名前付きグループ:
const n = '2024-01-01'.match(/(?<year>\d{4})-(?<month>\d{2})/);
n.groups.year; // '2024'
// 非キャプチャ:/(?:ab)+/ グループ化のみで捕捉しない

正規表現による置換

replace は $1 / $<name> でグループ参照、コールバック関数も使えます。replaceAll はすべて置換(g 相当)。

1
2
3
4
5
6
'2024-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');
// マスキング:
'13812345678'.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
// コールバック:
'abc'.replace(/\w/g, c => c.toUpperCase()); // 'ABC'
// 全部:'a-b'.replaceAll('-', '+')

よく使うパターン

メール、URL、IP、電話番号、英数字のみ。本番の検証では許可リストや専用ライブラリがより堅牢です。

1
2
3
4
5
6
7
8
9
const patterns = {
email: /^[^@\s]+@[^@\s]+\.[^@\s]+$/,
url: /^https?:\/\/[^\s]+$/,
ipv4: /^(\d{1,3}\.){3}\d{1,3}$/,
phone: /^1[3-9]\d{9}$/,
alnum: /^[a-zA-Z0-9]+$/,
};
patterns.email.test('[email protected]'); // true
// 注意:簡易検証で十分、本番は専用ライブラリで厳密に

正規表現のパフォーマンス

破滅的バックトラック(入れ子の量指定子)を避ける。入力が長い場合は正規表現を事前にコンパイルする。必要に応じて文字列メソッドで代替する。

1
2
3
4
5
6
7
8
9
// 遅い:ネストした量指定子はバックトラックしやすい
/(a+)+$/; // 'aaaaaaaaX' には遅くなり得る
// 最適化:
/(?:a+)$/;
// 事前コンパイルして再利用:
const re = /pattern/g; // 複数回 match で再利用
// 単純判定は文字列メソッド優先:
str.includes('x') 优于 /x/;
// 長いテキストはタイムアウト/分割に注意

先読みと後読み

アサーションは位置だけをマッチし文字を消費しません:先読み (?=)、否定先読み (?!)、後読み (?<=)。パスワードや境界の検証に有用です。

1
2
3
4
5
6
7
const re = /foo(?=\d)/; // 数字が続く foo
re.test('foo1'); // true
re.test('fooX'); // false
/\d+(?!px)/.test('10em'); // true(10 の後に px がない)
/(?<=@)\w+/.exec('[email protected]'); // '@' の後の b にマッチ
// パスワード強度:英字と数字の両方を含む
/^(?=.*[a-z])(?=.*\d).{6,}$/.test('abc123'); // true

19.ビルドとデバッグ

npm スクリプト、バンドラ、デバッグ、テスト、コード規約。

npm スクリプト

package.json の scripts でよく使うコマンドを定義し、npm run で実行します。&& で連結、pre / post フックも使えます。

1
2
3
4
5
6
7
8
9
10
11
12
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run",
"lint": "eslint . --fix",
"check": "npm run lint && npm run test"
}
}
// $ npm run dev / npm test
// prebuild は build の前に自動実行される

バンドラ

Vite(モダン推奨)、webpack、Rollup、esbuild が ES モジュールをブラウザ向けコードにバンドルします。

1
2
3
4
5
6
7
8
9
// Vite(esbuild/rollup ベース):
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: { outDir: 'dist' },
});
// $ npm create vite@latest my-app
// $ npm run dev # 開発サーバー(HMR)
// $ npm run build # 本番ビルド

デバッグ

console のレベル分け、debugger でブレークポイント、Node --inspect でデバッガ。ソースマップで元ソースへ。

1
2
3
4
5
6
7
8
9
console.log('info'); // 通常
console.warn('warn'); // 警告
console.error('error'); // エラー
console.debug('debug');
debugger; // ブレークポイント(DevTools を開くと停止)
// Node デバッグ:
// $ node --inspect-brk app.js
// その後 Chrome で chrome://inspect を開く
// または VS Code F5 + launch.json

テスト

Vitest / Jest の単体テスト:describe でグループ化、test / it でケース、expect でアサーション。npm test で実行。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { describe, it, expect } from 'vitest';
function add(a, b) { return a + b; }
describe('add', () => {
it('加和', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
});
// 非同期テスト:
it('async', async () => {
await expect(load()).resolves.toBeDefined();
});
// カバレッジ:vitest run --coverage

ESLint でのリント

ESLint が構文とスタイルをチェック、Prettier がフォーマットを統一、pre-commit フックで自動化します。

1
2
3
4
5
6
7
8
9
10
11
12
// eslint.config.js
import js from '@eslint/js';
export default [
js.configs.recommended,
{ rules: {
'eqeqeq': ['error', 'always'], // === を強制
'no-unused-vars': 'warn',
}},
];
// $ npx eslint src --fix
// Prettier:$ npx prettier --write .
// husky + lint-staged でコミット前チェック

TypeScript 統合

TS は型を追加します。tsc でコンパイル、ts-node / tsx で直接実行。Vite は .ts をネイティブにサポートします。

1
2
3
4
5
6
7
8
9
10
11
// $ npm i -D typescript
// tsconfig.json:
{ "compilerOptions": {
"target": "ES2022",
"strict": true,
"module": "ESNext",
"moduleResolution": "bundler"
} }
// $ npx tsc --noEmit # 型チェック
// Vite では直接 import './app.ts'
// tsx:$ npx tsx script.ts

CI とデプロイ

CI でテストとビルドを自動実行。GitHub Actions などのワークフロー設定。成果物を静的ホストやコンテナにデプロイします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
# デプロイ:dist/ を静的ホスティングにアップロード、またはイメージを構築

ESM と CJS

2 つのモジュールシステム:ESM は静的 import で tree-shake 可能、CJS は動的 require で実行時読み込み。type フィールドでデフォルトを制御。

1
2
3
4
5
6
7
8
9
// ESM(.mjs か package.json の type: module)
import { readFile } from 'node:fs/promises';
export const NAME = 'app';
// CJS(.cjs か type フィールドなし)
const fs = require('node:fs');
module.exports = { NAME: 'app' };
// ESM は動的 import で CJS を読み込める:
const mod = await import('cjs-pkg');
// バンドル時は tree-shaking を効かせるため ESM 優先

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは ECMAScript 2022(JavaScript)の自己完結型チートシートです。言語コアと、ブラウザ / Node のよく使う API を実プロジェクトの約 80% で扱う範囲でカバーします。内容はモダンなイディオム中心:let / const のブロックスコープ、アロー関数、テンプレート文字列、分割代入、スプレッド、Promise と async / await、オプショナル連鎖と null 合体演算子。JavaScript は 1995 年に Brendan Eich が Netscape で開発し、今ではすべてのブラウザがネイティブサポートする唯一の言語であり、Web フロントエンド、Node.js サーバー、デスクトップアプリの基盤となっています。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てます:基本構文、変数とスコープ、型、参照と値セマンティクス、制御フロー、関数とクロージャ、文字列、配列とオブジェクト、メモリと GC、プロトタイプとクラス、エラー処理、IO、よくある落とし穴、並行処理(イベントループ)、ネットワーク(fetch)、時間、プロセス、正規表現、ビルドツール。各サブセクションには「概念紹介 + コピペ可能なコード」が付いています。 すべてのコードとテキストはブラウザ内でローカルにレンダリングされ、データは一切デバイス外へ出ません。信頼すべきリファレンスは MDN Web Docs です。

バージョン 2.1.0