<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>JavaScript on 安橙的博客</title><link>https://blog.ans20xx.com/tags/javascript/</link><description>Recent content in JavaScript on 安橙的博客</description><generator>Hugo -- 0.161.1</generator><language>zh</language><lastBuildDate>Fri, 03 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.ans20xx.com/tags/javascript/index.xml" rel="self" type="application/rss+xml"/><item><title>js 复习</title><link>https://blog.ans20xx.com/posts/frontend/js-%E5%A4%8D%E4%B9%A0/</link><pubDate>Fri, 03 Apr 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/frontend/js-%E5%A4%8D%E4%B9%A0/</guid><description>&lt;h1 id="语言核心基础"&gt;语言核心基础&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-57468231"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-57468231" style="display:none;"&gt;
- 语言核心基础
- 变量与数据类型
- 变量声明
- JavaScript 有三种声明变量的关键字，它们的核心区别在于作用域和可变性
- var
- var 是 ES5 时代的声明方式，它的作用域是函数级别的，而不是块级的。
- 这意味着在 if 或 for 的大括号里用 var 声明的变量，在外面依然可以访问
- 另外 var 存在&amp;#34;变量提升&amp;#34;（hoisting）——声明会被提升到函数顶部，但赋值不会，所以在声明前访问会得到 undefined 而不是报错
-
```js
function example() {
console.log(a); // undefined（不会报错，因为声明被提升了）
var a = 10;
if (true) {
var b = 20;
}
console.log(b); // 20（var 没有块级作用域）
}
```
- let
- let 是 ES6 引入的，拥有块级作用域，不会被提升到可访问状态（存在&amp;#34;暂时性死区&amp;#34; TDZ），在声明前访问会直接报 ReferenceError。
-
```js
function example() {
// console.log(a); // ReferenceError!
let a = 10;
if (true) {
let b = 20;
}
// console.log(b); // ReferenceError! b 只存在于 if 块内
}
```
- const
- const 和 let 一样有块级作用域和 TDZ，但它要求声明时必须赋值，且绑定不可重新赋值。
- 注意&amp;#34;不可重新赋值&amp;#34;不等于&amp;#34;不可变&amp;#34;——如果 const 指向一个对象或数组，对象内部的属性是可以修改的。
-
```js
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable
const user = { name: &amp;#39;An&amp;#39; };
user.name = &amp;#39;Bob&amp;#39;; // ✅ 没问题，修改的是对象属性，不是重新赋值
// user = {}; // ❌ TypeError，不能重新绑定
```
- 七种原始类型
- JavaScript 的数据类型分为原始类型和引用类型。
- 原始类型有 7 种，它们的值是不可变的，按值传递。
- string 字符串
-
```js
const s1 = &amp;#39;hello&amp;#39;; // 单引号
const s2 = &amp;#34;world&amp;#34;; // 双引号
const s3 = `你好 ${s1}`; // 模板字符串，支持插值和换行
```
- 字符串是不可变的。s1[0] = &amp;#39;H&amp;#39; 不会报错但也不会生效。所有字符串方法都返回新字符串。
- number 数字
- JavaScript 只有一种数字类型，使用 IEEE 754 双精度浮点数。这带来了经典问题
-
```js
console.log(0.1 &amp;#43; 0.2); // 0.30000000000000004
console.log(0.1 &amp;#43; 0.2 === 0.3); // false
// 特殊值
console.log(Infinity); // 正无穷
console.log(-Infinity); // 负无穷
console.log(NaN); // Not a Number
console.log(NaN === NaN); // false！NaN 不等于自身
console.log(Number.isNaN(NaN)); // true，用这个判断
```
- bigint 大整数
- 当数字超过 Number.MAX_SAFE_INTEGER（2⁵³ - 1）时使用
-
```js
const big = 123456789012345678901234567890n; // 末尾加 n
console.log(typeof big); // &amp;#34;bigint&amp;#34;
// big &amp;#43; 1; // ❌ TypeError，bigint 不能和 number 混合运算
big &amp;#43; 1n; // ✅
```
- boolean 布尔值
- 只有 true 和 false。但在条件判断中，其他类型会被隐式转换为布尔值。
- 记住这些 falsy 值（转换为 false 的值）：
-
```js
// 以下全部是 falsy
false, 0, -0, 0n, &amp;#34;&amp;#34;, null, undefined, NaN
// 其他一切都是 truthy，包括：
// &amp;#34;0&amp;#34;, &amp;#34; &amp;#34;, [], {}, function(){} 都是 truthy！
```
- null 空值
- 表示&amp;#34;有意为空&amp;#34;，通常用于显式地标记一个变量&amp;#34;目前没有值&amp;#34;。
-
```js
let user = null; // 明确表示：当前没有用户
```
- undefined 未定义
- 表示变量已声明但未赋值，或者函数没有返回值
-
```js
let x;
console.log(x); // undefined
function foo() {}
console.log(foo()); // undefined
```
- symbol 符号
- ES6 引入，每个 Symbol 都是唯一的，主要用作对象属性的唯一标识符
-
```js
const id1 = Symbol(&amp;#39;id&amp;#39;);
const id2 = Symbol(&amp;#39;id&amp;#39;);
console.log(id1 === id2); // false，每次调用都创建新的
const obj = {
[id1]: &amp;#39;这个属性名是唯一的&amp;#39;
};
```
- 引用类型（Reference Types）
- 除了原始类型，剩下的都是引用类型（对象）。
- 包括普通对象 {}、数组 []、函数 function、Date、RegExp 等等。
- 引用类型按引用传递，变量存的是内存地址而不是值本身
-
```js
const a = { name: &amp;#39;An&amp;#39; };
const b = a; // b 和 a 指向同一个对象
b.name = &amp;#39;Bob&amp;#39;;
console.log(a.name); // &amp;#34;Bob&amp;#34; —— a 也被改了！
// 数组同理
const arr1 = [1, 2, 3];
const arr2 = arr1;
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4]
```
- typeof 操作符
- typeof 返回一个字符串，表示值的类型
-
```js
typeof 42; // &amp;#34;number&amp;#34;
typeof &amp;#39;hello&amp;#39;; // &amp;#34;string&amp;#34;
typeof true; // &amp;#34;boolean&amp;#34;
typeof undefined; // &amp;#34;undefined&amp;#34;
typeof Symbol(); // &amp;#34;symbol&amp;#34;
typeof 10n; // &amp;#34;bigint&amp;#34;
// ⚠️ 两个著名的&amp;#34;坑&amp;#34;
typeof null; // &amp;#34;object&amp;#34; —— 历史遗留 bug，null 其实是原始类型
typeof function(){};// &amp;#34;function&amp;#34; —— 虽然函数也是对象
typeof []; // &amp;#34;object&amp;#34;
typeof {}; // &amp;#34;object&amp;#34;
```
- 判断数组应该用 Array.isArray()，判断 null 用 === null
- 类型转换
- 显式转换
-
```js
// 转字符串
String(123); // &amp;#34;123&amp;#34;
(123).toString();// &amp;#34;123&amp;#34;
// 转数字
Number(&amp;#34;42&amp;#34;); // 42
Number(&amp;#34;&amp;#34;); // 0
Number(&amp;#34;hello&amp;#34;); // NaN
Number(true); // 1
Number(null); // 0
Number(undefined);// NaN
parseInt(&amp;#34;42px&amp;#34;);// 42 —— 会尽量解析
parseFloat(&amp;#34;3.14abc&amp;#34;); // 3.14
// 转布尔
Boolean(0); // false
Boolean(&amp;#34;&amp;#34;); // false
Boolean(&amp;#34;0&amp;#34;); // true ← 注意！非空字符串都是 true
Boolean([]); // true ← 注意！空数组也是 true
```
- 隐式转换
-
```js
// &amp;#43; 号：只要有一边是字符串，就做字符串拼接
&amp;#39;5&amp;#39; &amp;#43; 3; // &amp;#34;53&amp;#34;
5 &amp;#43; &amp;#39;3&amp;#39;; // &amp;#34;53&amp;#34;
// 其他算术运算符会把字符串转为数字
&amp;#39;5&amp;#39; - 3; // 2
&amp;#39;5&amp;#39; * 2; // 10
&amp;#39;5&amp;#39; / &amp;#39;2&amp;#39;; // 2.5
// == 的隐式转换（不推荐使用）
0 == false; // true
&amp;#34;&amp;#34; == false; // true
null == undefined; // true
null == 0; // false ← 特殊规则
// === 严格相等，不做类型转换（推荐始终使用）
0 === false; // false
&amp;#34;&amp;#34; === false; // false
```
- 运算符与流程控制
- 算术运算符
- 基本的 &amp;#43;、-、*、/ 之外，还有几个需要注意的
```js
// 取余（求模）
console.log(10 % 3); // 1
console.log(-10 % 3); // -1（结果符号跟被除数一致）
// 幂运算（ES2016）
console.log(2 ** 10); // 1024，等价于 Math.pow(2, 10)
// 自增/自减
let a = 5;
console.log(a&amp;#43;&amp;#43;); // 5（先返回再加）
console.log(a); // 6
console.log(&amp;#43;&amp;#43;a); // 7（先加再返回）
```
- 复合赋值运算符就是简写：&amp;#43;=、-=、*=、/=、%=、**=、??=、||=、&amp;&amp;=
```js
let count = 10;
count &amp;#43;= 5; // 等价于 count = count &amp;#43; 5
count **= 2; // 等价于 count = count ** 2
// 逻辑赋值（ES2021）
let name = null;
name ??= &amp;#39;Anonymous&amp;#39;; // 如果 name 是 null/undefined，才赋值
console.log(name); // &amp;#34;Anonymous&amp;#34;
let config = &amp;#39;&amp;#39;;
config ||= &amp;#39;default&amp;#39;; // 如果 config 是 falsy，就赋值
console.log(config); // &amp;#34;default&amp;#34;
```
- 比较运算符
- ==（宽松相等） 会在比较前做隐式类型转换，规则非常复杂：
-
```js
0 == false; // true（false 转为 0）
&amp;#39;&amp;#39; == false; // true（两边都转为 0）
&amp;#39;1&amp;#39; == 1; // true（字符串转为数字）
null == undefined; // true（特殊规则，它俩只跟对方相等）
null == 0; // false（null 不会被转为数字）
NaN == NaN; // false（NaN 不等于任何东西包括自己）
[] == false; // true（[] → &amp;#39;&amp;#39; → 0，false → 0）
[] == ![]; // true（这个最离谱，但确实是 true）
```
- ===（严格相等） 不做类型转换，类型不同直接返回 false：
-
```js
0 === false; // false（number vs boolean）
&amp;#39;&amp;#39; === false; // false
&amp;#39;1&amp;#39; === 1; // false
null === undefined; // false
```
- 实际开发原则：永远使用 === 和 !==。
- 唯一可能用 == 的场景是 x == null 来同时判断 null 和 undefined
- 更推荐写成 x === null || x === undefined，或者用 x ?? defaultValue
- 逻辑运算符与短路求值
- 逻辑运算符在 JS 中的行为和很多语言不同——它们返回的不是布尔值，而是操作数本身。
- &amp;&amp;（逻辑与）：如果左侧是 falsy，返回左侧；否则返回右侧。
-
```js
console.log(0 &amp;&amp; &amp;#39;hello&amp;#39;); // 0（左侧 falsy，直接返回左侧）
console.log(1 &amp;&amp; &amp;#39;hello&amp;#39;); // &amp;#34;hello&amp;#34;（左侧 truthy，返回右侧）
console.log(&amp;#39;a&amp;#39; &amp;&amp; &amp;#39;b&amp;#39; &amp;&amp; 0); // 0（逐个检查，遇到第一个 falsy 就返回）
console.log(&amp;#39;a&amp;#39; &amp;&amp; &amp;#39;b&amp;#39; &amp;&amp; &amp;#39;c&amp;#39;); // &amp;#34;c&amp;#34;（全 truthy，返回最后一个）
// 实际用法：条件执行
user &amp;&amp; user.login(); // 如果 user 存在才调用 login
```
- ||（逻辑或）：如果左侧是 truthy，返回左侧；否则返回右侧。
-
```js
console.log(0 || &amp;#39;default&amp;#39;); // &amp;#34;default&amp;#34;
console.log(&amp;#39;hello&amp;#39; || &amp;#39;default&amp;#39;); // &amp;#34;hello&amp;#34;
// 经典用法：提供默认值
const port = config.port || 3000;
// ⚠️ 陷阱：如果 config.port 是 0，也会被跳过！
```
- ??（空值合并，ES2020）：只有左侧是 null 或 undefined 时才返回右侧。这解决了 || 的陷阱。
-
```js
const port = config.port ?? 3000;
// config.port 为 0 → 返回 0 ✅
// config.port 为 &amp;#39;&amp;#39; → 返回 &amp;#39;&amp;#39; ✅
// config.port 为 null/undefined → 返回 3000
// 对比
0 || 3000; // 3000（0 是 falsy）
0 ?? 3000; // 0（0 不是 null/undefined）
&amp;#39;&amp;#39; || &amp;#39;default&amp;#39;; // &amp;#34;default&amp;#34;
&amp;#39;&amp;#39; ?? &amp;#39;default&amp;#39;; // &amp;#34;&amp;#34;
```
- ! （逻辑非）
-
```js
console.log(!0); // true
console.log(!&amp;#39;&amp;#39;); // true
console.log(!null); // true
// 双重取反 !! 可以将任意值转为布尔值
console.log(!!0); // false
console.log(!!&amp;#39;hello&amp;#39;); // true
console.log(!!null); // false
```
- 可选链与其他实用运算符
- ?.（可选链，Optional Chaining）：安全地访问嵌套属性，遇到 null/undefined 时短路返回 undefined 而不是报错。
-
```js
const user = {
name: &amp;#39;An&amp;#39;,
address: {
city: &amp;#39;Tokyo&amp;#39;
}
};
// 不用可选链
const zip = user &amp;&amp; user.address &amp;&amp; user.address.zip;
// 用可选链
const zip2 = user?.address?.zip; // undefined，不报错
const nothing = null?.foo?.bar; // undefined
// 也可以用在方法调用和下标访问上
user.greet?.(); // 如果 greet 方法存在才调用
const first = arr?.[0]; // 如果 arr 存在才取下标
```
- 逗号运算符：依次执行所有表达式，返回最后一个。用得不多，偶尔在 for 循环中见到
-
```js
const result = (1, 2, 3); // result === 3
for (let i = 0, j = 10; i &amp;lt; j; i&amp;#43;&amp;#43;, j--) { /* ... */ }
```
- 条件语句
- if/else if/else
-
```js
const score = 85;
if (score &amp;gt;= 90) {
console.log(&amp;#39;优秀&amp;#39;);
} else if (score &amp;gt;= 70) {
console.log(&amp;#39;良好&amp;#39;);
} else if (score &amp;gt;= 60) {
console.log(&amp;#39;及格&amp;#39;);
} else {
console.log(&amp;#39;不及格&amp;#39;);
}
```
- 三元表达式：适合简单的二选一，不要嵌套太深
-
```js
const status = score &amp;gt;= 60 ? &amp;#39;及格&amp;#39; : &amp;#39;不及格&amp;#39;;
// ❌ 不推荐：嵌套三元，可读性很差
const grade = score &amp;gt;= 90 ? &amp;#39;A&amp;#39; : score &amp;gt;= 70 ? &amp;#39;B&amp;#39; : score &amp;gt;= 60 ? &amp;#39;C&amp;#39; : &amp;#39;D&amp;#39;;
// ✅ 这种情况用 if/else 或函数更清晰
```
- switch：适合对同一个值做多重精确匹配，注意 break 不能忘
-
```js
const day = &amp;#39;Monday&amp;#39;;
switch (day) {
case &amp;#39;Monday&amp;#39;:
case &amp;#39;Tuesday&amp;#39;:
case &amp;#39;Wednesday&amp;#39;:
case &amp;#39;Thursday&amp;#39;:
case &amp;#39;Friday&amp;#39;:
console.log(&amp;#39;工作日&amp;#39;);
break; // ← 不写 break 会&amp;#34;穿透&amp;#34;到下一个 case
case &amp;#39;Saturday&amp;#39;:
case &amp;#39;Sunday&amp;#39;:
console.log(&amp;#39;周末&amp;#39;);
break;
default:
console.log(&amp;#39;无效日期&amp;#39;);
}
```
- switch 使用 === 进行比较，不会做隐式类型转换。
- 循环语句
- for 循环：最经典的循环，适合需要精确控制索引的场景：
```js
for (let i = 0; i &amp;lt; 5; i&amp;#43;&amp;#43;) {
console.log(i); // 0, 1, 2, 3, 4
}
// 倒序遍历
for (let i = arr.length - 1; i &amp;gt;= 0; i--) {
console.log(arr[i]);
}
```
- while 与 do...while：
```js
// while：先判断再执行
let n = 5;
while (n &amp;gt; 0) {
console.log(n);
n--;
}
// do...while：先执行一次再判断，至少执行一次
let input;
do {
input = prompt(&amp;#39;请输入密码&amp;#39;);
} while (input !== &amp;#39;secret&amp;#39;);
```
- for...of：遍历可迭代对象（数组、字符串、Map、Set 等）的值：
```js
const fruits = [&amp;#39;苹果&amp;#39;, &amp;#39;香蕉&amp;#39;, &amp;#39;橘子&amp;#39;];
for (const fruit of fruits) {
console.log(fruit); // &amp;#34;苹果&amp;#34;, &amp;#34;香蕉&amp;#34;, &amp;#34;橘子&amp;#34;
}
// 遍历字符串的每个字符（对中文友好）
for (const char of &amp;#39;你好世界&amp;#39;) {
console.log(char); // &amp;#34;你&amp;#34;, &amp;#34;好&amp;#34;, &amp;#34;世&amp;#34;, &amp;#34;界&amp;#34;
}
// 如果同时需要索引，用 entries()
for (const [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}
```
- for...in：遍历对象的可枚举属性名（键名）：
```js
const user = { name: &amp;#39;An&amp;#39;, age: 25, city: &amp;#39;Tokyo&amp;#39; };
for (const key in user) {
console.log(`${key}: ${user[key]}`);
}
// ⚠️ 不推荐用 for...in 遍历数组！
// 它会遍历原型链上的属性，顺序也不保证
// 遍历数组请用 for...of 或数组方法
```
- break、continue 与标签
-
```js
// break：立即退出整个循环
for (let i = 0; i &amp;lt; 10; i&amp;#43;&amp;#43;) {
if (i === 5) break;
console.log(i); // 0, 1, 2, 3, 4
}
// continue：跳过本次迭代，进入下一次
for (let i = 0; i &amp;lt; 10; i&amp;#43;&amp;#43;) {
if (i % 2 === 0) continue; // 跳过偶数
console.log(i); // 1, 3, 5, 7, 9
}
// 标签（label）：用于跳出嵌套循环，实际开发很少用
outer: for (let i = 0; i &amp;lt; 3; i&amp;#43;&amp;#43;) {
for (let j = 0; j &amp;lt; 3; j&amp;#43;&amp;#43;) {
if (i === 1 &amp;&amp; j === 1) break outer; // 直接跳出外层循环
console.log(i, j);
}
}
```
- 函数基础
- 函数声明 vs 函数表达式
- JavaScript 中定义函数有两种基本方式，它们最大的区别在于提升（hoisting）行为。
- 函数声明（Function Declaration）：整个函数会被提升到作用域顶部，因此可以在声明之前调用。
-
```js
// ✅ 可以在声明之前调用
greet(&amp;#39;An&amp;#39;);
function greet(name) {
console.log(`你好，${name}！`);
}
```
- 函数表达式（Function Expression）：把一个匿名或具名函数赋值给变量。变量声明会提升，但赋值不会，所以在赋值之前调用会报错。
-
```js
// ❌ TypeError: sayHi is not a function
// sayHi(&amp;#39;An&amp;#39;);
const sayHi = function(name) {
console.log(`Hi, ${name}!`);
};
sayHi(&amp;#39;An&amp;#39;); // ✅
// 具名函数表达式：函数名只在函数内部可见，主要用于递归和调试
const factorial = function fact(n) {
return n &amp;lt;= 1 ? 1 : n * fact(n - 1);
};
// fact(5); // ❌ ReferenceError，外部不可见
factorial(5); // ✅ 120
```
- 箭头函数
- ES6 引入的简洁写法，但它不仅仅是语法糖，和普通函数有几个本质区别。
- 基本语法
```js
// 完整写法
const add = (a, b) =&amp;gt; {
return a &amp;#43; b;
};
// 单个表达式可以省略 {} 和 return（隐式返回）
const add2 = (a, b) =&amp;gt; a &amp;#43; b;
// 只有一个参数可以省略括号
const double = x =&amp;gt; x * 2;
// 没有参数必须写空括号
const getRandom = () =&amp;gt; Math.random();
// ⚠️ 返回对象字面量必须加括号，否则 {} 会被当成函数体
const makeUser = (name) =&amp;gt; ({ name, active: true });
```
- 箭头函数与普通函数的区别
```js
// 1. 没有自己的 this，继承外层作用域的 this
const team = {
name: &amp;#39;Frontend&amp;#39;,
members: [&amp;#39;An&amp;#39;, &amp;#39;Bob&amp;#39;],
// ❌ 普通函数：this 指向调用者，forEach 的回调中 this 不是 team
showBad() {
this.members.forEach(function(member) {
console.log(`${member} belongs to ${this.name}`); // this.name 是 undefined
});
},
// ✅ 箭头函数：继承 showGood 的 this，即 team
showGood() {
this.members.forEach(member =&amp;gt; {
console.log(`${member} belongs to ${this.name}`); // &amp;#34;Frontend&amp;#34;
});
}
};
// 2. 没有 arguments 对象
function normalFn() {
console.log(arguments); // ✅ [1, 2, 3]
}
const arrowFn = () =&amp;gt; {
// console.log(arguments); // ❌ ReferenceError
};
normalFn(1, 2, 3);
// 3. 不能用作构造函数
const Person = (name) =&amp;gt; { this.name = name; };
// new Person(&amp;#39;An&amp;#39;); // ❌ TypeError: Person is not a constructor
// 4. 没有 prototype 属性
```
- 回调函数、数组方法的参数优先用箭头函数
- 对象方法用普通函数（需要自己的 this）
- 永远不要用箭头函数定义对象方法或构造函数。
- 参数处理
- 默认参数（Default Parameters）：
-
```js
function createUser(name, role = &amp;#39;viewer&amp;#39;, active = true) {
return { name, role, active };
}
createUser(&amp;#39;An&amp;#39;); // { name: &amp;#39;An&amp;#39;, role: &amp;#39;viewer&amp;#39;, active: true }
createUser(&amp;#39;An&amp;#39;, &amp;#39;admin&amp;#39;); // { name: &amp;#39;An&amp;#39;, role: &amp;#39;admin&amp;#39;, active: true }
createUser(&amp;#39;An&amp;#39;, undefined, false); // { name: &amp;#39;An&amp;#39;, role: &amp;#39;viewer&amp;#39;, active: false }
// 注意：传 undefined 会触发默认值，传 null 不会
createUser(&amp;#39;An&amp;#39;, null); // { name: &amp;#39;An&amp;#39;, role: null, active: true }
// 默认值可以是表达式，甚至引用前面的参数
function createId(prefix, timestamp = Date.now(), id = `${prefix}-${timestamp}`) {
return id;
}
```
- 剩余参数（Rest Parameters）：
- 用 ... 收集剩余的参数到一个真正的数组中（不同于 arguments 类数组对象）：
-
```js
function sum(first, ...rest) {
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]（真正的数组）
return rest.reduce((acc, val) =&amp;gt; acc &amp;#43; val, first);
}
sum(1, 2, 3, 4, 5); // 15
// 剩余参数必须是最后一个参数
// function bad(a, ...b, c) {} // ❌ SyntaxError
// 搭配解构使用
function processConfig({ host, port, ...otherOptions }) {
console.log(host); // &amp;#34;localhost&amp;#34;
console.log(port); // 3000
console.log(otherOptions); // { debug: true, timeout: 5000 }
}
processConfig({ host: &amp;#39;localhost&amp;#39;, port: 3000, debug: true, timeout: 5000 });
```
- 返回值
- 函数只能返回一个值。如果需要返回多个值，用对象或数组：
```js
// 不写 return 或 return 后没有值，返回 undefined
function noReturn() {
console.log(&amp;#39;hello&amp;#39;);
}
console.log(noReturn()); // undefined
// 返回多个值的模式
function getMinMax(arr) {
return {
min: Math.min(...arr),
max: Math.max(...arr)
};
}
const { min, max } = getMinMax([3, 1, 4, 1, 5, 9]);
// 用数组返回，配合解构
function divide(a, b) {
return [Math.floor(a / b), a % b]; // [商, 余数]
}
const [quotient, remainder] = divide(17, 5); // 3, 2
```
- return 换行陷阱
```js
function gotcha() {
return // ← JS 在这里自动插入分号，函数返回 undefined
{
name: &amp;#39;An&amp;#39;
};
}
console.log(gotcha()); // undefined！
// 正确写法：左大括号跟 return 同一行
function correct() {
return {
name: &amp;#39;An&amp;#39;
};
}
```
- 函数是一等公民
- 在 JavaScript 中，函数和其他值（数字、字符串）没有区别，可以被赋值、传递、返回。
- 赋值给变量：
```js
const greet = function(name) { return `Hello, ${name}`; };
const sayHi = greet; // 函数可以像值一样赋给另一个变量
sayHi(&amp;#39;An&amp;#39;); // &amp;#34;Hello, An&amp;#34;
```
- 作为参数传递（回调函数）：
```js
function repeat(n, action) {
for (let i = 0; i &amp;lt; n; i&amp;#43;&amp;#43;) {
action(i);
}
}
repeat(3, console.log); // 0, 1, 2
repeat(3, i =&amp;gt; console.log(i * i)); // 0, 1, 4
// 数组方法中大量使用回调
const nums = [1, 2, 3, 4, 5];
const evens = nums.filter(n =&amp;gt; n % 2 === 0); // [2, 4]
const doubled = nums.map(n =&amp;gt; n * 2); // [2, 4, 6, 8, 10]
const sum = nums.reduce((acc, n) =&amp;gt; acc &amp;#43; n, 0); // 15
```
- 作为返回值
```js
function multiplier(factor) {
return (number) =&amp;gt; number * factor; // 返回一个新函数
}
const double = multiplier(2);
const triple = multiplier(3);
double(5); // 10
triple(5); // 15
// 这就是闭包的雏形——返回的函数&amp;#34;记住&amp;#34;了 factor 的值
// Day 4 会深入讲解
```
- 存储在数据结构中：
```js
const strategies = {
add: (a, b) =&amp;gt; a &amp;#43; b,
subtract: (a, b) =&amp;gt; a - b,
multiply: (a, b) =&amp;gt; a * b,
};
function calculate(strategy, a, b) {
return strategies[strategy](a, b);
}
calculate(&amp;#39;add&amp;#39;, 10, 5); // 15
calculate(&amp;#39;multiply&amp;#39;, 10, 5); // 50
```
- IIFE（立即调用函数表达式）
- IIFE 是一种定义后立即执行的函数模式。在 ES6 模块化之前，它是避免全局变量污染的主要手段。
-
```js
// 经典写法
(function() {
const secret = &amp;#39;只在这里可见&amp;#39;;
console.log(secret);
})();
// console.log(secret); // ❌ ReferenceError
// 带参数
(function(global) {
global.myLib = { version: &amp;#39;1.0&amp;#39; };
})(window);
// 箭头函数版
(() =&amp;gt; {
console.log(&amp;#39;立即执行&amp;#39;);
})();
// 带返回值
const result = (() =&amp;gt; {
const a = 10;
const b = 20;
return a &amp;#43; b;
})();
console.log(result); // 30
```
- 现在有了 ES Modules 和块级作用域（let/const），IIFE 用得少了，但在一些老代码库和特定场景（如隔离作用域执行异步代码）中仍然常见。
- 作用域与闭包
- 作用域的三种层级
- 作用域决定了变量在哪里可以被访问。JavaScript 有三种作用域层级。
- 全局作用域：在所有函数和块之外声明的变量，任何地方都能访问。在浏览器中，全局变量会挂载到 window 对象上。
-
```js
const APP_NAME = &amp;#39;MyApp&amp;#39;; // 全局作用域
function foo() {
console.log(APP_NAME); // ✅ 可以访问
}
// 浏览器环境下
var x = 10;
console.log(window.x); // 10（var 声明的全局变量挂到 window）
// let/const 声明的不会挂到 window
let y = 20;
console.log(window.y); // undefined
```
- 函数作用域：在函数内部声明的变量，外部不可见。var、let、const 在函数内都有这个特性。
-
```js
function createSecret() {
var secret1 = &amp;#39;var secret&amp;#39;;
let secret2 = &amp;#39;let secret&amp;#39;;
const secret3 = &amp;#39;const secret&amp;#39;;
return &amp;#39;done&amp;#39;;
}
createSecret();
// console.log(secret1); // ❌ ReferenceError
// console.log(secret2); // ❌ ReferenceError
// console.log(secret3); // ❌ ReferenceError
```
- 块级作用域：let 和 const 在 {}（if、for、while 或单独的块）中声明时，只在该块内可见。var 没有块级作用域。
-
```js
{
let a = 1;
const b = 2;
var c = 3;
}
// console.log(a); // ❌ ReferenceError
// console.log(b); // ❌ ReferenceError
console.log(c); // 3 ✅ var 穿透了块
// 经典面试题：for 循环中的 var vs let
for (var i = 0; i &amp;lt; 3; i&amp;#43;&amp;#43;) {
setTimeout(() =&amp;gt; console.log(i), 100);
}
// 输出：3, 3, 3（var 没有块级作用域，循环结束后 i 是 3）
for (let j = 0; j &amp;lt; 3; j&amp;#43;&amp;#43;) {
setTimeout(() =&amp;gt; console.log(j), 100);
}
// 输出：0, 1, 2（let 每次迭代创建新的块级作用域）
```
- 作用域链
- 当代码访问一个变量时，JS 引擎会沿着作用域链由内向外逐层查找，直到找到或到达全局作用域。
-
```js
const global = &amp;#39;global&amp;#39;;
function outer() {
const outerVar = &amp;#39;outer&amp;#39;;
function middle() {
const middleVar = &amp;#39;middle&amp;#39;;
function inner() {
const innerVar = &amp;#39;inner&amp;#39;;
// inner 可以访问所有外层变量
console.log(innerVar); // &amp;#34;inner&amp;#34; ← 自身作用域
console.log(middleVar); // &amp;#34;middle&amp;#34; ← 父级作用域
console.log(outerVar); // &amp;#34;outer&amp;#34; ← 祖父级作用域
console.log(global); // &amp;#34;global&amp;#34; ← 全局作用域
}
inner();
// console.log(innerVar); // ❌ 外层不能访问内层
}
middle();
}
outer();
```
- 词法作用域
- JavaScript 使用词法作用域（也叫静态作用域），意思是函数的作用域在定义时就确定了，而不是在调用时。
-
```js
const x = &amp;#39;global&amp;#39;;
function printX() {
console.log(x); // 这里的 x 在定义时就绑定了全局的 x
}
function wrapper() {
const x = &amp;#39;wrapper&amp;#39;;
printX(); // 虽然在 wrapper 内部调用，但打印的是 &amp;#34;global&amp;#34;
}
wrapper(); // &amp;#34;global&amp;#34;，不是 &amp;#34;wrapper&amp;#34;
```
- 这个特性是闭包的基础。函数&amp;#34;记住&amp;#34;的是它被创建时的环境，而不是被调用时的环境。
-
```js
function createPrinter() {
const message = &amp;#39;来自 createPrinter&amp;#39;;
return function() {
console.log(message); // 词法作用域：绑定的是 createPrinter 内的 message
};
}
const printer = createPrinter();
function someOtherPlace() {
const message = &amp;#39;来自 someOtherPlace&amp;#39;;
printer(); // &amp;#34;来自 createPrinter&amp;#34;，不受调用位置影响
}
someOtherPlace();
```
- 闭包
- 闭包是指一个函数能够访问其词法作用域中的变量，即使这个函数在其词法作用域之外执行。
-
```js
function createCounter() {
let count = 0; // 这个变量被&amp;#34;封闭&amp;#34;在闭包中
return function() {
count&amp;#43;&amp;#43;;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 变量从外部完全无法访问
// console.log(count); // ❌ ReferenceError
// 每次调用 createCounter 创建独立的闭包
const counter2 = createCounter();
console.log(counter2()); // 1（独立的 count）
```
- 闭包的本质：createCounter 执行完后，正常来说内部变量 count 应该被垃圾回收。但因为返回的函数仍然引用着 count，JS 引擎就保留了这个变量。返回的函数 &amp;#43; 它引用的外部变量 = 闭包。
- 闭包应用场景
- 数据私有化 / 模块模式
```js
function createWallet(initialBalance) {
let balance = initialBalance; // 私有变量，外部无法直接修改
return {
deposit(amount) {
if (amount &amp;lt;= 0) throw new Error(&amp;#39;金额必须大于 0&amp;#39;);
balance &amp;#43;= amount;
return balance;
},
withdraw(amount) {
if (amount &amp;gt; balance) throw new Error(&amp;#39;余额不足&amp;#39;);
balance -= amount;
return balance;
},
getBalance() {
return balance;
}
};
}
const wallet = createWallet(100);
wallet.deposit(50); // 150
wallet.withdraw(30); // 120
wallet.getBalance(); // 120
// wallet.balance; // undefined，无法直接访问
// balance = 999999; // 不可能，balance 被封闭在闭包中
```
- 工厂函数 / 函数生成器
```js
function createGreeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const hello = createGreeter(&amp;#39;Hello&amp;#39;);
const nihao = createGreeter(&amp;#39;你好&amp;#39;);
hello(&amp;#39;An&amp;#39;); // &amp;#34;Hello, An!&amp;#34;
nihao(&amp;#39;An&amp;#39;); // &amp;#34;你好, An!&amp;#34;
// 更实用的例子：创建带预设配置的函数
function createLogger(prefix) {
return function(message) {
console.log(`[${prefix}] ${new Date().toISOString()}: ${message}`);
};
}
const dbLog = createLogger(&amp;#39;DB&amp;#39;);
const apiLog = createLogger(&amp;#39;API&amp;#39;);
dbLog(&amp;#39;连接成功&amp;#39;); // [DB] 2026-04-05T...: 连接成功
apiLog(&amp;#39;请求收到&amp;#39;); // [API] 2026-04-05T...: 请求收到
```
- 柯里化（Currying）：把接受多个参数的函数，变成一系列只接受一个参数的函数
```js
// 普通函数
function add(a, b, c) {
return a &amp;#43; b &amp;#43; c;
}
add(1, 2, 3); // 6
// 柯里化版本（手动）
function curriedAdd(a) {
return function(b) {
return function(c) {
return a &amp;#43; b &amp;#43; c; // 三层闭包，每层记住一个参数
};
};
}
curriedAdd(1)(2)(3); // 6
// 拆开用
const addOne = curriedAdd(1);
const addOneAndTwo = addOne(2);
addOneAndTwo(3); // 6
// 箭头函数简写
const curriedAdd2 = a =&amp;gt; b =&amp;gt; c =&amp;gt; a &amp;#43; b &amp;#43; c;
// 实际应用：事件处理器工厂
const handleClick = (action) =&amp;gt; (id) =&amp;gt; (event) =&amp;gt; {
console.log(`Action: ${action}, ID: ${id}, Target: ${event.target}`);
};
// button.addEventListener(&amp;#39;click&amp;#39;, handleClick(&amp;#39;delete&amp;#39;)(42));
```
- 防抖（Debounce）
```js
function debounce(fn, delay) {
let timerId = null; // 闭包保存定时器 ID
return function(...args) {
clearTimeout(timerId); // 每次调用都清除上一次的定时器
timerId = setTimeout(() =&amp;gt; {
fn.apply(this, args); // delay 毫秒后才真正执行
}, delay);
};
}
// 使用：搜索框输入时，停止输入 300ms 后才发请求
const search = debounce((query) =&amp;gt; {
console.log(`搜索: ${query}`);
}, 300);
search(&amp;#39;h&amp;#39;); // 被取消
search(&amp;#39;he&amp;#39;); // 被取消
search(&amp;#39;hel&amp;#39;); // 被取消
search(&amp;#39;hello&amp;#39;); // 300ms 后执行 → &amp;#34;搜索: hello&amp;#34;
```
- 节流（Throttle）
```js
function throttle(fn, interval) {
let lastTime = 0; // 闭包保存上次执行时间
return function(...args) {
const now = Date.now();
if (now - lastTime &amp;gt;= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用：滚动事件每 200ms 最多触发一次
// window.addEventListener(&amp;#39;scroll&amp;#39;, throttle(handleScroll, 200));
```
- 对象与数组
- 对象字面量与属性
- 对象是 JavaScript 中最基础的数据结构，用来存储键值对。
- 创建与属性访问：
```js
// 对象字面量
const user = {
name: &amp;#39;An&amp;#39;,
age: 25,
&amp;#39;favorite-color&amp;#39;: &amp;#39;blue&amp;#39;, // 包含特殊字符的键名需要引号
1: &amp;#39;one&amp;#39; // 数字键会被转为字符串
};
// 点语法访问
console.log(user.name); // &amp;#34;An&amp;#34;
// 方括号访问（适用于动态键名或特殊字符）
console.log(user[&amp;#39;favorite-color&amp;#39;]); // &amp;#34;blue&amp;#34;
console.log(user[1]); // &amp;#34;one&amp;#34;
const key = &amp;#39;age&amp;#39;;
console.log(user[key]); // 25（动态访问）
```
- 属性简写与计算属性名（ES6）：
```js
const name = &amp;#39;An&amp;#39;;
const age = 25;
// 属性简写：变量名和属性名相同时可以简写
const user = { name, age };
// 等价于 { name: name, age: age }
// 方法简写
const calculator = {
// ES6 之前：add: function(a, b) { return a &amp;#43; b; }
add(a, b) {
return a &amp;#43; b;
},
subtract(a, b) {
return a - b;
}
};
// 计算属性名：用表达式作为键名
const field = &amp;#39;email&amp;#39;;
const obj = {
[field]: &amp;#39;an@example.com&amp;#39;, // { email: &amp;#39;an@example.com&amp;#39; }
[`${field}Verified`]: true, // { emailVerified: true }
[&amp;#39;get&amp;#39; &amp;#43; &amp;#39;Name&amp;#39;]() { return &amp;#39;An&amp;#39;; } // { getName() { return &amp;#39;An&amp;#39;; } }
};
```
- 属性的增删改查：
```js
const user = { name: &amp;#39;An&amp;#39; };
// 增
user.age = 25;
user[&amp;#39;city&amp;#39;] = &amp;#39;Tokyo&amp;#39;;
// 改
user.name = &amp;#39;Bob&amp;#39;;
// 删
delete user.city;
console.log(user.city); // undefined
// 查（判断属性是否存在）
console.log(&amp;#39;name&amp;#39; in user); // true（包括原型链）
console.log(user.hasOwnProperty(&amp;#39;name&amp;#39;)); // true（只查自身）
console.log(user.toString !== undefined); // true（原型链上的）
console.log(user.hasOwnProperty(&amp;#39;toString&amp;#39;)); // false
```
- 对象的遍历与常用静态方法
-
```js
const user = { name: &amp;#39;An&amp;#39;, age: 25, city: &amp;#39;Tokyo&amp;#39; };
// Object.keys() — 返回键名数组
Object.keys(user); // [&amp;#39;name&amp;#39;, &amp;#39;age&amp;#39;, &amp;#39;city&amp;#39;]
// Object.values() — 返回值数组
Object.values(user); // [&amp;#39;An&amp;#39;, 25, &amp;#39;Tokyo&amp;#39;]
// Object.entries() — 返回 [key, value] 二维数组
Object.entries(user); // [[&amp;#39;name&amp;#39;,&amp;#39;An&amp;#39;], [&amp;#39;age&amp;#39;,25], [&amp;#39;city&amp;#39;,&amp;#39;Tokyo&amp;#39;]]
// 用 for...of 配合 entries 遍历
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// Object.fromEntries() — entries 的反操作
const entries = [[&amp;#39;a&amp;#39;, 1], [&amp;#39;b&amp;#39;, 2]];
const obj = Object.fromEntries(entries); // { a: 1, b: 2 }
// 实用场景：过滤对象属性
const filtered = Object.fromEntries(
Object.entries(user).filter(([key]) =&amp;gt; key !== &amp;#39;age&amp;#39;)
);
// { name: &amp;#39;An&amp;#39;, city: &amp;#39;Tokyo&amp;#39; }
```
- Object.assign() 与冻结：
```js
// Object.assign() — 合并对象（浅拷贝）
const defaults = { theme: &amp;#39;light&amp;#39;, lang: &amp;#39;zh&amp;#39;, debug: false };
const userPrefs = { theme: &amp;#39;dark&amp;#39;, debug: true };
const config = Object.assign({}, defaults, userPrefs);
// { theme: &amp;#39;dark&amp;#39;, lang: &amp;#39;zh&amp;#39;, debug: true }
// 后面的属性覆盖前面的
// Object.freeze() — 冻结对象，不能增删改
const frozen = Object.freeze({ name: &amp;#39;An&amp;#39;, age: 25 });
frozen.name = &amp;#39;Bob&amp;#39;; // 静默失败（严格模式下报错）
frozen.city = &amp;#39;NYC&amp;#39;; // 静默失败
console.log(frozen); // { name: &amp;#39;An&amp;#39;, age: 25 }
// ⚠️ freeze 是浅冻结
const obj = Object.freeze({ inner: { value: 1 } });
obj.inner.value = 999; // ✅ 生效了！内层对象没有被冻结
// Object.isFrozen()
console.log(Object.isFrozen(frozen)); // true
```
- 解构赋值 — 对象
- 解构让你用简洁的语法从对象中提取值。
```js
const user = {
name: &amp;#39;An&amp;#39;,
age: 25,
address: {
city: &amp;#39;Tokyo&amp;#39;,
zip: &amp;#39;100-0001&amp;#39;
},
hobbies: [&amp;#39;coding&amp;#39;, &amp;#39;reading&amp;#39;]
};
// 基本解构
const { name, age } = user;
console.log(name); // &amp;#34;An&amp;#34;
console.log(age); // 25
// 重命名
const { name: userName, age: userAge } = user;
console.log(userName); // &amp;#34;An&amp;#34;
// 默认值
const { name: n, role = &amp;#39;viewer&amp;#39; } = user;
console.log(role); // &amp;#34;viewer&amp;#34;（user 中没有 role）
// 嵌套解构
const { address: { city, zip } } = user;
console.log(city); // &amp;#34;Tokyo&amp;#34;
// 剩余属性
const { name: nm, ...rest } = user;
console.log(rest); // { age: 25, address: {...}, hobbies: [...] }
```
- 函数参数中的解构（非常常用）：
```js
// ❌ 不解构：参数含义不清晰
function createUser(name, age, role, active) {
// name 是第几个参数？容易搞混
}
// ✅ 解构：清晰且支持默认值、可选参数
function createUser({ name, age, role = &amp;#39;viewer&amp;#39;, active = true } = {}) {
return { name, age, role, active };
}
// 调用时不需要关心参数顺序
createUser({ age: 25, name: &amp;#39;An&amp;#39; });
createUser({}); // 全部使用默认值
createUser(); // 因为有 = {}，不传也不报错
```
- 数组基础与常用方法
- 创建数组：
```js
const arr1 = [1, 2, 3];
const arr2 = new Array(5); // [empty × 5]（5个空位，不推荐）
const arr3 = Array.from({ length: 5 }, (_, i) =&amp;gt; i); // [0, 1, 2, 3, 4]
const arr4 = Array.of(1, 2, 3); // [1, 2, 3]
```
- 增删元素（会修改原数组）：
```js
const arr = [1, 2, 3];
// 尾部操作
arr.push(4); // [1, 2, 3, 4]，返回新长度 4
arr.pop(); // [1, 2, 3]，返回被删除的元素 4
// 头部操作
arr.unshift(0); // [0, 1, 2, 3]，返回新长度 4
arr.shift(); // [1, 2, 3]，返回被删除的元素 0
// splice — 万能的增删改
const items = [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;];
// splice(起始索引, 删除个数, ...插入的元素)
items.splice(2, 1); // 删除：items = [&amp;#39;a&amp;#39;,&amp;#39;b&amp;#39;,&amp;#39;d&amp;#39;,&amp;#39;e&amp;#39;]，返回 [&amp;#39;c&amp;#39;]
items.splice(1, 0, &amp;#39;x&amp;#39;); // 插入：items = [&amp;#39;a&amp;#39;,&amp;#39;x&amp;#39;,&amp;#39;b&amp;#39;,&amp;#39;d&amp;#39;,&amp;#39;e&amp;#39;]
items.splice(1, 2, &amp;#39;y&amp;#39;,&amp;#39;z&amp;#39;);// 替换：items = [&amp;#39;a&amp;#39;,&amp;#39;y&amp;#39;,&amp;#39;z&amp;#39;,&amp;#39;d&amp;#39;,&amp;#39;e&amp;#39;]
```
- 不修改原数组的方法：
```js
const arr = [1, 2, 3, 4, 5];
// slice(start, end) — 切片，左闭右开
arr.slice(1, 3); // [2, 3]
arr.slice(-2); // [4, 5]（最后两个）
arr.slice(); // [1, 2, 3, 4, 5]（浅拷贝）
// concat — 拼接
arr.concat([6, 7]); // [1, 2, 3, 4, 5, 6, 7]
// indexOf / lastIndexOf — 查找索引
[1, 2, 3, 2].indexOf(2); // 1（第一个）
[1, 2, 3, 2].lastIndexOf(2); // 3（最后一个）
[1, 2, 3].indexOf(99); // -1（未找到）
// includes — 是否包含
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(99); // false
// ⚠️ indexOf 和 includes 对引用类型无效
const obj = { id: 1 };
[{ id: 1 }].includes(obj); // false（不同的引用）
// join — 数组转字符串
[&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;].join(&amp;#39;-&amp;#39;); // &amp;#34;a-b-c&amp;#34;
[&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;].join(&amp;#39;&amp;#39;); // &amp;#34;abc&amp;#34;
// flat — 扁平化
[1, [2, [3, [4]]]].flat(); // [1, 2, [3, [4]]]（默认一层）
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]（完全扁平）
// at — 支持负索引（ES2022）
const letters = [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;];
letters.at(0); // &amp;#34;a&amp;#34;
letters.at(-1); // &amp;#34;d&amp;#34;（最后一个）
letters.at(-2); // &amp;#34;c&amp;#34;
```
- 解构赋值-数组
-
```js
const rgb = [255, 128, 0];
// 基本解构
const [r, g, b] = rgb;
console.log(r, g, b); // 255 128 0
// 跳过元素
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3
// 默认值
const [a, b, c, d = 0] = [1, 2, 3];
console.log(d); // 0
// 剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// 交换变量（不需要临时变量）
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
// 从函数返回值解构
function getCoords() {
return [35.6762, 139.6503];
}
const [lat, lng] = getCoords();
// 嵌套解构
const matrix = [[1, 2], [3, 4]];
const [[a1, a2], [b1, b2]] = matrix;
```
- 扩展运算符（Spread Operator）
- ... 在不同上下文中扮演不同角色。用在函数参数中是&amp;#34;收集&amp;#34;（rest），用在数组/对象字面量中是&amp;#34;展开&amp;#34;（spread）
- 数组展开
```js
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// 合并数组
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 浅拷贝
const copy = [...arr1]; // [1, 2, 3]（新数组，不同引用）
// 在特定位置插入
const withInsert = [...arr1.slice(0, 1), 99, ...arr1.slice(1)];
// [1, 99, 2, 3]
// 展开字符串
const chars = [...&amp;#34;hello&amp;#34;]; // [&amp;#39;h&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;l&amp;#39;, &amp;#39;l&amp;#39;, &amp;#39;o&amp;#39;]
// 配合 Math 使用
const nums = [3, 1, 4, 1, 5, 9];
Math.max(...nums); // 9
```
- 对象展开
```js
const base = { a: 1, b: 2 };
const extra = { b: 3, c: 4 };
// 合并对象（后面覆盖前面）
const merged = { ...base, ...extra };
// { a: 1, b: 3, c: 4 }
// 浅拷贝 &amp;#43; 修改部分属性（React 中常用）
const user = { name: &amp;#39;An&amp;#39;, age: 25, city: &amp;#39;Tokyo&amp;#39; };
const updated = { ...user, age: 26 };
// { name: &amp;#39;An&amp;#39;, age: 26, city: &amp;#39;Tokyo&amp;#39; }
// 条件展开
const isAdmin = true;
const config = {
theme: &amp;#39;dark&amp;#39;,
...(isAdmin &amp;&amp; { adminPanel: true, debugMode: true })
};
// isAdmin 为 true 时：{ theme: &amp;#39;dark&amp;#39;, adminPanel: true, debugMode: true }
// isAdmin 为 false 时：{ theme: &amp;#39;dark&amp;#39; }（false 被展开为空）
```
- 浅拷贝与深拷贝
- JavaScript 中引用类型赋值只是复制地址，理解拷贝层级非常重要。
- 浅拷贝（Shallow Copy）：只拷贝第一层，嵌套的对象/数组仍然是共享引用。
```js
const original = {
name: &amp;#39;An&amp;#39;,
scores: [90, 85, 95],
address: { city: &amp;#39;Tokyo&amp;#39; }
};
// 三种浅拷贝方式
const copy1 = { ...original };
const copy2 = Object.assign({}, original);
const copy3 = structuredClone ? null : null; // 这个是深拷贝，后面说
// 浅拷贝的问题：嵌套对象是共享的
copy1.name = &amp;#39;Bob&amp;#39;; // ✅ 不影响 original
copy1.scores.push(100); // ❌ original.scores 也变了！
copy1.address.city = &amp;#39;Osaka&amp;#39;; // ❌ original.address.city 也变了！
console.log(original.scores); // [90, 85, 95, 100]
console.log(original.address.city); // &amp;#34;Osaka&amp;#34;
```
- 深拷贝（Deep Copy）：递归拷贝所有层级，完全独立。
```js
// 方法1：structuredClone（现代浏览器和 Node 17&amp;#43; 原生支持，推荐）
const deep1 = structuredClone(original);
deep1.scores.push(100);
deep1.address.city = &amp;#39;Osaka&amp;#39;;
console.log(original.scores); // [90, 85, 95] 不受影响 ✅
console.log(original.address.city); // &amp;#34;Tokyo&amp;#34; 不受影响 ✅
// 方法2：JSON 序列化（简单但有限制）
const deep2 = JSON.parse(JSON.stringify(original));
// ⚠️ 限制：
// - undefined、function、Symbol 会丢失
// - Date 会变成字符串
// - RegExp 会变成空对象
// - 不支持循环引用
// 示例：JSON 方法的数据丢失
const problematic = {
fn: () =&amp;gt; &amp;#39;hello&amp;#39;, // 函数
date: new Date(), // Date
undef: undefined, // undefined
regex: /abc/g // 正则
};
const jsonCopy = JSON.parse(JSON.stringify(problematic));
console.log(jsonCopy);
// { date: &amp;#34;2026-04-05T...&amp;#34;, regex: {} }
// fn 和 undef 直接丢了，date 变成了字符串，regex 变成了空对象
```
- 数组高阶方法
- 什么是高阶方法
- 高阶方法就是接受函数作为参数的方法
- 每个方法的回调函数都接收三个参数：(当前元素, 索引, 原数组)，大多数情况只用第一个。
- map - 映射转换
- 对数组每个元素执行回调，将返回值收集成一个等长的新数组。不修改原数组。
-
```js
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n =&amp;gt; n * 2);
// [2, 4, 6, 8, 10]
const strings = nums.map(n =&amp;gt; `第${n}项`);
// [&amp;#39;第1项&amp;#39;, &amp;#39;第2项&amp;#39;, &amp;#39;第3项&amp;#39;, &amp;#39;第4项&amp;#39;, &amp;#39;第5项&amp;#39;]
// 实际场景：从 API 数据中提取需要的字段
const users = [
{ id: 1, name: &amp;#39;An&amp;#39;, email: &amp;#39;an@test.com&amp;#39;, age: 25 },
{ id: 2, name: &amp;#39;Bob&amp;#39;, email: &amp;#39;bob@test.com&amp;#39;, age: 30 },
{ id: 3, name: &amp;#39;Cat&amp;#39;, email: &amp;#39;cat@test.com&amp;#39;, age: 22 }
];
const names = users.map(u =&amp;gt; u.name);
// [&amp;#39;An&amp;#39;, &amp;#39;Bob&amp;#39;, &amp;#39;Cat&amp;#39;]
const cards = users.map(({ id, name, age }) =&amp;gt; ({
id,
label: `${name} (${age}岁)`
}));
// [{ id: 1, label: &amp;#39;An (25岁)&amp;#39; }, ...]
// 使用索引参数
const indexed = [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;].map((item, i) =&amp;gt; `${i}: ${item}`);
// [&amp;#39;0: a&amp;#39;, &amp;#39;1: b&amp;#39;, &amp;#39;2: c&amp;#39;]
```
- 常见错误
```js
// ❌ map 中不 return（箭头函数加了 {} 就必须写 return）
nums.map(n =&amp;gt; { n * 2 }); // [undefined, undefined, ...]
// ❌ 用 map 做副作用操作（不关心返回值时应该用 forEach）
users.map(u =&amp;gt; console.log(u.name)); // 能跑，但语义错误
// ❌ parseInt 的经典陷阱
[&amp;#39;1&amp;#39;, &amp;#39;2&amp;#39;, &amp;#39;3&amp;#39;].map(parseInt);
// [1, NaN, NaN]
// 原因：map 传了 (元素, 索引, 数组) 三个参数给 parseInt
// parseInt(&amp;#39;1&amp;#39;, 0) → 1
// parseInt(&amp;#39;2&amp;#39;, 1) → NaN（1进制无效）
// parseInt(&amp;#39;3&amp;#39;, 2) → NaN（2进制没有3）
// ✅ 正确写法
[&amp;#39;1&amp;#39;, &amp;#39;2&amp;#39;, &amp;#39;3&amp;#39;].map(s =&amp;gt; parseInt(s, 10)); // [1, 2, 3]
[&amp;#39;1&amp;#39;, &amp;#39;2&amp;#39;, &amp;#39;3&amp;#39;].map(Number); // [1, 2, 3]
```
- filter-过滤筛选
- 对数组每个元素执行回调，保留返回值为 truthy 的元素，返回一个新数组（长度 ≤ 原数组）。
-
```js
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = nums.filter(n =&amp;gt; n % 2 === 0);
// [2, 4, 6, 8, 10]
const bigNums = nums.filter(n =&amp;gt; n &amp;gt; 5);
// [6, 7, 8, 9, 10]
// 过滤对象数组
const users = [
{ name: &amp;#39;An&amp;#39;, age: 25, active: true },
{ name: &amp;#39;Bob&amp;#39;, age: 17, active: true },
{ name: &amp;#39;Cat&amp;#39;, age: 30, active: false },
{ name: &amp;#39;Dan&amp;#39;, age: 22, active: true }
];
const activeAdults = users.filter(u =&amp;gt; u.active &amp;&amp; u.age &amp;gt;= 18);
// [{ name: &amp;#39;An&amp;#39;, ... }, { name: &amp;#39;Dan&amp;#39;, ... }]
// 移除 falsy 值
const messy = [0, &amp;#39;hello&amp;#39;, &amp;#39;&amp;#39;, null, 42, undefined, false, &amp;#39;world&amp;#39;];
const clean = messy.filter(Boolean);
// [&amp;#39;hello&amp;#39;, 42, &amp;#39;world&amp;#39;]
// 去重（Day 5 用的 Set 更简洁，这里展示 filter 做法）
const arr = [1, 2, 2, 3, 3, 3];
const unique = arr.filter((item, index) =&amp;gt; arr.indexOf(item) === index);
// [1, 2, 3]
```
- reduce — 归约累积
- reduce 是最强大也最灵活的数组方法，它将数组&amp;#34;归约&amp;#34;为一个值（可以是任何类型）。
- 语法：array.reduce((accumulator, currentValue, index, array) =&amp;gt; {...}, initialValue)
-
```js
// 最基本：求和
const sum = [1, 2, 3, 4, 5].reduce((acc, cur) =&amp;gt; acc &amp;#43; cur, 0);
// 执行过程：
// acc=0, cur=1 → 1
// acc=1, cur=2 → 3
// acc=3, cur=3 → 6
// acc=6, cur=4 → 10
// acc=10, cur=5 → 15
// 结果：15
// 求最大值
const max = [3, 1, 4, 1, 5, 9].reduce((a, b) =&amp;gt; a &amp;gt; b ? a : b);
// 没有初始值时，第一个元素作为初始值
// 结果：9
```
- find 与 findIndex - 查找
- find 返回第一个满足条件的元素本身，找不到返回 undefined。
- findIndex 返回第一个满足条件的元素索引，找不到返回 -1。
-
```js
const users = [
{ id: 1, name: &amp;#39;An&amp;#39;, role: &amp;#39;dev&amp;#39; },
{ id: 2, name: &amp;#39;Bob&amp;#39;, role: &amp;#39;admin&amp;#39; },
{ id: 3, name: &amp;#39;Cat&amp;#39;, role: &amp;#39;dev&amp;#39; }
];
const admin = users.find(u =&amp;gt; u.role === &amp;#39;admin&amp;#39;);
// { id: 2, name: &amp;#39;Bob&amp;#39;, role: &amp;#39;admin&amp;#39; }
const notFound = users.find(u =&amp;gt; u.role === &amp;#39;ceo&amp;#39;);
// undefined
const adminIndex = users.findIndex(u =&amp;gt; u.role === &amp;#39;admin&amp;#39;);
// 1
// findLast / findLastIndex（ES2023）从后往前找
const lastDev = users.findLast(u =&amp;gt; u.role === &amp;#39;dev&amp;#39;);
// { id: 3, name: &amp;#39;Cat&amp;#39;, role: &amp;#39;dev&amp;#39; }
```
- some 与 every — 条件判断
- some 只要有一个元素满足条件就返回 true（类似逻辑或）。
- every 要求所有元素都满足条件才返回 true（类似逻辑与）。
-
```js
const nums = [1, 2, 3, 4, 5];
nums.some(n =&amp;gt; n &amp;gt; 4); // true（5 &amp;gt; 4）
nums.some(n =&amp;gt; n &amp;gt; 10); // false
nums.every(n =&amp;gt; n &amp;gt; 0); // true（全部大于0）
nums.every(n =&amp;gt; n &amp;gt; 3); // false（1, 2, 3 不满足）
// 空数组的行为
[].some(() =&amp;gt; true); // false（没有元素满足）
[].every(() =&amp;gt; false); // true（没有元素不满足 —— 空真）
// 实际场景
const cart = [
{ name: &amp;#39;键盘&amp;#39;, inStock: true },
{ name: &amp;#39;鼠标&amp;#39;, inStock: true },
{ name: &amp;#39;显示器&amp;#39;, inStock: false }
];
const allInStock = cart.every(item =&amp;gt; item.inStock); // false
const anyOutOfStock = cart.some(item =&amp;gt; !item.inStock); // true
// 表单验证
const fields = [
{ name: &amp;#39;email&amp;#39;, valid: true },
{ name: &amp;#39;password&amp;#39;, valid: true },
{ name: &amp;#39;phone&amp;#39;, valid: false }
];
const canSubmit = fields.every(f =&amp;gt; f.valid); // false
```
- sort 排序
- sort 会修改原数组，并且默认按字符串的 Unicode 码点排序，这对数字来说通常不是你想要的。
-
```js
// ❌ 默认排序的坑
[10, 9, 2, 21, 3].sort();
// [10, 2, 21, 3, 9]
// 因为 &amp;#34;10&amp;#34; &amp;lt; &amp;#34;2&amp;#34;（按字符串比较，&amp;#39;1&amp;#39; 的码点 &amp;lt; &amp;#39;2&amp;#39; 的码点）
// ✅ 数字排序：提供比较函数
// 返回负数 → a 排前面
// 返回正数 → b 排前面
// 返回 0 → 保持原序
[10, 9, 2, 21, 3].sort((a, b) =&amp;gt; a - b);
// [2, 3, 9, 10, 21]（升序）
[10, 9, 2, 21, 3].sort((a, b) =&amp;gt; b - a);
// [21, 10, 9, 3, 2]（降序）
// 对象数组排序
const users = [
{ name: &amp;#39;Cat&amp;#39;, age: 30 },
{ name: &amp;#39;An&amp;#39;, age: 25 },
{ name: &amp;#39;Bob&amp;#39;, age: 22 }
];
// 按年龄升序
users.sort((a, b) =&amp;gt; a.age - b.age);
// 按名字字母序
users.sort((a, b) =&amp;gt; a.name.localeCompare(b.name));
// localeCompare 支持中文拼音排序：
// [&amp;#39;张三&amp;#39;,&amp;#39;李四&amp;#39;,&amp;#39;王五&amp;#39;].sort((a,b) =&amp;gt; a.localeCompare(b, &amp;#39;zh-CN&amp;#39;))
```
- 不想修改原数组？ 用 toSorted()（ES2023）或先拷贝：
```js
const original = [3, 1, 2];
// ES2023
const sorted = original.toSorted((a, b) =&amp;gt; a - b); // [1, 2, 3]
console.log(original); // [3, 1, 2]（不变）
// 兼容写法
const sorted2 = [...original].sort((a, b) =&amp;gt; a - b);
```
- flatMap — 映射 &amp;#43; 扁平化
- flatMap 等价于先 map 再 flat(1)，但只扁平一层。它特别适合一对多的映射场景。
-
```js
// 普通 map 产生嵌套数组
const sentences = [&amp;#39;hello world&amp;#39;, &amp;#39;goodbye moon&amp;#39;];
sentences.map(s =&amp;gt; s.split(&amp;#39; &amp;#39;));
// [[&amp;#39;hello&amp;#39;, &amp;#39;world&amp;#39;], [&amp;#39;goodbye&amp;#39;, &amp;#39;moon&amp;#39;]]
// flatMap 自动扁平一层
sentences.flatMap(s =&amp;gt; s.split(&amp;#39; &amp;#39;));
// [&amp;#39;hello&amp;#39;, &amp;#39;world&amp;#39;, &amp;#39;goodbye&amp;#39;, &amp;#39;moon&amp;#39;]
// 一对多：每个用户有多个标签
const users = [
{ name: &amp;#39;An&amp;#39;, tags: [&amp;#39;dev&amp;#39;, &amp;#39;js&amp;#39;] },
{ name: &amp;#39;Bob&amp;#39;, tags: [&amp;#39;design&amp;#39;, &amp;#39;css&amp;#39;] }
];
const allTags = users.flatMap(u =&amp;gt; u.tags);
// [&amp;#39;dev&amp;#39;, &amp;#39;js&amp;#39;, &amp;#39;design&amp;#39;, &amp;#39;css&amp;#39;]
// 条件性映射：过滤 &amp;#43; 转换一步完成
const nums = [1, 2, 3, 4, 5, 6];
const doubledEvens = nums.flatMap(n =&amp;gt; n % 2 === 0 ? [n * 2] : []);
// [4, 8, 12]
// 偶数返回 [n*2]（包含一个元素的数组），奇数返回 []（空数组被扁平掉了）
```
- forEach — 遍历执行
- forEach 对每个元素执行回调，没有返回值（返回 undefined）。它是纯粹用于副作用的方法。
-
```js
const users = [&amp;#39;An&amp;#39;, &amp;#39;Bob&amp;#39;, &amp;#39;Cat&amp;#39;];
users.forEach((name, index) =&amp;gt; {
console.log(`${index &amp;#43; 1}. ${name}`);
});
// 1. An
// 2. Bob
// 3. Cat
```
&lt;/textarea&gt;
&lt;h1 id="面向对象与核心机制"&gt;面向对象与核心机制&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-17568243"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-17568243" style="display:none;"&gt;
- 面向对象与核心机制
- this 指向
- 为什么 this 这么重要
- 和大多数语言不同，JS 中 this 的值不取决于函数在哪里定义，而取决于函数怎么被调用（箭头函数除外）
- 规则一：默认绑定
- 当函数作为独立函数调用（不通过对象、不用 call/apply/bind、不用 new），this 指向全局对象（浏览器中是 window，Node 中是 global）。严格模式下 this 是 undefined。
-
```js
function showThis() {
console.log(this);
}
showThis(); // 浏览器：window，Node：global
// 严格模式
function strictShow() {
&amp;#39;use strict&amp;#39;;
console.log(this);
}
strictShow(); // undefined
```
- 规则二：隐式绑定
- 当函数作为对象的方法调用时（obj.method()），this 指向调用它的那个对象。
-
```js
const user = {
name: &amp;#39;An&amp;#39;,
greet() {
console.log(this.name);
}
};
user.greet(); // &amp;#34;An&amp;#34;，this === user
// 链式对象：this 指向最近的调用者
const company = {
name: &amp;#39;MiniMax&amp;#39;,
department: {
name: &amp;#39;Agent Team&amp;#39;,
getName() {
return this.name;
}
}
};
company.department.getName(); // &amp;#34;Agent Team&amp;#34;，不是 &amp;#34;MiniMax&amp;#34;
// this 指向 department，因为 department 是最近的调用者
```
- 规则三：显式绑定 — call / apply / bind
- 当你想手动指定 this 时，使用这三个方法
- call — 立即调用，逐个传参
```js
function introduce(greeting, punctuation) {
console.log(`${greeting}, I&amp;#39;m ${this.name}${punctuation}`);
}
const user = { name: &amp;#39;An&amp;#39; };
introduce.call(user, &amp;#39;Hello&amp;#39;, &amp;#39;!&amp;#39;);
// &amp;#34;Hello, I&amp;#39;m An!&amp;#34;
// 第一个参数是 this 的值，后面依次是函数参数
```
- apply — 立即调用，参数以数组形式传入
```js
introduce.apply(user, [&amp;#39;Hi&amp;#39;, &amp;#39;~&amp;#39;]);
// &amp;#34;Hi, I&amp;#39;m An~&amp;#34;
// call 和 apply 的唯一区别就是参数传递方式
// 助记：Apply 用 Array
// 经典用法：借用方法
const arrayLike = { 0: &amp;#39;a&amp;#39;, 1: &amp;#39;b&amp;#39;, 2: &amp;#39;c&amp;#39;, length: 3 };
const realArray = Array.prototype.slice.call(arrayLike);
// [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;]
// 求数组最大值（ES5 时代）
Math.max.apply(null, [3, 1, 4, 1, 5]); // 5
// 现在用展开运算符更好：Math.max(...[3, 1, 4, 1, 5])
```
- bind — 返回新函数，永久绑定 this（不立即调用）：
```js
const user = { name: &amp;#39;An&amp;#39; };
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const greetAn = greet.bind(user);
greetAn(&amp;#39;Hello&amp;#39;); // &amp;#34;Hello, An&amp;#34;
greetAn(&amp;#39;Hey&amp;#39;); // &amp;#34;Hey, An&amp;#34;
// bind 也可以预设参数（偏函数/Partial Application）
const helloAn = greet.bind(user, &amp;#39;Hello&amp;#39;);
helloAn(); // &amp;#34;Hello, An&amp;#34;（greeting 已经被绑定为 &amp;#39;Hello&amp;#39;）
// 解决回调中 this 丢失的问题
setTimeout(user.greet.bind(user), 100); // ✅ 不会丢失 this
// bind 返回的函数，this 不可被再次修改
const bound = greet.bind(user);
bound.call({ name: &amp;#39;Bob&amp;#39; }, &amp;#39;Hi&amp;#39;); // &amp;#34;Hi, An&amp;#34;（仍然是 An，bind 的优先级更高）
```
- 规则四：new 绑定
- 当用 new 调用函数时，JS 会自动创建一个新对象，将 this 绑定到这个新对象上。
-
```js
function User(name, age) {
// new 调用时，this 自动指向新创建的空对象 {}
this.name = name;
this.age = age;
// 不需要 return，自动返回 this
}
const user = new User(&amp;#39;An&amp;#39;, 25);
console.log(user); // User { name: &amp;#39;An&amp;#39;, age: 25 }
// new 到底做了什么？等价于：
function myNew(Constructor, ...args) {
// 1. 创建空对象，原型指向构造函数的 prototype
const obj = Object.create(Constructor.prototype);
// 2. 执行构造函数，this 绑定到新对象
const result = Constructor.apply(obj, args);
// 3. 如果构造函数返回了对象，就用那个对象；否则返回新创建的对象
return result instanceof Object ? result : obj;
}
const user2 = myNew(User, &amp;#39;Bob&amp;#39;, 30);
console.log(user2); // User { name: &amp;#39;Bob&amp;#39;, age: 30 }
```
- 箭头函数中的 this
- 箭头函数没有自己的 this，它的 this 继承自定义时所在的外层作用域（词法 this），且永远不可改变。
-
```js
const user = {
name: &amp;#39;An&amp;#39;,
// 普通方法
greetNormal() {
console.log(this.name); // this 取决于调用方式
},
// 箭头函数作为方法 ❌
greetArrow: () =&amp;gt; {
console.log(this.name); // this 是外层作用域的 this，不是 user
}
};
user.greetNormal(); // &amp;#34;An&amp;#34; ✅
user.greetArrow(); // undefined ❌（箭头函数的 this 是全局/模块作用域）
```
- 绑定优先级
- new 绑定 &amp;gt; 显式绑定(call/apply/bind) &amp;gt; 隐式绑定(obj.fn()) &amp;gt; 默认绑定(fn())
- 原型与原型链
- 为什么需要原型
- 在没有原型机制的情况下，每次用构造函数创建实例，方法都会被重复创建
```js
function User(name) {
this.name = name;
// 每个实例都创建一个新的 greet 函数，浪费内存
this.greet = function() {
return `Hi, I&amp;#39;m ${this.name}`;
};
}
const u1 = new User(&amp;#39;An&amp;#39;);
const u2 = new User(&amp;#39;Bob&amp;#39;);
console.log(u1.greet === u2.greet); // false —— 两个不同的函数对象
// 如果创建 10000 个用户，就有 10000 份完全相同的 greet 函数
// 原型就是用来解决这个问题的
```
- prototype 与 __proto__
- prototype：只有函数才拥有的属性，它是一个对象，当用 new 调用该函数时，新创建的实例的原型会指向这个对象。
- __proto__（或者说 [[Prototype]]）：每个对象都有的内部属性，指向它的原型对象。__proto__ 是非标准但被广泛支持的访问方式，标准方式是 Object.getPrototypeOf()。
```js
function User(name) {
this.name = name;
}
// 在 prototype 上定义方法，所有实例共享
User.prototype.greet = function() {
return `Hi, I&amp;#39;m ${this.name}`;
};
const u1 = new User(&amp;#39;An&amp;#39;);
const u2 = new User(&amp;#39;Bob&amp;#39;);
// 实例的 __proto__ 指向构造函数的 prototype
console.log(u1.__proto__ === User.prototype); // true
console.log(Object.getPrototypeOf(u1) === User.prototype); // true（标准写法）
// 所有实例共享 prototype 上的方法
console.log(u1.greet === u2.greet); // true ✅ 同一个函数
u1.greet(); // &amp;#34;Hi, I&amp;#39;m An&amp;#34;
u2.greet(); // &amp;#34;Hi, I&amp;#39;m Bob&amp;#34;
```
- constructor 属性
- 每个函数的 prototype 对象默认有一个 constructor 属性，指回函数本身
-
```js
function User(name) {
this.name = name;
}
console.log(User.prototype.constructor === User); // true
const u = new User(&amp;#39;An&amp;#39;);
console.log(u.constructor === User); // true（通过原型链找到的）
// 可以用 constructor 判断实例是由哪个构造函数创建的
// 但不如 instanceof 可靠，因为 constructor 可以被覆盖
```
- 原型链（Prototype Chain）
- 当你访问一个对象的属性时，JS 引擎会沿着原型链逐层查找，直到找到该属性或到达链的顶端 null
-
```js
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return `${this.name} is eating`;
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父构造函数
this.breed = breed;
}
// 建立原型链：Dog.prototype 的原型指向 Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复 constructor
Dog.prototype.bark = function() {
return `${this.name} says woof!`;
};
const dog = new Dog(&amp;#39;Buddy&amp;#39;, &amp;#39;Golden&amp;#39;);
// 属性查找过程
dog.name; // &amp;#34;Buddy&amp;#34; ← 在 dog 自身找到
dog.bark(); // &amp;#34;Buddy says woof!&amp;#34; ← 在 Dog.prototype 上找到
dog.eat(); // &amp;#34;Buddy is eating&amp;#34; ← 在 Animal.prototype 上找到
dog.toString(); // &amp;#34;[object Object]&amp;#34; ← 在 Object.prototype 上找到
dog.whatever; // undefined ← 一路找到 null，没找到
```
- Object.create()
- Object.create(proto) 创建一个新对象，将它的 __proto__ 设为指定的对象。这是建立原型链最干净的方式。
- Class 语法与继承
- Class 的本质
- ES6 的 class 是原型继承的语法糖。
- 它不引入新的对象模型，底层仍然是原型机制，但写法更清晰、更接近传统面向对象语言。
-
```js
// ES5 写法
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.greet = function() {
return `Hi, I&amp;#39;m ${this.name}`;
};
// ES6 class 写法（完全等价）
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hi, I&amp;#39;m ${this.name}`;
}
}
// 验证底层仍然是原型
console.log(typeof User); // &amp;#34;function&amp;#34;
console.log(User.prototype.greet); // function
console.log(new User(&amp;#39;An&amp;#39;, 25).__proto__ === User.prototype); // true
```
- class 和构造函数的区别：
-
```js
// 1. class 必须用 new 调用
class Foo {}
// Foo(); // ❌ TypeError: Class constructor Foo cannot be invoked without &amp;#39;new&amp;#39;
// 构造函数不强制
function Bar() {}
Bar(); // ✅ 不报错（但通常是 bug）
// 2. class 内部默认严格模式
class StrictExample {
method() {
// 这里自动处于严格模式
// 未声明的变量赋值会报错等
}
}
// 3. class 声明不会提升（存在 TDZ）
// const u = new User(); // ❌ ReferenceError
// class User {}
// 4. class 中的方法不可枚举
class MyClass {
method() {}
}
console.log(Object.keys(MyClass.prototype)); // []（空，method 不可枚举）
// 对比 ES5
function MyFunc() {}
MyFunc.prototype.method = function() {};
console.log(Object.keys(MyFunc.prototype)); // [&amp;#39;method&amp;#39;]（可枚举）
```
- constructor 构造函数
- constructor 是 class 中的特殊方法，在 new 时自动调用。每个 class 只能有一个。
-
```js
class User {
constructor(name, age) {
// this 指向新创建的实例
this.name = name;
this.age = age;
this.createdAt = new Date();
}
}
const u = new User(&amp;#39;An&amp;#39;, 25);
console.log(u.name); // &amp;#34;An&amp;#34;
console.log(u.createdAt); // 当前时间
// 如果不写 constructor，会有一个默认的空 constructor
class Empty {}
// 等价于
class Empty {
constructor() {}
}
// constructor 中可以做参数验证
class PositiveNumber {
constructor(value) {
if (typeof value !== &amp;#39;number&amp;#39; || value &amp;lt;= 0) {
throw new Error(&amp;#39;必须是正数&amp;#39;);
}
this.value = value;
}
}
new PositiveNumber(5); // ✅
// new PositiveNumber(-1); // ❌ Error: 必须是正数
```
- 实例方法、访问器与类字段
-
```js
class User {
// --- 类字段（Class Fields，ES2022）---
// 直接在 class body 中声明，会成为实例自身的属性
role = &amp;#39;viewer&amp;#39;;
loginCount = 0;
constructor(name, email) {
this.name = name;
this.email = email;
}
// --- 实例方法 ---
// 定义在 User.prototype 上，所有实例共享
greet() {
return `Hi, I&amp;#39;m ${this.name}`;
}
login() {
this.loginCount&amp;#43;&amp;#43;;
return `${this.name} logged in (${this.loginCount} times)`;
}
// --- getter / setter ---
// 像属性一样访问，但实际上是方法调用
get displayName() {
return `${this.name} &amp;lt;${this.email}&amp;gt;`;
}
get isFrequent() {
return this.loginCount &amp;gt; 10;
}
set nickname(value) {
if (value.length &amp;lt; 2) {
throw new Error(&amp;#39;昵称至少2个字符&amp;#39;);
}
this._nickname = value;
}
get nickname() {
return this._nickname || this.name;
}
}
const u = new User(&amp;#39;An&amp;#39;, &amp;#39;an@test.com&amp;#39;);
// 类字段
console.log(u.role); // &amp;#34;viewer&amp;#34;
console.log(u.loginCount); // 0
// 实例方法
u.login(); // &amp;#34;An logged in (1 times)&amp;#34;
// getter（不加括号）
console.log(u.displayName); // &amp;#34;An &amp;lt;an@test.com&amp;gt;&amp;#34;
console.log(u.isFrequent); // false
// setter（像赋值一样使用）
u.nickname = &amp;#39;AnDev&amp;#39;;
console.log(u.nickname); // &amp;#34;AnDev&amp;#34;
// u.nickname = &amp;#39;A&amp;#39;; // ❌ Error: 昵称至少2个字符
```
- 静态方法与静态字段
- static 定义在类本身上，不是在实例上。通过类名调用，不能通过实例调用。
-
```js
class MathUtils {
// 静态方法
static add(a, b) {
return a &amp;#43; b;
}
static multiply(a, b) {
return a * b;
}
// 静态字段
static PI = 3.14159;
// 静态方法可以互相调用
static circleArea(radius) {
return MathUtils.PI * radius ** 2;
// 或者用 this（this 在静态方法中指向类本身）
// return this.PI * radius ** 2;
}
}
MathUtils.add(1, 2); // 3
MathUtils.PI; // 3.14159
MathUtils.circleArea(5); // 78.53975
// 不能通过实例调用
// const m = new MathUtils();
// m.add(1, 2); // ❌ TypeError: m.add is not a function
```
- 私有字段（Private Fields）
- ES2022 引入 # 前缀表示真正的私有属性和方法，只能在类内部访问。
```js
class BankAccount {
// 私有字段
#balance;
#owner;
#transactionHistory = [];
constructor(owner, initialBalance) {
this.#owner = owner;
this.#balance = initialBalance;
}
// 私有方法
#recordTransaction(type, amount) {
this.#transactionHistory.push({
type,
amount,
date: new Date(),
balance: this.#balance
});
}
// 公开方法（通过它们间接访问私有数据）
deposit(amount) {
if (amount &amp;lt;= 0) throw new Error(&amp;#39;金额必须大于0&amp;#39;);
this.#balance &amp;#43;= amount;
this.#recordTransaction(&amp;#39;deposit&amp;#39;, amount);
return this;
}
withdraw(amount) {
if (amount &amp;gt; this.#balance) throw new Error(&amp;#39;余额不足&amp;#39;);
this.#balance -= amount;
this.#recordTransaction(&amp;#39;withdraw&amp;#39;, amount);
return this;
}
get balance() {
return this.#balance;
}
get history() {
// 返回副本，防止外部修改
return [...this.#transactionHistory];
}
// 静态私有方法
static #validateAmount(amount) {
return typeof amount === &amp;#39;number&amp;#39; &amp;&amp; amount &amp;gt; 0;
}
}
const account = new BankAccount(&amp;#39;An&amp;#39;, 1000);
account.deposit(500).withdraw(200); // 链式调用
console.log(account.balance); // 1300
console.log(account.history); // [{...}, {...}]
// ❌ 私有字段外部完全不可访问
// account.#balance; // SyntaxError
// account.#recordTransaction; // SyntaxError
```
- extends 与 super — 类继承
- extends 建立继承关系，super 用于调用父类。
-
```js
class Animal {
constructor(name) {
this.name = name;
this.energy = 100;
}
eat(amount) {
this.energy &amp;#43;= amount;
return `${this.name} ate, energy: ${this.energy}`;
}
sleep(hours) {
this.energy &amp;#43;= hours * 10;
return `${this.name} slept ${hours}h, energy: ${this.energy}`;
}
info() {
return `${this.name} (energy: ${this.energy})`;
}
}
class Dog extends Animal {
// 子类的 constructor 必须在使用 this 之前调用 super()
constructor(name, breed) {
super(name); // 调用 Animal 的 constructor
this.breed = breed;
}
// 新方法
bark() {
this.energy -= 5;
return `${this.name} says Woof!`;
}
// 重写父类方法
eat(amount) {
// 调用父类的 eat 方法
super.eat(amount * 2); // 狗吃东西恢复双倍能量
return `${this.name} gobbled food! energy: ${this.energy}`;
}
// 扩展父类方法
info() {
return `${super.info()} [${this.breed}]`;
}
}
const dog = new Dog(&amp;#39;Buddy&amp;#39;, &amp;#39;Golden&amp;#39;);
dog.eat(10); // &amp;#34;Buddy gobbled food! energy: 120&amp;#34;（10*2=20，100&amp;#43;20=120）
dog.bark(); // &amp;#34;Buddy says Woof!&amp;#34;
dog.sleep(2); // &amp;#34;Buddy slept 2h, energy: 135&amp;#34;（继承自 Animal）
dog.info(); // &amp;#34;Buddy (energy: 135) [Golden]&amp;#34;
// 原型链验证
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
```
- 字符串与正则表达式
- 模板字符串深入
-
```js
// 基本插值
const name = &amp;#39;An&amp;#39;;
const greeting = `Hello, ${name}!`;
// 插值中可以放任意表达式
const price = 99;
const qty = 3;
console.log(`总价: ¥${price * qty}`); // &amp;#34;总价: ¥297&amp;#34;
console.log(`状态: ${qty &amp;gt; 0 ? &amp;#39;有货&amp;#39; : &amp;#39;缺货&amp;#39;}`); // &amp;#34;状态: 有货&amp;#34;
// 多行字符串（保留换行和缩进）
const html = `
&amp;lt;div class=&amp;#34;card&amp;#34;&amp;gt;
&amp;lt;h2&amp;gt;${name}&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Price: ¥${price}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
`;
// 嵌套模板
const items = [&amp;#39;JS&amp;#39;, &amp;#39;CSS&amp;#39;, &amp;#39;HTML&amp;#39;];
const list = `
&amp;lt;ul&amp;gt;
${items.map(item =&amp;gt; `&amp;lt;li&amp;gt;${item}&amp;lt;/li&amp;gt;`).join(&amp;#39;\n &amp;#39;)}
&amp;lt;/ul&amp;gt;
`;
```
- 模板标签
- 标签模板允许你用一个函数来处理模板字符串，这是一个强大但不太常见的特性：
-
```js
// 标签函数接收两个参数：
// strings — 模板中的静态文本部分（数组）
// values — 插值表达式的值（rest 参数）
function highlight(strings, ...values) {
return strings.reduce((result, str, i) =&amp;gt; {
const value = values[i] !== undefined ? `【${values[i]}】` : &amp;#39;&amp;#39;;
return result &amp;#43; str &amp;#43; value;
}, &amp;#39;&amp;#39;);
}
const lang = &amp;#39;JavaScript&amp;#39;;
const exp = 5;
console.log(highlight`我学了 ${lang} ${exp} 年`);
// &amp;#34;我学了 【JavaScript】 【5】 年&amp;#34;
// 实际应用1：SQL 防注入
function sql(strings, ...values) {
return {
text: strings.join(&amp;#39;$&amp;#39;), // 用占位符替代
values // 参数化查询
};
}
const userId = &amp;#34;1; DROP TABLE users; --&amp;#34;;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;
// { text: &amp;#34;SELECT * FROM users WHERE id = $&amp;#34;, values: [&amp;#34;1; DROP TABLE users; --&amp;#34;] }
// 实际应用2：国际化
// 实际应用3：CSS-in-JS（styled-components 就是用标签模板实现的）
```
- 常用字符串方法
-
```js
const str = &amp;#39; Hello, World! Hello, JavaScript! &amp;#39;;
// --- 查找类 ---
str.indexOf(&amp;#39;Hello&amp;#39;); // 2（第一次出现的位置）
str.lastIndexOf(&amp;#39;Hello&amp;#39;); // 17（最后一次出现的位置）
str.indexOf(&amp;#39;Python&amp;#39;); // -1（未找到）
str.includes(&amp;#39;World&amp;#39;); // true
str.startsWith(&amp;#39; Hello&amp;#39;); // true
str.endsWith(&amp;#39;! &amp;#39;); // true
str.search(/hello/i); // 2（用正则查找，返回索引）
// --- 提取类 ---
str.slice(2, 7); // &amp;#34;Hello&amp;#34;（起始, 结束索引，左闭右开）
str.slice(-5); // &amp;#34;t! &amp;#34;（从倒数第5个到末尾）
str.substring(2, 7); // &amp;#34;Hello&amp;#34;（类似 slice，但不支持负数）
str.at(2); // &amp;#34;H&amp;#34;（ES2022，支持负索引）
str.at(-3); // &amp;#34;! &amp;#34; → 不对，at 只取单个字符 → &amp;#34;!&amp;#34;
// 更正：str.at(-3) 返回 &amp;#39;!&amp;#39;
// --- 修改类（返回新字符串，原字符串不变）---
str.trim(); // &amp;#34;Hello, World! Hello, JavaScript!&amp;#34;
str.trimStart(); // &amp;#34;Hello, World! Hello, JavaScript! &amp;#34;
str.trimEnd(); // &amp;#34; Hello, World! Hello, JavaScript!&amp;#34;
str.toUpperCase(); // &amp;#34; HELLO, WORLD! HELLO, JAVASCRIPT! &amp;#34;
str.toLowerCase(); // &amp;#34; hello, world! hello, javascript! &amp;#34;
&amp;#39;hello&amp;#39;.repeat(3); // &amp;#34;hellohellohello&amp;#34;
// --- 填充类 ---
&amp;#39;42&amp;#39;.padStart(5, &amp;#39;0&amp;#39;); // &amp;#34;00042&amp;#34;（左填充到5位）
&amp;#39;42&amp;#39;.padEnd(5, &amp;#39;.&amp;#39;); // &amp;#34;42...&amp;#34;（右填充到5位）
&amp;#39;7&amp;#39;.padStart(2, &amp;#39;0&amp;#39;); // &amp;#34;07&amp;#34;（常用于格式化月份/日期）
// --- 分割类 ---
&amp;#39;a,b,c,d&amp;#39;.split(&amp;#39;,&amp;#39;); // [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;]
&amp;#39;hello&amp;#39;.split(&amp;#39;&amp;#39;); // [&amp;#39;h&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;l&amp;#39;, &amp;#39;l&amp;#39;, &amp;#39;o&amp;#39;]
&amp;#39;a-b-c&amp;#39;.split(&amp;#39;-&amp;#39;, 2); // [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;]（limit 参数）
// --- 替换类 ---
&amp;#39;hello world&amp;#39;.replace(&amp;#39;world&amp;#39;, &amp;#39;JS&amp;#39;); // &amp;#34;hello JS&amp;#34;（只替换第一个）
&amp;#39;aabbcc&amp;#39;.replace(&amp;#39;b&amp;#39;, &amp;#39;X&amp;#39;); // &amp;#34;aaXbcc&amp;#34;
&amp;#39;aabbcc&amp;#39;.replaceAll(&amp;#39;b&amp;#39;, &amp;#39;X&amp;#39;); // &amp;#34;aaXXcc&amp;#34;（ES2021，替换全部）
```
- replace 高级用法
-
```js
// 函数替换：每次匹配时调用函数，返回值作为替换内容
const result = &amp;#39;hello-world-foo&amp;#39;.replace(/\w&amp;#43;/g, (match) =&amp;gt; {
return match.charAt(0).toUpperCase() &amp;#43; match.slice(1);
});
// &amp;#34;Hello-World-Foo&amp;#34;
// 替换函数的参数：(match, ...groups, offset, fullString)
&amp;#39;2026-04-07&amp;#39;.replace(
/(\d{4})-(\d{2})-(\d{2})/,
(match, year, month, day) =&amp;gt; `${day}/${month}/${year}`
);
// &amp;#34;07/04/2026&amp;#34;
// 实际场景：模板引擎
function template(str, data) {
return str.replace(/\{\{(\w&amp;#43;)\}\}/g, (match, key) =&amp;gt; {
return data[key] ?? match; // 找不到就保留原文
});
}
template(&amp;#39;Hello, {{name}}! You have {{count}} messages.&amp;#39;, {
name: &amp;#39;An&amp;#39;,
count: 5
});
// &amp;#34;Hello, An! You have 5 messages.&amp;#34;
// 驼峰转换
function camelToKebab(str) {
return str.replace(/[A-Z]/g, (match) =&amp;gt; `-${match.toLowerCase()}`);
}
camelToKebab(&amp;#39;backgroundColor&amp;#39;); // &amp;#34;background-color&amp;#34;
camelToKebab(&amp;#39;fontSize&amp;#39;); // &amp;#34;font-size&amp;#34;
function kebabToCamel(str) {
return str.replace(/-([a-z])/g, (match, letter) =&amp;gt; letter.toUpperCase());
}
kebabToCamel(&amp;#39;background-color&amp;#39;); // &amp;#34;backgroundColor&amp;#34;
```
- 正则表达式基础语法
- 正则表达式用于模式匹配，由模式和标志组成。
- 创建方式
```js
// 字面量（推荐，不需要转义反斜杠）
const re1 = /hello/i;
// 构造函数（适用于动态构建正则）
const re2 = new RegExp(&amp;#39;hello&amp;#39;, &amp;#39;i&amp;#39;);
const keyword = &amp;#39;world&amp;#39;;
const re3 = new RegExp(keyword, &amp;#39;gi&amp;#39;); // 动态模式
```
- 常用标志
```js
/pattern/g // g — global，匹配所有，不只第一个
/pattern/i // i — case insensitive，忽略大小写
/pattern/m // m — multiline，^ 和 $ 匹配每行的开头结尾
/pattern/s // s — dotAll，让 . 也匹配换行符
/pattern/u // u — unicode，正确处理 Unicode（如 emoji、中文）
/pattern/d // d — indices，返回匹配的位置信息（ES2022）
// 可以组合
/pattern/giu
```
- 正则核心语法
- 字符类
```js
// 普通字符：匹配自身
/hello/ // 匹配 &amp;#34;hello&amp;#34;
// 元字符
. // 任意单个字符（除换行符，除非用 s 标志）
\d // 数字 [0-9]
\D // 非数字 [^0-9]
\w // 单词字符 [a-zA-Z0-9_]
\W // 非单词字符
\s // 空白字符（空格、tab、换行等）
\S // 非空白字符
\b // 单词边界
\B // 非单词边界
// 字符集合 []
[abc] // 匹配 a、b 或 c 中的任意一个
[a-z] // a 到 z
[A-Z0-9] // 大写字母或数字
[^abc] // 不是 a、b、c（^ 在 [] 内表示取反）
// 示例
/\d{3}-\d{4}/.test(&amp;#39;123-4567&amp;#39;); // true
/[aeiou]/i.test(&amp;#39;Hello&amp;#39;); // true（有元音字母）
/^[a-zA-Z_]\w*$/.test(&amp;#39;myVar_1&amp;#39;); // true（合法的变量名）
```
- 量词
```js
* // 0 次或多次
&amp;#43; // 1 次或多次
? // 0 次或 1 次
{n} // 恰好 n 次
{n,} // 至少 n 次
{n,m} // n 到 m 次
// 示例
/colou?r/ // &amp;#34;color&amp;#34; 或 &amp;#34;colour&amp;#34;（u 出现 0 或 1 次）
/\d{3,4}/ // 3 位或 4 位数字
/ha&amp;#43;/ // &amp;#34;ha&amp;#34;, &amp;#34;haa&amp;#34;, &amp;#34;haaa&amp;#34;...
// 贪婪 vs 非贪婪
// 默认贪婪：尽可能多地匹配
&amp;#39;&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;&amp;#39;.match(/&amp;lt;.&amp;#43;&amp;gt;/); // [&amp;#34;&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;&amp;#34;]（匹配了整个）
// 加 ? 变成非贪婪（懒惰）：尽可能少地匹配
&amp;#39;&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;&amp;#39;.match(/&amp;lt;.&amp;#43;?&amp;gt;/); // [&amp;#34;&amp;lt;div&amp;gt;&amp;#34;]（匹配第一个完整标签就停）
```
- 位置锚点
```js
^ // 字符串开头（m 标志下匹配行开头）
$ // 字符串结尾（m 标志下匹配行结尾）
\b // 单词边界
/^hello/.test(&amp;#39;hello world&amp;#39;); // true（以 hello 开头）
/world$/.test(&amp;#39;hello world&amp;#39;); // true（以 world 结尾）
/^hello$/.test(&amp;#39;hello&amp;#39;); // true（整个字符串就是 hello）
// \b 单词边界
&amp;#39;cat concatenate&amp;#39;.match(/\bcat\b/g); // [&amp;#34;cat&amp;#34;]（只匹配独立的 cat）
&amp;#39;cat concatenate&amp;#39;.match(/cat/g); // [&amp;#34;cat&amp;#34;, &amp;#34;cat&amp;#34;]（两个都匹配）
```
- 分组与或
```js
// 分组 ()
/(ab)&amp;#43;/.test(&amp;#39;ababab&amp;#39;); // true（ab 重复多次）
/(foo|bar)/.test(&amp;#39;foobar&amp;#39;); // true
// 或 |
/cat|dog/.test(&amp;#39;I have a cat&amp;#39;); // true
/^(http|https):\/\//.test(&amp;#39;https://example.com&amp;#39;); // true
// 非捕获分组 (?:) — 分组但不捕获
/(?:http|https):\/\/(\w&amp;#43;)/.exec(&amp;#39;https://example.com&amp;#39;);
// 结果中不包含 http/https 的捕获组
```
- 捕获组与反向引用
-
```js
// 编号捕获组
const dateRe = /(\d{4})-(\d{2})-(\d{2})/;
const match = dateRe.exec(&amp;#39;2026-04-07&amp;#39;);
console.log(match[0]); // &amp;#34;2026-04-07&amp;#34;（完整匹配）
console.log(match[1]); // &amp;#34;2026&amp;#34;（第1个捕获组）
console.log(match[2]); // &amp;#34;04&amp;#34;（第2个捕获组）
console.log(match[3]); // &amp;#34;07&amp;#34;（第3个捕获组）
// 命名捕获组（ES2018，强烈推荐）
const namedRe = /(?&amp;lt;year&amp;gt;\d{4})-(?&amp;lt;month&amp;gt;\d{2})-(?&amp;lt;day&amp;gt;\d{2})/;
const m = namedRe.exec(&amp;#39;2026-04-07&amp;#39;);
console.log(m.groups.year); // &amp;#34;2026&amp;#34;
console.log(m.groups.month); // &amp;#34;04&amp;#34;
console.log(m.groups.day); // &amp;#34;07&amp;#34;
// 解构命名捕获组
const { groups: { year, month, day } } = namedRe.exec(&amp;#39;2026-04-07&amp;#39;);
// 在 replace 中使用捕获组
&amp;#39;2026-04-07&amp;#39;.replace(
/(?&amp;lt;y&amp;gt;\d{4})-(?&amp;lt;m&amp;gt;\d{2})-(?&amp;lt;d&amp;gt;\d{2})/,
&amp;#39;$&amp;lt;d&amp;gt;/$&amp;lt;m&amp;gt;/$&amp;lt;y&amp;gt;&amp;#39;
);
// &amp;#34;07/04/2026&amp;#34;
// 编号引用
&amp;#39;2026-04-07&amp;#39;.replace(/(\d{4})-(\d{2})-(\d{2})/, &amp;#39;$3/$2/$1&amp;#39;);
// &amp;#34;07/04/2026&amp;#34;
// 反向引用：在正则内部引用前面的捕获组
// 匹配重复的单词
/\b(\w&amp;#43;)\s&amp;#43;\1\b/.test(&amp;#39;the the&amp;#39;); // true（\1 引用第一个捕获组）
/\b(\w&amp;#43;)\s&amp;#43;\1\b/.test(&amp;#39;the cat&amp;#39;); // false
```
- Map、Set 与迭代器
- Map — 键值映射
- Map 是 ES6 引入的键值对集合，和普通对象的核心区别在于：任何类型都可以作为键。
-
```js
const map = new Map();
// 设置键值对
map.set(&amp;#39;name&amp;#39;, &amp;#39;An&amp;#39;);
map.set(42, &amp;#39;number key&amp;#39;);
map.set(true, &amp;#39;boolean key&amp;#39;);
const objKey = { id: 1 };
const fnKey = () =&amp;gt; {};
map.set(objKey, &amp;#39;对象作为键&amp;#39;);
map.set(fnKey, &amp;#39;函数作为键&amp;#39;);
// 获取
map.get(&amp;#39;name&amp;#39;); // &amp;#34;An&amp;#34;
map.get(42); // &amp;#34;number key&amp;#34;
map.get(objKey); // &amp;#34;对象作为键&amp;#34;
map.get({ id: 1 }); // undefined！不是同一个引用
// 检查与删除
map.has(&amp;#39;name&amp;#39;); // true
map.delete(42); // true（删除成功）
map.size; // 4
// 清空
map.clear();
map.size; // 0
```
- 用可迭代对象初始化：
```js
// 用二维数组初始化
const map = new Map([
[&amp;#39;name&amp;#39;, &amp;#39;An&amp;#39;],
[&amp;#39;age&amp;#39;, 25],
[&amp;#39;city&amp;#39;, &amp;#39;Tokyo&amp;#39;]
]);
// 从对象转换
const obj = { a: 1, b: 2, c: 3 };
const mapFromObj = new Map(Object.entries(obj));
// 从 Map 转回对象
const backToObj = Object.fromEntries(mapFromObj);
// { a: 1, b: 2, c: 3 }
```
- Map vs 普通对象
```js
// 1. 键的类型
// 对象的键只能是 string 或 Symbol
const obj = {};
obj[1] = &amp;#39;one&amp;#39;;
obj[&amp;#39;1&amp;#39;] = &amp;#39;string one&amp;#39;;
console.log(obj[1]); // &amp;#34;string one&amp;#34;（数字键被转成了字符串，覆盖了）
// Map 的键保持原始类型
const map = new Map();
map.set(1, &amp;#39;number one&amp;#39;);
map.set(&amp;#39;1&amp;#39;, &amp;#39;string one&amp;#39;);
map.get(1); // &amp;#34;number one&amp;#34;
map.get(&amp;#39;1&amp;#39;); // &amp;#34;string one&amp;#34;（两个不同的键）
// 2. 顺序保证
// Map 始终按插入顺序遍历
// 对象的键顺序在现代引擎中也基本有序，但规范上数字键会排在前面
// 3. 性能
// Map 在频繁增删键值对时性能更好
// Map 有原生的 size 属性（O(1)），对象需要 Object.keys(obj).length
// 4. 原型污染
// 对象会继承 Object.prototype 上的属性
const plain = {};
console.log(&amp;#39;toString&amp;#39; in plain); // true（原型上的）
// Map 没有这个问题
const safeMap = new Map();
safeMap.has(&amp;#39;toString&amp;#39;); // false
```
- Set - 唯一值集合
-
```js
const set = new Set();
// 添加
set.add(1);
set.add(2);
set.add(3);
set.add(2); // 重复，被忽略
set.add(&amp;#39;2&amp;#39;); // 字符串 &amp;#34;2&amp;#34; 和数字 2 是不同的值
console.log(set.size); // 4
// 检查与删除
set.has(2); // true
set.delete(2); // true
set.has(2); // false
// 初始化
const set2 = new Set([1, 2, 3, 2, 1]); // Set { 1, 2, 3 }
// 清空
set.clear();
```
- 可迭代协议
- JavaScript 中 for...of、展开运算符 ...、解构赋值等特性，都依赖可迭代协议。
- 一个对象如果实现了 Symbol.iterator 方法，并且该方法返回一个迭代器，这个对象就是可迭代的。
-
```js
// 内置可迭代对象
// Array, String, Map, Set, TypedArray, arguments, NodeList
// 验证是否可迭代
function isIterable(obj) {
return obj != null &amp;&amp; typeof obj[Symbol.iterator] === &amp;#39;function&amp;#39;;
}
isIterable([1, 2, 3]); // true
isIterable(&amp;#39;hello&amp;#39;); // true
isIterable(new Map()); // true
isIterable(new Set()); // true
isIterable({ a: 1 }); // false（普通对象不可迭代）
isIterable(42); // false
```
- 迭代器协议：
- 迭代器是一个对象，它有一个 next() 方法，每次调用返回 { value, done }。
-
```js
// 手动使用迭代器
const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 10, done: false }
iterator.next(); // { value: 20, done: false }
iterator.next(); // { value: 30, done: false }
iterator.next(); // { value: undefined, done: true }
// for...of 本质就是自动调用迭代器
// 等价于：
const iter = arr[Symbol.iterator]();
let result = iter.next();
while (!result.done) {
console.log(result.value);
result = iter.next();
}
```
- 自定义可迭代对象
-
```js
// 实现一个范围对象
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
const step = this.step;
return {
next() {
if (current &amp;lt;= end) {
const value = current;
current &amp;#43;= step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const range = new Range(1, 5);
for (const n of range) {
console.log(n); // 1, 2, 3, 4, 5
}
// 因为实现了迭代协议，所有相关语法都自动支持
console.log([...range]); // [1, 2, 3, 4, 5]
const [a, b, c] = range; // a=1, b=2, c=3
const nums = Array.from(range); // [1, 2, 3, 4, 5]
const set = new Set(range); // Set { 1, 2, 3, 4, 5 }
// 带步长
const evens = new Range(0, 10, 2);
console.log([...evens]); // [0, 2, 4, 6, 8, 10]
```
- 生成器函数
- 生成器用 function* 声明，yield 暂停并产出值。它自动实现了迭代器协议。
-
```js
// 基本语法
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: undefined, done: true }
// 生成器自动可迭代
for (const n of numberGenerator()) {
console.log(n); // 1, 2, 3
}
console.log([...numberGenerator()]); // [1, 2, 3]
```
- 错误处理
- 错误类型
-
```js
// 1. SyntaxError — 语法错误，代码根本无法解析
// const x = ; // SyntaxError: Unexpected token &amp;#39;;&amp;#39;
// 这种错误在代码运行前就会被发现
// 2. ReferenceError — 引用了不存在的变量
// console.log(notDefined); // ReferenceError: notDefined is not defined
// 3. TypeError — 类型错误，对值进行了不支持的操作
// null.toString(); // TypeError: Cannot read properties of null
// (42)(); // TypeError: 42 is not a function
// const x = 1; x = 2; // TypeError: Assignment to constant variable
// 4. RangeError — 值超出有效范围
// new Array(-1); // RangeError: Invalid array length
// (1.5).toFixed(200); // RangeError: toFixed() digits argument must be between 0 and 100
// 5. URIError — URI 编解码错误
// decodeURIComponent(&amp;#39;%&amp;#39;); // URIError: URI malformed
// 6. EvalError — eval 相关（现代 JS 中很少见）
// 所有错误都继承自 Error
const err = new TypeError(&amp;#39;出错了&amp;#39;);
console.log(err instanceof TypeError); // true
console.log(err instanceof Error); // true
console.log(err.message); // &amp;#34;出错了&amp;#34;
console.log(err.name); // &amp;#34;TypeError&amp;#34;
console.log(err.stack); // 完整的调用栈信息
```
- try/catch/finally
-
```js
// 基本结构
try {
// 可能出错的代码
const data = JSON.parse(&amp;#39;invalid json&amp;#39;);
} catch (error) {
// 出错时执行
console.log(error.message); // &amp;#34;Unexpected token i in JSON at position 0&amp;#34;
console.log(error.name); // &amp;#34;SyntaxError&amp;#34;
} finally {
// 无论是否出错都执行
console.log(&amp;#39;清理工作&amp;#39;);
}
// catch 可以省略参数（ES2019）
try {
JSON.parse(&amp;#39;{}&amp;#39;);
} catch {
// 不需要 error 对象时可以省略
console.log(&amp;#39;解析失败&amp;#39;);
}
// finally 即使有 return 也会执行
function example() {
try {
return &amp;#39;来自 try&amp;#39;;
} finally {
console.log(&amp;#39;finally 仍然执行&amp;#39;);
// ⚠️ 如果 finally 里也有 return，会覆盖 try 的 return
// return &amp;#39;来自 finally&amp;#39;; // 不推荐这样做
}
}
example(); // 打印 &amp;#34;finally 仍然执行&amp;#34;，返回 &amp;#34;来自 try&amp;#34;
```
- throw 与自定义 Error
-
```js
// 基本自定义错误
class AppError extends Error {
constructor(message, code) {
super(message);
this.name = &amp;#39;AppError&amp;#39;;
this.code = code;
}
}
// 特定业务错误
class ValidationError extends AppError {
constructor(field, message) {
super(message, &amp;#39;VALIDATION_ERROR&amp;#39;);
this.name = &amp;#39;ValidationError&amp;#39;;
this.field = field;
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} with id ${id} not found`, &amp;#39;NOT_FOUND&amp;#39;);
this.name = &amp;#39;NotFoundError&amp;#39;;
this.resource = resource;
this.id = id;
}
}
class AuthenticationError extends AppError {
constructor(message = &amp;#39;未认证&amp;#39;) {
super(message, &amp;#39;UNAUTHORIZED&amp;#39;);
this.name = &amp;#39;AuthenticationError&amp;#39;;
}
}
// 使用
function getUser(id) {
if (typeof id !== &amp;#39;number&amp;#39;) {
throw new ValidationError(&amp;#39;id&amp;#39;, &amp;#39;ID 必须是数字&amp;#39;);
}
const user = database.find(u =&amp;gt; u.id === id);
if (!user) {
throw new NotFoundError(&amp;#39;User&amp;#39;, id);
}
return user;
}
```
- 模块化
- ES Modules — import / export
- ES Modules 是 JavaScript 的官方模块系统，每个文件就是一个模块，有自己的作用域。
- 命名导出（Named Exports）：
-
```js
// utils.js
// 声明时导出
export const PI = 3.14159;
export function add(a, b) {
return a &amp;#43; b;
}
export class Calculator {
multiply(a, b) { return a * b; }
}
// 也可以统一导出
const subtract = (a, b) =&amp;gt; a - b;
const divide = (a, b) =&amp;gt; a / b;
export { subtract, divide };
```
- 命名导入
-
```js
// main.js
// 导入指定的导出
import { add, PI, Calculator } from &amp;#39;./utils.js&amp;#39;;
console.log(add(1, 2)); // 3
console.log(PI); // 3.14159
// 重命名导入
import { add as sum, subtract as sub } from &amp;#39;./utils.js&amp;#39;;
sum(1, 2); // 3
// 导入全部到一个命名空间
import * as Utils from &amp;#39;./utils.js&amp;#39;;
Utils.add(1, 2);
Utils.PI;
```
- 默认导出
- 每个模块只能有一个默认导出。
-
```js
// logger.js
export default class Logger {
log(msg) { console.log(`[LOG] ${msg}`); }
error(msg) { console.error(`[ERR] ${msg}`); }
}
// 也可以导出函数或值
// export default function() { ... }
// export default 42;
```
-
```js
// 导入默认导出（不需要花括号，名字可以随意取）
import Logger from &amp;#39;./logger.js&amp;#39;;
import MyLogger from &amp;#39;./logger.js&amp;#39;; // 不同名字也行
const logger = new Logger();
logger.log(&amp;#39;hello&amp;#39;);
```
- 混合使用
```js
// api.js
export default class API {
constructor(baseUrl) { this.baseUrl = baseUrl; }
}
export const VERSION = &amp;#39;2.0&amp;#39;;
export function createAPI(url) {
return new API(url);
}
// 同时导入默认和命名导出
import API, { VERSION, createAPI } from &amp;#39;./api.js&amp;#39;;
```
- 高级模块用法
- 重新导出
```js
// models/user.js
export class User { /* ... */ }
// models/post.js
export class Post { /* ... */ }
// models/comment.js
export class Comment { /* ... */ }
// models/index.js — 统一入口
export { User } from &amp;#39;./user.js&amp;#39;;
export { Post } from &amp;#39;./post.js&amp;#39;;
export { Comment } from &amp;#39;./comment.js&amp;#39;;
// 重新导出全部
export * from &amp;#39;./user.js&amp;#39;;
// 重新导出并重命名
export { User as UserModel } from &amp;#39;./user.js&amp;#39;;
// 重新导出默认导出
export { default as User } from &amp;#39;./user.js&amp;#39;;
```
- 动态导入
```js
// 按需加载
async function loadChart() {
const { Chart } = await import(&amp;#39;./chart.js&amp;#39;);
const chart = new Chart(&amp;#39;#container&amp;#39;);
chart.render(data);
}
// 条件加载
async function loadLocale(lang) {
const messages = await import(`./locales/${lang}.js`);
return messages.default;
}
// 搭配 UI 事件
button.addEventListener(&amp;#39;click&amp;#39;, async () =&amp;gt; {
const { openModal } = await import(&amp;#39;./modal.js&amp;#39;);
openModal();
});
// 多个模块并行加载
const [moduleA, moduleB] = await Promise.all([
import(&amp;#39;./a.js&amp;#39;),
import(&amp;#39;./b.js&amp;#39;)
]);
```
- CommonJS 对比
- CommonJS 是 Node.js 传统的模块系统，和 ES Modules 有重要区别。
-
```js
// CommonJS 语法
// 导出
module.exports = { add, subtract };
// 或
exports.add = add;
// 导入
const { add, subtract } = require(&amp;#39;./utils&amp;#39;);
const utils = require(&amp;#39;./utils&amp;#39;);
// 主要区别：
// 1. CJS 是同步加载，ESM 是异步的
// 2. CJS 的 require 可以在任何地方调用，ESM 的 import 必须在顶层
// 3. CJS 导出的是值的拷贝，ESM 导出的是活绑定
// 4. CJS 用 .js 后缀，ESM 在 Node 中用 .mjs 或在 package.json 中设 &amp;#34;type&amp;#34;: &amp;#34;module&amp;#34;
// Node.js 中使用 ESM 的两种方式：
// 方式1：文件后缀用 .mjs
// 方式2：package.json 中添加 &amp;#34;type&amp;#34;: &amp;#34;module&amp;#34;
```
&lt;/textarea&gt;
&lt;h1 id="异步编程与浏览器-api"&gt;异步编程与浏览器 API&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-51483267"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-51483267" style="display:none;"&gt;
- 异步编程与浏览器 API
- 异步基础与回调
- 同步 vs 异步
- JavaScript 是单线程语言——同一时刻只能执行一段代码
- 现实中很多操作是耗时的（网络请求、文件读写、定时器），如果同步等待，整个程序就会卡住
- 事件循环
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/04/07/20260407204918171.png,531,461)
- 宏任务与微任务
-
```js
console.log(&amp;#39;1. 同步&amp;#39;);
setTimeout(() =&amp;gt; {
console.log(&amp;#39;2. 宏任务 - setTimeout&amp;#39;);
}, 0);
Promise.resolve().then(() =&amp;gt; {
console.log(&amp;#39;3. 微任务 - Promise.then&amp;#39;);
});
queueMicrotask(() =&amp;gt; {
console.log(&amp;#39;4. 微任务 - queueMicrotask&amp;#39;);
});
console.log(&amp;#39;5. 同步&amp;#39;);
// 输出顺序：
// 1. 同步
// 5. 同步
// 3. 微任务 - Promise.then
// 4. 微任务 - queueMicrotask
// 2. 宏任务 - setTimeout
// 解释：
// 第一轮：执行所有同步代码 → 1, 5
// 清空微任务队列 → 3, 4
// 取一个宏任务 → 2
```
- setTimeout 与 setInterval
- setTimeout — 延迟执行：
-
```js
// 基本用法
const timerId = setTimeout(() =&amp;gt; {
console.log(&amp;#39;3 秒后执行&amp;#39;);
}, 3000);
// 取消定时器
clearTimeout(timerId);
// 传递参数给回调
setTimeout((name, age) =&amp;gt; {
console.log(`${name}, ${age}`);
}, 1000, &amp;#39;An&amp;#39;, 25);
// 1 秒后打印 &amp;#34;An, 25&amp;#34;
// ⚠️ 延迟时间不是精确的
// setTimeout(fn, 100) 意思是&amp;#34;至少 100ms 后执行&amp;#34;
// 实际可能更长（调用栈忙、页面后台时浏览器会节流等）
// 最小延迟：嵌套 setTimeout 超过 5 层后，最小延迟被限制为 4ms
function nested(depth = 0) {
const start = performance.now();
setTimeout(() =&amp;gt; {
console.log(`depth ${depth}: ${(performance.now() - start).toFixed(1)}ms`);
if (depth &amp;lt; 10) nested(depth &amp;#43; 1);
}, 0);
}
// 前几层接近 0ms，深层后会变成 4ms&amp;#43;
```
- 回调函数模式
- 回调是 JavaScript 最早的异步处理方式——把&amp;#34;完成后要做的事&amp;#34;作为函数传进去。
-
```js
// 最简单的回调
function fetchData(callback) {
setTimeout(() =&amp;gt; {
const data = { name: &amp;#39;An&amp;#39;, age: 25 };
callback(data);
}, 1000);
}
fetchData((data) =&amp;gt; {
console.log(&amp;#39;收到数据:&amp;#39;, data);
});
// Node.js 风格的回调：错误优先（Error-first Callback）
function readFile(path, callback) {
setTimeout(() =&amp;gt; {
if (path === &amp;#39;&amp;#39;) {
callback(new Error(&amp;#39;路径不能为空&amp;#39;), null);
} else {
callback(null, `${path} 的内容`); // 第一个参数是错误，第二个是结果
}
}, 500);
}
readFile(&amp;#39;data.txt&amp;#39;, (error, data) =&amp;gt; {
if (error) {
console.error(&amp;#39;读取失败:&amp;#39;, error.message);
return;
}
console.log(&amp;#39;读取成功:&amp;#39;, data);
});
```
- Promise
- Promise 是什么
- Promise 是一个代表异步操作最终结果的对象。它解决了回调地狱的问题，让异步代码像同步一样线性书写。
- 一个 Promise 有三种状态，一旦从 pending 变为 fulfilled 或 rejected，就不可再改变：
-
```js
// pending（进行中）→ fulfilled（已成功）
// pending（进行中）→ rejected（已失败）
// 创建 Promise
const promise = new Promise((resolve, reject) =&amp;gt; {
// 这个函数（executor）立即同步执行
console.log(&amp;#39;executor 执行了&amp;#39;);
// 异步操作
setTimeout(() =&amp;gt; {
const success = true;
if (success) {
resolve(&amp;#39;成功的数据&amp;#39;); // pending → fulfilled
} else {
reject(new Error(&amp;#39;失败了&amp;#39;)); // pending → rejected
}
}, 1000);
});
console.log(&amp;#39;Promise 创建后&amp;#39;);
// 输出顺序：
// &amp;#34;executor 执行了&amp;#34;（同步）
// &amp;#34;Promise 创建后&amp;#34;（同步）
// （1 秒后）promise 被 resolve
```
- then/catch/finally
- then — 处理成功和失败：
-
```js
const promise = new Promise((resolve) =&amp;gt; {
setTimeout(() =&amp;gt; resolve(42), 1000);
});
// then 接受两个回调：onFulfilled 和 onRejected
promise.then(
(value) =&amp;gt; console.log(&amp;#39;成功:&amp;#39;, value), // 42
(error) =&amp;gt; console.log(&amp;#39;失败:&amp;#39;, error)
);
// 通常只传第一个参数，用 catch 处理错误
promise
.then(value =&amp;gt; console.log(&amp;#39;成功:&amp;#39;, value))
.catch(error =&amp;gt; console.log(&amp;#39;失败:&amp;#39;, error));
```
- then 的返回值——链式调用的基础：
- then 始终返回一个新的 Promise。返回值决定了新 Promise 的状态：
-
```js
// 1. 返回普通值 → 新 Promise 以该值 resolve
Promise.resolve(1)
.then(v =&amp;gt; v &amp;#43; 1) // 返回 2
.then(v =&amp;gt; v * 3) // 返回 6
.then(v =&amp;gt; console.log(v)); // 6
// 2. 返回 Promise → 新 Promise 跟随返回的 Promise
Promise.resolve(1)
.then(v =&amp;gt; {
return new Promise(resolve =&amp;gt; {
setTimeout(() =&amp;gt; resolve(v &amp;#43; 10), 500);
});
})
.then(v =&amp;gt; console.log(v)); // 11（等待 500ms 后）
// 3. 抛出异常 → 新 Promise 被 reject
Promise.resolve(1)
.then(v =&amp;gt; {
throw new Error(&amp;#39;出错了&amp;#39;);
})
.then(v =&amp;gt; console.log(&amp;#39;不会执行&amp;#39;))
.catch(err =&amp;gt; console.log(err.message)); // &amp;#34;出错了&amp;#34;
// 4. 不写 return → 返回 undefined
Promise.resolve(1)
.then(v =&amp;gt; { v &amp;#43; 1; }) // 忘了 return！
.then(v =&amp;gt; console.log(v)); // undefined
```
- catch 错误处理
-
```js
// catch 是 .then(null, onRejected) 的语法糖
// 它能捕获前面所有 then 中抛出的错误
Promise.resolve(1)
.then(v =&amp;gt; {
throw new Error(&amp;#39;then 中的错误&amp;#39;);
})
.then(v =&amp;gt; {
console.log(&amp;#39;跳过&amp;#39;); // 不执行
})
.catch(err =&amp;gt; {
console.log(&amp;#39;捕获:&amp;#39;, err.message); // &amp;#34;then 中的错误&amp;#34;
return &amp;#39;已恢复&amp;#39;; // catch 也可以返回值，让链继续
})
.then(v =&amp;gt; {
console.log(v); // &amp;#34;已恢复&amp;#34;（链恢复了）
});
// ⚠️ then 的第二个参数 vs catch 的区别
Promise.reject(new Error(&amp;#39;初始错误&amp;#39;))
.then(
v =&amp;gt; console.log(&amp;#39;成功&amp;#39;),
err =&amp;gt; {
console.log(&amp;#39;then 的 onRejected:&amp;#39;, err.message);
throw new Error(&amp;#39;新错误&amp;#39;);
}
);
// &amp;#34;新错误&amp;#34; 无法被捕获！因为同一个 then 的 onRejected 不能捕获 onFulfilled 的错误
// ✅ 用 catch 更安全
Promise.reject(new Error(&amp;#39;初始错误&amp;#39;))
.then(v =&amp;gt; console.log(&amp;#39;成功&amp;#39;))
.catch(err =&amp;gt; console.log(&amp;#39;catch 捕获:&amp;#39;, err.message));
```
- finally — 无论成败都执行：
-
```js
function fetchData() {
showLoading();
return fetch(&amp;#39;/api/data&amp;#39;)
.then(res =&amp;gt; res.json())
.then(data =&amp;gt; {
displayData(data);
return data;
})
.catch(err =&amp;gt; {
showError(err.message);
})
.finally(() =&amp;gt; {
hideLoading(); // 无论成功失败都要隐藏 loading
});
}
// finally 的特殊行为：
// 1. 不接收参数（不知道是成功还是失败）
// 2. 默认&amp;#34;透传&amp;#34;前一步的值
Promise.resolve(42)
.finally(() =&amp;gt; {
console.log(&amp;#39;清理&amp;#39;); // 执行
return 999; // 返回值被忽略（除非抛错）
})
.then(v =&amp;gt; console.log(v)); // 42（透传，不是 999）
// 3. 如果 finally 中抛出错误，会覆盖之前的结果
Promise.resolve(42)
.finally(() =&amp;gt; {
throw new Error(&amp;#39;finally 出错&amp;#39;);
})
.catch(err =&amp;gt; console.log(err.message)); // &amp;#34;finally 出错&amp;#34;
```
- Promise 静态方法
- Promise.resolve / Promise.reject — 快速创建：
-
```js
// 创建一个立即 resolve 的 Promise
const p1 = Promise.resolve(42);
p1.then(v =&amp;gt; console.log(v)); // 42
// 如果传入 Promise，原样返回
const p2 = Promise.resolve(Promise.resolve(&amp;#39;hello&amp;#39;));
p2.then(v =&amp;gt; console.log(v)); // &amp;#34;hello&amp;#34;（不会嵌套）
// 如果传入 thenable（有 then 方法的对象），会展开
const thenable = {
then(resolve) {
resolve(&amp;#39;from thenable&amp;#39;);
}
};
Promise.resolve(thenable).then(v =&amp;gt; console.log(v)); // &amp;#34;from thenable&amp;#34;
// 创建一个立即 reject 的 Promise
const p3 = Promise.reject(new Error(&amp;#39;失败&amp;#39;));
p3.catch(err =&amp;gt; console.log(err.message)); // &amp;#34;失败&amp;#34;
```
- Promise.all — 全部成功才成功：
-
```js
const p1 = fetch(&amp;#39;/api/users&amp;#39;).then(r =&amp;gt; r.json());
const p2 = fetch(&amp;#39;/api/posts&amp;#39;).then(r =&amp;gt; r.json());
const p3 = fetch(&amp;#39;/api/comments&amp;#39;).then(r =&amp;gt; r.json());
// 并行执行，全部完成后返回结果数组（顺序与传入一致）
Promise.all([p1, p2, p3])
.then(([users, posts, comments]) =&amp;gt; {
console.log(users, posts, comments);
})
.catch(err =&amp;gt; {
// ⚠️ 任何一个失败，整个 all 就失败
// 其他 Promise 仍然会执行完，但结果被丢弃
console.error(&amp;#39;有一个请求失败:&amp;#39;, err);
});
// 传入空数组立即 resolve
Promise.all([]).then(v =&amp;gt; console.log(v)); // []
// 非 Promise 值会被自动包装
Promise.all([1, &amp;#39;hello&amp;#39;, Promise.resolve(42)])
.then(v =&amp;gt; console.log(v)); // [1, &amp;#39;hello&amp;#39;, 42]
// 实际应用：批量操作
const userIds = [1, 2, 3, 4, 5];
const users = await Promise.all(
userIds.map(id =&amp;gt; getUser(id))
);
```
- async/await
- async 函数基础
- async/await 是 Promise 的语法糖，让异步代码看起来像同步代码一样。
-
```js
// async 函数始终返回 Promise
async function greet() {
return &amp;#39;Hello&amp;#39;; // 自动包装为 Promise.resolve(&amp;#39;Hello&amp;#39;)
}
greet().then(v =&amp;gt; console.log(v)); // &amp;#34;Hello&amp;#34;
// 等价于
function greet() {
return Promise.resolve(&amp;#39;Hello&amp;#39;);
}
// 即使返回普通值，也是 Promise
async function getNumber() {
return 42;
}
console.log(getNumber() instanceof Promise); // true
// 抛出的错误变成 rejected Promise
async function fail() {
throw new Error(&amp;#39;出错了&amp;#39;);
}
fail().catch(err =&amp;gt; console.log(err.message)); // &amp;#34;出错了&amp;#34;
```
- await 表达式
- await 只能在 async 函数（或模块顶层）内部使用。它暂停函数执行，等待 Promise resolve，然后返回 resolve 的值。
-
```js
async function example() {
console.log(&amp;#39;1. 开始&amp;#39;);
// await 暂停，等 Promise resolve
const result = await new Promise(resolve =&amp;gt; {
setTimeout(() =&amp;gt; resolve(&amp;#39;数据&amp;#39;), 1000);
});
console.log(&amp;#39;2. 收到:&amp;#39;, result); // 1 秒后
console.log(&amp;#39;3. 继续&amp;#39;);
}
example();
console.log(&amp;#39;4. async 函数外部&amp;#39;);
// 输出：
// 1. 开始
// 4. async 函数外部（不等待 async 函数）
// （1 秒后）
// 2. 收到: 数据
// 3. 继续
```
- await 的本质——对比 then 链：
```js
// Promise 链
function getData() {
return getUser(1)
.then(user =&amp;gt; getOrders(user.id))
.then(orders =&amp;gt; getOrderDetail(orders[0].id))
.then(detail =&amp;gt; {
console.log(detail);
return detail;
});
}
// async/await（完全等价，但可读性好得多）
async function getData() {
const user = await getUser(1);
const orders = await getOrders(user.id);
const detail = await getOrderDetail(orders[0].id);
console.log(detail);
return detail;
}
// 最大的优势：中间变量全部可用
// 不需要像 Promise 链那样用嵌套或外部变量传递
```
- 错误处理
- try/catch
```js
async function fetchUserData(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const detail = await getOrderDetail(orders[0].id);
return { user, orders, detail };
} catch (error) {
// 任何一步的 reject 或 throw 都会被捕获
console.error(&amp;#39;获取数据失败:&amp;#39;, error.message);
throw error; // 重新抛出或返回兜底数据
}
}
// 精细化错误处理：每步单独 try/catch
async function fetchWithFallback(userId) {
let user;
try {
user = await getUser(userId);
} catch (error) {
console.warn(&amp;#39;用户服务异常，使用默认数据&amp;#39;);
user = { id: userId, name: &amp;#39;Unknown&amp;#39; };
}
let orders;
try {
orders = await getOrders(user.id);
} catch (error) {
console.warn(&amp;#39;订单服务异常&amp;#39;);
orders = [];
}
return { user, orders };
}
```
&lt;/textarea&gt;</description></item></channel></rss>