Skip to content

JavaScript 速查表

Web 的语言——前端、后端及更广泛领域。

01

入门

Hello World 与注释

JavaScript 在浏览器和 Node.js 中运行。console.log() 是主要的调试输出。JSDoc 注释(/** */)为 IDE 提供类型信息和文档。使用 'use strict' 或 ES 模块以获得更安全的解析。注释在运行时被忽略。

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

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

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

严格模式与模块

'use strict' 启用更严格的解析,捕获静默错误。ES 模块(import/export)是现代标准;CommonJS(require/module.exports)是 Node.js 的传统方式。始终使用模块以避免污染全局作用域。浏览器支持 <script type='module'>。

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

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

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

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

变量:let、const、var

始终优先使用 const;仅在需要重新赋值时使用 let;完全避免 var。const 阻止重新赋值但不阻止修改——对象/数组内容仍可改变。let/const 是块作用域;var 是函数作用域且被提升(导致 bug)。TDZ 阻止在声明前使用变量。

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

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

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

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

数据类型与 typeof

JavaScript 有 7 种原始类型(string、number、bigint、boolean、undefined、null、symbol)和引用类型(对象、数组、函数)。原始类型不可变且按值复制;对象可变且按引用传递。typeof null 返回 'object' 是历史 bug——使用 === null 检查 null。

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

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

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

类型转换与强制转换

JavaScript 的类型强制转换以令人困惑著称。始终使用 ===(严格相等)而非 ==(宽松)以避免意外转换。+ 运算符在任一操作数为字符串时进行拼接;其他运算符强制转换为数字。假值:false、0、''、null、undefined、NaN。使用 Number.isNaN() 检查 NaN(NaN !== NaN)。

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

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

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

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

字符串

字符串方法

字符串不可变——方法返回新字符串。slice() 支持负索引(从末尾);substring() 不支持。replace() 仅替换第一个匹配;使用 replaceAll()(ES2021)替换所有。at()(ES2022)允许负索引。split() + join() 是替换字符串中字符的惯用方式。

javascript
const s = "Hello, World";

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

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

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

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

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

模板字面量

模板字面量(反引号)允许使用 ${} 进行字符串插值、多行字符串和标记模板。它们比字符串拼接可读性更强。标记模板让你用函数处理模板字面量——被 styled-components、graphql-tag 等使用。

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

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

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

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

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

字符串搜索与正则表达式

JavaScript 字符串通过 match()、matchAll()、replace()、search() 和 split() 支持正则表达式。命名捕获组 (?<name>...)(ES2018)使正则更易读。matchAll() 返回迭代器(比全局正则的 match() 更高效)。使用 .test() 检查模式是否匹配而不提取。

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

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

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

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

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

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

字符串迭代与展开

字符串可用 for...of 迭代。展开运算符 [...] 将字符串拆分为字符——对于正确的 emoji 处理(代理对)至关重要。字符串比较按 UTF-16 代码单元进行字典序,所以使用 localeCompare() 进行区域感知排序。Emoji 和某些字符长 2 个代码单元。

javascript
const s = "Hello";

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

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

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

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

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

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

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

数字与数学

数字与运算符

JavaScript 只有一种数字类型(64 位浮点数)——没有单独的 int/float。BigInt(n 后缀)处理超过 2^53 的整数。浮点运算有精度问题(0.1 + 0.2 !== 0.3)——比较时使用 Number.EPSILON。** 是幂运算符(ES2016)。

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

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

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

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

Math 对象

Math 对象提供常量和函数。所有三角函数使用弧度。Math.random() 返回 [0, 1)——乘以并取整得到整数范围。对于加密随机性,使用 crypto.getRandomValues()。Math.max/min 不直接接受数组——用 ... 展开。

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

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

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

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

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

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

Number 方法与解析

始终为 parseInt() 指定基数——旧浏览器将前导零解释为八进制。Number.isNaN() 可靠;全局 isNaN() 会强制转换(isNaN('abc') 为 true)。toFixed() 返回字符串而非数字。超过 MAX_SAFE_INTEGER 的数字会丢失精度——使用 BigInt。

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

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

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

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

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

数据结构

数组

数组是动态、有序的,可持有混合类型。push/pop 是 O(1);shift/unshift/splice 是 O(n)。find()/findIndex() 接受谓词函数。forEach() 不返回任何东西;用 map() 转换。数组是对象——typeof [] 是 'object'。使用 Array.isArray() 检查。

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

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

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

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

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

数组方法(map、filter、reduce)

map/filter/reduce 是函数式数组编程的三位一体。map 转换,filter 选择,reduce 聚合。它们可链式调用且不修改原数组(reverse/sort 除外)。sort() 默认转换为字符串——数字始终提供比较器。flat() 扁平化嵌套数组。

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

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

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

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

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

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

对象

对象是键值集合(键是字符串或符号)。静态键用点表示法,动态/特殊键用方括号表示法。计算属性名 {[expr]: val} 是 ES6。Object.keys/values/entries 提取数组;Object.fromEntries 反转 entries。for...in 迭代键(包括继承的)。

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

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

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

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

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

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

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

