本工具使用的開源套件

本工具程式碼中捆綁了 1 個開源套件。

TypeScript 速查 — 簡明參考

TypeScript 5 語法、類型系統與最常用慣用法速查手冊,覆蓋約 80% 日常場景。

TS

TypeScript TypeScript 5.x

ECMAScript + 類型 · 多範式 · 結構化 · 靜態(漸進)類型於 JS 之上

學習路徑

先搭好編譯環境(tsc/tsconfig)並理解 TS 是 JS 的超集 → 掌握基礎類型標註、聯合類型與接口 → 深入類型系統(泛型、類型守衞、類型操作)→ 用 class 與模塊組織代碼 → 處理異步與 DOM/Node 類型 → 最後按需查構建配置、測試與調試。FAQ 節適合回頭避坑。

1.Hello World 與構建環境

編譯運行 TypeScript,理解 tsc、tsconfig 與類型檢查流程。

最小程序

TS 是 JS 超集:合法 JS 就是合法 TS。加類型標註後經 tsc 編譯為 JS。

1
2
3
4
5
6
// hello.ts
const message: string = 'Hello, world!';
console.log(message);
// 類型標註:變量名後的 : string
// $ npx tsc hello.ts # 編譯生成 hello.js
// $ node hello.js

tsc 編譯

tsc 編譯 .ts 為 .js。--noEmit 只做類型檢查不輸出、--watch 監聽改動、--strict 嚴格模式。

1
2
3
4
5
// $ npx tsc app.ts # 編譯單文件
// $ npx tsc --noEmit # 只檢查類型
// $ npx tsc --watch # 監聽自動編譯
// $ npx tsc --strict # 嚴格類型檢查
// $ npx tsc --outDir dist # 輸出目錄

tsconfig.json

tsconfig.json 配置編譯:target、module、strict、outDir。npx tsc 自動讀取。

1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true, // 嚴格模式
"outDir": "dist",
"sourceMap": true // 調試用 source map
},
"include": ["src"]
}
// 用 tsc --init 生成默認配置

直接運行 TS

用 tsx/ts-node 直接運行 TS 無需先編譯。Node 22 原生支持 --experimental-strip-types。

1
2
3
4
5
// $ npx tsx script.ts # 直接運行 TS
// $ npx ts-node script.ts # 老方案
// Node 22+:
// $ node --experimental-strip-types script.ts
// 開發腳本常用 tsx,生產 tsc 編譯後運行

嚴格模式

strict: true 開啓全部嚴格檢查:null 檢查、隱式 any 報錯、未使用變量警告。新項目必須開。

1
2
3
4
5
6
7
8
// strict 包含:
// 1. strictNullChecks null 不能賦給普通類型
// 2. noImplicitAny 參數無類型時報錯
// 3. noUnusedLocals 未使用變量報錯
function greet(name: string) { // 顯式類型
return 'Hi ' + name;
}
// 不開 strict 時 name 隱式 any 不報錯

TS 與 JS 的關係

TS 在編譯期檢查類型,編譯後 JS 無類型信息。類型只在編譯期存在,運行時被擦除。

1
2
3
4
5
6
7
8
interface User {
name: string;
age: number;
}
const u: User = { name: 'Nick', age: 30 };
// 編譯產物(類型被擦除):
// const u = { name: 'Nick', age: 30 };
// 類型錯誤在編譯期捕獲,不影響運行

依賴與類型包

庫需類型定義。@types/* 是 DefinitelyTyped 社區類型包。typescript 是編譯工具依賴。

1
2
3
4
5
6
// $ npm i -D typescript @types/node
// $ npm i -D @types/express # 社區類型
// 庫自帶類型:
// axios 直接 export 類型,無需 @types
// // @ts-ignore 註釋:跳過下一行檢查(慎用)
// import axios from 'axios'; 有類型定義

編輯器集成

VS Code 內置 TS 支持:懸停看類型、報錯紅波浪線、自動補全、重構。錯誤信息直接顯示。

1
2
3
4
5
6
7
// VS Code 快捷鍵:
// 懸停:查看類型
// 快速修復:Ctrl+. (Cmd+.)
// 重命名符號:F2
// 跳到定義:F12
// 類型錯誤出現在 Problems 面板
// 配置:使用工作區 tsconfig 需要 `tsc` 版本一致

2.變量與類型標註

類型標註、類型推斷、聯合類型、any/unknown 與類型斷言。

類型標註

變量名後 : 類型 標註類型。標註後類型固定,賦其他類型報編譯錯誤。

1
2
3
4
5
6
const name: string = 'Nick';
let age: number = 30;
const isAdmin: boolean = true;
// 錯誤示例:
// age = 'thirty'; // 類型錯誤
// 初始化後類型推斷,可不顯式標註

類型推斷

TS 從初始化值推斷類型,多數情況無需顯式標註。複雜類型推薦顯式接口。

1
2
3
4
5
6
7
8
const name = 'Nick'; // 推斷為 string
let count = 42; // number
const arr = [1, 2, 3]; // number[]
const obj = { a: 1 }; // { a: number }
// 賦值時推斷聯合:
let status = 'idle' as const;
// 複雜場景顯式標註更清晰:
let data: Map<string, User> = new Map();

聯合類型

| 聯合類型表示「或」:string | number 兩者皆可。訪問成員需先類型守衞。

1
2
3
4
5
6
7
8
9
10
11
12
function print(value: string | number) {
// 聯合類型只能訪問公共成員
console.log(value.toString());
// 分支處理:
if (typeof value === 'string') {
console.log(value.toUpperCase()); // string
} else {
console.log(value.toFixed(2)); // number
}
}
// 字面量聯合:
type Dir = 'up' | 'down' | 'left' | 'right';

any 與 unknown

any 關閉類型檢查(避免);unknown 表示未知(需守衞後才可用)。安全處理外部數據用 unknown。

1
2
3
4
5
6
7
8
9
let risky: any = 'text'; // any:不檢查,慎用
risky.method(); // 編譯通過但運行時可能崩
// unknown:安全
let data: unknown = getApi();
if (typeof data === 'string') {
console.log(data.length); // 守衞後可用
}
// 斷言 unknown:data as string
// 優先 unknown,避免 any

類型斷言

as 斷言告訴編譯器你比它懂。斷言不改變運行時,只是編譯期聲明。濫用會掩蓋錯誤。

1
2
3
4
5
6
7
const el = document.getElementById('btn') as HTMLButtonElement;
// 不推薦雙重斷言(有坑):
// const n = value as unknown as number;
// 更安全:先守衞再斷言
const json = JSON.parse(text) as User[];
// as const:字面量收窄
const modes = ['dev', 'prod'] as const;

非空斷言

! 後綴斷言值非 null/undefined。僅當確定時用,否則運行時可能崩潰。

1
2
3
4
5
6
7
8
9
let name: string | null = getMaybe();
const len = name!.length; // 斷言非空(有風險)
// 更安全寫法:
if (name) {
const l2 = name.length;
}
// 或空值合併:
const l3 = name?.length ?? 0;
// 非空斷言是編譯期承諾,運行時仍是 null 會崩

字面量類型

字面量類型把類型精確到具體值:'up'、42、true。配合聯合枚舉選項。

1
2
3
4
5
6
7
8
let direction: 'up' | 'down' = 'up';
// direction = 'sideways'; // 錯誤:不在聯合裏
const yes: true = true;
// 對象屬性收窄:
const config = {
mode: 'production',
} as const; // mode: 'production' 字面量
// as const 讓對象成員變只讀字面量類型

解構與類型

解構賦值保留類型。函數參數解構需標註整個參數對象類型。

1
2
3
4
5
6
7
8
9
10
interface User { name: string; age: number; }
const { name, age }: User = getUser();
// 函數參數解構:
function show({ name, age }: User) {
console.log(name, age);
}
// 數組解構:
const [first, second] = [1, 2] as const;
// 可選屬性解構:
const { name = 'guest' }: { name?: string } = data;

3.類型系統

基礎類型、對象/數組/元組、接口、泛型、類型別名與類型操作。

基礎類型

string、number、boolean、null、undefined、void、symbol、bigint 基礎類型集合。

1
2
3
4
5
6
7
8
const s: string = 'text';
const n: number = 42;
const b: boolean = true;
const v: void = undefined; // 無返回值函數
const nl: null = null;
const u: undefined = undefined;
const sym: symbol = Symbol('id');
const big: bigint = 10n;

數組與元組

number[] 數組、[string, number] 元組(定長定序)、readonly 只讀數組。

1
2
3
4
5
6
7
8
9
const nums: number[] = [1, 2, 3];
const strs: Array<string> = ['a', 'b']; // 泛型寫法
// 元組:
let pair: [string, number] = ['age', 30];
// pair[0] = 42; // 錯誤:類型不匹配
// 只讀數組:
const fixed: readonly number[] = [1, 2];
// fixed.push(3); // 錯誤:只讀
// 可選元組元素:type T = [string, number?]

對象類型

對象類型描述形狀:屬性、可選 ?、只讀 readonly、方法簽名。

1
2
3
4
5
6
7
8
9
10
11
12
interface Point {
readonly x: number; // 只讀
y: number;
label?: string; // 可選
}
const p: Point = { x: 1, y: 2 };
// p.x = 10; // 錯誤:readonly
// 方法:
interface Greeter {
greet(name: string): string;
// 或 greet: (name: string) => string;
}

interface 與 type

interface 定義對象形狀(可擴展),type 別名更靈活(聯合/交叉/元組)。日常優先 interface。

1
2
3
4
5
6
7
8
9
10
11
interface User {
name: string;
}
// interface 可合併/繼承:
interface Admin extends User {
permissions: string[];
}
// type 別名:
type ID = string | number; // 聯合
type Pair = [string, number]; // 元組
type Shape = { area: number } & { color: string }; // 交叉

枚舉

enum 命名常量集合:數字枚舉、字符串枚舉、const enum。字符串枚舉更常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Color {
Red, // 0
Green, // 1
Blue, // 2
}
enum Status {
Active = 'active',
Inactive = 'inactive',
}
const c: Color = Color.Green;
const s: string = Status.Active; // 'active'
// 反向映射:Color[0] === 'Red'(數字枚舉)
// 只想要類型:type S = 'active' | 'inactive'

泛型

泛型把類型參數化:T 是類型參數。函數/類/接口通用複用,編譯期確定。

1
2
3
4
5
6
7
8
9
10
11
function identity<T>(value: T): T {
return value;
}
const s = identity('hello'); // string
const n = identity(42); // number
// 泛型接口:
interface Box<T> {
value: T;
}
const box: Box<number> = { value: 42 };
// 多參數:function pair<A, B>(a: A, b: B)

keyof 與索引

keyof 取對象鍵聯合、索引訪問 T[K]、映射類型。類型操作的核心工具。

1
2
3
4
5
6
7
8
9
10
interface User { name: string; age: number; }
type Keys = keyof User; // 'name' | 'age'
// 索引訪問:
type NameType = User['name']; // string
// 映射類型:
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Partial<T>、Required<T>、Pick<T,K> 內置工具
// 部分可選:type PartialUser = Partial<User>

內置工具類型

Partial/Omit/Pick/Record/Exclude/ReturnType 等映射類型簡化常見轉換。

1
2
3
4
5
6
7
type PartialUser = Partial<User>; // 全部可選
type PickName = Pick<User, 'name'>; // 只取 name
type NoAge = Omit<User, 'age'>; // 去掉 age
type Rec = Record<string, number>; // 鍵值映射
type WithoutZero = Exclude<0 | 1 | 2, 0>; // 1 | 2
type R = ReturnType<typeof fn>; // 函數返回類型
// Parameters<typeof fn> 參數類型

模板字面量類型

模板字符串語法構造字符串類型。結合聯合生成排列組合。字符串解析類型級技巧。

1
2
3
4
5
6
7
8
type Event = `on${'Click' | 'Hover'}`;
// Event = 'onClick' | 'onHover'
type Size = `${'small' | 'large'}-${number}`;
// Size = 'small-1' | 'large-2' ...
// 從字符串提取:
// type Extracted = 'a:b'.split<'a:b', ':'>; // 類型級 split
// 簡單字符串解析:
// type First = 'abc' extends `${infer F}bc` ? F : never; // 'a'

4.類型守衞與空值

類型守衞、類型收縮、可選鏈、null 處理與引用語義。

類型守衞

typeof、instanceof、in 判斷收窄聯合類型。分支內類型自動收縮。

1
2
3
4
5
6
7
8
9
10
11
function f(v: string | number | Date) {
if (typeof v === 'string') {
v.toUpperCase(); // string
} else if (v instanceof Date) {
v.getTime(); // Date
} else {
v.toFixed(2); // number
}
}
// in 守衞:
if ('permissions' in user) { /* Admin */ }

