本工具使用的開源套件

本工具程式碼中捆綁了 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 異步加載。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 初始化

npm init 創建 package.json 項目。npm install 安裝依賴並生成 node_modules 與 lock 文件。

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);

嚴格模式

use strict 開啓嚴格模式:禁止隱式全局變量、靜默錯誤變異常。ESM 模塊自動嚴格模式。

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 判斷原始類型;數組/日期/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'(歷史 bug)
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
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 回收閉包鏈

對象相等比較

=== 對對象比較的是引用而非內容,兩個內容相同的對象也不相等。按字段比較需手動深比較。

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
2
3
4
5
6
7
let i = 0;
while (i < 5) { i++; }
let x = 0;
do {
x++;
} while (x < 3); // 至少執行一次
// 文件流/輪詢常用 while (true) + break

switch

switch 用嚴格相等 === 比較。case 匹配後執行到 break。注意 fall-through(漏 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、不能作構造函數。單表達式隱式返回。

1
2
3
4
5
6
7
8
const square = x => x * x; // 單參數可去括號
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 在調用時決定:對象方法指向對象、普通函數非嚴格模式指向全局、箭頭函數繼承外層。

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 碼元序列,Emoji 等增補平面字符佔 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 元素被移除但監聽器仍引用,或監聽器持外部對象導致泄漏。移除元素前解綁或節流。

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 操作、防抖節流高頻事件。

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 定義對象模板:constructor 構造、方法、屬性。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、錯誤類型、拋出、異步錯誤處理。

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 捕不到異步拋錯。

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));

未處理錯誤

Promise 無 catch 導致 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

錯誤隔離

批量處理時單個失敗不應中斷整體。逐項 try/catch 或 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 建目錄、exists 判斷。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 逐行交互、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();
});
// 逐行處理管道輸入:
// 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
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().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') 老引擎按八進制?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
// 順序:同步 → 微任務清空 → 宏任務
// 每完成一個宏任務都檢查清空微任務隊列

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 第二個參數配置 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 解析。

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 構造 multipart 表單,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。month 是 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 epoch)。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,前端按本地顯示

時間差

兩個 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 格式最可靠('2024-01-01T12:00:00Z')。其他格式有兼容性差異。

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 默認)信號處理,優雅關閉(存數據、斷連接)。

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();
});
// 防止重複觸發:用 flag 標記清理中

進程信息

process.pid 進程 ID、process.uptime() 運行秒數、process.memoryUsage() 內存、process.platform 平台。

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 三大流。管道數據處理、輸出重定向與管道協作。

1
2
3
4
5
6
7
8
9
10
11
// 輸出:
process.stdout.write('text'); // 無換行
process.stderr.write('error'); // 錯誤流
// 逐行讀 stdin(管道):
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 腳本。注意 shell 注入。

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 傳數組參數,避免 shell 注入

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 返回布爾、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 調試器。source map 定位源碼。

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 等平台 workflow 配置。產物部署到靜態託管/容器。

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

兩種模塊系統: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');
// 打包時優先 ESM 以啓用 tree-shaking
關於本速查

本頁是 ECMAScript 2022(JavaScript)的自包含速查手冊,覆蓋語言核心與瀏覽器/Node 常用 API 在真實項目中約 80% 的常見用法。內容偏向現代慣用法:let/const 塊級作用域、箭頭函數、模板字符串、解構賦值、展開運算符、Promise 與 async/await、可選鏈與空值合併。JavaScript 由 Brendan Eich 於 1995 年在 Netscape 發明,如今是唯一一種所有瀏覽器原生支持的語言,也是 Web 前端、Node.js 服務端與桌面應用的基礎。 19 個章節各自聚焦一個主題:基礎語法、變量與作用域、類型、引用與值語義、控制流、函數與閉包、字符串、數組與對象、內存與垃圾回收、原型與類、錯誤處理、輸入輸出、常見誤區、併發(事件循環)、網絡(fetch)、時間、進程、正則與構建工具。每個小節都配有「概念介紹 + 可直接複製的代碼片段」。 所有代碼與文字均在瀏覽器本地渲染,無任何數據離開你的設備。權威參考見 MDN Web Docs。

版本 2.1.0