解构与展开

解构简洁地从对象/数组中提取值。支持重命名(key: newName)、默认值(= value)和剩余(...rest)。展开(...)扩展可迭代对象/对象——非常适合合并和浅拷贝。对象展开覆盖重复键(后者胜出)。函数参数中的解构对于可选配置很强大。

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

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

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

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

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

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

Map、Set、WeakMap

Map 允许任何键类型(对象,不仅是字符串)并保持插入顺序——不同于普通对象。Set 存储唯一值——非常适合去重。WeakMap/WeakSet 的键是弱引用(可被垃圾回收),防止内存泄漏。需要非字符串键或频繁添加/删除时使用 Map。

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

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

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

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

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

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

控制流

If / Else 与三元运算符

复杂逻辑用 if/else;简单值选择用三元运算符。&& 和 || 短路(对默认值/条件很有用)。??(空值合并)仅检查 null/undefined,而 || 检查所有假值。?.(可选链)安全访问嵌套属性而不报错。

javascript
const score = 85;

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

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

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

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

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

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

Switch 语句

switch 使用严格相等(===)比较,所以类型很重要。不要忘记 break——否则执行会落入下一个 case。通过堆叠分组 case(case 6: case 7:)。对于离散值,switch 比长 if/else 链更清晰。现代代码有时偏好对象查找表。

javascript
const day = 3;

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

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

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

循环

数组/字符串用 for...of(值),对象用 for...in(键)——永远不要对数组用 for...in(它将索引作为字符串迭代并包含原型)。while 在运行前检查;do...while 至少运行一次。break 退出,continue 跳过。用 .entries() 在 for...of 中获取索引+值。

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

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

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

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

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

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

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

迭代器与生成器

迭代器实现 next() 返回 {value, done}。生成器(function*)用 yield 简化迭代器创建——它们暂停执行并在 next() 时恢复。生成器是惰性的(按需计算)且可以是无限的。将它们用于自定义可迭代对象、序列和异步流。

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

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

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

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

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

函数

函数声明与表达式

函数声明被提升(可在定义前调用);表达式则不会。箭头函数简洁且没有自己的 'this'(它们从封闭作用域继承)。IIFE 创建私有作用域(有了模块后不太需要)。函数是一等公民——作为参数传递、返回、存储在变量中。

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

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

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

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

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

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

箭头函数与 this

箭头函数没有自己的 'this'、'arguments'、'super' 或 'new.target'——它们从封闭作用域继承。这使它们非常适合回调(尤其是类方法中)。但它们不能用作构造函数或需要自己 'this' 的方法。对象方法使用常规函数。

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

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

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

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

闭包

闭包是“记住”其定义作用域变量的函数,即使该作用域已退出。它们实现数据隐私(模块模式)、记忆化、柯里化和部分应用。JavaScript 中的每个函数都是闭包。内部函数保持对外部变量的引用,而非副本。

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

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

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

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

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

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

参数与 Rest/Spread

默认参数提供回退值。剩余参数(...name)将额外参数收集到真正的数组中——优先于旧版 'arguments' 对象。展开(...)将数组扩展为单个参数。参数中的解构启用命名的、可选的配置对象——常见的 API 模式。

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

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

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

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

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

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

OOP 与类

类与构造函数