類型收縮

守衞後類型在作用域內收縮(narrowing)。null 檢查、truthy 檢查都會收窄。

1
2
3
4
5
6
7
8
9
10
11
let value: string | null = getMaybe();
if (value) {
value.length; // string(收窄)
}
// 真值收縮:
function f(s: string | undefined) {
s ?? console.log('missing'); // 空值合併收窄
}
// 判空後:
value = null;
if (value === null) return; // 之後 value 非 null

可辨識聯合

可辨識聯合:共享判別字段(kind/type),switch 後類型精確收縮。狀態機建模。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function area(s: Shape): number {
switch (s.kind) {
case 'circle': return Math.PI * s.radius ** 2;
case 'square': return s.side * s.side;
// 窮盡檢查:
default: {
const _exhaustive: never = s;
return _exhaustive;
}
}
}

可選鏈與空合併

?. 安全訪問、?? 空值兜底、??= 賦默認。鏈式深訪問防 null 崩潰。

1
2
3
4
5
6
7
8
9
const name = user?.profile?.name ?? 'guest';
const count = data?.items?.length ?? 0;
// 空合併賦值:
let settings = getConfig();
settings ??= { theme: 'dark' };
// 可選調用:
callback?.();
// ?? 與 || 區別:
// 0 ?? 'x' 是 0;0 || 'x' 是 'x'

null 與 undefined

strictNullChecks 下 null/undefined 不能賦給普通類型。需顯式聯合或處理。

1
2
3
4
5
6
7
8
9
let name: string | null = null; // 聯合包含 null
let title: string | undefined;
// 函數返回可空:
function find(): User | null {
return Math.random() > 0.5 ? null : { name: 'x' };
}
const u = find();
if (u) { u.name; } // 收縮後訪問
// 斷言安全:u!.name 或 u ?? { name: '?' }

引用語義

TS 不改變 JS 引用語義:對象按引用共享、數組淺拷貝。類型層面只描述形狀。

1
2
3
4
5
6
7
8
9
const a = { x: 1 };
const b = a; // 引用共享
b.x = 99;
console.log(a.x); // 99
// 拷貝仍是淺拷貝:
const c = { ...a };
c.x = 1; // a.x 不變
// TS 類型不保證不可變,除非 readonly
// 深度凍結用 as const + 庫

自定義類型守衞

is 語法聲明函數是類型守衞:返回 boolean 且參數收窄。過濾數組常用。

1
2
3
4
5
6
7
8
9
function isString(v: unknown): v is string {
return typeof v === 'string';
}
const values: unknown[] = ['a', 1, 'b', null];
const strs = values.filter(isString);
// strs 類型是 string[](守衞生效)
// 箭頭函數寫法:
const s2 = values.filter(
(v): v is string => typeof v === 'string');

斷言函數

asserts 斷言函數聲明不變量:返回 void 但調用後類型收窄。拋錯則中斷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function assertString(v: unknown): asserts v is string {
if (typeof v !== 'string') {
throw new Error('期望 string');
}
}
function process(v: unknown): void {
assertString(v);
v.toUpperCase(); // 收窄為 string
}
// 無條件斷言:
function assert(cond: unknown): asserts cond {}
// 斷言值:
function assertNonNull<T>(v: T): asserts v is NonNullable<T> {}
// 用於運行時不變量 + 類型收窄雙保險

5.流程控制

分支、循環、switch 與類型收縮的配合。

if / else

if/else 分支 + 類型守衞。條件表達式收窄變量類型。

1
2
3
4
5
6
7
8
9
function describe(v: string | number) {
if (typeof v === 'string') {
return `字符串: ${v.toUpperCase()}`;
} else {
return `數字: ${v.toFixed(2)}`;
}
}
// 多分支 else-if + 守衞逐層收窄
// truthy 檢查:if (value) 收窄非空

switch 與窮盡

switch 處理可辨識聯合。default 分支用 never 檢查所有情況已覆蓋。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Action =
| { type: 'add'; n: number }
| { type: 'reset' };
function reducer(a: Action) {
switch (a.type) {
case 'add': return a.n;
case 'reset': return 0;
default: {
const exhaustive: never = a; // 缺分支則編譯錯
return exhaustive;
}
}
}

循環

for/for...of/while 遍歷。數組迭代 for...of,需要索引 for 或 entries。

1
2
3
4
5
6
7
8
9
for (let i = 0; i < 10; i++) { }
for (const item of items) { }
for (const [i, v] of items.entries()) {
console.log(i, v);
}
let n = 0;
while (n < 5) { n++; }
// 遍歷對象鍵:
for (const key of Object.keys(obj) as (keyof typeof obj)[]) { }

三元與類型

三元分支兩側類型會聯合。條件返回不同類型時結果是聯合類型。

1
2
3
4
5
6
const result = cond ? 'yes' : 0;
// result 類型是 'yes' | 0(字面量聯合)
// 需要統一類型時顯式標註:
const msg: string = cond ? 'yes' : String(0);
// 嵌套三元可讀性差,改用守衞
// 空值:v ?? fallback

break 與 continue

continue 跳本輪、break 退出、帶標籤 break/continue 控制嵌套。

1
2
3
4
5
6
7
8
9
10
11
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) continue;
if (i > 7) break;
}
outer:
for (const a of list) {
for (const b of a.items) {
if (b.done) continue outer;
}
}
// 類型不變,純控制流

提前返回

守衞子句提前 return 減少嵌套。空值檢查後類型收縮。

1
2
3
4
5
6
7
8
function process(u: User | null) {
if (u === null) return; // 提前返回
if (u.age < 18) return;
console.log(u.name); // 已收窄為非空
}
// 多守衞讓主邏輯扁平
// 空值合併提前給默認:
const name = u?.name ?? 'guest';

do...while

do...while 先執行一次再判斷。至少執行一次的循環用。類型不參與。

1
2
3
4
5
6
7
8
9
10
11
let attempts = 0;
do {
attempts++;
const ok = tryOnce();
if (ok) break;
} while (attempts < 3);
// 至少執行一次,再判斷條件
// while:先判斷再執行(可能零次)
// 使用場景:
// 重試、菜單選擇、輸入校驗
// 注意死循環風險:條件必須最終為假

對象遍歷

Object.keys 遍歷對象鍵。類型斷言 keyof 保證安全。值遍歷用 Object.values。

1
2
3
4
5
6
7
8
9
10
11
const config = { host: 'x', port: 3000 };
// 鍵遍歷(需斷言):
for (const key of Object.keys(config) as (keyof typeof config)[]) {
console.log(key, config[key]);
}
// 值遍歷:
for (const value of Object.values(config)) { }
// 鍵值對:
for (const [k, v] of Object.entries(config)) { }
// 記錄類型遍歷:
// Object.keys 返回 string[],斷言後取值安全

6.函數

函數標註、可選/默認參數、重載、剩餘參數與 this。

函數類型

函數類型標註:參數類型與返回類型。箭頭函數類型與函數聲明等價。

1
2
3
4
5
6
7
8
9
10
11
// 函數聲明:
function add(a: number, b: number): number {
return a + b;
}
// 箭頭函數:
const add = (a: number, b: number): number => a + b;
// 函數類型變量:
type Fn = (a: number, b: number) => number;
const f: Fn = add;
// void 返回:
function log(msg: string): void { console.log(msg); }

可選與默認參數

? 可選參數、= 默認參數。默認參數隱含可選。可選參數在必選參數之後。

1
2
3
4
5
6
7
8
9
10
11
function greet(name: string, title?: string): string {
return title ? `${title} ${name}` : name;
}
// 默認參數:
function mul(a: number, b = 2): number {
return a * b;
}
mul(3); // 6
// 默認參數可省略不傳
// 可選參數放後面:
// greet('Nick', undefined) 也可以

