入门
Hello World 与注释
JavaScript 在浏览器和 Node.js 中运行。console.log() 是主要的调试输出。JSDoc 注释(/** */)为 IDE 提供类型信息和文档。使用 'use strict' 或 ES 模块以获得更安全的解析。注释在运行时被忽略。
// Single-line comment
/* Multi-line
comment */
// Console output
console.log("Hello, World!");
console.warn("Warning message");
console.error("Error message");
console.table([{a: 1}, {a: 2}]);
// JSDoc comments (for documentation)
/**
* Adds two numbers
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} The sum
*/
function add(a, b) { return a + b; }严格模式与模块
'use strict' 启用更严格的解析,捕获静默错误。ES 模块(import/export)是现代标准;CommonJS(require/module.exports)是 Node.js 的传统方式。始终使用模块以避免污染全局作用域。浏览器支持 <script type='module'>。
"use strict"; // enables strict mode (catches common mistakes)
x = 10; // ReferenceError: x is not defined (strict mode)
// ES Modules (modern)
// import { greet } from './utils.js';
// export function greet(name) { return `Hello, ${name}`; }
// CommonJS (Node.js traditional)
// const fs = require('fs');
// module.exports = { greet: (name) => `Hello, ${name}` };
// Running JS
// Browser: include in <script> or via dev server
// Node.js: node script.js
// Deno: deno run script.ts变量:let、const、var
始终优先使用 const;仅在需要重新赋值时使用 let;完全避免 var。const 阻止重新赋值但不阻止修改——对象/数组内容仍可改变。let/const 是块作用域;var 是函数作用域且被提升(导致 bug)。TDZ 阻止在声明前使用变量。
// const - cannot be reassigned (use by default)
const PI = 3.14159;
const user = { name: "Alice" };
user.name = "Bob"; // OK - object contents can change
// user = {}; // TypeError - can't reassign const
// let - block-scoped, can be reassigned
let count = 0;
count = 1;
if (true) {
let local = 5; // only accessible in this block
}
// var - function-scoped, hoisted (AVOID in modern code)
var old = "legacy"; // hoisted, can cause bugs
// Temporal Dead Zone (TDZ)
// console.log(x); // ReferenceError
// let x = 5;数据类型与 typeof
JavaScript 有 7 种原始类型(string、number、bigint、boolean、undefined、null、symbol)和引用类型(对象、数组、函数)。原始类型不可变且按值复制;对象可变且按引用传递。typeof null 返回 'object' 是历史 bug——使用 === null 检查 null。
// Primitive types (immutable, passed by value)
const str = "hello"; // string
const num = 42; // number (no separate int/float)
const big = 9007199254740993n; // bigint
const bool = true; // boolean
const undef = undefined; // undefined (no value)
const empty = null; // null (intentional empty)
const sym = Symbol("id"); // symbol (unique identifier)
// Reference types (mutable, passed by reference)
const arr = [1, 2, 3]; // object (array)
const obj = { a: 1 }; // object
const fn = function() {}; // object (function)
// typeof operator
console.log(typeof "hi"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (historical bug!)
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof function(){}); // "function"类型转换与强制转换
JavaScript 的类型强制转换以令人困惑著称。始终使用 ===(严格相等)而非 ==(宽松)以避免意外转换。+ 运算符在任一操作数为字符串时进行拼接;其他运算符强制转换为数字。假值:false、0、''、null、undefined、NaN。使用 Number.isNaN() 检查 NaN(NaN !== NaN)。
// Explicit conversion
String(42); // "42"
(42).toString(); // "42"
Number("42"); // 42
Number("3.14"); // 3.14
Number(""); // 0
Number("abc"); // NaN
parseInt("42px"); // 42
parseFloat("3.14abc"); // 3.14
Boolean(0); // false
Boolean(""); // false
Boolean("x"); // true
// Implicit coercion (often confusing)
console.log("5" + 3); // "53" (string concatenation)
console.log("5" - 3); // 2 (numeric subtraction)
console.log("5" * "2"); // 10
console.log(1 + "2" + 3); // "123"
console.log(true + 1); // 2
// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("0") console.log("truthy"); // runs! non-empty string
// Strict vs loose equality
console.log(1 == "1"); // true (loose, coerces)
console.log(1 === "1"); // false (strict, no coercion)
console.log(null == undefined); // true
console.log(null === undefined); // false字符串
字符串方法
字符串不可变——方法返回新字符串。slice() 支持负索引(从末尾);substring() 不支持。replace() 仅替换第一个匹配;使用 replaceAll()(ES2021)替换所有。at()(ES2022)允许负索引。split() + join() 是替换字符串中字符的惯用方式。
const s = "Hello, World";
// Length & access
console.log(s.length); // 12
console.log(s[0]); // "H"
console.log(s.charAt(0)); // "H"
console.log(s.at(-1)); // "d" (ES2022, negative index)
// Case
console.log(s.toUpperCase()); // "HELLO, WORLD"
console.log(s.toLowerCase()); // "hello, world"
// Search
console.log(s.indexOf("World")); // 7 (-1 if not found)
console.log(s.includes("World")); // true
console.log(s.startsWith("Hello")); // true
console.log(s.endsWith("World")); // true
// Extract
console.log(s.slice(0, 5)); // "Hello"
console.log(s.slice(-5)); // "World"
console.log(s.substring(0, 5)); // "Hello" (no negative)
console.log(s.split(", ")); // ["Hello", "World"]
// Modify
console.log(s.replace("o", "0")); // "Hell0, World"
console.log(s.replaceAll("o", "0")); // "Hell0, W0rld"
console.log(s.trim()); // remove whitespace
console.log(s.padStart(15, "*")); // "***Hello, World"
console.log("a,b,c".split(",").join("-")); // "a-b-c"模板字面量
模板字面量(反引号)允许使用 ${} 进行字符串插值、多行字符串和标记模板。它们比字符串拼接可读性更强。标记模板让你用函数处理模板字面量——被 styled-components、graphql-tag 等使用。
const name = "Alice";
const age = 30;
// Template literals (backticks)
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);
// Multi-line strings
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// Expressions inside ${}
console.log(`Next year: ${age + 1}`);
console.log(`Upper: ${name.toUpperCase()}`);
console.log(`${name.length} chars`);
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `<b>${values[i]}</b>` : ''), '');
}
const result = highlight`Name: ${name}, Age: ${age}`;
// "Name: <b>Alice</b>, Age: <b>30</b>"字符串搜索与正则表达式
JavaScript 字符串通过 match()、matchAll()、replace()、search() 和 split() 支持正则表达式。命名捕获组 (?<name>...)(ES2018)使正则更易读。matchAll() 返回迭代器(比全局正则的 match() 更高效)。使用 .test() 检查模式是否匹配而不提取。
const text = "The quick brown fox jumps over the lazy dog";
// Search methods
console.log(text.search(/brown/)); // 10 (index of match)
console.log(text.match(/\w+/g)); // ["The","quick","brown",...]
console.log(text.matchAll(/\w+/g)); // iterator of matches
// Replace with regex
console.log(text.replace(/o/g, "0")); // replace all 'o'
console.log(text.replace(/(\w+)/g, "\$1!")); // capture group
// Capture groups
const date = "2024-01-15";
const [, year, month, day] = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year, month, day); // 2024 01 15
// Named capture groups (ES2018)
const m = date.match(/(?<year>\d{4})-(?<month>\d{2})/);
console.log(m.groups.year); // 2024
console.log(m.groups.month); // 01
// Test if pattern matches
const emailRe = /^[^@]+@[^@]+\.[^@]+$/;
console.log(emailRe.test("[email protected]")); // true字符串迭代与展开
字符串可用 for...of 迭代。展开运算符 [...] 将字符串拆分为字符——对于正确的 emoji 处理(代理对)至关重要。字符串比较按 UTF-16 代码单元进行字典序,所以使用 localeCompare() 进行区域感知排序。Emoji 和某些字符长 2 个代码单元。
const s = "Hello";
// Iterate characters
for (const char of s) {
console.log(char); // H, e, l, l, o
}
// Spread into array
const chars = [...s]; // ["H", "e", "l", "l", "o"]
console.log(chars);
// Spread with map/filter
const upper = [...s].map(c => c.toUpperCase()).join("");
console.log(upper); // HELLO
// String comparison
console.log("a" < "b"); // true (lexicographic)
console.log("apple" < "banana"); // true
console.log("Z" < "a"); // true (uppercase < lowercase in ASCII)
// Locale-aware comparison
console.log("ö".localeCompare("o", "de")); // locale-specific
// Repeat
console.log("ab".repeat(3)); // "ababab"
// Code points (handles emoji correctly)
const emoji = "😀";
console.log(emoji.length); // 2 (surrogate pair!)
console.log([...emoji].length); // 1 (correct)
console.log("😀".codePointAt(0)); // 128512数字与数学
数字与运算符
JavaScript 只有一种数字类型(64 位浮点数)——没有单独的 int/float。BigInt(n 后缀)处理超过 2^53 的整数。浮点运算有精度问题(0.1 + 0.2 !== 0.3)——比较时使用 Number.EPSILON。** 是幂运算符(ES2016)。
// JavaScript has one number type (IEEE 754 double)
const int = 42;
const float = 3.14;
const exp = 1e6; // 1000000
const hex = 0xff; // 255
const bin = 0b1010; // 10
const oct = 0o755; // 493
const big = 9007199254740993n; // BigInt (arbitrary precision)
// Arithmetic
console.log(10 / 3); // 3.333...
console.log(10 % 3); // 1 (remainder)
console.log(2 ** 10); // 1024 (exponent)
console.log(Math.floor(10 / 3)); // 3
console.log(Math.trunc(-3.7)); // -3 (toward zero)
// Increment/decrement
let x = 5;
console.log(x++); // 5 (post-increment, returns old)
console.log(++x); // 7 (pre-increment, returns new)
// Floating point issues
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false!
// Fix: use Number.EPSILON or round
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // trueMath 对象
Math 对象提供常量和函数。所有三角函数使用弧度。Math.random() 返回 [0, 1)——乘以并取整得到整数范围。对于加密随机性,使用 crypto.getRandomValues()。Math.max/min 不直接接受数组——用 ... 展开。
// Constants
console.log(Math.PI); // 3.141592653589793
console.log(Math.E); // 2.718281828459045
console.log(Math.SQRT2); // 1.4142135623730951
// Rounding
console.log(Math.round(3.7)); // 4 (nearest)
console.log(Math.floor(3.7)); // 3 (down)
console.log(Math.ceil(3.2)); // 4 (up)
console.log(Math.trunc(-3.7)); // -3 (toward zero)
console.log(Math.sign(-5)); // -1 (sign: -1, 0, or 1)
// Power & roots
console.log(Math.pow(2, 10)); // 1024
console.log(Math.sqrt(144)); // 12
console.log(Math.cbrt(27)); // 3
console.log(Math.abs(-5)); // 5
// Min/Max
console.log(Math.max(1, 5, 3)); // 5
console.log(Math.min(1, 5, 3)); // 1
console.log(Math.max(...[1, 5, 3])); // 5 (spread array)
// Trigonometry (radians)
console.log(Math.sin(Math.PI / 2)); // 1
console.log(Math.cos(0)); // 1
console.log(Math.PI / 180 * 90); // radians from degrees
// Random
console.log(Math.random()); // 0 to <1
console.log(Math.floor(Math.random() * 100)); // 0-99 integerNumber 方法与解析
始终为 parseInt() 指定基数——旧浏览器将前导零解释为八进制。Number.isNaN() 可靠;全局 isNaN() 会强制转换(isNaN('abc') 为 true)。toFixed() 返回字符串而非数字。超过 MAX_SAFE_INTEGER 的数字会丢失精度——使用 BigInt。
// Number methods
const num = 1234.5678;
console.log(num.toFixed(2)); // "1234.57" (string)
console.log(num.toPrecision(3)); // "1.23e+3"
console.log(num.toString(2)); // binary string
console.log((255).toString(16)); // "ff" (hex)
// Number object methods
console.log(Number.isInteger(42)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(NaN)); // true (reliable)
console.log(Number.isNaN("NaN")); // false (global isNaN would be true)
console.log(Number.parseInt("42px")); // 42
console.log(Number.parseFloat("3.14")); // 3.14
// Parsing strings
console.log(parseInt("42", 10)); // 42 (always specify radix!)
console.log(parseInt("0xff", 16)); // 255
console.log(parseFloat("3.14abc")); // 3.14
// Safe integers
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.isSafeInteger(2 ** 53)); // false
// NaN checks
const result = Number("abc"); // NaN
console.log(Number.isNaN(result)); // true
console.log(result === NaN); // false! NaN !== NaN数据结构
数组
数组是动态、有序的,可持有混合类型。push/pop 是 O(1);shift/unshift/splice 是 O(n)。find()/findIndex() 接受谓词函数。forEach() 不返回任何东西;用 map() 转换。数组是对象——typeof [] 是 'object'。使用 Array.isArray() 检查。
// Creating arrays
const nums = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null];
const empty = new Array(5); // [empty x 5]
// Access & modify
console.log(nums[0]); // 1
console.log(nums.length); // 5
nums[0] = 0; // modify
console.log(nums.at(-1)); // 5 (negative index, ES2022)
// Add/remove
nums.push(6); // add to end, returns new length
nums.pop(); // remove from end, returns element
nums.unshift(0); // add to start
nums.shift(); // remove from start
nums.splice(1, 2); // remove 2 elements at index 1
nums.splice(1, 0, "a"); // insert at index 1
// Search
console.log(nums.indexOf(3)); // index or -1
console.log(nums.includes(3)); // true/false
console.log(nums.find(n => n > 3)); // first match
console.log(nums.findIndex(n => n > 3)); // index of first match
// Iterate
nums.forEach((val, idx) => console.log(idx, val));
for (const [idx, val] of nums.entries()) {
console.log(idx, val);
}数组方法(map、filter、reduce)
map/filter/reduce 是函数式数组编程的三位一体。map 转换,filter 选择,reduce 聚合。它们可链式调用且不修改原数组(reverse/sort 除外)。sort() 默认转换为字符串——数字始终提供比较器。flat() 扁平化嵌套数组。
const nums = [1, 2, 3, 4, 5];
// map - transform each element (returns new array)
const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10]
const withIndex = nums.map((n, i) => `${i}:${n}`);
// filter - keep elements that pass test (returns new array)
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
// reduce - accumulate to single value
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120
const max = nums.reduce((a, b) => Math.max(a, b));
// Chaining
const result = nums
.filter(n => n % 2 === 0) // [2, 4]
.map(n => n * 10) // [20, 40]
.reduce((sum, n) => sum + n, 0); // 60
// Other useful methods
console.log(nums.slice(1, 3)); // [2, 3] (copy portion)
console.log(nums.concat([6, 7])); // [1,2,3,4,5,6,7]
console.log([...nums, 6, 7]); // spread (modern)
console.log(nums.reverse()); // reverse in-place
console.log(nums.sort((a, b) => a - b)); // numeric sort
console.log([3, 1, 2].sort()); // [1, 2, 3] (string sort!)
console.log(nums.flat()); // flatten one level
console.log([1, [2, [3]]].flat(Infinity)); // [1, 2, 3]对象
对象是键值集合(键是字符串或符号)。静态键用点表示法,动态/特殊键用方括号表示法。计算属性名 {[expr]: val} 是 ES6。Object.keys/values/entries 提取数组;Object.fromEntries 反转 entries。for...in 迭代键(包括继承的)。
// Object literal
const user = {
name: "Alice",
age: 30,
"is-admin": false, // keys with special chars need quotes
greet() { // method shorthand
return `Hello, I'm ${this.name}`;
}
};
// Access
console.log(user.name); // dot notation
console.log(user["is-admin"]); // bracket notation (for dynamic/special keys)
// Computed property names (ES6)
const key = "dynamic";
const obj = { [key]: "value", [`id_${1}`]: 100 };
// Add/modify/delete
user.email = "[email protected]"; // add
user.age = 31; // modify
delete user["is-admin"]; // delete
// Check property existence
console.log("name" in user); // true
console.log(user.hasOwnProperty("name")); // true
// Iterate
for (const key in user) {
console.log(key, user[key]);
}
// Object methods
console.log(Object.keys(user)); // ["name", "age", ...]
console.log(Object.values(user)); // ["Alice", 31, ...]
console.log(Object.entries(user)); // [["name","Alice"], ...]
console.log(Object.fromEntries([["a", 1]])); // {a: 1}解构与展开
解构简洁地从对象/数组中提取值。支持重命名(key: newName)、默认值(= value)和剩余(...rest)。展开(...)扩展可迭代对象/对象——非常适合合并和浅拷贝。对象展开覆盖重复键(后者胜出)。函数参数中的解构对于可选配置很强大。
// Object destructuring
const user = { name: "Alice", age: 30, email: "[email protected]" };
const { name, age } = user; // extract by key
const { name: fullName, email = "N/A" } = user; // rename + default
const { ...rest } = user; // rest pattern
// name -> undefined, fullName -> "Alice", rest -> {age, email}
// Array destructuring
const [a, b, c] = [1, 2, 3];
const [first, , third] = [1, 2, 3]; // skip elements
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2,3,4]
// Swap variables
let x = 1, y = 2;
[x, y] = [y, x]; // x=2, y=1
// Nested destructuring
const { data: { users } } = response;
const [[a, b], [c, d]] = [[1, 2], [3, 4]];
// Function parameters
function greet({ name, greeting = "Hello" }) {
return `${greeting}, ${name}`;
}
greet({ name: "Alice" }); // "Hello, Alice"
// Spread operator
const arr1 = [1, 2], arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
const obj1 = { a: 1 }, obj2 = { b: 2 };
const combined = { ...obj1, ...obj2 }; // {a:1, b:2}
const clone = { ...user }; // shallow copyMap、Set、WeakMap
Map 允许任何键类型(对象,不仅是字符串)并保持插入顺序——不同于普通对象。Set 存储唯一值——非常适合去重。WeakMap/WeakSet 的键是弱引用(可被垃圾回收),防止内存泄漏。需要非字符串键或频繁添加/删除时使用 Map。
// Map - key-value pairs, any key type, maintains insertion order
const map = new Map();
map.set("name", "Alice");
map.set(42, "number key");
map.set({ obj: true }, "object key");
console.log(map.get("name")); // "Alice"
console.log(map.has("name")); // true
console.log(map.size); // 3
map.delete(42);
map.clear();
// Iterate Map
for (const [key, value] of map) {
console.log(key, value);
}
// Set - unique values
const set = new Set([1, 2, 3, 2, 1]);
console.log(set.size); // 3 (duplicates removed)
set.add(4);
set.has(2); // true
set.delete(1);
// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2,3}
const difference = new Set([...a].filter(x => !b.has(x))); // {1}
// WeakMap/WeakSet - keys must be objects, GC-friendly
const weakMap = new WeakMap();
weakMap.set({}, "value"); // key can be garbage collected控制流
If / Else 与三元运算符
复杂逻辑用 if/else;简单值选择用三元运算符。&& 和 || 短路(对默认值/条件很有用)。??(空值合并)仅检查 null/undefined,而 || 检查所有假值。?.(可选链)安全访问嵌套属性而不报错。
const score = 85;
// if / else if / else
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B");
} else {
console.log("C");
}
// Ternary operator (expression)
const grade = score >= 60 ? "pass" : "fail";
// Nested ternary (avoid - hard to read)
const status = score >= 90 ? "excellent"
: score >= 80 ? "good"
: score >= 60 ? "pass"
: "fail";
// Short-circuit evaluation
const name = user?.name || "Anonymous"; // default value
const value = condition && doSomething(); // execute if true
// Nullish coalescing (??) - only null/undefined, not falsy
const count = 0 ?? 10; // 0 (not 10, because 0 is not null/undefined)
const name2 = null ?? "default"; // "default"
// Optional chaining (?.)
const city = user?.address?.city; // undefined if any link is null
const length = user?.name?.length; // undefined if user or name is nullSwitch 语句
switch 使用严格相等(===)比较,所以类型很重要。不要忘记 break——否则执行会落入下一个 case。通过堆叠分组 case(case 6: case 7:)。对于离散值,switch 比长 if/else 链更清晰。现代代码有时偏好对象查找表。
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 中获取索引+值。
// 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() 时恢复。生成器是惰性的(按需计算)且可以是无限的。将它们用于自定义可迭代对象、序列和异步流。
// Iterable protocol (Symbol.iterator)
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
for (const num of range) console.log(num); // 1, 2, 3, 4, 5
console.log([...range]); // [1, 2, 3, 4, 5]
// Generator function (function*)
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next().value); // 3
// Generator with yield*
function* nested() {
yield 1;
yield* [2, 3, 4]; // delegate to another iterable
yield 5;
}函数
函数声明与表达式
函数声明被提升(可在定义前调用);表达式则不会。箭头函数简洁且没有自己的 'this'(它们从封闭作用域继承)。IIFE 创建私有作用域(有了模块后不太需要)。函数是一等公民——作为参数传递、返回、存储在变量中。
// Function declaration (hoisted - can be called before definition)
greet("Alice"); // works (hoisted)
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression (not hoisted)
const greet2 = function(name) {
return `Hi, ${name}!`;
};
// Named function expression (for recursion/stack traces)
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1);
};
// Arrow function (ES6) - concise, no own this
const add = (a, b) => a + b;
const square = x => x * x;
const greet3 = () => "Hello!";
const log = x => { console.log(x); }; // block body needs return
// IIFE (Immediately Invoked Function Expression)
const result = (function() {
const private = "secret";
return private.toUpperCase();
})();
// Functions are first-class (can be passed/returned)
function apply(fn, value) {
return fn(value);
}
console.log(apply(square, 5)); // 25箭头函数与 this
箭头函数没有自己的 'this'、'arguments'、'super' 或 'new.target'——它们从封闭作用域继承。这使它们非常适合回调(尤其是类方法中)。但它们不能用作构造函数或需要自己 'this' 的方法。对象方法使用常规函数。
// Arrow function variations
const add = (a, b) => a + b; // implicit return
const greet = name => `Hi ${name}`; // single param, no parens
const log = () => console.log("hi"); // no params
const obj = (x, y) => ({ x, y }); // return object needs parens
const multi = (a, b) => { // block body
const sum = a + b;
return sum * 2;
};
// Arrow functions don't have their own 'this'
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 'this' refers to Timer instance
console.log(this.seconds);
}, 1000);
}
// Regular function has its own 'this'
function Counter() {
this.count = 0;
// Regular function: 'this' is undefined (strict) or global
document.addEventListener("click", function() {
// this.count++; // ERROR: this is not Counter
});
// Arrow function: 'this' is Counter
document.addEventListener("click", () => {
this.count++; // works!
});
}
// Arrow functions can't be constructors
// const obj = new arrowFunc(); // TypeError闭包
闭包是“记住”其定义作用域变量的函数,即使该作用域已退出。它们实现数据隐私(模块模式)、记忆化、柯里化和部分应用。JavaScript 中的每个函数都是闭包。内部函数保持对外部变量的引用,而非副本。
// A closure is a function that remembers its outer variables
function makeCounter() {
let count = 0; // private variable
return function() {
return ++count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is private - can't access directly
// Module pattern (pre-ES6)
const calculator = (function() {
const result = 0; // private
return {
add(x) { return result + x; },
multiply(x) { return result * x; }
};
})();
// Practical: memoization
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (!(key in cache)) {
cache[key] = fn.apply(this, args);
}
return cache[key];
};
}
const slowFib = memoize(n =>
n < 2 ? n : slowFib(n - 1) + slowFib(n - 2)
);
// Currying with closures
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // 10参数与 Rest/Spread
默认参数提供回退值。剩余参数(...name)将额外参数收集到真正的数组中——优先于旧版 'arguments' 对象。展开(...)将数组扩展为单个参数。参数中的解构启用命名的、可选的配置对象——常见的 API 模式。
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
return `${greeting}, ${name}!`;
}
greet(); // "Hello, Guest!"
greet("Alice"); // "Hello, Alice!"
greet("Bob", "Hi"); // "Hi, Bob!"
// Rest parameters (...args collects into array)
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
function log(tag, ...args) {
console.log(tag, args);
}
// Spread (opposite of rest)
const nums = [1, 2, 3];
console.log(Math.max(...nums)); // 3 (spread array as args)
console.log(sum(...nums)); // 6
// arguments object (old way, avoid in modern code)
function old() {
console.log(arguments); // array-like, not a real array
const args = Array.from(arguments); // convert
}
// Destructuring parameters
function process({ name, age = 0 } = {}) {
console.log(name, age);
}
process({ name: "Alice" }); // Alice 0
process(); // undefined 0 (default empty object)OOP 与类
类与构造函数
ES6 类是原型的语法糖。私有字段(#name)是 ES2022 且真正私有(不同于 _name 约定)。Getter/setter 允许计算属性。静态成员属于类而非实例。类字段(name = value)无需构造函数即可初始化实例属性。
class Person {
// Fields (class fields proposal - ES2022)
species = "human"; // instance field
// Private fields (ES2022)
#ssn = "secret"; // truly private
// Static field/method
static count = 0;
// Constructor
constructor(name, age) {
this.name = name;
this.age = age;
Person.count++;
}
// Instance method
greet() {
return `Hello, I'm ${this.name}`;
}
// Getter
get info() {
return `${this.name}, ${this.age}`;
}
// Setter
set age(value) {
if (value < 0) throw new Error("Invalid age");
this._age = value;
}
get age() {
return this._age;
}
// Static method
static create(name) {
return new Person(name, 0);
}
}
const alice = new Person("Alice", 30);
console.log(alice.greet()); // Hello, I'm Alice
console.log(alice.info); // Alice, 30
console.log(Person.count); // 1
// alice.#ssn; // SyntaxError - private!继承与多态
extends 创建继承;super() 调用父构造函数(在使用 'this' 前必须调用)。通过重新定义方法来覆盖。JavaScript 是单继承,但混入(类工厂)提供组合。instanceof 检查原型链。多态通过方法覆盖实现。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
// Static method can be inherited
static create(type, name) {
return new type(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // must call super() first
this.breed = breed;
}
speak() {
return `${this.name} barks`; // override
}
fetch() {
return `${this.name} fetches`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} meows`;
}
}
// Polymorphism
const animals = [new Dog("Rex", "Lab"), new Cat("Whiskers")];
animals.forEach(a => console.log(a.speak()));
// Rex barks
// Whiskers meows
// instanceof checks
const dog = new Dog("Buddy", "Poodle");
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor.name); // "Dog"
// Mixins (multiple inheritance alternative)
const Walker = (Base) => class extends Base {
walk() { return `${this.name} walks`; }
};
class Robot extends Walker(Animal) {}原型
JavaScript 使用原型继承——对象通过原型链从其他对象继承。__proto__ 已弃用;使用 Object.getPrototypeOf/setPrototypeOf。类是此系统的语法糖。修改内置原型(Array.prototype)很危险——可能破坏代码。优先使用组合而非深度继承。
// Every object has a prototype (chain of inheritance)
const obj = {};
console.log(obj.__proto__); // Object.prototype
console.log(Object.getPrototypeOf(obj)); // preferred
// Constructor function (pre-class syntax)
function OldPerson(name) {
this.name = name;
}
OldPerson.prototype.greet = function() {
return `Hi, ${this.name}`;
};
const p = new OldPerson("Alice");
console.log(p.greet()); // Hi, Alice
// Prototype chain
// p -> OldPerson.prototype -> Object.prototype -> null
// Adding to prototype (affects all instances)
Array.prototype.last = function() {
return this[this.length - 1];
};
console.log([1, 2, 3].last()); // 3
// Object.create (prototypal inheritance)
const animal = { type: "unknown" };
const dog = Object.create(animal);
dog.type = "dog";
console.log(dog.type); // "dog" (own property)
// Check own vs inherited
console.log(dog.hasOwnProperty("type")); // true
console.log("type" in dog); // true (includes inherited)
// Get prototype chain
let proto = Object.getPrototypeOf(dog);
while (proto) {
console.log(proto);
proto = Object.getPrototypeOf(proto);
}错误处理
Try / Catch / Finally
try/catch/finally 处理异常。catch 绑定错误对象(有 .message 和 .stack)。使用 instanceof 以不同方式处理特定错误类型。处理预期错误后始终重新抛出未知错误。通过继承 Error 创建自定义错误以进行应用特定的错误处理。
try {
const result = JSON.parse('{"invalid json"');
} catch (error) {
console.error("Parse error:", error.message);
console.error("Stack:", error.stack);
} finally {
console.log("Always runs");
}
// Catch specific error types
try {
const data = JSON.parse(input);
if (!data.name) throw new TypeError("name is required");
} catch (error) {
if (error instanceof SyntaxError) {
console.log("JSON syntax error");
} else if (error instanceof TypeError) {
console.log("Type error:", error.message);
} else {
throw error; // re-throw unknown errors
}
}
// Error properties
const err = new Error("Something went wrong");
err.code = "CUSTOM_ERROR";
err.statusCode = 500;
throw err;
// Built-in error types
// Error, TypeError, RangeError, ReferenceError
// SyntaxError, URIError, EvalError自定义错误
继承 Error 创建带额外上下文(字段、代码)的自定义错误类型。始终将 this.name 设置为与类名匹配。使用 instanceof 捕获特定错误类型。错误链(ES2022 { cause })保留原始错误以便调试。良好的错误层次结构使错误处理精确。
// Custom error class
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = "DatabaseError";
this.query = query;
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError("email", "Email is required");
}
if (!user.email.includes("@")) {
throw new ValidationError("email", "Invalid email format");
}
}
// Handling custom errors
try {
validateUser({ name: "Alice" });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed: ${error.field} - ${error.message}`);
} else if (error instanceof DatabaseError) {
console.log(`DB error in query: ${error.query}`);
} else {
console.log("Unexpected error:", error);
}
}
// Error chaining (ES2022)
try {
throw new Error("Original cause");
} catch (cause) {
throw new Error("Failed to process", { cause });
}异步与 Promise
回调
回调是稍后被调用的函数。错误优先约定(err, data)是 Node.js 的标准。嵌套回调创建“回调地狱”——深度嵌套、难以阅读的代码。Promise 和 async/await 解决了这个问题。setTimeout/setInterval 是常见的基于回调的 API。
// Callback pattern (old way)
function fetchData(url, callback) {
setTimeout(() => {
callback(null, { data: "result" });
}, 1000);
}
// Callback with error-first convention (Node.js style)
fetchData("/api", (error, data) => {
if (error) {
console.error(error);
return;
}
console.log(data);
});
// Callback hell (pyramid of doom)
fetchUser(userId, (err, user) => {
if (err) return handleError(err);
fetchPosts(user.id, (err, posts) => {
if (err) return handleError(err);
fetchComments(posts[0].id, (err, comments) => {
if (err) return handleError(err);
// deeply nested...
});
});
});
// Event listeners are callbacks
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
});
// setTimeout / setInterval
setTimeout(() => console.log("After 1s"), 1000);
const id = setInterval(() => console.log("tick"), 1000);
clearInterval(id); // stopPromise
Promise 表示未来值,有三种状态:pending(待定)、fulfilled(已实现)、rejected(已拒绝)。.then() 处理成功,.catch() 处理错误,.finally() 始终运行。Promise.all() 等待全部(快速失败);allSettled() 等待全部(永不失败);race() 返回第一个 settled;any() 返回第一个成功的。
// Creating a promise
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Data received");
} else {
reject(new Error("Failed"));
}
});
// Consuming a promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("Done"));
// Chaining
fetch("/api/users")
.then(response => response.json())
.then(users => users.filter(u => u.active))
.then(active => console.log(active))
.catch(err => console.error("Error:", err));
// Promise.all - wait for all (fails if any fails)
Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]).then(([users, posts]) => {
console.log(users, posts);
});
// Promise.allSettled - wait for all (never fails)
Promise.allSettled([p1, p2]).then(results => {
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.log(r.reason);
});
});
// Promise.race - first to settle wins
Promise.race([p1, p2]).then(first => console.log(first));
// Promise.any - first to succeed
Promise.any([p1, p2]).then(first => console.log(first));Async / Await
async/await 是 Promise 的语法糖——使异步代码看起来同步。'await' 暂停函数直到 Promise settled。始终将 await 包在 try/catch 中进行错误处理。并行操作使用 Promise.all()(比循环中顺序 await 更快)。顶层 await 在 ES 模块中工作。
// async function always returns a Promise
async function fetchData() {
// await pauses until the promise resolves
const response = await fetch("/api/users");
const data = await response.json();
return data;
}
// Error handling with try/catch
async function getUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return await res.json();
} catch (error) {
console.error("Failed:", error);
return null;
}
}
// Sequential vs parallel
async function sequential() {
const a = await fetch("/api/a"); // waits for a
const b = await fetch("/api/b"); // then waits for b
return [a, b];
}
async function parallel() {
const [a, b] = await Promise.all([ // both at once
fetch("/api/a"),
fetch("/api/b")
]);
return [a, b];
}
// Top-level await (ES2022, in modules)
// const data = await fetch("/api").then(r => r.json());
// Iterating with async
async function processUrls(urls) {
for (const url of urls) {
const data = await fetch(url);
console.log(data);
}
}
// Promise.all with map (parallel)
async function fetchAll(urls) {
return Promise.all(urls.map(url => fetch(url)));
}DOM 操作
选择与修改元素
querySelector/querySelectorAll(CSS 选择器)是选择元素的现代方式。textContent 比 innerHTML 更安全(防止 XSS)。classList 提供 add/remove/toggle/contains 用于类。dataset 访问 data-* 属性。设置 innerHTML 前始终清理用户输入。
// Selecting elements
const el = document.querySelector("#app"); // first match
const all = document.querySelectorAll(".item"); // all matches (NodeList)
const byId = document.getElementById("app");
const byClass = document.getElementsByClassName("item"); // HTMLCollection
// Modifying content
el.textContent = "Hello"; // text only (safe from XSS)
el.innerHTML = "<b>Bold</b>"; // HTML (XSS risk with user input!)
el.innerText = "Visible text"; // respects CSS visibility
// Attributes
el.setAttribute("data-id", "123");
const id = el.getAttribute("data-id");
el.removeAttribute("disabled");
el.dataset.id; // access data-* attributes
el.id = "new-id";
el.className = "active highlighted";
el.classList.add("active");
el.classList.remove("old");
el.classList.toggle("hidden");
el.classList.contains("active");
// Styles
el.style.color = "red";
el.style.backgroundColor = "blue"; // camelCase
el.style.cssText = "color: red; font-size: 16px;";
// Creating elements
const div = document.createElement("div");
div.textContent = "New element";
div.classList.add("box");
document.body.appendChild(div);
document.body.prepend(div); // add to beginning
document.body.insertBefore(div, referenceEl);事件
addEventListener 优于 on<event> 属性(允许多个监听器)。事件委托(在父级监听)对动态添加的元素高效。e.target 是被点击的元素;e.currentTarget 是带监听器的元素。preventDefault() 阻止默认行为;stopPropagation() 阻止冒泡。
// Add event listener
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
console.log("Current target:", event.currentTarget);
});
// Common events
// click, dblclick, mousedown, mouseup, mousemove
// keydown, keyup, keypress
// submit, change, input, focus, blur
// load, DOMContentLoaded, resize, scroll
// Event object properties
input.addEventListener("keydown", (e) => {
console.log(e.key); // "Enter", "a", etc.
console.log(e.code); // "KeyA", "Enter"
console.log(e.ctrlKey); // true if Ctrl held
e.preventDefault(); // stop default behavior
e.stopPropagation(); // stop bubbling
});
// Event delegation (efficient for many elements)
document.addEventListener("click", (e) => {
if (e.target.matches(".delete-btn")) {
const id = e.target.dataset.id;
deleteItem(id);
}
});
// Custom events
const customEvent = new CustomEvent("userLogin", {
detail: { userId: 123 }
});
element.dispatchEvent(customEvent);
element.addEventListener("userLogin", (e) => {
console.log("User logged in:", e.detail.userId);
});
// Remove listener (must be same function reference)
const handler = () => console.log("click");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);模块与 JSON
ES 模块
ES 模块(import/export)是现代标准,在浏览器和 Node.js 中受支持。默认导出(每个模块一个)vs 命名导出(多个)。动态 import() 启用懒加载。模块始终处于严格模式且有自己作用域。在 HTML script 标签中使用 type='module'。
// math.js - exporting
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }
// Default export (one per module)
export default function greet(name) {
return `Hello, ${name}`;
}
// main.js - importing
import greet from "./math.js"; // default
import { add, PI } from "./math.js"; // named
import * as math from "./math.js"; // namespace
import { add as plus } from "./math.js"; // rename
import greet, { add } from "./math.js"; // default + named
// Dynamic import (returns Promise)
const module = await import("./math.js");
console.log(module.add(2, 3));
// Conditional/dynamic loading
if (featureEnabled) {
const { default: Feature } = await import("./feature.js");
new Feature();
}
// Re-export
export { add } from "./math.js";
export * from "./utils.js";
// In HTML
// <script type="module" src="app.js"></script>
// Modules are deferred and in strict mode by defaultJSON
JSON.stringify() 将 JS 值转换为 JSON 字符串;JSON.parse() 反转。使用 replacers/revivers 在转换期间过滤或转换。JSON 不支持函数、undefined、Date(它们变为字符串)或循环引用。fetch().json() 自动解析 JSON 响应。始终将 JSON.parse 包在 try/catch 中处理不受信任的输入。
// JSON is the standard data interchange format
// JSON supports: string, number, boolean, null, array, object
// JavaScript object to JSON string
const user = { name: "Alice", age: 30, active: true };
const jsonStr = JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'
// Pretty print
const pretty = JSON.stringify(user, null, 2);
// {
// "name": "Alice",
// "age": 30,
// "active": true
// }
// JSON string to JavaScript object
const parsed = JSON.parse('{"name":"Bob","age":25}');
console.log(parsed.name); // "Bob"
// Replacer function (filter/transform during stringify)
const filtered = JSON.stringify(user, (key, value) => {
if (key === "age") return undefined; // exclude
return value;
});
// Reviver function (transform during parse)
const data = JSON.parse(jsonStr, (key, value) => {
if (key === "date") return new Date(value);
return value;
});
// Fetch JSON from API
async function getUsers() {
const res = await fetch("/api/users");
return res.json(); // parses JSON automatically
}
// JSON limitations
// - No functions, undefined, or dates
// - No circular references (throws error)
// - Keys must be strings (with quotes)日期与时间
JavaScript Date 以笨拙著称——月份从 0 开始(一月 = 0),日期从 1 开始。Date 对象可变。toISOString() 给出 UTC;toLocaleString() 给出本地时间。对于严肃的日期工作,使用 date-fns 或 dayjs 等库。Intl.DateTimeFormat 提供区域感知格式化。
// Create dates
const now = new Date();
const specific = new Date("2024-01-15T10:30:00");
const fromMs = new Date(1705315200000);
const fromParts = new Date(2024, 0, 15, 10, 30); // month is 0-indexed!
// Get components
console.log(now.getFullYear()); // 2024
console.log(now.getMonth()); // 0-11 (January = 0!)
console.log(now.getDate()); // 1-31
console.log(now.getDay()); // 0-6 (Sunday = 0)
console.log(now.getHours()); // 0-23
console.log(now.getTime()); // milliseconds since epoch
// Set components
now.setFullYear(2025);
now.setMonth(11); // December
// Formatting
console.log(now.toISOString()); // "2024-01-15T10:30:00.000Z"
console.log(now.toLocaleDateString()); // "1/15/2024" (locale)
console.log(now.toLocaleString()); // "1/15/2024, 10:30:00 AM"
// Timestamps
const start = Date.now(); // milliseconds since epoch
// ... do work ...
console.log(`Took ${Date.now() - start}ms`);
// Intl for formatting (modern)
const formatter = new Intl.DateTimeFormat("zh-CN", {
year: "numeric", month: "long", day: "numeric"
});
console.log(formatter.format(now)); // "2024年1月15日"ES6+ 特性
let、const 与块作用域
默认优先 const,需要重新赋值时用 let,完全避免 var。const 阻止重新赋值但对象/数组仍可变。let 和 const 是块作用域,在声明前处于暂时性死区(不像 var 被提升为 undefined)。这防止了许多微妙的 bug。
// var is function-scoped (hoisted, leaks out of blocks)
// let and const are block-scoped (stay inside {})
{
var x = 1; // accessible outside the block
let y = 2; // block-scoped
const z = 3; // block-scoped, cannot reassign
}
console.log(x); // 1
// console.log(y); // ReferenceError
// const prevents reassignment, NOT mutation
const arr = [1, 2, 3];
arr.push(4); // OK — mutating the array
// arr = [5]; // TypeError — reassigning const
// Temporal Dead Zone: let/const can't be used before declaration
// console.log(a); // ReferenceError (not undefined like var)
let a = 10;箭头函数与 this
箭头函数简洁且从封闭作用域继承 'this'——非常适合回调和需要外部 'this' 的方法。但它们不能用作构造函数且没有 'arguments' 对象。如果需要 'this' 指向对象,不要对对象方法使用箭头函数(改用常规方法)。
// Arrow functions: concise syntax, lexically bound 'this'
const add = (a, b) => a + b;
const square = x => x * x; // single param, no parens
const greet = name => `Hello, ${name}`; // template literal
const noop = () => {}; // no params, empty body
// Returning an object literal needs parens
const makeUser = (name, age) => ({ name, age });
// Arrow functions DON'T have their own 'this' — they inherit it
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 'this' is the Counter instance (lexical)
console.log(this.count);
}, 1000);
}
// Regular function would lose 'this' (it'd be window/undefined)解构赋值
解构在一行中将对象/数组的值提取到变量中——比手动属性访问更清晰。对象解构使用 { key },数组解构使用 [index]。支持重命名(key: alias)、默认值(key = default)、剩余(...rest)和嵌套模式。在 React props 和函数参数中大量使用。
// Object destructuring
const user = { name: "Alice", age: 30, city: "NYC" };
const { name, age } = user;
console.log(name, age); // Alice 30
// Rename and default values
const { name: fullName, country = "USA" } = user;
console.log(fullName, country); // Alice USA
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest); // 1 2 [3,4,5]
// Swap variables (no temp needed!)
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
// Destructuring in function parameters
function greet({ name, greeting = "Hello" }) {
console.log(`${greeting}, ${name}`);
}
greet({ name: "Bob" }); // Hello, Bob
// Nested destructuring
const { data: { results } } = response;展开与剩余运算符
... 运算符在扩展时(数组/对象/调用中)是“展开”,在收集时(参数/解构中)是“剩余”。展开创建浅拷贝并合并对象(后面的键覆盖前面的)。剩余参数替代旧的 'arguments' 对象且是真正的数组。两者都是现代 JS 的重要惯用法。
// Spread (...) expands iterables into individual elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5] (copy + append)
const merged = [...arr1, ...arr2];
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 } (shallow copy)
const updated = { ...obj1, b: 99 }; // override b: { a: 1, b: 99 }
// Rest (...) collects multiple elements into an array
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
// Spread in function calls
Math.max(...[1, 5, 3]); // 5 (same as Math.max(1, 5, 3))模板字面量与标记模板
模板字面量(反引号)支持多行字符串和 ${} 插值——比字符串拼接清晰得多。标记模板让函数处理字面量部分和插值,启用自定义格式化、清理(例如转义 HTML)或 i18n。在 styled-components 和 graphql-tag 中流行。
// Template literals: backticks, multi-line, interpolation
const name = "Alice";
const msg = `Hello ${name},
this spans
multiple lines`;
// Expressions inside ${}
const price = 19.99;
const tax = 0.08;
console.log(`Total: ${(price * (1 + tax)).toFixed(2)}`); // Total: 21.59
// Nested template literals
const items = ["apple", "banana"];
const html = `
<ul>
${items.map(i => `<li>${i}</li>`).join("")}
</ul>
`;
// Tagged templates: function processes the literal
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `**${values[i]}**` : ""), "");
}
const result = highlight`Name: ${name}, Age: ${30}`;
// "Name: **Alice**, Age: **30**"可选链与空值合并
可选链(?.)在链的任何部分为 null/undefined 时短路为 undefined——消除冗长的手动检查。空值合并(??)仅对 null/undefined 提供默认值,不像 || 还会覆盖 0、'' 和 false。两者共同安全处理最常见的“缺失数据”模式。自 ES2020 起可用。
// Optional chaining (?.): safely access nested properties
const user = { profile: { name: "Alice" } };
console.log(user?.profile?.name); // "Alice"
console.log(user?.settings?.theme); // undefined (no error!)
console.log(user?.profile?.address?.city); // undefined
// Without ?., you'd write:
// user && user.profile && user.profile.name
// Optional method calls
const result = obj?.method?.();
const firstChar = str?.[0];
// Nullish coalescing (??): default only for null/undefined
const theme = user.settings?.theme ?? "light"; // "light"
const count = 0 ?? 10; // 0 (NOT 10 — ?? keeps falsy but valid values)
const name = "" ?? "Anonymous"; // "" (empty string is kept!)
// vs || which treats all falsy values (0, "", false) as missing
const count2 = 0 || 10; // 10 (probably not what you want!)事件与事件处理
addEventListener 基础
addEventListener 是绑定事件的现代方式——它允许多个处理程序、支持事件委托,并提供 once、passive 和 capture 等选项。如果稍后需要移除,始终保留处理程序的引用(匿名函数无法移除)。事件对象携带 target、currentTarget、preventDefault() 和 stopPropagation()。
// Modern event binding (preferred over onclick=)
const button = document.querySelector("#myBtn");
button.addEventListener("click", function(event) {
console.log("Clicked!", event.target);
event.preventDefault(); // stop default action (e.g., form submit)
});
// Multiple listeners can be attached
button.addEventListener("click", handler1);
button.addEventListener("click", handler2);
// Remove a specific listener (must be same reference)
button.removeEventListener("click", handler1);
// Common event types
// "click", "dblclick", "mousedown", "mouseup", "mousemove"
// "keydown", "keyup", "keypress"
// "submit", "change", "input", "focus", "blur"
// "load", "DOMContentLoaded", "resize", "scroll"
// Once option: auto-remove after first trigger
button.addEventListener("click", handler, { once: true });事件委托
事件委托在父级附加一个监听器,通过事件冒泡处理所有子级的事件。使用 event.target.matches(selector) 过滤。这比为每个子级绑定高效得多,且自动处理动态添加的元素。权衡:父级必须是始终存在的共同祖先。
// Delegate events to a parent instead of each child
// Efficient for dynamically added elements
const list = document.querySelector("#item-list");
list.addEventListener("click", (event) => {
// event.target is the actual element clicked
if (event.target.matches("li.item")) {
console.log("Clicked:", event.target.textContent);
event.target.classList.toggle("selected");
}
});
// Now dynamically added items automatically work
list.insertAdjacentHTML("beforeend", "<li class='item'>New Item</li>");
// Benefits:
// 1. One listener instead of many (memory efficient)
// 2. Works for elements added after binding
// 3. No need to re-bind when DOM changes事件传播(冒泡与捕获)
事件在三个阶段传播:捕获(自顶向下)、目标和冒泡(自底向上,默认)。大多数处理程序在冒泡阶段运行。stopPropagation() 阻止事件到达父元素;stopImmediatePropagation() 还阻止同一元素上的其他处理程序。对于必须在子处理程序之前运行的处理程序,使用捕获(第三个参数 true)。
<div id="outer">
<div id="inner">
<button id="btn">Click</button>
</div>
</div>
// Events flow in three phases:
// 1. Capturing: top -> target (window -> document -> ... -> target)
// 2. Target: at the target element
// 3. Bubbling: target -> top (default phase most handlers run in)
// Bubbling (default): child fires first, then parents
btn.addEventListener("click", () => console.log("button"));
inner.addEventListener("click", () => console.log("inner"));
outer.addEventListener("click", () => console.log("outer"));
// Click button logs: button -> inner -> outer
// stopPropagation: prevent bubbling to parents
btn.addEventListener("click", (e) => {
e.stopPropagation();
console.log("only button");
});
// Capturing phase (third arg = true)
outer.addEventListener("click", handler, true); // runs during capture
// stopImmediatePropagation: stop other handlers on SAME element too自定义事件
CustomEvent 让你创建应用特定的事件,载荷数据在 'detail' 属性中。结合 dispatchEvent,这启用了发布/订阅模式来解耦组件——模块无需直接引用即可通信。使用 'namespace:action' 等命名约定避免冲突。这是许多自定义元素框架的基础。
// Create and dispatch custom events
const event = new CustomEvent("userLoggedIn", {
detail: { userId: 42, name: "Alice" },
bubbles: true, // allow bubbling
});
document.dispatchEvent(event);
// Listen for the custom event
document.addEventListener("userLoggedIn", (e) => {
console.log("User logged in:", e.detail.userId, e.detail.name);
});
// Practical: decoupled communication between components
class Cart {
constructor() {
this.items = [];
}
add(item) {
this.items.push(item);
window.dispatchEvent(new CustomEvent("cart:updated", {
detail: { count: this.items.length }
}));
}
}
// Any module can listen without knowing about Cart internals
window.addEventListener("cart:updated", (e) => {
updateBadge(e.detail.count);
});键盘与表单事件
键盘事件给出 e.key(逻辑键如 'a'、'Enter')和 e.code(物理键如 'KeyA')。大多数逻辑使用 e.key。表单提交始终需要 preventDefault() 阻止页面重载。FormData + Object.fromEntries 轻松收集表单数据。'input' 持续触发;'change' 在字段失去焦点时触发——根据验证时机选择。
// Keyboard events
document.addEventListener("keydown", (e) => {
console.log(e.key, e.code); // e.key="a", e.code="KeyA"
if (e.key === "Escape") closeModal();
if (e.ctrlKey && e.key === "s") { e.preventDefault(); save(); }
});
// Form events
const form = document.querySelector("#myForm");
form.addEventListener("submit", (e) => {
e.preventDefault(); // stop page reload
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data); // { username: "...", email: "..." }
});
// Input validation on change/blur
const emailInput = document.querySelector("#email");
emailInput.addEventListener("blur", () => {
if (!emailInput.value.includes("@")) {
emailInput.setCustomValidity("Enter a valid email");
} else {
emailInput.setCustomValidity("");
}
});
// Change vs input:
// 'input' fires on every keystroke; 'change' fires on blurFetch API 与 AJAX
基本 fetch(GET)
fetch() 是 XMLHttpRequest 的现代替代品——基于 Promise 且更简洁。关键是,fetch 仅在网络错误时拒绝,而非 HTTP 错误状态(404、500)。解析前始终检查 response.ok(状态 200-299)。使用 async/await 获得可读的顺序代码。response.json() 是异步的,因为它读取主体流。
// fetch() returns a Promise; .json() returns another Promise
fetch("https://api.example.com/users")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json(); // or .text(), .blob(), .arrayBuffer()
})
.then(data => console.log(data))
.catch(error => console.error("Fetch failed:", error));
// fetch does NOT reject on HTTP errors (404, 500) — only on network
// failures. You MUST check response.ok manually.
// Same with async/await (preferred)
async function getUsers() {
try {
const res = await fetch("https://api.example.com/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error(err);
}
}POST、PUT、DELETE 与 fetch
fetch 选项对象配置 method、headers 和 body。对于 JSON,设置 Content-Type: application/json 并 JSON.stringify 主体。对于文件上传,使用 FormData(不要手动设置 Content-Type——浏览器添加 multipart 边界)。PUT 完全替换资源;PATCH 部分更新。
// POST: create a resource
async function createUser(data) {
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return res.json();
}
createUser({ name: "Alice", email: "[email protected]" });
// PUT: update a resource (full replace)
await fetch("/api/users/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice Updated" }),
});
// DELETE
await fetch("/api/users/1", { method: "DELETE" });
// Sending form data (file uploads)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: formData });
// Don't set Content-Type for FormData — browser sets it with boundary请求头与认证
请求头携带元数据和认证令牌。Bearer 令牌(JWT)放在 Authorization 头中。某些头是“禁止”的(浏览器控制)如 Host 和 Cookie。CORS 由浏览器而非服务器强制执行——你无法从客户端 JS 绕过它;服务器必须发送 Access-Control-Allow-Origin。非简单请求会发生预检 OPTIONS 请求。
// Custom headers (e.g., Bearer token auth)
async function fetchWithAuth(url, options = {}) {
const token = localStorage.getItem("authToken");
const res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"Accept": "application/json",
...options.headers, // allow overrides
},
});
return res;
}
// Common headers:
// Content-Type: application/json | application/x-www-form-urlencoded
// Authorization: Bearer <token> | Basic <base64>
// Accept: application/json
// X-Requested-With: XMLHttpRequest (anti-CSRF)
// CORS: browser blocks cross-origin requests unless the server
// returns Access-Control-Allow-Origin. fetch can't bypass CORS.AbortController(取消请求)
AbortController 取消 fetch 请求——对于边输入边搜索、导航离开或超时至关重要。将 signal 传给 fetch;调用 controller.abort() 触发 AbortError。没有它,过期请求可能乱序更新 UI。AbortController 也适用于其他异步 API,是现代 JS 的标准取消机制。
// AbortController lets you cancel in-flight fetch requests
const controller = new AbortController();
async function fetchWithTimeout(url, ms = 5000) {
const timeout = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} catch (err) {
if (err.name === "AbortError") {
console.log("Request was aborted (timeout or cancel)");
} else {
throw err;
}
} finally {
clearTimeout(timeout);
}
}
// Cancel manually (e.g., user navigated away)
// controller.abort();
// Practical: cancel on new search input
searchInput.addEventListener("input", (e) => {
controller.abort(); // cancel previous request
fetchResults(e.target.value);
});流式响应
response.body 是 ReadableStream——你可以在数据到达时分块处理,而不是将整个响应缓冲在内存中。这对于大文件、流式日志或实时数据至关重要。文本流使用 TextDecoder。reader.read() 循环持续到 done 为 true。流式处理避免大载荷的内存峰值。
// Read a large response in chunks (streaming)
async function streamJson(url) {
const res = await fetch(url);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Process complete lines
const lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete line
for (const line of lines) {
if (line.trim()) console.log(JSON.parse(line));
}
}
}
// Useful for: large files, NDJSON logs, real-time data feeds
// Processes data as it arrives instead of waiting for the full responseWeb 存储(LocalStorage 与 SessionStorage)
localStorage 与 sessionStorage 基础
localStorage 无限期持久化;sessionStorage 在标签页关闭时清除。两者都只存储字符串——对象使用 JSON.stringify/parse。存储是同步的并阻塞主线程,所以避免存储大数据。在所有现代浏览器中可用,但在隐私浏览模式下可能被禁用。每个源容量约 5-10MB。
// Both store strings only — JSON.stringify objects
// localStorage: persists until cleared (survives browser restart)
// sessionStorage: cleared when the tab closes
// localStorage API
localStorage.setItem("username", "Alice");
const name = localStorage.getItem("username"); // "Alice"
localStorage.removeItem("username");
localStorage.clear(); // remove ALL items
// sessionStorage API (same interface, tab-scoped)
sessionStorage.setItem("tempToken", "abc123");
// Storing objects (must serialize)
const prefs = { theme: "dark", lang: "en" };
localStorage.setItem("prefs", JSON.stringify(prefs));
const loaded = JSON.parse(localStorage.getItem("prefs"));
console.log(loaded.theme); // "dark"
// Check if storage exists (private mode may disable it)
if (typeof Storage !== "undefined") {
// localStorage/sessionStorage available
}带 TTL 与 JSON 的存储助手
Web Storage 没有内置过期——此包装器通过在值旁边存储过期时间戳来添加 TTL(生存时间)。这是缓存应过期的 API 响应或会话数据的标准模式。在生产中始终将存储访问包在 try/catch 中,因为 JSON.parse 在损坏数据上会抛出且可能超出配额。
// 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。
// 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 用于离线优先应用、大型缓存或复杂的客户端数据。
// 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。
// COOKIES: sent with every HTTP request, ~4KB limit
document.cookie = "session=abc123; max-age=3600; path=/; Secure; SameSite=Strict";
console.log(document.cookie); // "session=abc123; theme=dark"
// Set cookie with attributes
document.cookie = "token=xyz; max-age=86400; Secure; HttpOnly; SameSite=Lax";
// Note: HttpOnly can't be set via JS (server-only for security)
// LOCAL STORAGE: ~5-10MB, NOT sent to server, synchronous
localStorage.setItem("theme", "dark");
// SESSION STORAGE: ~5MB, cleared on tab close
sessionStorage.setItem("draft", "work in progress");
// WHEN TO USE WHAT:
// Cookies -> auth tokens that the server needs to read, server-side sessions
// localStorage -> user preferences, cached data that persists
// sessionStorage -> temporary per-tab state (form drafts, wizard steps)
// IndexedDB -> large datasets, offline data, complex queries定时器(setTimeout、setInterval)
setTimeout 与 setInterval
setTimeout 在延迟后运行一次回调;setInterval 重复运行。两者都返回 ID 用于通过 clearTimeout/clearInterval 取消。定时器不精确——它们是受事件循环、标签页可见性(后台标签页被节流)和主线程可用性影响的最小延迟。嵌套定时器中低于 4ms 的延迟可能被钳制为 4ms。
// setTimeout: run once after a delay (milliseconds)
const timeoutId = setTimeout(() => {
console.log("Runs after 2 seconds");
}, 2000);
// Cancel before it fires
clearTimeout(timeoutId);
// setInterval: run repeatedly every interval
const intervalId = setInterval(() => {
console.log("Runs every 1 second");
}, 1000);
// Stop the interval
clearInterval(intervalId);
// Pass arguments to the callback
setTimeout((greeting, name) => {
console.log(`${greeting}, ${name}`);
}, 1000, "Hello", "Alice");
// NOTE: timers don't guarantee exact timing — they run after the
// minimum delay, but only when the call stack is empty (event loop)递归 setTimeout(优于 setInterval)
对于重复的异步任务,递归 setTimeout 优于 setInterval,因为它保证上一次调用在下一次开始前完成——无重叠执行。它还允许动态间隔(例如错误时更长的退避)。如果处理程序花费时间超过间隔,setInterval 可能堆叠调用,导致性能问题。
// setInterval problems: doesn't wait for the previous call to finish
// If a call takes longer than the interval, they can stack up.
// Better: recursive setTimeout — guarantees the gap between completions
function poll() {
fetch("/api/status")
.then(r => r.json())
.then(data => {
console.log(data);
setTimeout(poll, 1000); // schedule next AFTER this finishes
})
.catch(err => {
console.error(err);
setTimeout(poll, 5000); // retry with backoff on error
});
}
poll(); // start the loop
// This pattern: always waits for the previous call to complete
// before scheduling the next, avoiding overlap/stacking.requestAnimationFrame(平滑动画)
requestAnimationFrame(rAF)是做视觉动画的正确方式——它与浏览器重绘周期同步(约 60fps),在标签页隐藏时避免不必要的帧,产生比 setInterval 更平滑的动画。使用时间戳参数进行增量时间计算,以在不同刷新率下保持动画速度一致。完成后始终用 cancelAnimationFrame 取消。
// requestAnimationFrame: syncs with the display refresh (~60fps)
// More efficient than setInterval for visual animations
function animate() {
element.style.left = parseInt(element.style.left || 0) + 2 + "px";
if (parseInt(element.style.left) < 300) {
requestAnimationFrame(animate); // schedule next frame
}
}
requestAnimationFrame(animate);
// Cancel if needed
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);
// Timestamp for delta-time calculations
let lastTime = 0;
function loop(timestamp) {
const delta = timestamp - lastTime;
lastTime = timestamp;
update(delta); // frame-rate independent movement
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// rAF pauses in background tabs (saves CPU/battery)防抖与节流
防抖在调用暂停后执行(例如用户停止输入 300ms 后才搜索)。节流限制每个间隔执行一次(例如最多每 200ms 更新滚动位置)。两者都防止高频事件的性能问题。防抖 = “将快速调用合并为一个”;节流 = “限制调用速率”。
// 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 中最有用的小工具之一。
// A promise-based delay for use with async/await
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function countdown() {
console.log("3...");
await delay(1000);
console.log("2...");
await delay(1000);
console.log("1...");
await delay(1000);
console.log("Go!");
}
countdown();
// Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return await res.json();
throw new Error(`HTTP ${res.status}`);
} catch (err) {
if (i === retries - 1) throw err;
const wait = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Retry ${i + 1} in ${wait}ms`);
await delay(wait);
}
}
}Map、Set 与 WeakMap
Map(任意键类型的键值对)
Map 是合适的键值集合,键可以是任何类型(对象、函数、数字)——不同于将键强制转换为字符串的 Object。Map 保持插入顺序,有 .size 属性,且可直接迭代。需要非字符串键、频繁添加/删除或集合不是记录/DTO 时使用 Map。固定形状数据使用 Object。
// Map: like an Object but keys can be ANY type (not just strings)
const map = new Map();
// Keys can be objects, functions, primitives
const objKey = { id: 1 };
map.set("name", "Alice");
map.set(objKey, "data for this object");
map.set(42, "numeric key");
map.set(true, "boolean key");
console.log(map.get("name")); // "Alice"
console.log(map.get(objKey)); // "data for this object"
console.log(map.size); // 4
console.log(map.has(42)); // true
// Iteration preserves insertion order
for (const [key, value] of map) {
console.log(key, value);
}
// Map vs Object:
// - Map keys can be any type; Object keys are strings/symbols
// - Map has .size; Object needs Object.keys().length
// - Map is iterable by default; Object needs Object.entries()
// - Map performs better for frequent add/removeSet(唯一值)
Set 存储唯一值——非常适合去重和成员测试。将数组转换为 Set 再转回([...new Set(arr)])是去重的惯用方式。Set.has() 是 O(1) 而 Array.includes() 是 O(n),所以对于频繁检查的大型集合使用 Set。Set 没有 map/filter——先展开为数组。
// Set: collection of unique values (no duplicates)
const set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) { 1, 2, 3 }
console.log(set.size); // 3
set.add(4);
set.add(4); // ignored (already exists)
console.log(set.has(3)); // true
set.delete(2);
// Iterate (preserves insertion order)
for (const item of set) {
console.log(item);
}
// Common use cases:
// 1. Remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]
// 2. Check for membership (faster than Array.includes for large sets)
const validIds = new Set([101, 102, 103]);
if (validIds.has(userId)) { /* ... */ }
// 3. Set operations (union, intersection)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersect = new Set([...a].filter(x => b.has(x))); // {2,3}WeakMap 与 WeakSet(内存安全引用)
WeakMap 和 WeakSet 持有对象的弱引用——当没有其他引用时,条目会自动被垃圾回收。这防止了将数据与 DOM 元素或其他对象关联时的内存泄漏。键必须是对象。WeakMap/WeakSet 不可迭代且没有 .size,因为条目在 GC 期间随时可能消失。将它们用于与对象生命周期绑定的缓存/元数据。
// WeakMap: keys must be objects; references are "weak"
// (doesn't prevent garbage collection of the key object)
const weakMap = new WeakMap();
let user = { name: "Alice" };
weakMap.set(user, "metadata");
console.log(weakMap.get(user)); // "metadata"
user = null; // remove the only strong reference
// Now the object can be GC'd, and WeakMap entry disappears automatically
// Practical: attach data to DOM elements without memory leaks
const elementData = new WeakMap();
function cacheData(element, data) {
elementData.set(element, data);
}
// When the DOM element is removed, its data is auto-cleaned
// WeakSet: collection of objects, weakly held
const processed = new WeakSet();
function processOnce(obj) {
if (processed.has(obj)) return; // already done
processed.add(obj);
doWork(obj);
}
// WeakMap/WeakSet are NOT iterable (no .size, no for...of)Map 迭代与转换
Map 通过 for...of(默认 entries)、.keys()、.values() 或 .forEach() 按插入顺序可迭代。使用 Object.entries() 和 Object.fromEntries() 在 Map 和 Object 之间转换。当 API 期望普通对象但你想要 Map 的内部特性时,这种双向转换很方便。记住:转换中 Object 键变为字符串类型。
const map = new Map([
["name", "Alice"],
["age", 30],
["city", "NYC"],
]);
// Iterate entries (default)
for (const [key, value] of map) {
console.log(`${key} = ${value}`);
}
// Iterate keys only
for (const key of map.keys()) {
console.log(key);
}
// Iterate values only
for (const value of map.values()) {
console.log(value);
}
// forEach (callback style)
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Convert to Array
const entries = [...map.entries()]; // [["name","Alice"], ["age",30], ...]
const keys = [...map.keys()]; // ["name", "age", "city"]
const values = [...map.values()]; // ["Alice", 30, "NYC"]
// Convert Object to Map and back
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));
const objFromMap = Object.fromEntries(map);在 Map、Set、Object 与 Array 之间选择
选择合适的集合:数组用于带重复和索引访问的有序列表;对象用于固定形状记录和 JSON;Map 用于带任意键类型的动态键值集合;Set 用于唯一性和快速查找。使用错误的结构会导致冗长代码和性能问题——例如在循环中检查 Array.includes() vs Set.has()。
// Decision guide:
// Use ARRAY when:
// - You need ordered data with duplicates
// - You need index-based access (arr[5])
// - You'll map/filter/reduce frequently
const todoList = ["buy milk", "walk dog"];
// Use OBJECT when:
// - You have a fixed known shape (like a user record)
// - You need JSON serialization (JSON.stringify)
// - Keys are strings and known at write time
const user = { name: "Alice", age: 30 };
// Use MAP when:
// - Keys are not strings (objects, functions, numbers)
// - You frequently add/remove key-value pairs
// - You need to iterate in insertion order
// - The collection size changes dynamically
const handlers = new Map();
handlers.set(buttonElement, () => onClick());
// Use SET when:
// - You need unique values (no duplicates)
// - You need fast membership testing (.has is O(1))
// - Order doesn't matter much
const seen = new Set();
if (!seen.has(url)) { seen.add(url); visit(url); }生成器与迭代器
迭代器与 Symbol.iterator
迭代器有 next() 方法返回 {value, done}。可迭代对象实现 Symbol.iterator,返回迭代器。内置可迭代对象(数组、字符串、Map、Set)适用于 for...of、展开(...)、解构和 Array.from()。实现 Symbol.iterator 让你的自定义对象无缝使用所有这些语言特性。
// An iterator is an object with a next() method returning {value, done}
function makeIterator(arr) {
let i = 0;
return {
next() {
return i < arr.length
? { value: arr[i++], done: false }
: { value: undefined, done: true };
},
};
}
const it = makeIterator(["a", "b", "c"]);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: "c", done: false }
console.log(it.next()); // { value: undefined, done: true }
// An iterable implements Symbol.iterator (returns an iterator)
const range = {
from: 1, to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
},
};
},
};
// Now range works with for...of, spread, etc.
console.log([...range]); // [1, 2, 3]生成器函数(function*)
生成器函数(function*)使用 yield 暂停和恢复执行——它们惰性产生值,一次一个。这启用了无限序列、惰性求值和内存高效管道。生成器既是迭代器又是可迭代对象。每次调用 next() 运行到下一个 yield(或 return)。它们是异步生成器和 JS 协程模式的基础。
// Generators: pause execution with 'yield', resume with next()
function* idGenerator() {
let id = 1;
while (true) {
yield id++; // pause here, return value, resume on next()
}
}
const gen = idGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
// Infinite sequence — only computes values on demand
// Generators are iterables (work with for...of)
function* take(n, iterable) {
for (const item of iterable) {
if (n-- <= 0) return;
yield item;
}
}
function* naturalNumbers() {
let n = 1;
while (true) yield n++;
}
const firstFive = [...take(5, naturalNumbers())];
console.log(firstFive); // [1, 2, 3, 4, 5]yield*(委托给另一个生成器)
yield* 将所有 yield 委托给另一个可迭代对象或生成器——扁平化嵌套结构并干净地组合生成器。它是 Python 'yield from' 的 JS 等价物。被委托生成器的值像外层生成器的一部分一样逐个 yield。这是递归组合生成器的标准方式。
// yield* delegates to another iterable/generator
function* inner() {
yield "a";
yield "b";
}
function* outer() {
yield 1;
yield* inner(); // delegates: yields "a", then "b"
yield* [10, 20]; // also works with arrays
yield 2;
}
console.log([...outer()]); // [1, "a", "b", 10, 20, 2]
// Practical: flatten nested structures
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // recursive delegation
} else {
yield item;
}
}
}
const nested = [1, [2, [3, 4]], 5];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5]通过 next(value) 的生成器 send()
生成器支持双向通信:next(value) 传入值(成为上次 yield 表达式的结果),throw(error) 在 yield 点注入异常。这启用了协程、状态机和为早期 async/await 实现提供动力的基于生成器的异步模式。第一次 next() 调用无法传入值(还没有东西接收它)。
// next(value) passes a value INTO the generator (becomes yield's result)
function* conversation() {
const name = yield "What's your name?"; // receives via next()
const age = yield `Hello ${name}, how old are you?`;
return `${name} is ${age} years old`;
}
const talk = conversation();
console.log(talk.next().value); // "What's your name?"
console.log(talk.next("Alice").value); // "Hello Alice, how old are you?"
console.log(talk.next(30).value); // "Alice is 30 years old"
// throw(error) injects an exception at the yield point
function* safeGen() {
try {
yield "step 1";
} catch (e) {
console.log("Caught:", e.message);
yield "recovered";
}
}
const g = safeGen();
g.next(); // "step 1"
g.throw(new Error("oops")); // "Caught: oops" -> "recovered"异步生成器
异步生成器(async function*)将生成器与 async/await 结合——每个 yield 可以在 await 后产生值。用 for await...of 消费。这非常适合分页 API、流数据或任何异步产生值的场景。它们是 JS 中无需回调或手动 Promise 链处理流数据的现代方式。
// async function* : yields promises, can use await
async function* fetchPages(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.items.length === 0) break; // no more pages
yield data.items;
page++;
}
}
// Consume with for await...of
(async () => {
for await (const items of fetchPages("/api/products")) {
console.log(`Got ${items.length} items`);
renderItems(items);
}
console.log("All pages loaded");
})();
// Practical: stream data as it arrives
async function* streamLines(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) yield line;
}
}Proxy 与 Reflect
Proxy 基础(拦截操作)
Proxy 让你拦截和自定义对象的基本操作——get、set、has、deleteProperty、ownKeys 等。处理程序对象定义“陷阱”(类似 getter/setter 但适用于所有属性)。用例:验证、日志、默认值、访问控制、响应式系统(Vue 3 用 Proxy 实现响应式)。在严格模式下从 set 返回 true 表示成功。
// Proxy wraps an object and intercepts fundamental operations
const target = { name: "Alice", age: 30 };
const handler = {
get(obj, prop) {
console.log(`Reading ${prop}`);
return prop in obj ? obj[prop] : "N/A"; // default value
},
set(obj, prop, value) {
console.log(`Setting ${prop} = ${value}`);
if (prop === "age" && value < 0) return false; // validation
obj[prop] = value;
return true; // indicate success
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // logs "Reading name", returns "Alice"
proxy.age = 25; // logs "Setting age = 25"
// proxy.age = -5; // logs "Setting age = -5", rejected
console.log(proxy.unknown); // logs "Reading unknown", returns "N/A"常见 Proxy 用例
Proxy 启用强大的元编程:负索引、惰性属性计算、只读视图、验证、日志和响应式数据绑定。Vue 3 的响应式系统使用 Proxy 跟踪属性访问并自动触发重新渲染。权衡是小的性能开销,所以在抽象值得的地方使用 Proxy,而非每个对象。
// 1. Negative array indices (like Python)
function negativeArray(arr) {
return new Proxy(arr, {
get(target, prop) {
const idx = Number(prop);
if (Number.isInteger(idx)) {
return target[idx < 0 ? target.length + idx : idx];
}
return target[prop];
},
});
}
const arr = negativeArray(["a", "b", "c"]);
console.log(arr[-1]); // "c"
// 2. Auto-populating / lazy properties
const lazy = new Proxy({}, {
get(target, prop) {
if (!(prop in target)) {
target[prop] = expensiveCompute(prop); // compute on first access
}
return target[prop];
},
});
// 3. Read-only object
const readOnly = new Proxy(data, {
set() { throw new Error("This object is read-only"); },
deleteProperty() { throw new Error("Cannot delete"); },
});Reflect API
Reflect 提供 Proxy 陷阱拦截的相同操作——在陷阱内使用它干净地转发到默认行为。Reflect 方法返回布尔值(成功/失败)而非抛出,使其对条件逻辑更安全。Reflect.ownKeys 返回所有键(字符串和符号),不像 Object.keys 只返回可枚举字符串键。Proxy 和 Reflect 共同构成 JS 的元编程工具包。
// Reflect provides default behavior for proxy traps and
// functional equivalents of object operations
const obj = { x: 1, y: 2 };
// Reflect.get / Reflect.set (instead of obj[prop])
console.log(Reflect.get(obj, "x")); // 1
Reflect.set(obj, "z", 3); // obj.z = 3
console.log(Reflect.has(obj, "x")); // true (like "x" in obj)
Reflect.deleteProperty(obj, "y"); // delete obj.y
console.log(Reflect.ownKeys(obj)); // ["x", "z"]
// In a Proxy, use Reflect to forward to the default behavior
const proxy = new Proxy({}, {
get(target, prop, receiver) {
console.log(`get ${prop}`);
return Reflect.get(target, prop, receiver); // default behavior
},
set(target, prop, value, receiver) {
console.log(`set ${prop}`);
return Reflect.set(target, prop, value, receiver);
},
});
// Reflect.construct: call a constructor with an array of args
const instance = Reflect.construct(Array, [1, 2, 3]);使用 Proxy 的响应式对象(Vue 风格)
这是 Vue 3 响应式背后的核心思想——Proxy 拦截 get(跟踪哪些 effect 依赖属性)和 set(在属性改变时触发这些 effect)。这启用声明式 UI 更新:你修改状态,框架自动重新渲染。Proxy 在 ES6 中使这成为可能;早期框架(Vue 2)使用 Object.defineProperty 但有局限。
// A minimal reactive system using Proxy
function reactive(target) {
const subscribers = new Set();
const handler = {
get(obj, prop) {
track(prop); // record who reads this property
const value = Reflect.get(obj, prop);
return typeof value === "object" && value !== null
? reactive(value) // deeply reactive
: value;
},
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
trigger(prop); // notify subscribers
return result;
},
};
let currentEffect = null;
function track(prop) {
if (currentEffect) subscribers.add(currentEffect);
}
function trigger(prop) {
subscribers.forEach(fn => fn());
}
return new Proxy(target, handler);
}
const state = reactive({ count: 0 });
// When state.count changes, effects re-run automaticallyWeb Workers
创建 Web Worker
Web Workers 在单独的后台线程中运行 JavaScript,实现真正的并行而不阻塞 UI。主线程和 worker 通过 postMessage 通信(数据被复制/结构化克隆,而非共享)。Worker 无法访问 DOM 或 window 对象——它们是隔离的。将 worker 用于 CPU 密集型任务如图像处理、解析大文件或复杂计算。
// main.js — workers run JS in a background thread
const worker = new Worker("worker.js");
// Send data to the worker
worker.postMessage({ command: "calculate", data: [1, 2, 3, 4, 5] });
// Receive results from the worker
worker.onmessage = (event) => {
console.log("Result from worker:", event.data);
};
worker.onerror = (error) => {
console.error("Worker error:", error.message);
};
// Terminate when done (frees resources)
// worker.terminate();
// --- worker.js (separate file) ---
// self.onmessage = (event) => {
// const { command, data } = event.data;
// const result = heavyComputation(data);
// self.postMessage(result);
// };内联 Worker(Blob URL)
内联 worker 通过 Blob URL 从代码字符串创建 Worker——无需单独文件。这对于演示、小工具或构建系统不易处理单独 worker 文件的情况很方便。记得撤销对象 URL 以避免内存泄漏。worker 代码是字符串,所以会失去编辑器语法高亮和类型检查——在生产中谨慎使用。
// 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