ES6 类是原型的语法糖。私有字段(#name)是 ES2022 且真正私有(不同于 _name 约定)。Getter/setter 允许计算属性。静态成员属于类而非实例。类字段(name = value)无需构造函数即可初始化实例属性。

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

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

继承与多态

extends 创建继承;super() 调用父构造函数(在使用 'this' 前必须调用)。通过重新定义方法来覆盖。JavaScript 是单继承,但混入(类工厂)提供组合。instanceof 检查原型链。多态通过方法覆盖实现。

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

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

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

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

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

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

原型

JavaScript 使用原型继承——对象通过原型链从其他对象继承。__proto__ 已弃用;使用 Object.getPrototypeOf/setPrototypeOf。类是此系统的语法糖。修改内置原型(Array.prototype)很危险——可能破坏代码。优先使用组合而非深度继承。

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

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

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

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

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

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

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

错误处理

Try / Catch / Finally

try/catch/finally 处理异常。catch 绑定错误对象(有 .message 和 .stack)。使用 instanceof 以不同方式处理特定错误类型。处理预期错误后始终重新抛出未知错误。通过继承 Error 创建自定义错误以进行应用特定的错误处理。

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

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

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

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

自定义错误

继承 Error 创建带额外上下文(字段、代码)的自定义错误类型。始终将 this.name 设置为与类名匹配。使用 instanceof 捕获特定错误类型。错误链(ES2022 { cause })保留原始错误以便调试。良好的错误层次结构使错误处理精确。

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

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

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

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

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

异步与 Promise

回调

回调是稍后被调用的函数。错误优先约定(err, data)是 Node.js 的标准。嵌套回调创建“回调地狱”——深度嵌套、难以阅读的代码。Promise 和 async/await 解决了这个问题。setTimeout/setInterval 是常见的基于回调的 API。

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

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

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

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

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

Promise

Promise 表示未来值,有三种状态:pending(待定)、fulfilled(已实现)、rejected(已拒绝)。.then() 处理成功,.catch() 处理错误,.finally() 始终运行。Promise.all() 等待全部(快速失败);allSettled() 等待全部(永不失败);race() 返回第一个 settled;any() 返回第一个成功的。

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

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

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

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

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

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

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

Async / Await

async/await 是 Promise 的语法糖——使异步代码看起来同步。'await' 暂停函数直到 Promise settled。始终将 await 包在 try/catch 中进行错误处理。并行操作使用 Promise.all()(比循环中顺序 await 更快)。顶层 await 在 ES 模块中工作。

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

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

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

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

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

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

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

DOM 操作

选择与修改元素

querySelector/querySelectorAll(CSS 选择器)是选择元素的现代方式。textContent 比 innerHTML 更安全(防止 XSS)。classList 提供 add/remove/toggle/contains 用于类。dataset 访问 data-* 属性。设置 innerHTML 前始终清理用户输入。

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

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

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

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

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

事件

addEventListener 优于 on<event> 属性(允许多个监听器)。事件委托(在父级监听)对动态添加的元素高效。e.target 是被点击的元素;e.currentTarget 是带监听器的元素。preventDefault() 阻止默认行为;stopPropagation() 阻止冒泡。

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

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

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

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

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

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

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

模块与 JSON

ES 模块

ES 模块(import/export)是现代标准,在浏览器和 Node.js 中受支持。默认导出(每个模块一个)vs 命名导出(多个)。动态 import() 启用懒加载。模块始终处于严格模式且有自己作用域。在 HTML script 标签中使用 type='module'。

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

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

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

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

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

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

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

JSON

JSON.stringify() 将 JS 值转换为 JSON 字符串;JSON.parse() 反转。使用 replacers/revivers 在转换期间过滤或转换。JSON 不支持函数、undefined、Date(它们变为字符串)或循环引用。fetch().json() 自动解析 JSON 响应。始终将 JSON.parse 包在 try/catch 中处理不受信任的输入。

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

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

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

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

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

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

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

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

日期与时间

JavaScript Date 以笨拙著称——月份从 0 开始(一月 = 0),日期从 1 开始。Date 对象可变。toISOString() 给出 UTC;toLocaleString() 给出本地时间。对于严肃的日期工作,使用 date-fns 或 dayjs 等库。Intl.DateTimeFormat 提供区域感知格式化。

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

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

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

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

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

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

ES6+ 特性

let、const 与块作用域

默认优先 const,需要重新赋值时用 let,完全避免 var。const 阻止重新赋值但对象/数组仍可变。let 和 const 是块作用域,在声明前处于暂时性死区(不像 var 被提升为 undefined)。这防止了许多微妙的 bug。

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

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

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

箭头函数与 this

箭头函数简洁且从封闭作用域继承 'this'——非常适合回调和需要外部 'this' 的方法。但它们不能用作构造函数且没有 'arguments' 对象。如果需要 'this' 指向对象,不要对对象方法使用箭头函数(改用常规方法)。

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

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

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

解构赋值

解构在一行中将对象/数组的值提取到变量中——比手动属性访问更清晰。对象解构使用 { key },数组解构使用 [index]。支持重命名(key: alias)、默认值(key = default)、剩余(...rest)和嵌套模式。在 React props 和函数参数中大量使用。

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

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

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

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

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

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

展开与剩余运算符

... 运算符在扩展时(数组/对象/调用中)是“展开”,在收集时(参数/解构中)是“剩余”。展开创建浅拷贝并合并对象(后面的键覆盖前面的)。剩余参数替代旧的 'arguments' 对象且是真正的数组。两者都是现代 JS 的重要惯用法。

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

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

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

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

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

模板字面量与标记模板

模板字面量(反引号)支持多行字符串和 ${} 插值——比字符串拼接清晰得多。标记模板让函数处理字面量部分和插值,启用自定义格式化、清理(例如转义 HTML)或 i18n。在 styled-components 和 graphql-tag 中流行。

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

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

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

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

可选链与空值合并

可选链(?.)在链的任何部分为 null/undefined 时短路为 undefined——消除冗长的手动检查。空值合并(??)仅对 null/undefined 提供默认值,不像 || 还会覆盖 0、'' 和 false。两者共同安全处理最常见的“缺失数据”模式。自 ES2020 起可用。

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

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

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

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

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

事件与事件处理

addEventListener 基础

addEventListener 是绑定事件的现代方式——它允许多个处理程序、支持事件委托,并提供 once、passive 和 capture 等选项。如果稍后需要移除,始终保留处理程序的引用(匿名函数无法移除)。事件对象携带 target、currentTarget、preventDefault() 和 stopPropagation()。

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

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

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

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

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

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

事件委托

事件委托在父级附加一个监听器,通过事件冒泡处理所有子级的事件。使用 event.target.matches(selector) 过滤。这比为每个子级绑定高效得多,且自动处理动态添加的元素。权衡:父级必须是始终存在的共同祖先。

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

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

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

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

事件传播(冒泡与捕获)

事件在三个阶段传播:捕获(自顶向下)、目标和冒泡(自底向上,默认)。大多数处理程序在冒泡阶段运行。stopPropagation() 阻止事件到达父元素;stopImmediatePropagation() 还阻止同一元素上的其他处理程序。对于必须在子处理程序之前运行的处理程序,使用捕获(第三个参数 true)。

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

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

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

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

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

// stopImmediatePropagation: stop other handlers on SAME element too

自定义事件

CustomEvent 让你创建应用特定的事件,载荷数据在 'detail' 属性中。结合 dispatchEvent,这启用了发布/订阅模式来解耦组件——模块无需直接引用即可通信。使用 'namespace:action' 等命名约定避免冲突。这是许多自定义元素框架的基础。

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

document.dispatchEvent(event);

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

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

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

键盘与表单事件

键盘事件给出 e.key(逻辑键如 'a'、'Enter')和 e.code(物理键如 'KeyA')。大多数逻辑使用 e.key。表单提交始终需要 preventDefault() 阻止页面重载。FormData + Object.fromEntries 轻松收集表单数据。'input' 持续触发;'change' 在字段失去焦点时触发——根据验证时机选择。

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

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

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

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

Fetch API 与 AJAX

基本 fetch(GET)

fetch() 是 XMLHttpRequest 的现代替代品——基于 Promise 且更简洁。关键是,fetch 仅在网络错误时拒绝,而非 HTTP 错误状态(404、500)。解析前始终检查 response.ok(状态 200-299)。使用 async/await 获得可读的顺序代码。response.json() 是异步的,因为它读取主体流。

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

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

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

POST、PUT、DELETE 与 fetch

fetch 选项对象配置 method、headers 和 body。对于 JSON,设置 Content-Type: application/json 并 JSON.stringify 主体。对于文件上传,使用 FormData(不要手动设置 Content-Type——浏览器添加 multipart 边界)。PUT 完全替换资源;PATCH 部分更新。

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

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

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

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

请求头与认证

请求头携带元数据和认证令牌。Bearer 令牌(JWT)放在 Authorization 头中。某些头是“禁止”的(浏览器控制)如 Host 和 Cookie。CORS 由浏览器而非服务器强制执行——你无法从客户端 JS 绕过它;服务器必须发送 Access-Control-Allow-Origin。非简单请求会发生预检 OPTIONS 请求。

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

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

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

AbortController(取消请求)

AbortController 取消 fetch 请求——对于边输入边搜索、导航离开或超时至关重要。将 signal 传给 fetch;调用 controller.abort() 触发 AbortError。没有它,过期请求可能乱序更新 UI。AbortController 也适用于其他异步 API,是现代 JS 的标准取消机制。

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

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

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

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

流式响应

response.body 是 ReadableStream——你可以在数据到达时分块处理,而不是将整个响应缓冲在内存中。这对于大文件、流式日志或实时数据至关重要。文本流使用 TextDecoder。reader.read() 循环持续到 done 为 true。流式处理避免大载荷的内存峰值。

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

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

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

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

Web 存储(LocalStorage 与 SessionStorage)

localStorage 与 sessionStorage 基础

localStorage 无限期持久化;sessionStorage 在标签页关闭时清除。两者都只存储字符串——对象使用 JSON.stringify/parse。存储是同步的并阻塞主线程,所以避免存储大数据。在所有现代浏览器中可用,但在隐私浏览模式下可能被禁用。每个源容量约 5-10MB。

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

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

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

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

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

带 TTL 与 JSON 的存储助手

Web Storage 没有内置过期——此包装器通过在值旁边存储过期时间戳来添加 TTL(生存时间)。这是缓存应过期的 API 响应或会话数据的标准模式。在生产中始终将存储访问包在 try/catch 中,因为 JSON.parse 在损坏数据上会抛出且可能超出配额。

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

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

存储事件(跨标签页同步)

storage 事件是内置的跨标签页通信通道——当一个标签页修改 localStorage 时,同一源上的所有其他标签页都会收到事件(但发起标签页不会)。这无需 WebSocket 即可跨标签页同步登录/登出、购物车更新或主题更改等状态。事件包含 key、oldValue、newValue 和 url。

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

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

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

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

IndexedDB(大型结构化存储)

IndexedDB 是浏览器中强大的 NoSQL 数据库——异步、事务性,能存储比 localStorage 多得多的数据(数百 MB)。它支持索引、游标和事务。原始 API 基于回调且冗长;'idb' npm 包提供干净的基于 Promise 的包装器。将 IndexedDB 用于离线优先应用、大型缓存或复杂的客户端数据。

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

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

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

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

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

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

Cookie 与存储比较

Cookie 随每个 HTTP 请求发送(增加带宽),是服务器端认证的标准(会话 ID、CSRF 令牌)。localStorage/sessionStorage 仅客户端且持有更多数据。IndexedDB 用于大型结构化数据。根据以下选择:服务器需要它吗?(cookie)多少数据?(storage vs IndexedDB)多久?(session vs local)。在安全敏感的 cookie 上设置 Secure、HttpOnly 和 SameSite。

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

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

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

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

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

定时器(setTimeout、setInterval)

setTimeout 与 setInterval

setTimeout 在延迟后运行一次回调;setInterval 重复运行。两者都返回 ID 用于通过 clearTimeout/clearInterval 取消。定时器不精确——它们是受事件循环、标签页可见性(后台标签页被节流)和主线程可用性影响的最小延迟。嵌套定时器中低于 4ms 的延迟可能被钳制为 4ms。

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

// Cancel before it fires
clearTimeout(timeoutId);

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

// Stop the interval
clearInterval(intervalId);

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

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

递归 setTimeout(优于 setInterval)

对于重复的异步任务,递归 setTimeout 优于 setInterval,因为它保证上一次调用在下一次开始前完成——无重叠执行。它还允许动态间隔(例如错误时更长的退避)。如果处理程序花费时间超过间隔,setInterval 可能堆叠调用,导致性能问题。

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

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

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

requestAnimationFrame(平滑动画)

requestAnimationFrame(rAF)是做视觉动画的正确方式——它与浏览器重绘周期同步(约 60fps),在标签页隐藏时避免不必要的帧,产生比 setInterval 更平滑的动画。使用时间戳参数进行增量时间计算,以在不同刷新率下保持动画速度一致。完成后始终用 cancelAnimationFrame 取消。

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

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

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

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

防抖与节流

防抖在调用暂停后执行(例如用户停止输入 300ms 后才搜索)。节流限制每个间隔执行一次(例如最多每 200ms 更新滚动位置)。两者都防止高频事件的性能问题。防抖 = “将快速调用合并为一个”;节流 = “限制调用速率”。

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

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

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

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

Promise 化的 setTimeout(delay)

将 setTimeout 包装在 Promise 中为 async/await 创建干净的 'delay' 函数——比回调链可读性强得多。此模式支持带指数退避的重试逻辑(重试之间等待 1s、2s、4s)、顺序动画和速率限制。delay 助手是现代异步 JS 中最有用的小工具之一。

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

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

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

Map、Set 与 WeakMap

Map(任意键类型的键值对)

Map 是合适的键值集合,键可以是任何类型(对象、函数、数字)——不同于将键强制转换为字符串的 Object。Map 保持插入顺序,有 .size 属性,且可直接迭代。需要非字符串键、频繁添加/删除或集合不是记录/DTO 时使用 Map。固定形状数据使用 Object。

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

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

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

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

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

Set(唯一值)

Set 存储唯一值——非常适合去重和成员测试。将数组转换为 Set 再转回([...new Set(arr)])是去重的惯用方式。Set.has() 是 O(1) 而 Array.includes() 是 O(n),所以对于频繁检查的大型集合使用 Set。Set 没有 map/filter——先展开为数组。

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

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

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

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

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

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

WeakMap 与 WeakSet(内存安全引用)

WeakMap 和 WeakSet 持有对象的弱引用——当没有其他引用时,条目会自动被垃圾回收。这防止了将数据与 DOM 元素或其他对象关联时的内存泄漏。键必须是对象。WeakMap/WeakSet 不可迭代且没有 .size,因为条目在 GC 期间随时可能消失。将它们用于与对象生命周期绑定的缓存/元数据。

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

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

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

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

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

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

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

Map 迭代与转换

Map 通过 for...of(默认 entries)、.keys()、.values() 或 .forEach() 按插入顺序可迭代。使用 Object.entries() 和 Object.fromEntries() 在 Map 和 Object 之间转换。当 API 期望普通对象但你想要 Map 的内部特性时,这种双向转换很方便。记住:转换中 Object 键变为字符串类型。

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

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

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

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

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

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

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

在 Map、Set、Object 与 Array 之间选择

选择合适的集合:数组用于带重复和索引访问的有序列表;对象用于固定形状记录和 JSON;Map 用于带任意键类型的动态键值集合;Set 用于唯一性和快速查找。使用错误的结构会导致冗长代码和性能问题——例如在循环中检查 Array.includes() vs Set.has()。

javascript
// Decision guide:

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

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

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

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

生成器与迭代器

迭代器与 Symbol.iterator

迭代器有 next() 方法返回 {value, done}。可迭代对象实现 Symbol.iterator,返回迭代器。内置可迭代对象(数组、字符串、Map、Set)适用于 for...of、展开(...)、解构和 Array.from()。实现 Symbol.iterator 让你的自定义对象无缝使用所有这些语言特性。

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

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

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

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

生成器函数(function*)

生成器函数(function*)使用 yield 暂停和恢复执行——它们惰性产生值,一次一个。这启用了无限序列、惰性求值和内存高效管道。生成器既是迭代器又是可迭代对象。每次调用 next() 运行到下一个 yield(或 return)。它们是异步生成器和 JS 协程模式的基础。

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

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

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

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

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

yield*(委托给另一个生成器)

yield* 将所有 yield 委托给另一个可迭代对象或生成器——扁平化嵌套结构并干净地组合生成器。它是 Python 'yield from' 的 JS 等价物。被委托生成器的值像外层生成器的一部分一样逐个 yield。这是递归组合生成器的标准方式。

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

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

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

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

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

通过 next(value) 的生成器 send()

生成器支持双向通信:next(value) 传入值(成为上次 yield 表达式的结果),throw(error) 在 yield 点注入异常。这启用了协程、状态机和为早期 async/await 实现提供动力的基于生成器的异步模式。第一次 next() 调用无法传入值(还没有东西接收它)。

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

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

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

异步生成器

异步生成器(async function*)将生成器与 async/await 结合——每个 yield 可以在 await 后产生值。用 for await...of 消费。这非常适合分页 API、流数据或任何异步产生值的场景。它们是 JS 中无需回调或手动 Promise 链处理流数据的现代方式。

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

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

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

Proxy 与 Reflect

Proxy 基础(拦截操作)

Proxy 让你拦截和自定义对象的基本操作——get、set、has、deleteProperty、ownKeys 等。处理程序对象定义“陷阱”(类似 getter/setter 但适用于所有属性)。用例:验证、日志、默认值、访问控制、响应式系统(Vue 3 用 Proxy 实现响应式)。在严格模式下从 set 返回 true 表示成功。

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

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

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

常见 Proxy 用例

Proxy 启用强大的元编程:负索引、惰性属性计算、只读视图、验证、日志和响应式数据绑定。Vue 3 的响应式系统使用 Proxy 跟踪属性访问并自动触发重新渲染。权衡是小的性能开销,所以在抽象值得的地方使用 Proxy,而非每个对象。

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

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

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

Reflect API

Reflect 提供 Proxy 陷阱拦截的相同操作——在陷阱内使用它干净地转发到默认行为。Reflect 方法返回布尔值(成功/失败)而非抛出,使其对条件逻辑更安全。Reflect.ownKeys 返回所有键(字符串和符号),不像 Object.keys 只返回可枚举字符串键。Proxy 和 Reflect 共同构成 JS 的元编程工具包。

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

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

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

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

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

使用 Proxy 的响应式对象(Vue 风格)

这是 Vue 3 响应式背后的核心思想——Proxy 拦截 get(跟踪哪些 effect 依赖属性)和 set(在属性改变时触发这些 effect)。这启用声明式 UI 更新:你修改状态,框架自动重新渲染。Proxy 在 ES6 中使这成为可能;早期框架(Vue 2)使用 Object.defineProperty 但有局限。

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

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

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

  return new Proxy(target, handler);
}

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