剩餘參數

...rest 收集不定數量參數為數組。rest 參數需標註數組類型。

1
2
3
4
5
6
7
8
9
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
// 泛型 rest 保留元組:
function tuple<T extends unknown[]>(...args: T): T {
return args;
}
const t = tuple(1, 'a', true); // [number, string, boolean]

重載簽名

重載:多個簽名聲明 + 一個實現。調用時按簽名匹配。按參數組合約束返回類型。

1
2
3
4
5
6
7
8
function pick(obj: Record<string, unknown>, key: string): unknown;
function pick(obj: number[], index: number): number;
function pick(obj: any, key: string | number): unknown {
return obj[key];
}
// 實現簽名不對外可見
// 重載按順序匹配,把寬泛的放最後
// 返回類型差異場景:DOM API 類型常用

泛型函數

泛型參數約束:T extends 約束。約束限定類型後可用約束上的成員。

1
2
3
4
5
6
7
8
9
10
function first<T extends string | number[]>(arr: T): T[number] {
return arr[0];
}
const s = first('hello'); // string
const n = first([1, 2, 3]); // number
// 約束調用:
function getLen<T extends { length: number }>(v: T): number {
return v.length;
}
// 多泛型:<K, V extends keyof K>

this 類型

this 參數標註 this 類型。方法鏈返回 this 實現鏈式調用。箭頭函數不綁定 this。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Builder {
private items: string[] = [];
add(item: string): this {
this.items.push(item);
return this; // 鏈式
}
}
const b = new Builder().add('a').add('b');
// 顯式 this 參數(放首位):
function log(this: { name: string }) {
console.log(this.name);
}
// 箭頭函數繼承外層 this

回調與函數參數

回調函數作為參數:用函數類型標註。數組高階函數 map/filter/reduce 的類型推導。

1
2
3
4
5
6
7
8
9
function withLog(fn: (n: number) => number) {
return fn(42);
}
withLog(n => n * 2); // 參數類型自動推導
// 數組高階函數:
const doubled = [1, 2, 3].map(n => n * 2);
const evens = [1, 2, 3, 4].filter(n => n % 2 === 0);
const total = [1, 2, 3].reduce((acc, n) => acc + n, 0);
// 回調 this 類型注意,避免丟失

函數約束技巧

參數聯合收窄、可選回調、返回推斷。函數參數儘量用接口而非具體類。

1
2
3
4
5
6
7
8
9
10
function handle(v: string | number, cb?: (r: string) => void) {
const r = typeof v === 'string' ? v.toUpperCase() : String(v);
cb?.(r); // 可選回調
}
// 參數用接口(結構類型):
interface HasId { id: number }
function findById<T extends HasId>(arr: T[], id: number): T | undefined {
return arr.find(x => x.id === id);
}
// 結構類型:形狀匹配即可,不必是同類實例

7.字符串

模板字符串、常用方法、正則與字符處理。

模板字符串

反引號模板字符串:${} 插值、多行保留。類型仍是 string。

1
2
3
4
5
6
7
8
9
10
11
12
const name = 'Nick';
const greeting = `Hello, ${name}!`;
// 多行:
const lines = `
line 1
line 2
`;
// 表達式:
const total = `Sum: ${1 + 2}`;
// 模板字面量類型:
// type T = `id-${string}`
// 類型仍是 string,模板只是語法糖

常用方法

slice/substring 截取、toUpperCase 轉換、split 分割、includes/startsWith 查詢、replace 替換。

1
2
3
4
5
6
7
8
9
const s = 'TypeScript';
const sub = s.slice(0, 4); // 'Type'
const upper = s.toUpperCase(); // 'TYPESCRIPT'
const parts = s.split(''); // 字符數組
s.includes('Script'); // true
s.startsWith('Type'); // true
s.endsWith('t'); // true
const r = 'a-b-c'.replace(/-/g, '_'); // 'a_b_c'
const padded = '7'.padStart(3, '0'); // '007'

模板字面量類型

類型級模板字符串:${} 拼接類型。推斷 infer 提取字符串結構。

1
2
3
4
5
6
7
8
type Route = `/users/${string}/profile`;
const good: Route = '/users/123/profile';
// 類型級提取:
type ExtractId<S extends string> =
S extends `/users/${infer Id}/profile` ? Id : never;
type Id = ExtractId<'/users/42/profile'>; // '42'
// 大寫轉換:Uppercase<T>、Lowercase<T>
// 組合:type All = `${'a'|'b'}${'1'|'2'}` // 'a1'|'a2'|'b1'|'b2'

字符與碼點

length 是 UTF-16 碼元數(emoji 算兩個)。碼點遍歷用 for...of / Array.from。

1
2
3
4
5
6
7
8
9
const emoji = '👋';
console.log(emoji.length); // 2(代理對)
console.log([...emoji].length); // 1
const s = 'abc';
for (const ch of s) { } // 逐碼點
// 碼點訪問:
const first = Array.from(s)[0];
// charCodeAt/fromCodePoint 處理碼點:
String.fromCodePoint(128075); // '👋'

正則

RegExp 匹配替換。match 返回捕獲組數組,matchAll 全局遍歷。類型層面 RegExp 固定。

1
2
3
4
5
6
7
8
9
10
11
const re = /(\w+)@(\w+)/g;
const all = s.matchAll(re);
for (const m of all) {
console.log(m[1], m[2]); // 捕獲組
}
// 替換帶捕獲:
const r = '2026-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1');
// 斷言非空:
const match = s.match(/(\w+)@/);
if (match) { console.log(match[1]); }

區域與比較

localeCompare 區域比較、toLocaleLowerCase 區域轉換、Intl 格式化數字日期。

1
2
3
4
5
6
const arr = ['ä', 'a', 'z'].sort((a, b) => a.localeCompare(b, 'zh'));
const num = (1234.5).toLocaleString('zh-CN'); // '1,234.5'
const date = new Date().toLocaleDateString('zh-CN');
// Intl.NumberFormat:
const nf = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' });
// 區域列表:Intl.supportedValuesOf('language')

轉義字符

字符串轉義:\n 換行、\t 製表、\\ 反斜槓、\u 碼點。單雙引號內轉義。

1
2
3
4
5
6
7
8
9
10
const nl = '第一行\n第二行';
const tab = 'a\tb'; // a 空格製表 b
const backslash = 'C:\\path'; // C:\path
const quote = 'He said \'hi\'';
const uni = '\u4e2d'; // '中'
const code = '\u{1F600}'; // emoji 碼點
// 模板字符串不需要轉義單雙引號
const tmpl = `She said "hi" and 'bye'`;
// 常用:\n 換行 \t 縮進 \\ 路徑
// JSON 輸出要轉義引號

反轉與比較

字符串反轉用 split/數組、去空格、去重。比較用 localeCompare 或規範比較。

1
2
3
4
5
6
7
8
9
10
11
12
const s = 'hello';
// 反轉:
const rev = [...s].reverse().join(''); // 'olleh'
// 去首尾空白:
const t = ' text '.trim();
// 去所有空白:
const compact = s.replace(/\s+/g, '');
// 重複:'ab'.repeat(3) // 'ababab'
// 判斷迴文:
const isPalindrome = s === [...s].reverse().join('');
// 規範比較(忽略大小寫):
s.toLowerCase() === 'HELLO'.toLowerCase()

8.集合與對象

數組、對象、Map/Set 與不可變更新模式。

數組操作

push/pop 尾部、unshift/shift 頭部、splice 增刪、slice 拷貝、includes/indexOf 查詢。

1
2
3
4
5
6
7
8
9
const arr = [1, 2, 3];
arr.push(4); // [1,2,3,4]
const last = arr.pop(); // 4
arr.unshift(0); // [0,1,2,3]
const first = arr.shift(); // 0
const removed = arr.splice(1, 1); // 刪除
const copy = arr.slice(); // 淺拷貝
arr.includes(2);
arr.indexOf(2); // 首次位置或 -1

map / filter / reduce

函數式遍歷:map 轉換、filter 過濾、reduce 聚合、find 查找、every/some 判斷。返回新數組不改原數組。

1
2
3
4
5
6
7
8
9
const nums = [1, 2, 3, 4];
const doubled = nums.map(n => n * 2);
const evens = nums.filter(n => n % 2 === 0);
const sum = nums.reduce((acc, n) => acc + n, 0);
const found = nums.find(n => n > 2); // 3 | undefined
const ok = nums.every(n => n > 0); // true
const has = nums.some(n => n === 4); // true
// 鏈式:
nums.filter(n => n % 2).map(n => n * 10).reduce((a, b) => a + b, 0);

對象操作

展開合併 {...a, ...b}、Object.keys/values/entries 遍歷、keyof 類型鍵。

1
2
3
4
5
6
7
8
9
10
const base = { id: 1, name: 'Nick' };
const extended = { ...base, age: 30 }; // 合併
const override = { ...base, name: 'New' }; // 覆蓋
const keys = Object.keys(base);
// 類型安全遍歷:
for (const key of Object.keys(base) as (keyof typeof base)[]) {
console.log(base[key]);
}
// entries:
for (const [k, v] of Object.entries(base)) { }

Map

Map 任意鍵映射:set/get/has/delete/size。保持插入順序,O(1) 查找。

1
2
3
4
5
6
7
8
9
10
11
const scores = new Map<string, number>();
scores.set('alice', 90);
const v = scores.get('alice'); // number | undefined
scores.has('bob'); // false
scores.delete('alice');
scores.size;
// 遍歷:
for (const [k, val] of scores) { }
for (const key of scores.keys()) { }
// 初始化:
new Map([['a', 1], ['b', 2]]);

Set

Set 去重集合:add/delete/has/size。去重數組、差集並集。

1
2
3
4
5
6
7
8
9
10
const set = new Set<number>();
set.add(1).add(2).add(1); // {1, 2}
set.has(1); // true
set.delete(2);
// 數組去重:
const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]
// 並集:
const union = new Set([...a, ...b]);
// 交集:
const inter = new Set([...a].filter(x => bSet.has(x)));

不可變更新

用展開/拷貝更新而非原地改。對象替換、數組增刪返回新引用。React 狀態常見。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface State {
items: string[];
count: number;
}
const next: State = {
...state,
items: [...state.items, 'new'], // 追加
count: state.count + 1,
};
// 刪除一項:
const filtered = state.items.filter(x => x !== 'old');
// 更新某項:
const updated = state.items.map((x, i) => i === 0 ? 'new' : x);
// readonly 幫助類型檢查防原地改

元組與 Record

元組定長、Record 鍵值映射。對象字面量 as const 轉常量。

1
2
3
4
5
6
7
8
9
10
11
const point: [number, number] = [10, 20];
const [x, y] = point; // 解構
// Record:
type Config = Record<string, boolean>;
const flags: Config = { debug: true };
// as const 常量對象:
const statusMap = {
active: '運行中',
stopped: '已停止',
} as const;
// statusMap.active 類型是 '運行中' 字面量

WeakMap / WeakSet

WeakMap/WeakSet 鍵必須對象且弱引用。不阻止 GC。緩存元數據/副作用用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const meta = new WeakMap<object, { visited: boolean }>();
const node = document.getElementById('a');
if (node) meta.set(node, { visited: true });
// 鍵只能是對象:
// meta.set(1, {}) // 錯誤
// 不可遍歷(無 keys/size)
// 用途:
// 1. 給對象掛私有元數據
// 2. 緩存計算結果避免泄漏
// 3. 監聽器標記
// WeakSet 用於標記集合:
const processed = new WeakSet<object>();
processed.add(obj);
processed.has(obj); // true

9.性能與內存

垃圾回收下的內存觀、大型數據、字符串優化與監控。

垃圾回收

TS/JS 有 GC,無需手動釋放。對象不再引用即回收。長生命週期對象小心閉包持有。

1
2
3
4
5
6
7
8
9
10
// 對象離開作用域且無引用後由 GC 回收
function create() {
const big = new Array(1e6);
return () => big.length; // 閉包持有 big
}
// 閉包讓 big 存活:
const f = create(); // big 無法回收
// 釋放:
f = null; // 解除引用
// 避免全局緩存無限增長

大型數組

大數組操作考慮 TypedArray/流式。filter/map 產生新數組注意開銷。循環複用更省。

1
2
3
4
5
6
7
8
9
10
// TypedArray 處理二進制:
const buf = new Float64Array(1e6);
// 大數組避免反覆 map/filter 鏈:
// BAD:多趟遍歷
// GOOD:一次循環處理
const src = new Array(1e6).fill(0);
let sum = 0;
for (let i = 0; i < src.length; i++) sum += src[i];
// 二進制:DataView + ArrayBuffer
// 數值精度:BigInt 大整數,BigInt.asIntN 截斷

字符串內存

字符串不可變,拼接產生新字符串。大量拼接用數組 join 或模板。字符串駐留(interning)。

1
2
3
4
5
6
7
8
9
// 循環拼接慢:
let s = '';
for (let i = 0; i < 1e5; i++) s += i; // BAD
// 數組 join:
const parts: string[] = [];
for (let i = 0; i < 1e5; i++) parts.push(String(i));
const s2 = parts.join(''); // GOOD
// 或模板字符串分段
// 長字符串切片用 slice(O(n))

WeakRef 與緩存

WeakRef 弱引用不阻止回收、WeakMap/WeakSet 鍵弱引用。緩存或記錄副作用用弱容器。

1
2
3
4
5
6
7
8
9
const cache = new WeakMap<object, number>();
const obj = { id: 1 };
cache.set(obj, compute(obj));
// obj 被回收後條目自動消失
// WeakRef:
const ref = new WeakRef(obj);
const alive = ref.deref(); // object | undefined
// FinalizationRegistry 監聽回收:
const reg = new FinalizationRegistry(held => console.log('collected', held));

性能技巧

避免 any 拖慢優化、減少重分配、緩存結果。V8 優化依賴穩定形狀對象。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 避免動態屬性改變對象形狀:
// BAD:
const obj: Record<string, number> = {};
obj.a = 1; obj.b = 2; // 形狀變化
// GOOD:聲明完整形狀
const obj = { a: 0, b: 0 };
// 緩存長鏈:
const len = arr.length; // 循環中避免重複取
// 避免隱式類型轉換:
const s = String(n) + x;
// 熱路徑避免閉包分配:
for (let i = 0; i < n; i++) { }
// 而非 map 每項新建箭頭函數

內存監控

Node 查看內存:process.memoryUsage()、--max-old-space-size。瀏覽器用 Performance API。

1
2
3
4
5
6
7
8
9
// Node:
console.log(process.memoryUsage());
// heapUsed 已用堆、heapTotal 總堆
// 增大堆:node --max-old-space-size=4096 app.js
// 瀏覽器:
performance.measureMemory?.()
.then(m => console.log(m.bytes));
// 堆快照:Chrome DevTools Memory 面板
// 泄漏特徵:heapUsed 持續上升不回落

閉包與內存

閉包持有外部變量,延長其生命週期。循環中閉包陷阱。釋放引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function makeCounter() {
let count = 0; // 被閉包捕獲
return () => ++count; // 存活
}
const c = makeCounter();
c(); // 1
// 循環中閉包捕獲(var 陷阱):
// BAD:var 共享
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 3 3 3
}
// GOOD:let 塊級捕獲
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 0 1 2
}
// 釋放長生命週期閉包:
// fn = null

TypedArray 與二進制

TypedArray 處理二進制數值:Uint8Array/Float64Array。視圖共享底層 buffer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const bytes = new Uint8Array(16); // 16 字節
bytes[0] = 255;
// 創建自已有數據:
const arr = new Uint8Array([1, 2, 3]);
// 浮點:
const floats = new Float64Array(8);
// 底層共享:
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 42);
view.getInt32(0); // 42
// 轉普通數組:
const plain = Array.from(bytes);
// 編碼:
new TextEncoder().encode('中文');
// 大文件/網絡協議/Canvas 像素常用

10.類與面向對象

class、訪問修飾符、繼承、抽象類、泛型類。

class 基礎

class 語法:字段、構造器、方法。字段可標註類型和可見性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `Hi, I'm ${this.name}`;
}
}
const p = new Person('Nick', 30);
// 參數屬性簡寫:
class P2 {
constructor(public name: string, private age: number) {}
}

訪問修飾符

public 公開、private 私有、protected 受保護、readonly 只讀。都是編譯期檢查。

1
2
3
4
5
6
7
8
9
10
11
12
class Account {
public owner: string; // 默認 public
private balance = 0; // 私有
protected type = 'basic'; // 子類可訪問
readonly id: string; // 只讀
constructor(owner: string) {
this.owner = owner;
this.id = crypto.randomUUID();
}
}
// # 私有字段(運行時私有,ES2022):
class C { #secret = 1; get() { return this.#secret; } }

繼承與 override

extends 繼承、super() 調父構造、override 覆蓋父方法。子類是父類(is-a)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal {
constructor(public name: string) {}
speak(): string { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name); // 先調父構造
}
override speak(): string { // override 顯式標記
return `${this.name} barks`;
}
}
const d = new Dog('Rex', 'Husky');
// d 是 Dog 也是 Animal

抽象類與接口

abstract 類不能實例化,抽象方法子類必須實現。接口約束形狀。抽象類可有實現。

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Shape {
abstract area(): number; // 子類必須實現
describe(): string {
return `Area: ${this.area()}`;
}
}
class Circle extends Shape {
constructor(private r: number) { super(); }
override area(): number { return Math.PI * this.r ** 2; }
}
// 接口約束:
interface HasArea { area(): number }
function printArea(s: HasArea) { console.log(s.area()); }

implements

class implements 接口:類必須滿足接口形狀。一個類可實現多個接口。

1
2
3
4
5
6
7
8
9
10
11
12
interface Runnable {
run(): void;
}
interface Jumpable {
jump(): void;
}
class Player implements Runnable, Jumpable {
run(): void { console.log('running'); }
jump(): void { console.log('jump'); }
}
// 實現缺方法則編譯錯誤
// implements 只約束形狀,不要求繼承關係

泛型類

泛型類:類型參數用於字段與方法。約束限定泛型範圍。

1
2
3
4
5
6
7
8
9
10
11
class Box<T> {
private value: T;
constructor(value: T) { this.value = value; }
get(): T { return this.value; }
set(v: T): void { this.value = v; }
}
const numBox = new Box<number>(42);
const strBox = new Box('hi'); // 推斷 string
// 靜態成員不能引用類型參數:
// static arr: T[] // 錯誤
// 泛型約束:class Box<T extends { id: number }>

getter / setter

get/set 訪問器包裝字段讀寫。可加校驗與計算邏輯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Temperature {
private _celsius = 0;
get celsius(): number { return this._celsius; }
set celsius(value: number) {
if (value < -273.15) throw new Error('絕對零度以下');
this._celsius = value;
}
get fahrenheit(): number {
return this._celsius * 9 / 5 + 32;
}
}
const t = new Temperature();
t.celsius = 25;
console.log(t.fahrenheit); // 77

靜態成員

static 定義類級字段方法。靜態成員不依賴實例。工廠方法與常量用 static。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MathHelper {
static readonly PI = 3.14159; // 類常量
static max(arr: number[]): number {
return Math.max(...arr);
}
// 工廠方法:
static create(name: string): MathHelper {
return new MathHelper(name);
}
constructor(private name: string) {}
}
MathHelper.PI;
MathHelper.max([1, 5, 3]);
// 靜態成員通過類名訪問:
// new MathHelper().PI // 錯誤
// 靜態屬性初始化在類定義時執行

11.異常處理

throw/try-catch、錯誤類型、自定義錯誤、異步錯誤。

throw / try-catch

throw 拋錯、try-catch 捕獲、finally 收尾。catch 變量默認 unknown 需收窄。

1
2
3
4
5
6
7
8
9
10
11
try {
const n = JSON.parse(text);
if (typeof n !== 'number') throw new Error('需要數字');
} catch (err) {
if (err instanceof Error) {
console.log(err.message); // 收窄後訪問
}
} finally {
cleanup(); // 無論成敗都執行
}
// 不捕獲則向上拋,未處理則崩潰

自定義錯誤