Web Workers

创建 Web Worker

Web Workers 在单独的后台线程中运行 JavaScript,实现真正的并行而不阻塞 UI。主线程和 worker 通过 postMessage 通信(数据被复制/结构化克隆,而非共享)。Worker 无法访问 DOM 或 window 对象——它们是隔离的。将 worker 用于 CPU 密集型任务如图像处理、解析大文件或复杂计算。

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

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

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

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

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

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

内联 Worker(Blob URL)

内联 worker 通过 Blob URL 从代码字符串创建 Worker——无需单独文件。这对于演示、小工具或构建系统不易处理单独 worker 文件的情况很方便。记得撤销对象 URL 以避免内存泄漏。worker 代码是字符串,所以会失去编辑器语法高亮和类型检查——在生产中谨慎使用。

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

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

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

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

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

可转移对象(零拷贝)

通常 postMessage 复制数据(结构化克隆),对大缓冲区很慢。转移列表(第二个参数)将 ArrayBuffer/MessagePort/ImageBitmap 的所有权以零拷贝方式移动到 worker——无论大小都近乎瞬时。原始缓冲区变为分离(不可用)。将此用于大数据集、图像处理或音频以避免拷贝开销。

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

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

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

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

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

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

SharedArrayBuffer 与 Atomics

SharedArrayBuffer 允许线程间真正的共享内存(无拷贝),Atomics 提供线程安全操作(add、load、store、compareExchange、wait/notify)。这启用了 JS 中的高性能并行算法。由于 Spectre 安全问题,SharedArrayBuffer 需要跨源隔离 HTTP 头——没有它们,在现代浏览器中被禁用。用于 WASM 互操作和重度并行计算。

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

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

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

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

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