extends Error 自定義錯誤類。攜帶額外信息。錯誤名用於區分類型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ValidationError extends Error {
constructor(public field: string, message: string) {
super(message);
this.name = 'ValidationError';
}
}
try {
throw new ValidationError('email', '郵箱格式錯誤');
} catch (err) {
if (err instanceof ValidationError) {
console.log(err.field, err.message);
}
}
// 設置原型鏈確保 instanceof:
// Object.setPrototypeOf(this, ValidationError.prototype)

常見錯誤類型

Error 基類:TypeError 類型錯誤、RangeError 越界、ReferenceError 引用錯誤。判斷用 instanceof。

1
2
3
4
5
6
7
8
9
10
11
12
try {
// TypeError: 調用不存在的方法
// RangeError: 數組越界 / 遞歸過深
// ReferenceError: 引用未聲明變量
const arr = [1, 2];
arr[5].toFixed(); // TypeError
} catch (err) {
if (err instanceof TypeError) { }
else if (err instanceof RangeError) { }
// instanceof Error 兜底
}
// 用錯誤 name 屬性判斷跨 realm 情況

異步錯誤

async 函數 throw 變 rejected Promise。await 時 try-catch 捕獲。Promise.catch 鏈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function load(): Promise<void> {
try {
const res = await fetch('/api');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
} catch (err) {
console.error('加載失敗', err);
}
}
// Promise 鏈:
fetch('/api').then(r => r.json()).catch(e => {
console.error(e);
});
// 未捕獲的 rejected Promise 觸發 unhandledrejection

錯誤邊界

模塊邊界捕獲並轉換錯誤類型。第三方錯誤統一包裝。錯誤不逃逸導致崩潰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function safeParse<T>(json: string): T {
try {
return JSON.parse(json) as T;
} catch {
throw new Error('JSON 解析失敗'); // 統一包裝
}
}
// 入口捕獲:
process.on('uncaughtException', (err) => {
console.error('未捕獲異常', err);
process.exit(1);
});
// Node 裏同步異常在頂層兜底
// 瀏覽器:window.onerror / unhandledrejection

錯誤處理模式

可預測錯誤返回結果,意外錯誤拋異常。Result 風格(ok/err)與 throw 各有適用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可預期失敗:返回 null / 結果對象
type Result<T> = { ok: true; value: T } | { ok: false; error: string };
function parseNum(s: string): Result<number> {
const n = Number(s);
return Number.isNaN(n)
? { ok: false, error: '不是數字' }
: { ok: true, value: n };
}
const r = parseNum('abc');
if (r.ok) console.log(r.value);
else console.log(r.error);
// 意外錯誤:throw + 上層捕獲
// 規則:調用方能處理的用返回,否則拋
// 避免吞異常:catch 後至少 log

錯誤信息質量

錯誤信息包含上下文:什麼、哪裏、如何修。自定義錯誤攜帶字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class HttpError extends Error {
constructor(
public status: number,
public url: string,
message: string,
) {
super(message);
this.name = 'HttpError';
}
}
// GOOD:帶上下文
throw new HttpError(404, url, `資源不存在: ${url}`);
// BAD:無信息
// throw new Error('失敗了');
// 日誌包含堆棧:
console.error(err); // 保留 stack
// 錯誤原因鏈:
new Error('外層失敗', { cause: innerErr })

未處理拒絕

未捕獲的 rejected Promise 觸發 unhandledrejection。頂層兜底防靜默失敗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Node:
process.on('unhandledRejection', (reason) => {
console.error('未處理的 Promise 拒絕', reason);
});
// 瀏覽器:
window.addEventListener('unhandledrejection', (e) => {
e.preventDefault();
console.error('未處理拒絕', e.reason);
});
// 同步異常:
process.on('uncaughtException', (err) => {
console.error('未捕獲異常', err);
process.exit(1);
});
// 審計:拒絕監聽在測試中抓遺漏
// 兜底不是吞錯,要記錄並定位

12.輸入輸出

console 輸出、Node 文件/流、fetch 網絡、類型化 JSON。

console 輸出

console.log/info/warn/error、模板輸出、%o 格式化、分組與計數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log('Hello');
console.info('info 消息');
console.warn('警告');
console.error('錯誤');
// 格式化:
console.log('%o', { a: 1 }); // 對象展開
console.table([{ a: 1 }, { a: 2 }]);
// 分組:
console.group('組');
console.log('內容');
console.groupEnd();
// 計時:
console.time('t');
console.timeEnd('t');

fetch 網絡

fetch 異步請求。await 響應、json 解析、錯誤處理、類型斷言結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function getUsers(): Promise<User[]> {
const res = await fetch('/api/users');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<User[]>; // 斷言
}
// POST:
await fetch('/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Nick' }),
});
// AbortController 超時:
const ac = new AbortController();
setTimeout(() => ac.abort(), 3000);
await fetch(url, { signal: ac.signal });

Node 文件

fs/promises 異步讀寫。readFile/writeFile/mkdir/readdir 返回 Promise。

1
2
3
4
5
6
7
8
9
10
import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
const text = await readFile('data.txt', 'utf8');
await writeFile('out.txt', text.toUpperCase());
await mkdir('dir', { recursive: true });
const files = await readdir('.');
// 流式大文件:
import { createReadStream } from 'node:fs';
const stream = createReadStream('big.log');
// 需要 @types/node:
// npm i -D @types/node

JSON 與類型

JSON.parse/stringify 序列化。parse 結果需守衞驗證,stringify 忽略函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface User { name: string; age: number }
const u: User = { name: 'Nick', age: 30 };
const json = JSON.stringify(u);
// 解析加校驗:
function isUser(v: unknown): v is User {
return typeof v === 'object' && v !== null
&& typeof (v as any).name === 'string'
&& typeof (v as any).age === 'number';
}
const data = JSON.parse(json);
if (isUser(data)) console.log(data.name);
// JSON.parse 返回 any,斷言前最好守衞
// 序列化選項:JSON.stringify(u, null, 2) 美化

流式處理

ReadableStream/Web Streams 處理大響應。讀取進度、逐塊處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
const res = await fetch('/big');
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let total = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
total += value.length;
const chunk = decoder.decode(value, { stream: true });
process(chunk);
}
// 大文件流讀:
// for await (const chunk of createReadStream('big.log')) { }

環境變量與參數

Node 讀取 process.argv / process.env。參數解析、環境變量類型收窄。

1
2
3
4
5
6
7
8
9
10
11
// process.argv[0]=node, [1]=腳本, [2..] 參數
const args = process.argv.slice(2);
// 環境變量:
const port = Number(process.env.PORT ?? 3000);
const mode = process.env.NODE_ENV ?? 'development';
// 判斷:
if (mode === 'production') { }
// .env 加載:
// npm i dotenv
// import 'dotenv/config'
// 類型安全環境:process.env 是 Record<string, string | undefined>

瀏覽器 Web API

localStorage/sessionStorage、navigator、WebSocket 的類型。存儲值需序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 本地存儲:
localStorage.setItem('token', 'abc');
const t = localStorage.getItem('token'); // string | null
// 存對象需序列化:
localStorage.setItem('user', JSON.stringify(user));
// 讀回需解析 + 校驗:
const raw = localStorage.getItem('user');
if (raw) { const u = JSON.parse(raw) as User; }
// 導航:
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((pos) => {
console.log(pos.coords.latitude);
});
}
// WebSocket 事件類型:
ws.addEventListener('message', (e: MessageEvent) => {
console.log(e.data);
});

文件讀取

input[type=file] 獲取 File、FileReader 讀文本/DataURL、對象 URL 預覽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const input = document.querySelector('input[type=file]') as HTMLInputElement;
input.addEventListener('change', async () => {
const file = input.files?.[0];
if (!file) return;
// 讀文本:
const text = await file.text();
// 讀為 DataURL:
const reader = new FileReader();
reader.onload = () => console.log(reader.result);
reader.readAsDataURL(file);
// 預覽圖片:
const url = URL.createObjectURL(file);
img.src = url;
// File 有 name/size/type
console.log(file.name, file.size, file.type);
});

13.常見誤區

日常開發最容易踩的坑與正確寫法。

濫用 any

any 關掉類型檢查,掩蓋真實錯誤。能用 unknown + 守衞就用。

1
2
3
4
5
6
7
8
9
10
// BAD:any 吞掉錯誤
function getLen(v: any): number {
return v.length; // 編譯通過,運行時可能崩
}
// GOOD:unknown + 守衞
function getLen(v: unknown): number {
if (typeof v !== 'string') return 0;
return v.length;
}
// any 的隱式傳播:返回值 any 傳染調用方

空值判斷

!! 判斷真值、== null 同時判 null/undefined、數組空判斷。別把 0/'' 當空。

1
2
3
4
5
6
7
8
9
10
// BAD:值可能為 0/''
if (value) { } // 0 是 falsy
// GOOD:顯式判 null
if (value !== null && value !== undefined) { }
// 簡寫 == null 同時判兩者:
if (value == null) { } // null 或 undefined
// 數組空判斷:
if (arr.length === 0) { }
// 對象空判斷:
if (Object.keys(obj).length === 0) { }

async 誤用

async 函數必返回 Promise。忘 await 變 Promise 泄漏。forEach 不支持 async 等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
// BAD:forEach 不等待
async function main() {
[1, 2, 3].forEach(async n => { await work(n); });
console.log('done'); // 先輸出!
}
// GOOD:for...of
for (const n of [1, 2, 3]) {
await work(n);
}
// BAD:忘 await
const res = fetch('/api'); // 是 Promise
// 並行:
await Promise.all([a(), b()]);

等值比較

=== 嚴格相等。NaN !== NaN。對象比較引用。深比較需手寫或庫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD:== 寬鬆比較會做類型轉換
'1' == 1; // true,坑
// GOOD:用 === 嚴格比較
'1' === 1; // false
// NaN 特殊:
NaN === NaN; // false
Number.isNaN(NaN); // true
// 對象按引用比較:
{} === {}; // false
// 數組內容比較:
[1, 2].join() === [1, 2].join(); // 簡單方案
// 深比較庫:
// import { isEqual } from 'lodash-es';
// 性能:深度大對象避免頻繁深比較

null 與 undefined 混淆

null 是有意空值,undefined 是未賦值。strictNullChecks 下分別處理。可選鏈 vs 斷言。

1
2
3
4
5
6
7
8
9
10
11
12
// BAD:斷言掩蓋可能為 null
const len = name!.length; // name 可能真的為 null
// GOOD:先判斷
if (name) {
const len = name.length;
}
// 空值合併:
const n = name ?? 'default';
// 可選鏈安全訪問:
user?.profile?.email;
// 用 ?? 不用 ||:0 和 '' 是有效值
const port = port ?? 3000; // port=0 時保留 0

this 丟失

回調中 this 變 undefined。箭頭函數綁定或顯式綁定。類字段箭頭函數常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Counter {
count = 0;
increment = () => { this.count++; }; // 箭頭綁定
}
// BAD:回調中 this 丟失
class C {
value = 1;
method() { return this.value; }
}
const fn = new C().method;
fn(); // undefined 報錯
// GOOD:
const fn2 = new C().method.bind(new C());
// 或調用時 .method() 方式

收窄失效

屬性訪問後收窄丟失。解構後才收窄。函數參數重新賦值會變寬。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BAD:屬性收窄不保持
function f(p: { a?: string }) {
if (p.a) {
p.a.toUpperCase(); // 可能已變
}
}
// GOOD:先存變量
function f2(p: { a?: string }) {
const a = p.a;
if (a) {
a.toUpperCase();
}
}
// 可選屬性對象訪問兩次有競態
// 參數重新賦值丟失收窄:
// let x = v; 再判斷

窮盡檢查

可辨識聯合 default 用 never 檢查。新增類型分支漏處理編譯報錯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// GOOD:default 用 never 做窮盡檢查
type Event =
| { kind: 'open' }
| { kind: 'close' };
function handle(e: Event) {
switch (e.kind) {
case 'open': break;
case 'close': break;
default: {
const _exhaustive: never = e;
// 新增 kind 時此處編譯報錯
return _exhaustive;
}
}
}
// BAD:不加 default,新增類型靜默漏處理
// 更新類型後編譯器提醒所有 switch

拷貝與修改

數組/對象引用共享導致誤改。更新要拷貝。sort 原地改數組。

1
2
3
4
5
6
7
8
9
10
11
12
13
const a = [1, 2, 3];
// BAD:sort 原地改
const sorted = a.sort(); // a 也被改
// GOOD:先拷貝
const sorted = [...a].sort();
// BAD:引用共享誤改
const b = a;
b.push(4); // a 也變
// 淺拷貝:
const copy = [...a];
// 對象展開淺拷貝:
const o2 = { ...o1 };
// 嵌套深拷貝需逐層處理

14.併發與異步

Promise、async/await、Worker 線程與事件循環。

Promise

Promise 表示異步結果。then 鏈、catch 錯誤、finally 收尾。類型標註 Promise<T>。

1
2
3
4
5
6
7
8
9
10
const p: Promise<number> = new Promise((resolve, reject) => {
setTimeout(() => resolve(42), 1000);
});
p.then(n => console.log(n))
.catch(err => console.error(err))
.finally(() => console.log('done'));
// 立即創建:
Promise.resolve(1);
Promise.reject(new Error('x'));
// 泛型錯誤:Promise<T> 成功值類型 T

async / await

async 函數返回 Promise。await 解包。錯誤用 try-catch。頂層 await 需 ESM。

1
2
3
4
5
6
7
8
9
10
11
12
async function load(): Promise<User> {
const res = await fetch('/api/user');
if (!res.ok) throw new Error('加載失敗');
return res.json() as Promise<User>;
}
// 調用:
const user = await load();
// 錯誤:
try { await load(); } catch (err) { }
// 並行執行:
const [a, b] = await Promise.all([load(), load()]);
// 頂層 await(ESM .mjs)

並行與競態

Promise.all 全部完成、allSettled 全部(含失敗)、race 最先完成、any 首個成功。

1
2
3
4
5
6
7
8
9
10
11
const tasks = [fetch1(), fetch2(), fetch3()];
// 全部成功(任一失敗整體失敗):
const all = await Promise.all(tasks);
// 不中斷收集所有結果:
const settled = await Promise.allSettled(tasks);
// 首個完成(含失敗):
const first = await Promise.race(tasks);
// 首個成功(全敗才報 AggregateError):
const any = await Promise.any(tasks);
// 併發限制:
// for 循環分塊或 p-limit 庫

Worker 線程

Web Worker / Node worker_threads 並行計算。postMessage 通信、transferable 轉移所有權。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 主線程:
const worker = new Worker('/worker.js');
worker.postMessage({ n: 42 });
worker.onmessage = (e) => console.log(e.data);
worker.onerror = (e) => console.error(e);
// worker.js:
self.onmessage = (e) => {
const result = heavyCompute(e.data.n);
self.postMessage(result);
};
// Node:
// import { Worker } from 'node:worker_threads';
// 大對象用轉移:
// postMessage(buf, [buf.buffer])

事件循環

同步代碼先執行,微任務(Promise)先於宏任務(setTimeout)。阻塞會卡住一切。

1
2
3
4
5
6
7
8
9
console.log('1'); // 同步
Promise.resolve().then(() =>
console.log('2')); // 微任務
setTimeout(() => console.log('3'), 0); // 宏任務
console.log('4');
// 輸出順序:1 4 2 3
// 長任務阻塞事件循環:
// for (let i=0;i<1e9;i++){} // 卡住
// 拆塊或讓出:await new Promise(r => setTimeout(r, 0))

生成器

function* 生成器惰性產出。yield 暫停恢復。Generator 類型標註。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* count(max: number): Generator<number> {
let i = 0;
while (i < max) {
yield i++; // 暫停產出
}
}
for (const n of count(3)) console.log(n); // 0 1 2
// 手動驅動:
const g = count(2);
g.next(); // { value: 0, done: false }
g.next();
// 無限序列 + 惰性:
function* naturals(): Generator<number> {
let n = 0;
while (true) yield n++;
}
// 與迭代器協議同構

異步迭代器

for await 遍歷異步數據源。異步生成器 async function*。流式數據消費。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function* generate(): AsyncGenerator<number> {
let i = 0;
while (i < 3) {
await new Promise(r => setTimeout(r, 100));
yield i++;
}
}
// 消費:
for await (const n of generate()) {
console.log(n); // 0 1 2
}
// 異步迭代對象:
const res = await fetch('/big');
const reader = res.body!.getReader();
// Node 流:
// for await (const chunk of createReadStream('f'))
// 惰性 + 背壓,內存友好

併發限制

控制同時運行的異步任務數。分批、信號量或 p-limit 思路。避免打爆資源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function mapLimit<T, R>(
items: T[], limit: number, fn: (x: T) => Promise<R>,
): Promise<R[]> {
const results: R[] = [];
let i = 0;
const workers = Array.from({ length: Math.min(limit, items.length) },
async () => {
while (i < items.length) {
const idx = i++;
results[idx] = await fn(items[idx]);
}
});
await Promise.all(workers);
return results;
}
// 用:分批下載/請求,limit 控制併發
// 批量下載等場景避免一次性全發

15.網絡與模塊

模塊系統、import/export、fetch 與類型化 API 封裝。

ES 模塊

import/export 靜態導入導出。TS 類型導出用 export type。模塊隔離作用域。

1
2
3
4
5
6
7
8
9
10
11
// utils.ts:
export const version = '1.0';
export function add(a: number, b: number): number { return a + b; }
export type ID = string; // 類型導出
export default class App { } // 默認導出
// main.ts:
import App, { add, version, type ID } from './utils';
// 重命名:
import { add as plus } from './utils';
// 整體導入:
import * as utils from './utils';

類型導入

import type 只導入類型,編譯時擦除。避免運行時依賴與循環引用。

1
2
3
4
5
6
7
8
9
10
11
// BAD:類型運行時仍存在(esbuild 下可能殘留)
// GOOD:
import type { User } from './types';
// 混合:
import { fetchUsers, type User } from './api';
// 僅類型:
import type { Options } from './config';
// 類型導出:
export type { ID } from './utils';
// 編譯器自動 elision,顯式更清晰
// 循環引用時用 import type 打破

類型化 API 封裝

封裝 fetch 返回強類型。響應校驗、錯誤統一處理。泛型請求函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function api<T>(
url: string, opts?: RequestInit
): Promise<T> {
const res = await fetch(url, opts);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res.json() as Promise<T>;
}
// 使用:
interface User { name: string }
const user = await api<User>('/api/user');
// 泛型讓調用點類型安全
// 邊界:schema 校驗(zod)防運行時錯位
// import { z } from 'zod';

Node HTTP 服務

node:http 或框架(Express/Fastify)。類型化請求響應。路由處理。

1
2
3
4
5
6
7
8
9
10
11
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
});
server.listen(3000);
// Express:
// import express from 'express';
// @types/express 提供類型
// 參數類型:
// app.get('/user/:id', (req: Request<{id: string}>, res: Response) => {})

DOM 類型

document.getElementById 返回類型、事件類型、HTML 元素類型映射。

1
2
3
4
5
6
7
8
9
10
const btn = document.getElementById('btn');
// HTMLElement | null,需要非空:
if (btn) { btn.addEventListener('click', handler); }
// 斷言具體元素:
const input = document.querySelector('input') as HTMLInputElement;
// 事件類型:
function handler(e: MouseEvent) { console.log(e.clientX); }
// 泛型事件:
const f = (e: KeyboardEvent) => { e.key };
// 表單取值:input.value 是 string

URL 與參數

URLSearchParams 構造查詢串、URL 解析。類型安全讀參數。

1
2
3
4
5
6
7
8
9
const params = new URLSearchParams({ q: 'ts', page: '2' });
params.toString(); // 'q=ts&page=2'
const url = new URL('https://example.com/search?q=ts');
const q = url.searchParams.get('q'); // 'ts'
// 構造:
url.searchParams.set('page', '3');
// 請求頭:
headers.append('Authorization', `Bearer ${token}`);
// 參數類型:get 返回 string | null,收窄後使用

WebSocket