Worker 池模式

创建 worker 有开销,所以 worker 池重用固定数量的 worker 并排队任务——类似其他语言中的线程池。此模式最大化 CPU 利用率(每核一个 worker)同时避免每个任务生成 worker 的成本。池将任务分派给空闲 worker 并在全部忙时排队。对于处理许多独立工作块至关重要。

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

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

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

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

DOM 操作深入

querySelector

querySelector 返回第一个匹配,querySelectorAll 返回静态 NodeList。getElementsByClassName 返回实时 HTMLCollection。NodeList 支持 forEach;HTMLCollection 不支持。

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

创建与插入

createElement 创建新元素。appendChild 添加为最后一个子元素,prepend 添加为第一个。textContent 比 innerHTML 更安全,因为它防止 XSS。

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

事件委托

事件委托在父级附加一个监听器而非多个子级。closest 查找匹配选择器的最近祖先。处理动态添加的元素。

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

classList API

classList 提供安全操作 CSS 类的方法。toggle 在添加时返回 true,移除时返回 false。比操作 className 字符串更干净。

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

Dataset 属性

data-* 属性存储自定义数据。dataset 提供驼峰式访问:data-user-id 变为 dataset.userId。值始终是字符串。

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

Canvas API

基本绘图

getContext("2d") 返回 2D 渲染上下文。fillRect 绘制填充矩形,strokeRect 绘制轮廓。绘图前设置 fillStyle/strokeStyle。

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

路径与线条

moveTo 定位光标而不绘图。lineTo 绘制线条。closePath 连接回起点。arc 绘制圆/弧。

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

文本与渐变

createLinearGradient 创建渐变。addColorStop 定义位置(0-1)处的颜色。绘图文本前设置 font 和 textAlign。

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

动画循环

requestAnimationFrame 与显示刷新同步(约 60fps)。clearRect 在每帧前清除。用 cancelAnimationFrame 取消。

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

图像处理

drawImage 渲染图像。getImageData 返回 RGBA 数组形式的像素数据。操作像素进行滤镜。putImageData 写回修改的像素。

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

WebSocket

基本 WebSocket

WebSocket 通过单个 TCP 连接提供全双工通信。onopen 在连接时触发,onmessage 在数据到达时触发。始终处理 onerror。

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

发送与接收 JSON

WebSocket 数据以字符串或二进制传输。JSON.stringify/parse 启用结构化数据交换。type 字段启用消息路由。

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

重连

WebSocket 连接可能断开。指数退避(2^retries)防止压垮服务器。延迟上限 30s。成功时重置重试计数。

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

二进制数据

WebSocket 通过 ArrayBuffer 支持二进制数据。将 binaryType 设置为 arraybuffer。DataView 提供类型化访问。二进制对数值数据更高效。

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