WebSocket 雙向通信。onmessage 事件、泛型數據、readyState 狀態。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ws = new WebSocket('wss://example.com/socket');
// 連接就緒:
ws.onopen = () => ws.send(JSON.stringify({ type: 'join' }));
// 收消息:
ws.onmessage = (e: MessageEvent) => {
// e.data 可能是 string | Blob | ArrayBuffer
const data = JSON.parse(e.data as string);
console.log(data);
};
ws.onerror = (e) => console.error('連接錯誤', e);
ws.onclose = (e) => console.log('關閉', e.code);
// 主動關閉:
ws.close(1000, '正常關閉');
// 自動重連:onclose 裏 setTimeout 重連

請求頭與鑑權

Headers 類型安全設置。Authorization Bearer、Content-Type。攔截器統一注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Authorization', `Bearer ${token}`);
const res = await fetch('/api', { headers });
// 讀響應頭:
const type = res.headers.get('content-type');
// 統一封裝:
function authFetch(url: string, init?: RequestInit) {
return fetch(url, {
...init,
headers: {
...init?.headers,
Authorization: `Bearer ${getToken()}`,
},
});
}
// 注意:token 泄露不要放日誌/URL
// 刷新:401 時刷新 token 重試

16.時間與日期

Date 對象、時間戳、格式化與時區。

Date 基礎

Date 構造、getFullYear/getMonth/getDate 讀取、set 系列設置。月份 0 起。

1
2
3
4
5
6
7
8
9
10
11
const now = new Date();
const d = new Date('2026-08-02T12:00:00Z');
const y = d.getFullYear(); // 2026
const m = d.getMonth(); // 7(0 起!)
const day = d.getDate(); // 2
// 設置:
d.setFullYear(2030);
d.setMonth(0); // 一月
// 本地與 UTC:
// getUTCFullYear() / getUTCHours()
// getTimezoneOffset() 分鐘差

時間戳

getTime() 毫秒時間戳、Date.now()、Date.parse。日期比較用時間戳。

1
2
3
4
5
6
7
8
9
const ms = Date.now(); // 當前毫秒
const d = new Date(ms);
const older = new Date('2020-01-01');
if (d.getTime() > older.getTime()) { } // 比較
// 秒級:Math.floor(Date.now() / 1000)
// Date.parse('2026-08-02') 返回毫秒
// 加減時間:
d.setDate(d.getDate() + 7); // 加 7 天
// 注意 setMonth/setDate 跨月自動進位

格式化

toISOString UTC 格式、toLocaleDateString 本地格式、Intl.DateTimeFormat 自定義。

1
2
3
4
5
6
7
8
9
10
const d = new Date();
const iso = d.toISOString(); // '2026-08-02T04:00:00.000Z'
const local = d.toLocaleDateString('zh-CN');
// Intl 自定義:
new Intl.DateTimeFormat('zh-CN', {
year: 'numeric', month: 'long', day: 'numeric',
hour: '2-digit', minute: '2-digit',
}).format(d);
// 時間相對:
// d.toLocaleTimeString('zh-CN')

時區

時間戳是 UTC 毫秒,格式化才有本地時區。Intl 按 timeZone 選項指定時區。

1
2
3
4
5
6
7
8
9
10
11
const d = new Date();
// 指定時區:
new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
hour12: false,
}).format(d);
// getTimezoneOffset 返回本地與 UTC 分鐘差
// 存儲用 ISO/時間戳,展示用本地化
// 跨時區轉換:
// 存 UTC,讀時 toLocaleString('zh-CN', { timeZone })
// 簡單計算用 dayjs/date-fns 庫

定時器

setTimeout 延時、setInterval 週期、clearTimeout 取消。返回值類型 number。

1
2
3
4
5
6
7
8
9
10
11
const timer = setTimeout(() => {
console.log('1s 後');
}, 1000);
clearTimeout(timer); // 取消
const interval = setInterval(() => {
console.log('每秒');
}, 1000);
clearInterval(interval); // 停止
// 異步等待:
await new Promise(r => setTimeout(r, 500));
// 注意:定時器回調在事件循環宏任務階段執行

時長與區間

兩個時間戳相減得毫秒時長。區間判斷用時間戳比較。防濫用 setInterval 漂移。

1
2
3
4
5
6
7
8
9
10
11
const start = Date.now();
// 耗時統計:
const elapsed = Date.now() - start; // 毫秒
console.log(`${elapsed}ms`);
// 區間判斷:
const inWindow = t >= start && t <= end;
// 每 N 毫秒一次(輪詢):
const poll = setInterval(() => { }, 1000);
// setInterval 漂移修正:
// 用 setTimeout 遞歸 + 計算補償
// 精度:performance.now() 更高

日期庫

dayjs/date-fns 提供清晰 API 與時區處理。體積小、不可變。複雜時區建議用庫。

1
2
3
4
5
6
7
8
9
10
11
12
13
// dayjs:
// import dayjs from 'dayjs';
// dayjs().format('YYYY-MM-DD');
// dayjs().add(7, 'day').toDate();
// dayjs('2026-08-02').isBefore('2026-09-01');
// date-fns:
// import { format, addDays, isBefore } from 'date-fns';
// format(new Date(), 'yyyy-MM-dd');
// addDays(new Date(), 7);
// 時區:
// import { formatInTimeZone } from 'date-fns-tz';
// formatInTimeZone(d, 'Asia/Shanghai', 'yyyy-MM-dd HH:mm')
// 均不可變:返回新值不修改原 Date

高精度計時

performance.now() 毫秒級高精度、不受系統時間調整影響。性能測量用。

1
2
3
4
5
6
7
8
9
10
11
12
13
const t0 = performance.now();
// 測量代碼…
const elapsed = performance.now() - t0;
console.log(`${elapsed.toFixed(2)}ms`);
// 掛鐘時間:Date.now() 可被修改
// performance.now() 單調遞增
// 瀏覽器/Node 均可用
// 打點分析:
performance.mark('start');
// …
performance.mark('end');
performance.measure('任務', 'start', 'end');
// 測量結果:performance.getEntriesByName('任務')

17.進程與系統

Node 進程、命令行工具、標準流與構建產物運行。

Node 進程

process 全局對象:argv 參數、env 環境、exit 退出碼、stdout/stderr 流。

1
2
3
4
5
6
7
8
9
10
import { argv, env, exit, stdout, stderr } from 'node:process';
const args = argv.slice(2);
const mode = env.NODE_ENV;
exit(0); // 成功退出
stdout.write('輸出');
stderr.write('錯誤');
// 退出碼:0 成功,1 一般錯誤
process.exitCode = 1; // 優雅設置
// 信號處理:
process.on('SIGINT', () => { console.log('Ctrl+C'); process.exit(0); });

命令行工具

寫 CLI:解析參數、幫助輸出、退出碼。shebang 讓腳本可執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env node
const [cmd, ...rest] = process.argv.slice(2);
if (cmd === '--help' || cmd === '-h') {
console.log('用法: ts-tool <命令> [選項]');
process.exit(0);
}
if (!cmd) {
console.error('缺少命令');
process.exit(1);
}
// 參數解析庫:commander / yargs
// 選項:
// ts-tool build --out dist --watch

標準流

stdin 讀輸入、stdout 輸出、stderr 錯誤。readline 交互。管道數據。

1
2
3
4
5
6
7
8
9
10
11
import { stdin, stdout } from 'node:process';
import * as readline from 'node:readline/promises';
const rl = readline.createInterface({ input: stdin, output: stdout });
const name = await rl.question('名字? ');
console.log(`你好, ${name}`);
rl.close();
// 讀全部 stdin:
import { readFileSync } from 'node:fs';
// 管道:echo hi | ts-tool
// 逐行處理:
for await (const line of rl) { process(line); }

運行編譯產物

tsc 編譯後 node dist/main.js 運行。package.json bin 註冊命令。類型聲明 d.ts。

1
2
3
4
5
6
7
8
9
10
11
// package.json:
// {
// "bin": { "ts-tool": "./dist/cli.js" },
// "types": "./dist/index.d.ts",
// "main": "./dist/index.js"
// }
// 編譯:npx tsc
// 運行:node dist/cli.js
// 發佈 npm:npm publish
// 聲明文件 .d.ts 供其他 TS 項目引用
// tsconfig declaration: true

執行系統命令

execFile 執行外部命令、spawn 流式。child_process 類型。注意轉義避免注入。

1
2
3
4
5
6
7
8
9
10
11
import { execFile } from 'node:child_process';
execFile('ls', ['-l'], (err, stdout) => {
if (err) { console.error(err); return; }
console.log(stdout);
});
// 流式 spawn:
import { spawn } from 'node:child_process';
const child = spawn('node', ['worker.js']);
child.stdout.on('data', (d) => console.log(d.toString()));
// 安全性:避免 exec + 字符串拼接
// 傳參用數組,不拼 shell 命令

退出碼

0 成功、非 0 失敗。約定:1 通用錯誤、2 用法錯誤。腳本在 CI 依賴退出碼。

1
2
3
4
5
6
7
8
9
process.exit(0); // 成功
process.exit(1); // 一般錯誤
process.exit(2); // 用法/參數錯誤
// 未捕獲異常退出碼 1
// 手動設置:
process.exitCode = 2;
// CI 中退出碼非 0 即失敗:
// ts-tool check && echo OK || echo FAIL
// 信號退出:SIGTERM 默認 143

npm scripts

package.json scripts 編排命令。pre/post 鈎子、串聯 &&、並行 &。構建鏈常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// package.json:
// {
// "scripts": {
// "dev": "tsx src/dev.ts",
// "typecheck": "tsc --noEmit",
// "lint": "eslint src",
// "test": "vitest run",
// "build": "npm run typecheck && tsup src/index.ts",
// "prepublishOnly": "npm run build"
// }
// }
// 串聯:&&(失敗中斷)
// 並行:& 或 concurrently 庫
// pre/post 鈎子:prebuild 在 build 前自動跑
// npx 一次性:npx vitest

配置與環境

dotenv 加載 .env、類型化配置對象、運行時校驗。配置與代碼分離。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 安裝:npm i dotenv
// import 'dotenv/config';
const port = Number(process.env.PORT ?? 3000);
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) {
throw new Error('缺少 DATABASE_URL');
}
// 類型化配置:
interface Config {
port: number;
debug: boolean;
apiKey: string;
}
// 校驗函數返回 Config,保證類型
// .env 不進版本庫(gitignore)
// 環境差異:.env.development / .env.production