心跳

心跳检测过期连接。发送 ping 并期望 pong。如果超时内无 pong,关闭并重连。readyState 检查连接状态。

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

Service Worker

注册

Service worker 在单独线程中运行,拦截网络请求。注册必须在 HTTPS 或 localhost 上发生。SW 文件位置决定作用域。

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

缓存

缓存优先策略:从缓存服务,回退到网络。install 预缓存资产。fetch 拦截请求。其他策略:网络优先、stale-while-revalidate。

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

后台同步

后台同步将操作推迟到连接恢复。网络可用时触发 sync 事件。在 IndexedDB 中存储待处理操作。

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

推送通知

推送通知即使在应用关闭时也工作。subscribe 使用 VAPID 密钥向推送服务注册。userVisibleOnly 要求显示通知。

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

更新与激活

skipWaiting 立即激活新 SW。activate 清理旧缓存。clients.claim 立即接管控制。版本化缓存名以触发更新。

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

IndexedDB

打开数据库

IndexedDB 是浏览器中的 NoSQL 数据库。onupgradeneeded 在版本更改时触发,用于创建对象存储。keyPath 定义主键。

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

Add 与 Put

事务原子地分组操作。add 在重复键上失败,put 覆盖。readwrite 允许修改。oncomplete 在成功时触发。

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

查询数据

get 按键检索。openCursor 迭代记录。cursor.continue 移到下一个。包装在 Promise 中以使用 async/await。

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

索引查询

索引启用对非键字段的查询。IDBKeyRange 创建边界。索引必须在 onupgradeneeded 中创建。

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

删除与清除

delete 删除单条记录。clear 删除所有记录。deleteDatabase 删除整个数据库。所有修改需要 readwrite 事务。

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

WebRTC

获取用户媒体

getUserMedia 请求摄像头和麦克风访问。返回 MediaStream。srcObject 将流分配给视频元素。需要 HTTPS 和用户权限。

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

对等连接

RTCPeerConnection 建立 P2P 连接。addTrack 添加媒体。ontrack 接收远程流。ICE 候选是通过信令交换的网络路径。

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

Offer 与 Answer

SDP 协商:offer/answer 交换描述媒体格式。setLocalDescription 设置本地 SDP,setRemoteDescription 设置远程 SDP。

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

数据通道

数据通道通过 WebRTC 启用低延迟的任意数据传输。createDataChannel 在 offerer 上创建。ondatachannel 在 answerer 上接收。

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

屏幕共享

getDisplayMedia 捕获屏幕、窗口或标签页。浏览器显示选择器。onended 在用户停止共享时触发。始终处理清理。

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

性能优化

防抖与节流

防抖延迟执行直到调用停止(适合搜索)。节流限制为每间隔一次(适合滚动)。两者都防止过度调用。

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

Web Workers

Web Workers 在单独线程中运行 JavaScript 处理 CPU 密集型任务。数据通过 postMessage 传递。Worker 无法访问 DOM。

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

懒加载

IntersectionObserver 在元素进入视口时触发。data-src 持有真实 URL;可见时设置 src。减少图像密集页面的初始加载。

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

批量 DOM 更新

DocumentFragment 将 DOM 插入批量合并为单次重排,比逐个追加快得多。最小化布局抖动。

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

内存管理

WeakMap 允许 GC 键,防止泄漏。元素移除时始终移除事件监听器。将大对象设置为 null 以便 GC。

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

安全(XSS/CSRF)

XSS 防护

XSS 通过用户输入注入恶意脚本。永远不要用不受信任的数据使用 innerHTML。textContent 是安全的。CSP 头限制脚本来源。

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

CSRF 防护

CSRF 诱骗用户执行非预期操作。令牌确保请求来自你的应用。SameSite=Strict cookie 不跨站发送。对状态更改操作使用 CSRF 令牌。

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

内容安全策略

CSP 限制可加载的资源。default-src 是回退。script-src 控制 JavaScript。强制执行前从 report-only 模式开始。

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

安全 Cookie

HttpOnly 阻止 JavaScript 访问 cookie,缓解 XSS。Secure 确保仅 HTTPS。SameSite=Strict 防止 CSRF。会话 cookie 应始终同时使用两者。

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

输入验证

客户端验证改善 UX 但不是安全。始终在服务器上验证。使用 DOMPurify 进行 HTML 清理。白名单允许的标签。

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

设计模式

单例

单例确保只存在一个实例。构造函数返回现有实例。适用于配置、日志和数据库连接。

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

观察者/发布-订阅

观察者模式让对象订阅事件。on 注册,emit 触发,off 取消订阅。事件驱动架构的基础。

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

工厂

工厂在不暴露实例化逻辑的情况下创建对象。调用者指定类型,工厂决定实例化哪个类。

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

模块模式

模块模式使用闭包封装私有状态。IIFE 创建私有作用域。仅返回的方法是公共的。

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

策略

策略模式封装可互换的算法。上下文委托给选定的策略。避免大型 if/else 链。

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

常见陷阱

this 绑定

this 由函数如何调用决定。箭头函数从封闭作用域继承 this。分离的方法丢失 this。使用 bind。

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

浮点数

JavaScript 使用 IEEE 754 浮点数。比较时使用 Number.EPSILON,或乘以 10 的幂以处理整数。

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

== vs ===

== 执行类型强制转换,导致意外结果。=== 检查类型和值而不转换。始终使用 === 和 !==。

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

循环中的闭包

var 是函数作用域,所以所有闭包共享同一变量。let 是块作用域,每次迭代创建新绑定。始终使用 let/const。

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

异步错误处理

未处理的 Promise 拒绝可能使 Node.js 崩溃。始终将 await 包在 try/catch 中,或使用 .catch()。监听 unhandledRejection 事件。

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

相关 JavaScript 代码片段

Copy-paste ready code for common tasks.

数组 Map Filter Reduce

在数组上链式调用 map、filter、reduce、find、some 和 every。

数组去重

使用 Set 对数组进行去重。

深克隆

深克隆对象,支持常见数据类型。

防抖函数

事件触发后等待一段时间再执行;若在等待期间再次触发则重置计时器。

节流函数

限制函数在一个时间间隔内最多执行一次。

Promise.all 并发控制

带并发限制的 Promise 执行器。

async/await 错误处理

包装异步函数以统一捕获异常。

Fetch 封装

封装 fetch,支持超时、错误处理和 JSON 解析。

localStorage 操作

封装 localStorage,支持过期时间和 JSON。

Cookie 操作

封装 Cookie 的读取、写入和删除操作。

URL 参数解析

将 URL 查询字符串解析为对象。

日期格式化

将日期格式化为指定字符串。

金额格式化

将数字格式化为千分位分隔的金额字符串。

随机数生成

生成指定范围内的随机数和随机字符串。

颜色转换

在 RGB 和 HEX 颜色之间转换。

UUID 生成

生成符合 UUID v4 规范的唯一标识符。

字符串截断

截断字符串并追加省略号。

数组扁平化

将多维数组扁平化为一维。

对象合并

深度合并多个对象。

类型判断

精确判断 JavaScript 数据类型。

事件委托

通过事件冒泡实现事件委托。

DOM 操作

动态创建和操作 DOM 元素。

表单验证

常用表单验证规则集合。

文件上传

封装文件上传,支持进度和分片。

图片懒加载

使用 IntersectionObserver 实现图片懒加载。

复制到剪贴板

跨浏览器的剪贴板复制方法。

全屏 API

封装浏览器全屏操作。

地理位置

获取用户地理位置信息。

Web Worker

创建 Web Worker 运行耗时任务。

Service Worker

注册 Service Worker 实现离线缓存。

IndexedDB 操作

封装 IndexedDB 增删改查操作。

Canvas 绘图

基础 Canvas 绘图示例。

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。