18.正則與文本處理

正則語法、標誌、類型化匹配與文本處理慣用法。

正則語法

字面量 /.../ 或 RegExp 構造。字符類、量詞、分組、錨點。

1
2
3
4
5
6
7
8
9
const re = /\b\w+@\w+\.com\b/;
// \d 數字 \w 單詞 \s 空白 \b 詞邊界
// [abc] 字符類 [^abc] 否定
// * 零次多 + 一次多 ? 零或一 {2,4} 區間
// ^ 開頭 $ 結尾
// () 分組 (?:) 非捕獲
// 或:/cat|dog/
// 測試:
re.test('hi [email protected]'); // true

標誌

g 全局、i 忽略大小寫、m 多行、s 點通配換行、u unicode、y 粘性。

1
2
3
4
5
6
7
8
9
10
const g = /a/g; // 全局(matchAll 需要)
const i = /HELLO/i; // 忽略大小寫
const m = /^line/m; // 每行匹配錨點
const s = /a.b/s; // . 匹配換行
const u = /\p{Emoji}/u; // unicode 屬性
const y = /a/y; // 粘性(從 lastIndex 匹配)
// 組合:/foo/gim
// 動態構造:
new RegExp(`\\d{${len}}`, 'gi');
// 字面量必須轉義 \ 字符

匹配與提取

match 返回數組(0 全匹配 + 捕獲組)、matchAll 全局遍歷、match 失敗 null 需判空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const s = 'id=123&id=456';
const re = /id=(\d+)/g;
// 全量匹配:
for (const m of s.matchAll(re)) {
console.log(m[1]); // '123' '456'
}
// 單次:
const first = s.match(re);
if (first) { console.log(first[0]); }
// 具名捕獲:
const named = /(?<year>\d{4})-(?<month>\d{2})/;
const r = s.match(named);
if (r) { r.groups!.year; }
// 惰性 ?:/(\d+?)(x)/ 最少匹配

替換

replace 字符串/函數替換。$1 捕獲引用、全局 g 全替換。函數式替換處理邏輯。

1
2
3
4
5
6
7
8
9
10
11
const s = '2026-08-02';
// 捕獲引用:
const a = s.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1');
// 函數式替換:
const b = s.replace(/(\d+)/g, (m) => String(Number(m) + 1));
// 全部替換(g 必須):
'aaa'.replace(/a/g, 'b'); // 'bbb'
// 去掉空白:
s.replace(/\s+/g, ' ').trim();
// 簡單替換可用 split/join:
s.split('-').join('/');

驗證慣用法

整體匹配用 ^...$ 錨定。數字/郵箱/URL 常用模式。test 返回 boolean。

1
2
3
4
5
6
7
8
9
10
11
12
13
function isEmail(v: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
}
function isInt(v: string): boolean {
return /^[-+]?\d+$/.test(v);
}
function isUrl(v: string): boolean {
try { new URL(v); return true; }
catch { return false; }
}
// 嚴格匹配必須 ^ 開頭 $ 結尾
// 校驗長度:/^.{8,20}$/
// 貪婪轉義:/^\.$/ 匹配點號

正則性能

避免災難性回溯:嵌套量詞。預編譯正則。g 狀態 lastIndex 注意。

1
2
3
4
5
6
7
8
9
10
11
// BAD:災難性回溯
// /^(a+)+$/ 匹配 'aaaaaaaaaaaaaaaaaaaa!' 極慢
// GOOD:避免嵌套量詞
/^(a+)$/;
// 預編譯避免重複創建:
const re = /\d+/g; // 模塊級複用
// g 標誌有狀態:
re.lastIndex = 0; // 重置
// 大文本逐段處理
// 簡單解析優先 indexOf / split
// 正則只做結構匹配,業務邏輯用代碼

正則常見坑

字面量需轉義、g 標誌 lastIndex 狀態、貪婪匹配、\ 在字符串裏的雙重轉義。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 字符串構造時 \ 要寫兩次:
new RegExp('\\d+'); // 等價 /\d+/
// g 標誌有 lastIndex 狀態:
const re = /a/g;
re.lastIndex = 0; // 用前重置
// 貪婪匹配:
'<a><b>'.match(/<.*>/); // 匹配到最後一個 >
'<a><b>'.match(/<.*?>/); // 非貪婪,最短
// 字面量.要轉義:
/1\.0/; // 匹配 '1.0' 不是 '1X0'
// ^ 在 [] 裏是取反:
/[^a]/; // 非 a
// 空匹配:/(?:)/ 匹配任意位置

常用模式

身份證/手機號/顏色/日期等常用正則片段。匹配業務格式校驗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 手機號(中國大陸):
const mobile = /^1[3-9]\d{9}$/;
// 郵箱:
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// 十六進制顏色:
const color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
// 日期 yyyy-mm-dd:
const date = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
// URL:
const url = /^https?:\/\/[^\s]+$/;
// 中文字符:
const zh = /[\u4e00-\u9fff]/;
// 空白行:
const blank = /^\s*$/;
// 記憶要點:錨定 ^$、數量限定、字符類

19.構建與工程化

tsconfig、打包工具、Lint/格式化、測試與 CI。

tsconfig 詳解

常用編譯選項:moduleResolution、declaration、noUnusedLocals、esModuleInterop、paths 路徑別名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true, // 生成 .d.ts
"outDir": "dist",
"noUnusedLocals": true,
"paths": { "@/*": ["./src/*"] },
"resolveJsonModule": true
},
"include": ["src"]
}
// npm-run: tsc --noEmit 類型檢查

打包工具

Vite/Webpack/Rollup 打包。Vite 是 TS/前端默認。庫用 tsup/Rollup 出 ESM+CJS。

1
2
3
4
5
6
7
8
9
10
11
12
// Vite:dev server + 構建
// vite.config.ts:
export default {
build: { target: 'esnext' },
// plugins: [react(), vue()]
};
// 命令:
// npm run dev 開發
// npm run build 構建
// 庫構建:tsup
// tsup src/index.ts --format esm,cjs --dts
// 環境變量:import.meta.env.VITE_XXX

Lint 與格式化

ESLint 規則檢查、Prettier 格式化。ts-eslint 提供類型感知規則。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ESLint 配置:
// {
// "parser": "@typescript-eslint/parser",
// "plugins": ["@typescript-eslint"],
// "rules": {
// "@typescript-eslint/no-explicit-any": "warn"
// }
// }
// 命令:
// npx eslint src --fix
// Prettier:
// npx prettier --write "src/**/*.ts"
// 規則模板:
// npx eslint --init
// 提交前:husky + lint-staged

測試

Vitest/Jest 單元測試。describe/it/expect。類型與運行分離。ts 直接測。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { describe, it, expect } from 'vitest';
import { add } from './add';
describe('add', () => {
it('兩數相加', () => {
expect(add(1, 2)).toBe(3);
});
it('類型錯誤編譯失敗', () => {
// add('a', 2) // 編譯期攔截
});
});
// 類型測試:
// import { expectType } from 'tsd';
// 覆蓋率:vitest run --coverage
// Mock:vi.mock() / vi.fn()

CI 與部署

CI 階段:類型檢查、Lint、測試、構建。tsc --noEmit 攔截類型錯誤。

1
2
3
4
5
6
7
8
9
10
11
// GitHub Actions:
// steps:
// - run: npm ci
// - run: npx tsc --noEmit # 類型檢查
// - run: npx eslint src
// - run: npm test
// - run: npm run build
// - run: npm publish --dry-run
// 類型檢查放最先:失敗立即中斷
// 緩存:actions/cache 加速依賴
// 多版本測試:node 18/20/22 矩陣

調試

sourceMap + Node inspect 斷點調試。console 調試、類型斷言輔助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tsconfig sourceMap: true
// VS Code launch.json:
// {
// "type": "node",
// "request": "launch",
// "program": "${file}",
// "runtimeArgs": ["--loader", "tsx"]
// }
// 命令:
// node --inspect dist/main.js
// 瀏覽器:DevTools Sources + sourcemap
// 類型調試:
// type Debug<T> = T; hover 查看
// console 輸出輔助定位

npm 發佈

發佈庫:files 指定發佈內容、version 語義化、main/types/exports 入口。發佈前構建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package.json:
// {
// "name": "@org/my-lib",
// "version": "1.2.0",
// "main": "./dist/index.js",
// "types": "./dist/index.d.ts",
// "exports": {
// ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs" }
// },
// "files": ["dist"],
// "sideEffects": false
// }
// 發佈:
// npm run build && npm publish
// 預發佈:npm publish --dry-run
// 版本:npm version patch|minor|major
// 權限:npm publish --access public

monorepo 配置

npm/pnpm workspaces 多包倉庫。共享依賴、包間引用、統一腳本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pnpm-workspace.yaml:
// packages:
// - 'packages/*'
// - 'apps/*'
// npm workspaces:
// {
// "workspaces": ["packages/*"]
// }
// 包間引用:
// npm i @org/shared -w packages/web
// 根腳本批量:
// pnpm -r run build
// 共享 TS 配置:
// tsconfig.base.json 被各包 extends
// 依賴提升:pnpm 默認隔離,需顯式聲明
關於本速查

本頁是 TypeScript 5.x 的自包含速查手冊,覆蓋類型系統與語言核心在真實項目中約 80% 的常見用法。內容偏向現代慣用法:接口與類型別名、泛型、聯合/交叉類型、類型收窄、字面量類型、keyof/typeof、映射類型與條件類型,以及與異步編程的結合。TypeScript 由微軟於 2012 年發佈,是 JavaScript 的超集,在編譯期為 JavaScript 添加靜態類型檢查,大幅提升大型項目的可維護性,是當前前端工程化的主流選擇。 19 個章節各自聚焦一個主題:基礎語法、變量與類型推斷、類型系統、引用與值語義、控制流、函數與重載、字符串與模板、集合、內存與類型擦除、類與接口、錯誤處理、輸入輸出、常見誤區、併發(異步類型)、網絡、時間、進程、正則與構建工具(tsc/tsconfig)。每個小節都配有「概念介紹 + 可直接複製的代碼片段」。 所有代碼與文字均在瀏覽器本地渲染,無任何數據離開你的設備。權威參考見 TypeScript 官方手冊。

版本 2.1.0