20251017-ES6八股文整理版

152 阅读35分钟

🌸 第 1–5 页:var、let、const 的区别详解


🧋一、整体概念先感受一下

你可以把 varletconst 想成三种“放原料的柜子”:

关键字作用范围特点比喻
var函数作用域会“提前声明”老式开放柜子(谁都能乱翻)
let块级作用域不会提前声明,不能重复声明独立储物格(进门前要钥匙)
const块级作用域声明后不能改锁死的储物柜(封印配方)

🧃1.1 var:老前辈,开放式“公共奶茶柜”

📘特点:

  1. 会变量提升(hoisting) 意思是:即使你还没声明变量,JS 也会先“默默创建”它。
  2. 作用域是函数级(function scope) 在函数里有效,不受花括号影响。
  3. 可以重复声明

🍬代码解释:

console.log(a) // 输出 undefined
var a = 10

JS 运行时其实偷偷做了这件事:

var a // 先声明
console.log(a) // undefined
a = 10 // 再赋值

🧋比喻:

“var”就像老式奶茶铺的原料柜—— 还没放料时,柜子就已经在那里(只是空的)。 所以你去拿东西不会报错,但只能拿到“undefined 的空气”。


🍰再看块级问题:

{
  var drink = "奶茶"
}
console.log(drink) // 输出 奶茶

👉 即使在 {} 花括号里定义的,也能在外面拿到。 这说明:var 没有“块级作用域”概念。

🧋生活类比:

奶茶师把原料放在大厅公共区, 无论在哪个角落都能拿到那杯“奶茶”。


🍵1.2 let:聪明的“小分区储物柜”

📘特点:

  1. 不能重复声明。
  2. 块级作用域
  3. 声明前不能访问(会报错)。
  4. 没有变量提升。

🍬示例:

let tea = "绿茶"
let tea = "奶茶" // ❌ 会报错:变量已存在

🧋比喻:

你在储物柜 A 里放了绿茶粉, 想再放一份奶茶粉进去——系统说:“这格子已经被占啦!”


🍭块级作用域例子:

{
  let sugar = "三分糖"
  console.log(sugar) // ✅ 三分糖
}
console.log(sugar) // ❌ 报错

🧋比喻:

“let” 就像前台每个员工的私人物品柜。 只能本人(当前作用域)打开,其他人拿不到。


🧨临时死区(Temporal Dead Zone):

console.log(tea) // ❌ 报错:Cannot access 'tea' before initialization
let tea = "乌龙"

JS 会先“预留”变量位置,但不能在声明前访问。

🧋比喻:

“柜子已经造好了,但钥匙还没发给你”, 你想偷开门取料,直接被保安(JS 引擎)拦下 🚫。


🍯1.3 const:恒定不变的“密封配方柜”

📘特点:

  1. 声明必须初始化。
  2. 不可重新赋值。
  3. 块级作用域。
  4. 适合存放“不会变的常量”。

🍬示例:

const brand = "喜茶"
brand = "奈雪" // ❌ 报错:Assignment to constant variable

🧋比喻:

const 是“店长专用密封柜”, 里头存放的是“招牌奶茶秘方”, 谁都改不了——除非拆柜子!


🍭但注意:

const menu = { drink: "绿茶" }
menu.drink = "奶茶" // ✅ 可以修改对象内容

🧋比喻:

const 锁的是“柜子”,不是“柜子里的原料”~ 所以对象属性还能改,但不能换整个对象。


🍩1.4 小结:三者的区别表格

特性varletconst
作用域函数级块级块级
变量提升✅ 有❌ 无❌ 无
可重复声明
暂时性死区
是否可修改❌(值不可改)

🧃口诀总结记忆:

🧋 “var 老好人,啥都能干;let 小心翼翼,只在小格;const 店长专用,动不得!”


🧋1️⃣ 为什么 var 会“变量提升”?

🧠 核心原理: 在 JavaScript 执行前,解释器会先“预扫描”代码,把所有用 var 声明的变量提前创建。 但注意!只声明提前,不赋值提前

console.log(drink) // undefined
var drink = '奶茶'

相当于:

var drink
console.log(drink)
drink = '奶茶'

🧋生活类比:

想象你在开店前,店长已经提前把所有“奶茶柜子”装好了标签(比如“珍珠”“红茶”), 只是柜子里还没装东西。 所以当你开门营业(代码执行)前去看柜子时——它确实在,但还是空的!☁️

🧩面试总结句:

var 的变量提升,是因为 JavaScript 引擎在编译阶段就创建了变量声明的“存储空间”, 而赋值操作则留到执行阶段才进行。


🧋2️⃣ let 和 const 的“暂时性死区”是什么?

🧠 概念: 在代码块 {} 内,如果用 letconst 声明变量,在声明之前访问它,就会报错。 这个“声明前禁止访问”的区域,就叫 暂时性死区(TDZ)

console.log(milk) // ❌ ReferenceError
let milk = '全脂奶'

💡 JS 会先“知道”这里有个变量 milk,但不给你提前访问。


🧋生活比喻:

店里新进了一台“自动封口机”,但员工培训还没结束。 虽然机器已经摆在那里了(JS 预留了位置), 可在正式培训完成(声明执行)之前,任何人都不能用。 你要是硬上,店长直接骂你:“Reference Error!” 😂


📦为什么设计 TDZ? 为了避免程序员在变量尚未准备好时就误用它。 ——就像防止你在“奶茶机加热中”就去倒奶,避免出事故。


🍵3️⃣ 对象的 const 是“浅冻结”吗?

🧠 是的!

const menu = { drink: '奶茶' }
menu.drink = '绿茶' // ✅ 可以改
menu = { drink: '乌龙茶' } // ❌ 报错

📖 原理: const 锁的是变量指向的内存地址,不是对象内部的内容。 也就是说,对象本身还能动刀,但你不能让变量去指向另一个新对象。


🧋生活比喻:

店长锁住了“奶茶配方柜”的门(变量不能换), 但柜子里那几袋原料(对象属性)你还是能换。


💡那怎么让它彻底冻结呢? 可以用 Object.freeze()

const recipe = Object.freeze({ tea: '红茶', milk: '纯牛奶' })
recipe.tea = '绿茶' // ❌ 无效修改

🧋生活比喻:

如果你用了 Object.freeze(),那就像往柜子上贴了“冷藏警告 + 防拆封条”, 谁动都白搭,真正的“封印奶茶柜”❄️。


🧃总结一下:

问题简答总结奶茶铺记忆法
var 为什么会提升JS 预编译阶段创建变量声明柜子先造好,内容后加
let / const 暂时性死区声明前禁止访问新机器没培训前不能用
const 对象能改属性吗是“浅冻结”,锁地址不锁内容锁柜子,不锁原料袋

🧋一句话记住:

“var 先建柜,let 有封印,const 锁柜门。”

🌸 第 6–10 页:var、let、const 实战 + ES6 数组扩展


🧋1.4.2 暂时性死区演示(Temporal Dead Zone)

// var
console.log(tea) // undefined
var tea = "红茶"// let
console.log(milk) // ❌ 报错 ReferenceError
let milk = "鲜奶"

💡解释: var 会被“提前声明”成一个空变量,所以打印出 undefined。 而 let 则“封印”在暂时性死区内,没声明之前谁都不能碰。

🧋生活比喻:

“var”就像老式开放仓库——你能提前进去看货,只是货架是空的。 “let”是高科技仓库,系统没解锁前你进都进不去 🚫。


🍵1.4.3 重复声明

var a = 10
var a = 20 // ✅ 可以重复
let b = 10
let b = 20 // ❌ 报错:重复声明

🧋生活比喻:

“var” 像开放柜子,大家都能塞同样的标签; “let” 像智能储物柜,一个名字只能绑定一格,防止混乱~


🍰1.4.4 块级作用域

if (true) {
  var c = 1
}
console.log(c) // ✅ 1
if (true) {
  let d = 1
}
console.log(d) // ❌ 报错 d 未定义

🧋比喻:

“var” 放的奶茶大家都能喝(全店共享)。 “let” 放的奶茶只在那张桌子上能喝(只在当前代码块)。


🍩1.4.5 循环中的闭包问题

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000)
}
// 输出: 3 3 3
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000)
}
// 输出: 0 1 2

💡解释: var 是全局共享同一个 i; let 为每次循环都创建了一个独立的“小空间”。

🧋比喻:

“var” 像三个人共用一杯奶茶,最后都喝到第 3 口; “let” 像每人一杯,喝到哪杯就记录哪杯。


🧃2. ES6 中数组的“六味奶茶升级包”✨

ES6 给数组加了很多超级方便的新功能,像升级成了智能奶茶机:

功能模块代表方法比喻
扩展运算符...一键“倒满所有杯子”
新增遍历方法for...ofentries()keys()values()更聪明的“巡查员”
查找方法find()findIndex()快速查找“哪杯奶茶出问题”
填充方法fill()copyWithin()批量补货
includes 判断includes()检查库存里有没有“珍珠奶茶”
sort 排序改进sort() 稳定性提升排队更有序,不再乱序打架!😆

🧋2.1 扩展运算符(Spread Operator)

🍬语法:

const arr = [1, 2, 3]
console.log(...arr) // 输出:1 2 3

💡解释: 扩展运算符会把数组“拆开”成独立的元素。

🧋生活比喻:

“...” 就像自动分杯机,把一壶奶茶自动平均分到三杯里。 console.log(...arr) 就是喊:“服务员,分别上这三杯!”


🍵例子 1:合并数组

const a = [1, 2]
const b = [3, 4]
const c = [...a, ...b]
console.log(c) // [1, 2, 3, 4]

🧋比喻:

两个奶茶桶里的原料倒进一个大桶中,完美融合成新菜单。


🍰例子 2:复制数组

const arr1 = [5, 6]
const arr2 = [...arr1]
console.log(arr2) // [5, 6]

🧋比喻:

拿同样配方复制一桶新奶茶,不会影响原来的那桶。


🍩例子 3:传参神器

function makeTea(a, b, c) {
  console.log(a + b + c)
}
const nums = [1, 2, 3]
makeTea(...nums) // 输出 6

🧋比喻:

以前要一个个手动倒料,现在“...” 自动帮你一次倒三份配料,超省事!


🍵2.2 ES6 数组扩展总结表(小抄)

方法功能奶茶铺记忆法
...拆/合数组倒料机
find()找到第一个符合条件的元素找到第一个加错糖的杯子
fill()批量填充数组一次倒满所有空杯
includes()判断存在性看菜单有没有“乌龙奶”
sort()稳定排序顾客按号排队不乱序
copyWithin()复制一段元素把珍珠从第1层搬到第3层

🧠小可爱复习口诀:

“var 爱乱跑,let 会守规矩,const 不动摇; spread 会拆杯,fill 会补料,find 会巡查。” 💫

🌸 第 11–15 页:数组方法进阶篇(Array 大升级)


🧋2.2 构造函数新增方法

ES6 给数组加了几个新建数组的“偷懒神器”。 以前做一杯奶茶要手工配,现在一行命令就能批量开工!


🍵2.2.1 Array.from()

📘 功能: 把“类数组”或“可遍历对象”变成真正的数组。

let str = 'milk'
let arr = Array.from(str)
console.log(arr) // ['m', 'i', 'l', 'k']

🧋生活比喻:

像把一大桶“奶茶液体”倒进模具里,瞬间切成四小杯。 每一杯就是一个字母,完美分装💡


💡还能带“转换功能”:

let nums = [1, 2, 3]
let double = Array.from(nums, x => x * 2)
console.log(double) // [2, 4, 6]

🧋比喻:

加工厂模式:倒进去的是原奶,出来的是“加倍浓郁奶茶”。


🍰2.2.2 Array.of()

📘 功能: 根据参数,快速创建一个数组。

Array.of(3) // [3]
Array.of(1, 2, 3) // [1, 2, 3]

🧋比喻:

想要几杯奶茶就报几个数~ “Array.of(3)”相当于:店长喊一声“来一杯3号饮品!”

💡区别:

Array(3)     // [空 × 3](坑!)
Array.of(3)  // [3] ✅

前者是“预约了 3 个位置但没装奶茶”, 后者是真实倒了 1 杯“编号 3 的奶茶”。


🍩2.2.3 常用数组新方法汇总(脑图记忆 🧠)

方法功能奶茶铺记忆法
Array.from()把“类数组”变真数组倒模分装奶茶
Array.of()快速创建新数组点单建新杯
copyWithin()复制部分到数组别处把珍珠从杯1搬到杯3
find()找出第一个符合条件的元素找到第一个加错糖的杯子
fill()批量填充内容一次倒满空杯
entries() / keys() / values()遍历数组检查每杯编号、口味、客人
includes()判断存在性看库存有没有“芋圆奶茶”

🧃2.2.4 copyWithin()

📘 功能: 复制数组内的一段元素到其他位置(原地修改!)

let arr = [1, 2, 3, 4, 5]
arr.copyWithin(0, 3, 4)
console.log(arr) // [4, 2, 3, 4, 5]

🧋比喻:

店员把第 4 杯“奶茶”复制到第 1 个杯位上, 结果出现两个“4 号奶茶”,直接内卷!


🍵2.2.5 find()findIndex()

📘 功能:

  • find() 找到第一个符合条件的值。
  • findIndex() 找到第一个符合条件的下标。
const arr = [10, 20, 30, 40]
let tea = arr.find(n => n > 25)        // 30
let index = arr.findIndex(n => n > 25) // 2

🧋比喻:

find() 像店长巡视: “哪杯奶茶加的糖太多?” 🍯 第一次发现(30)就停下!

findIndex() 则记录位置:“是第 3 杯!”


🍰2.2.6 fill()

📘 功能: 批量填充数组中的元素。

let arr = [1, 2, 3, 4]
arr.fill('奶茶', 1, 3)
console.log(arr) // [1, '奶茶', '奶茶', 4]

🧋比喻:

把第 2、3 个杯位都装上“奶茶”, 批量出货,效率 200%!


🍡2.2.7 entries()keys()values()

📘 功能: 这仨是 ES6 的“巡检三兄弟”,帮你查看数组内容:

let arr = ['红茶', '绿茶', '乌龙']
for (let [index, value] of arr.entries()) {
  console.log(index, value)
}

🧋比喻:

  • keys():只看“奶茶编号”
  • values():只看“奶茶口味”
  • entries():两者都看,一起巡店 👮‍♀️

🧋2.2.8 includes()

📘 功能: 判断数组是否包含某个元素。

let menu = ['珍珠奶茶', '绿茶拿铁']
console.log(menu.includes('绿茶拿铁')) // true
console.log(menu.includes('柠檬水'))   // false

🧋比喻:

顾客问:“你家有柠檬水吗?” 店员打开菜单一看:includes() 一秒回答 ✅❌!


🍓小结(第 15 页)

方法功能奶茶铺比喻
Array.from()把“类数组”转真数组倒模分装奶茶
Array.of()用参数建数组点单建新杯
find() / findIndex()找元素 / 找位置店长巡查问题奶茶
fill()批量替换内容批量倒料
copyWithin()内部复制把珍珠搬杯
entries() / keys() / values()遍历检查巡检三兄弟
includes()判断是否存在看菜单有没有那杯

🧠口诀记忆法:

“from 倒模,of 点单,fill 补杯,find 巡查,copyWithin 复制珍珠,includes 查菜单。”

🌸 第 16–20 页:数组进阶 + 函数扩展篇


🧋2.2.9 flat()flatMap()

🍵1️⃣ flat() —— 数组拍平神器

📘 功能: 把“嵌套数组”拍平成一维的。

let arr = [1, [2, [3, [4]]]]
console.log(arr.flat())    // [1, 2, [3, [4]]]
console.log(arr.flat(2))   // [1, 2, 3, [4]]
console.log(arr.flat(Infinity)) // [1, 2, 3, 4]

🧋生活比喻:

“flat()” 像一台“压扁机”, 你把堆叠的奶茶杯放进去,它能帮你压成一层平整的托盘。 想压几层?就调参数几级~

  • flat(1) → 压一层
  • flat(2) → 压两层
  • flat(Infinity) → 压到底!👊

🍰2️⃣ flatMap() —— 拍平 + 映射二合一!

📘 功能: 相当于 map() + flat() 的组合操作。

let arr = [1, 2, 3]
console.log(arr.flatMap(x => [x, x * 2])) // [1, 2, 2, 4, 3, 6]

🧋生活比喻:

“flatMap()” 就像一台“自动加料奶茶机”: 每倒一杯奶茶(map), 它会自动帮你“摊平”所有配料(flat)~ 最终一气呵成,杯杯饱满,效率翻倍💪


🧃2.2.10 排序改进(sort)

📘 以前: JS 的 sort() 会把元素先转成字符串再排! 比如:

[1, 10, 2].sort()
// ["1", "10", "2"][1, 10, 2]

📘 改进后: 你可以自定义比较函数:

[1, 10, 2].sort((a, b) => a - b)
// [1, 2, 10]

🧋生活比喻:

老版店员是“文科生”——只会按字母顺序排。 新版是“理科生”——会按数字大小排, 从此奶茶订单不再乱七八糟🍹。


🍵3. 函数扩展篇

ES6 对函数也进行了“大翻新”,主要有:

模块功能比喻
参数默认值、解构、剩余参数奶茶店接单灵活加料
属性name、length奶茶机型号与容量
箭头函数简洁写法自动封口机,不用“this”

🧋3.1 参数扩展

🍓1️⃣ 默认参数
function makeTea(flavor = '奶茶') {
  console.log(`一杯${flavor}出炉!`)
}
makeTea()        // 一杯奶茶出炉!
makeTea('绿茶')  // 一杯绿茶出炉!

🧋比喻:

顾客不点口味,默认就是“原味奶茶”。 店长节约脑力,配置更智能~


🍰2️⃣ 剩余参数(...rest
function add(...nums) {
  return nums.reduce((a, b) => a + b)
}
console.log(add(1, 2, 3)) // 6

🧋比喻:

“...” 就像多杯打包袋, 顾客要几杯都能一次提走。 JS 把所有参数自动打包进 nums 这个袋子。


🍵3️⃣ 参数解构
function serve({name, size}) {
  console.log(`${size}杯奶茶给${name}`)
}
serve({name: '小可爱', size: '大'})
// 大杯奶茶给小可爱

🧋比喻:

顾客下单时不需要记“第1个参数是谁”, 直接点名:“我要大杯给小可爱”,一目了然💖。


🍩3.2 函数属性升级

🧋3.2.1 length 属性

📘 表示函数形参的数量(不算有默认值的)。

function tea(a, b, c = 3) {}
console.log(tea.length) // 2

🧋比喻:

“length” 就是机器“可放几种料”的容量, 但如果某些料有默认配置,就不算进容量。


🍡3.2.2 name 属性

📘 表示函数的名称。

function brewTea() {}
console.log(brewTea.name) // brewTea

🧋比喻:

每台奶茶机都有一个型号编号,方便维护。 name 就是那块铭牌~


🌸小结(第 20 页)

模块方法 / 特性奶茶铺记忆法
数组拍平flat()flatMap()奶茶“压扁机”
排序sort((a,b)=>a-b)理科生店员排序
默认参数function(x=1)默认口味“原味奶茶”
剩余参数...rest多杯打包袋
参数解构function({x,y})“点单名牌式”传参
函数属性.name / .length奶茶机铭牌 & 容量指标

🌈复习口诀:

flat 压奶茶,flatMap 加配料,sort 排队稳,默认口味原味料~

继续来讲第 21–25 页 的内容! 这几页主要讲的是:

💡 函数的进阶(作用域、箭头函数) + 💡 对象的神奇新特性(属性简写、方法简写、动态属性等)

我还是用“奶茶铺”的比喻方式,让你一下就能记住 💪✨


🌸 第 21–25 页:函数作用域 + 箭头函数 + 对象扩展篇


🧋3.3 作用域(Scope)

📘 定义: 作用域就是——“变量在代码中能被访问的范围”。

let flavor = '奶茶'function makeTea() {
  let sugar = '三分糖'
  console.log(flavor) // ✅ 奶茶
  console.log(sugar)  // ✅ 三分糖
}
​
makeTea()
console.log(sugar) // ❌ 报错:sugar 未定义

🧋生活比喻:

“flavor” 是奶茶铺的公共调料台,谁都能用。 “sugar” 是厨师自己工作台上的私密配料,只能厨房内访问。

👉 所以:

  • 全局变量(global) :前台、厨房都能看到。
  • 局部变量(local) :只在那间“厨房(函数)”内有效。

🍵3.4 严格模式('use strict')

📘 作用: 防止“代码乱写”,让 JS 更安全、更规范。

"use strict"
​
a = 10  // ❌ 报错,必须用 let/const 声明

🧋生活比喻:

“严格模式”就像给奶茶店上了 ISO 管理认证。 不打卡、没工牌、私自操作奶茶机——都要被系统报警 🚨。


🍰3.5 箭头函数(Arrow Function)

🍓1️⃣ 基本写法

const makeTea = (name) => {
  console.log(`一杯${name}出炉!`)
}
makeTea('乌龙奶茶')

🧋比喻:

箭头函数就像自动封口奶茶机—— 不需要太多人工操作(functionreturn), 一键制作,干净利落✨!


🍡2️⃣ 简写形式

const add = (a, b) => a + b
console.log(add(1, 2)) // 3

💡规则:

  • 只有一个参数 → 括号可省略
  • 只有一行 return → return 可省略

🧋比喻:

“一口操作”:不废话,机器自动算出结果~ 就像奶茶机自己称量、搅拌、封口,速度飞快 ⚡


🍵3️⃣ 箭头函数中的 this

const shop = {
  tea: '奶茶',
  make: function() {
    setTimeout(() => {
      console.log(this.tea)
    }, 1000)
  }
}
shop.make() // 奶茶 ✅

🧋解释: 箭头函数不会创建自己的 this, 它会自动继承外层的那个 this

🧋比喻:

箭头函数像一个“实习生助手”, 不自己决定事情,而是听主厨(外层函数)的指令~


🧃4. 对象的扩展(ES6 的神仙操作 ✨)

ES6 给对象新增了一堆好用的语法糖(超级省心!)

功能举例比喻
属性简写{name} 代替 {name: name}订单自动补全客户名
方法简写speak() {}员工报工简写
计算属性[key]: value菜单上“今日特价”
Object.assign合并对象多店库存合并
Object.keys/values/entries获取属性名/值查看“菜单列表”
super继承父级方法分店调总部系统

🍵4.1 属性简写

以前写法:

const name = '小可爱'
const shop = { name: name }

ES6 新写法:

const name = '小可爱'
const shop = { name }

🧋比喻:

以前点单要写全名:“顾客姓名:小可爱”。 现在智能系统能自动识别姓名字段,省了一半字✍️~


🍰4.2 方法简写

以前:

const shop = {
  makeTea: function() {
    console.log('制作奶茶中...')
  }
}

现在:

const shop = {
  makeTea() {
    console.log('制作奶茶中...')
  }
}

🧋比喻:

以前你要说“我现在要执行函数 makeTea()”, 现在直接喊“makeTea()”就能开始泡茶~ 就像用语音助手点单:“Siri,来杯乌龙奶茶!” ☕💨


🌸小结(第 25 页)

模块新语法奶茶铺记忆法
作用域全局 / 局部厨房内外的配料区
严格模式"use strict"奶茶店上管理系统
箭头函数()=>{}自动封口奶茶机
属性简写{name}智能订单字段
方法简写makeTea(){}语音下单助手

🧠 复习口诀:

作用域分区厨房料,严格模式防乱搞; 箭头函数一键造,属性简写更省稿~

🌸 第 26–30 页:对象扩展与新方法篇


🧋4.2 计算属性名(Computed Property Names)

📘 以前:

let key = 'flavor'
let tea = {}
tea[key] = '奶茶'
console.log(tea.flavor) // 奶茶

📘 现在(ES6 写法):

let key = 'flavor'
let tea = { [key]: '奶茶' }
console.log(tea.flavor) // 奶茶

🧋比喻:

[key] 就像一个智能点餐机。 顾客在屏幕上输入名字(变量 key),机器自动创建那杯奶茶的标签。 不再需要手写“flavor: 奶茶”,系统自动帮你填上!

💡口诀:

[] = “动态命名标签机”。


🍵4.3 super 关键字

📘 功能: 在对象方法里,super 可以调用“父对象”的方法。

const parent = {
  sayHi() { return '欢迎光临~' }
}
​
const child = {
  sayHi() {
    return super.sayHi() + ' 这杯给小可爱 💖'
  }
}
​
Object.setPrototypeOf(child, parent)
console.log(child.sayHi())
// 欢迎光临~ 这杯给小可爱 💖

🧋比喻:

super 就像奶茶总店的“标准话术库”。 每家分店(child)都可以继承总部的问候语,再自由发挥添加“个性台词”。


🍰4.4 扩展运算符在对象中的应用(...)

📘 语法:

let base = { tea: '奶茶', size: '中' }
let order = { ...base, sugar: '三分糖' }
console.log(order)
// { tea: '奶茶', size: '中', sugar: '三分糖' }

🧋比喻:

“...” 就像奶茶模板复制器。 总店有一份标准配方 {tea, size}, 分店复制后再加点本地口味,比如“糖度三分”,快速出新品 🚀。

💡注意: 后面的属性会覆盖前面的:

{ ...base, size: '大' } // 中 → 被替换成 大

👉 就像分店“自定义配方”,盖掉总部默认比例~


🍡4.5 属性的遍历(对象迭代)

📘 常见遍历方式:

方法说明比喻
for...in遍历键名(包括继承)总店 + 分店菜单都看
Object.keys(obj)返回自家属性的键名数组看自己分店菜单名字
Object.values(obj)返回属性值数组看菜单上奶茶内容
Object.entries(obj)返回键值对数组拍成“菜单清单”

🧋示例:

let tea = { name: '奶茶', sugar: '半糖', size: '大' }
for (let [key, value] of Object.entries(tea)) {
  console.log(`${key}${value}`)
}

输出:

name:奶茶
sugar:半糖
size:大

🧋比喻:

entries() 就像打出一份“奶茶菜单打印表”, 每行都是 “项目:值” 的格式,方便盘点库存 📋。


🍵4.6 对象新增的实用方法

4.6.1 Object.is()

📘 功能: 更准确地判断两个值是否相等(比 === 更聪明)。

Object.is(NaN, NaN) // true ✅
NaN === NaN          // false ❌

🧋比喻:

普通服务员(===)会说:“NaN 和 NaN 不一样”😵‍💫 新培训的店长(Object.is)知道:“这俩其实是同一批奇怪口味的奶茶。”

💡还能分辨 +0-0Object.is(+0, -0) → false)。


4.6.2 Object.assign()

📘 功能: 合并对象(或浅拷贝)。

const base = { tea: '奶茶' }
const addon = { sugar: '三分糖' }
const order = Object.assign({}, base, addon)
console.log(order)
// { tea: '奶茶', sugar: '三分糖' }

🧋比喻:

就像总部调出“奶茶底料 + 糖度方案”, 一键合并成成品菜单。 ⚠️ 注意:如果两个有同名属性,会被后者覆盖(后面赢!)。


4.6.3 Object.getOwnPropertyDescriptors()

📘 功能: 一次性获取对象所有属性的“详细配置”。

const tea = { name: '奶茶' }
console.log(Object.getOwnPropertyDescriptors(tea))

输出(示例):

{
  name: {
    value: '奶茶',
    writable: true,
    enumerable: true,
    configurable: true
  }
}

🧋比喻:

就像你打开奶茶系统后台, 看到每个配方的详细权限(是否能修改、能显示、能删除)。


4.6.4 Object.setPrototypeOf()

📘 功能: 手动设置对象的“原型”(继承关系)。

const base = { slogan: '欢迎光临奶茶店~' }
const branch = {}
Object.setPrototypeOf(branch, base)
console.log(branch.slogan) // 欢迎光临奶茶店~

🧋比喻:

这就像让“分店”继承“总部口号”, 只要总部改 slogan,所有分店都会自动更新~!


🌸小结(第 30 页)

模块方法 / 特性奶茶铺记忆法
计算属性名[key]: value动态标签机
super调用父方法总店话术
展开运算符{...obj}复制标准配方
遍历方法keys/values/entries菜单清单打印机
Object.is更精准比较智能检测机
Object.assign合并配置奶茶混合机
Object.getOwnPropertyDescriptors查看详细配置后台权限管理
Object.setPrototypeOf设置继承关系总店-分店管理链

🧠 复习口诀:

方括号起标签,super 调总部, 扩展三点复制香,assign 合并不慌。 keys values 查库存,is 判真假最稳当。

🌸 第 31–35 页:对象高级方法 + Promise 异步入门


🧋4.8 对象的更多方法

🧃4.8.5 Object.getPrototypeOf(obj)

📘 作用: 返回指定对象的原型(也就是它“继承自谁”)。

const base = { slogan: '欢迎光临~' }
const shop = Object.create(base)
console.log(Object.getPrototypeOf(shop) === base) // true ✅

🧋比喻:

想知道某家奶茶分店是哪个总部开的? 用 getPrototypeOf() 查一下就知道“上级公司”啦~ (它能帮你确认继承链条!)


🍰4.8.6 Object.keys(obj)

📘 作用: 返回对象所有“可枚举的键名”数组。

const tea = { name: '奶茶', sugar: '三分糖', size: '大' }
console.log(Object.keys(tea)) // ['name', 'sugar', 'size']

🧋比喻:

打印出奶茶菜单的“所有项目名”📜。 (相当于列出所有“标签”——方便盘点!)


🍡4.8.7 Object.values(obj)

📘 作用: 返回对象属性对应的值。

console.log(Object.values(tea)) // ['奶茶', '三分糖', '大']

🧋比喻:

菜单右侧的“配方内容列表”—— 看每种奶茶的具体口味、甜度、规格 ✨。


🍵4.8.8 Object.entries(obj)

📘 作用: 返回一个二维数组,每个子项是 [key, value]

console.log(Object.entries(tea))
// [['name','奶茶'], ['sugar','三分糖'], ['size','大']]

🧋比喻:

打印完整的菜单对照表: “项目 → 内容” 一行一项,非常直观。 (就像库存表格的每行记录 🧾)


🍩4.8.9 Object.fromEntries()

📘 作用:entries() 相反!它能把 [key, value] 形式的数组变回对象。

const arr = [['name', '奶茶'], ['sugar', '三分糖']]
console.log(Object.fromEntries(arr))
// { name: '奶茶', sugar: '三分糖' }

🧋比喻:

entries() 是把菜单“打印成表格”, fromEntries() 就是“扫描回系统”,重新生成奶茶菜单!


🌈5. Promise:异步的灵魂登场!

🌟 场景:当系统需要“等待”——比如奶茶制作中、请求接口、倒计时完成等,就要用 Promise!


🧃5.1 Promise 的概念

📘 一句话解释: Promise 是一种「承诺」机制,用来处理异步任务。

👉 它保证你在“任务完成”后再去执行后续逻辑。

const makeTea = new Promise((resolve, reject) => {
  let success = true
  if (success) resolve('奶茶制作完成 ✅')
  else reject('机器坏了 ❌')
})

🧋比喻:

顾客下单 → 系统生成一个“承诺单(Promise)”📄 它会告诉你:奶茶正在制作中~ 完成后会自动通知你 “OK” 或 “失败”!


🍰5.1.1 三种状态

状态含义奶茶铺比喻
pending进行中奶茶制作中 ⏳
fulfilled成功奶茶做好啦 ✅
rejected失败奶茶机罢工了 ❌

📘 Promise 对象状态只能从 pending → fulfilled / rejected,一旦确定就不再改变。

🧋口诀:

“Promise 一旦做出承诺,就不能反悔!”


🍡5.1.2 状态图解(第 33 页)

       ┌───────────────┐
       │   pending     │  等待奶茶制作
       └──────┬────────┘
              │
       成功 resolve()
              │
              ▼
       fulfilled(完成)
              │
       失败 reject()
              ▼
       rejected(失败)

🧋生活类比:

奶茶店后台状态:

  • 「制作中」= pending
  • 「顾客取走」= fulfilled
  • 「机器爆浆」= rejected

🍵5.2 Promise 的用法


🧋5.2.1 .then()

📘 作用: 当 Promise 成功时(fulfilled),执行回调函数。

makeTea.then((result) => {
  console.log(result)
})

输出:

奶茶制作完成 ✅

🧋比喻:

.then() 就像顾客取号后,系统通知:“小可爱,奶茶做好啦~🍹”


🍰5.2.2 .catch()

📘 作用: 捕获 Promise 的失败结果。

makeTea.catch((error) => {
  console.log(error)
})

输出:

机器坏了 ❌

🧋比喻:

.catch() 像是客服部门。 一旦奶茶机出问题,它会立刻弹窗通知维修部门(捕获错误!)。


🍡5.2.3 .finally()

📘 作用: 不管成功还是失败,都会执行。

makeTea.finally(() => {
  console.log('感谢光临奶茶店~')
})

输出:

奶茶制作完成 ✅
感谢光临奶茶店~

🧋比喻:

无论奶茶是否出炉,前台都会说: “谢谢惠顾,下次再来呀~” 💖


🌸 小结(第 35 页)

模块方法 / 特性奶茶铺记忆法
getPrototypeOf查原型看总公司是谁
keys/values/entries遍历对象菜单清单
fromEntries还原对象扫描菜单入库
Promise异步机制奶茶下单系统
状态pending/fulfilled/rejected制作中 / 成功 / 失败
.then()成功回调通知奶茶完成
.catch()错误捕获报修客服
.finally()统一收尾前台感谢语

🌈 复习口诀:

对象方法查清单,Promise 承诺最心安; 成功 then,失败 catch,最后 finally 送客欢~

🌸 第 36–40 页:Promise 高级篇(多任务 + 并发控制)


🧋5.2.2 链式调用(then 的接力棒)

📘 示例:

new Promise((resolve) => {
  console.log('开始制作奶茶 🫖')
  resolve()
})
.then(() => {
  console.log('加珍珠 🍡')
})
.then(() => {
  console.log('加奶盖 🥛')
})
.then(() => {
  console.log('封口 ✅')
})

输出:

开始制作奶茶 🫖
加珍珠 🍡
加奶盖 🥛
封口 ✅

🧋比喻:

每个 .then() 就像奶茶制作流程的下一道工序。 上一步成功后 → 立刻“把奶茶杯”传给下一位师傅接着做! 这就是 Promise 链式调用

💡口诀:

“上一步 .then() 结束,下一步才开工。”


🍰5.2.3 Promise 的常用方法总览(第 37 页)

方法含义奶茶铺比喻
all()所有任务成功才算成功所有奶茶都做完,才能开门卖
race()谁先完成用谁哪杯先好,先卖那杯
any()任意成功就算成功任意一个分店出单就行
allSettled()全部任务结束才执行(不论成功失败)所有奶茶都制作完,统一清点结果

🧃5.2.4 Promise.all()

📘 用法:

const p1 = Promise.resolve('珍珠准备好 🍡')
const p2 = Promise.resolve('奶盖准备好 🥛')
const p3 = Promise.resolve('茶底准备好 🍵')
​
Promise.all([p1, p2, p3]).then(res => {
  console.log('全部完成 ✅', res)
})

输出:

全部完成 ✅ [ '珍珠准备好 🍡', '奶盖准备好 🥛', '茶底准备好 🍵' ]

🧋比喻:

只有当“所有配料”都准备好后,奶茶师才能开始出杯。 任何一项失败(比如奶盖打不出来)→ 整杯作废 ❌

💡口诀:

“全员 OK 才能出货”~ 👨‍🍳


🍡5.2.5 Promise.race()

📘 用法:

const p1 = new Promise(resolve => setTimeout(() => resolve('分店1 奶茶完成 🧋'), 1000))
const p2 = new Promise(resolve => setTimeout(() => resolve('分店2 奶茶完成 🧋'), 500))
​
Promise.race([p1, p2]).then(res => {
  console.log('最快的结果:', res)
})

输出:

最快的结果: 分店2 奶茶完成 🧋

🧋比喻:

谁先做完奶茶,谁赢!🏁 其他分店即使还没好,也不管了~这就是“竞速模式”!

💡口诀:

“谁先出奶茶,就用谁的!”


🍰5.2.6 Promise.allSettled()

📘 用法:

const orders = [
  Promise.resolve('订单1 完成'),
  Promise.reject('订单2 失败'),
  Promise.resolve('订单3 完成')
]
​
Promise.allSettled(orders).then(res => console.log(res))

输出:

[
  { status: 'fulfilled', value: '订单1 完成' },
  { status: 'rejected', reason: '订单2 失败' },
  { status: 'fulfilled', value: '订单3 完成' }
]

🧋比喻:

不管奶茶成没成功, 都要“统计结果”——这就是收工清点环节 🧾。

Manager 会说:“今天 3 单 → 2 成功,1 失败。”

💡口诀:

“结果都要报,不管好坏。”


🍵5.2.7 Promise.any()

📘 用法:

const branches = [
  Promise.reject('分店1 机器坏了 💥'),
  Promise.reject('分店2 员工迟到 😴'),
  Promise.resolve('分店3 奶茶完成 ✅')
]
​
Promise.any(branches).then(res => console.log(res))

输出:

分店3 奶茶完成 ✅

🧋比喻:

“谁家能先供货成功,就用谁的!” 其他分店出状况无所谓~总得有家能出杯吧? ☕️

💡口诀:

“至少一杯成功,我就能开业!”


🍩5.3 使用场景举例(第 40 页)

📘 场景:多个接口请求并行,再统一处理结果。

function getData1() {
  return Promise.resolve('库存数据 ✅')
}
function getData2() {
  return Promise.resolve('用户订单 ✅')
}
​
Promise.all([getData1(), getData2()])
  .then(res => console.log('全部加载完毕~', res))
  .catch(err => console.log('有任务失败', err))

输出:

全部加载完毕~ [ '库存数据 ✅', '用户订单 ✅' ]

🧋比喻:

这就像前台系统需要“库存 + 订单”都加载好才能开始营业。

两个接口都 OK → 门店页面加载完成 ✅ 有一个挂了 → 页面直接报错 ❌


🌸 小结(第 40 页)

方法含义奶茶铺记忆法
.then()成功回调做好奶茶通知顾客
.catch()捕获失败奶茶机坏报修
.finally()无论结果都执行前台说谢谢
all()全部成功才算成功所有原料齐才能出杯
race()谁先好用谁竞速分店模式
any()任意成功即可任意分店能出杯就行
allSettled()统计所有结果下班结算清单

🌈 复习口诀:

Promise 一家人,任务分工各不同; all 全成,race 竞速,any 有一就行,settled 全都报明。

🌸 第 41–45 页:Async/Await & 模块化 Module 篇


🍹5.4 async / await —— Promise 的进化版(第 41 页)


🧋核心概念:

async/awaitPromise 的语法糖。 它让异步代码像写同步代码一样顺滑 ✨。


🌟例子一:用 Promise 写

function makeTea() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('奶茶制作完成 ✅')
    }, 2000)
  })
}
​
makeTea().then(res => console.log(res))

输出:

奶茶制作完成 ✅

🧋比喻:

顾客点单 → Promise 记录下来 → 两秒后系统通知“奶茶好了”。 但 .then() 写多了像“嵌套地狱”,一层接一层的回调太折磨!


🌟例子二:用 async / await 写(更清爽)

async function makeTea() {
  let result = await new Promise((resolve) => {
    setTimeout(() => {
      resolve('奶茶制作完成 ✅')
    }, 2000)
  })
  console.log(result)
}
​
makeTea()

输出:

奶茶制作完成 ✅

🧋比喻:

await 就像奶茶师傅在“等茶泡好”。 等待期间不干别的(但其他订单仍能跑)。 一旦泡好 → 接着下一步动作,整个逻辑读起来超顺!

💡口诀:

“async 表示这家奶茶店能做异步单, await 表示师傅在等奶茶出锅。”


☕例子三:多个 await 串联

async function processOrder() {
  console.log('下单成功 🧾')
  let tea = await new Promise(r => setTimeout(() => r('茶底完成 🍵'), 1000))
  console.log(tea)
  let milk = await new Promise(r => setTimeout(() => r('奶盖完成 🥛'), 1000))
  console.log(milk)
  console.log('奶茶出杯 ✅')
}
processOrder()

输出:

下单成功 🧾
茶底完成 🍵
奶盖完成 🥛
奶茶出杯 ✅

🧋比喻:

await 像一个个“工序等待点”—— 茶底好后才能加奶盖,逻辑清晰又不乱。

💡口诀:

await 等任务完成,像排队工序一环扣一环。”


🌈 async 函数返回值:

async 函数永远返回一个 Promise!

async function getTea() {
  return '奶茶完成 ✅'
}
getTea().then(console.log)

输出:

奶茶完成 ✅

🧋比喻:

哪怕你直接 return,一样会被自动包装成一个“Promise 外卖盒”📦。


🌸6. Module 模块化(第 43 页开始)


🧃6.1.1 模块化的意义

📘 问题:为什么需要模块化?

以前 JS 文件像“大杂烩火锅”,所有变量都混在一起:

  • 全局污染严重
  • 命名冲突
  • 加载顺序混乱
  • 无法复用

🧋比喻:

就像奶茶铺没分工: 调茶的、打奶盖的、收银的全挤在一张桌上,乱成一锅粥 🥴


📘 模块化的目标:

  • 每个功能独立封装
  • 可复用(import)
  • 不互相污染作用域
  • 按需加载(懒加载)

🧋比喻:

模块化 = 奶茶连锁分店制度。 各店独立营业,但都归总部统一管理!✨


🍰6.1.2 模块化的历史阶段(第 44 页)

模块化规范代表环境特点
AMD浏览器端异步加载(适合网页)
CommonJSNode.js同步加载(适合服务器)
ES ModuleES6 原生方案官方统一标准

🧋AMD 示例:

define(['jquery'], function ($) {
  return {
    sayHi: function () {
      console.log('Hello from AMD 👋')
    }
  }
})

🧋比喻:

“AMD” 就像网页点单系统, 顾客(浏览器)告诉店员要什么材料(依赖), 然后系统异步加载进来用。


🍡CommonJS 示例:

// 导出
module.exports = {
  sayHi() { console.log('Hello from CommonJS 👋') }
}
​
// 导入
const m = require('./m.js')
m.sayHi()

🧋比喻:

“CommonJS” 像 Node 后台的仓库订货系统。 调用 require() → 立即“拉一箱货”回来。 它是同步的(立刻拿到结果)。


🍵ES6 Module(现代写法)

📘 语法结构:

  • export 导出
  • import 导入
// utils.js
export const slogan = '欢迎光临奶茶店 🍹'
export function makeTea() { console.log('制作中...') }
​
// main.js
import { slogan, makeTea } from './utils.js'
console.log(slogan)
makeTea()

输出:

欢迎光临奶茶店 🍹
制作中...

🧋比喻:

ES Module 就像总部发“标准配方卡”📜。 你想要“奶盖制作流程”或“宣传语”, 直接 import 来用,安全又不冲突!

💡口诀:

“export 出货,import 进货。”


🧠6.2.1 export 用法详解(第 45 页)

📘 方式一:单独导出

export const name = '奶茶'
export const price = 12

📘 方式二:统一导出

const name = '奶茶'
const price = 12
export { name, price }

📘 方式三:默认导出

export default function () {
  console.log('默认制作流程 🧋')
}

📘 导入方式:

import makeTea, { name, price } from './tea.js'

🧋比喻:

default = 主打招牌奶茶; {} 内导入的 = 配料菜单。

所以 import main, { sugar } 就像“先点主奶茶,再加个糖选项。”


🌸 第 45 页小结

模块功能奶茶铺记忆法
async / await让异步更像同步奶茶师等泡茶
await等任务完成一环扣一环
export导出模块总部配方卡
import导入模块分店拿配方
default export主打产品招牌奶茶

🌈 复习口诀:

async 等奶茶,await 看泡法; 模块配方卡,import 来拿!

🌸 第 46–50 页:ES Module 进阶 + Generator 初探


🧋6.2.1 export 进阶补充(第 46 页)

前面我们学了最基本的导出导入,现在来点更灵活的写法~


🍹1️⃣ 重命名导出

// tea.js
const price = 15
const name = '奶茶'
export { price as teaPrice, name as teaName }
// main.js
import { teaPrice, teaName } from './tea.js'
console.log(teaName, teaPrice)

输出:

奶茶 15

🧋比喻:

你在总部导出的时候, 把“奶茶”改名成“teaName”—— 就像配方卡上的“内部命名”和“门店命名”不一样,但指的是同一种奶茶。

💡口诀:

“出口可以改名(as),进口得认准名字。”


🍰2️⃣ 统一导出对象(整体打包)

const drink = { name: '奶茶', price: 15 }
export default drink
import drink from './tea.js'
console.log(drink.name)

🧋比喻:

这就像直接发一整箱“奶茶物料” 📦。 你不拆开、整箱收下!

💡区别提醒:

  • export default → 一次只能有一个;
  • 普通 export → 可以有多个。

🍡6.2.2 import 的多种玩法(第 47 页)


🌟1️⃣ 基本导入

import { teaName, teaPrice } from './tea.js'

🧋比喻:

就像点奶茶时, “我只要【奶茶名】和【价格】,不用菜单全拿~”


🌟2️⃣ 全量导入

import * as tea from './tea.js'
console.log(tea.teaName)

🧋比喻:

相当于直接说“我全要!” 整个 tea 模块作为一个对象导入。 就像整本“奶茶操作手册”拿回家 📖。

💡口诀:

“想偷懒,就用 * as 一次性全拿。”


🌟3️⃣ 默认导入 + 普通导入同时使用

import main, { teaName } from './tea.js'

🧋比喻:

“先点主奶茶(default),再加个小料({ teaName })。” 一次搞定两种口味!


🌟4️⃣ 导入时重命名

import { teaPrice as price } from './tea.js'

🧋比喻:

分店喜欢自己叫法?OK! 总部叫 teaPrice,你可以在分店称它 price,都能对上号。


☕️6.2.3 结合导出与导入的高级玩法(第 48 页)

🧃直接转发导出(re-export)

// drinks.js
export { teaName, teaPrice } from './tea.js'

🧋比喻:

“总店”把“分店 A” 的菜单直接转发给“分店 B”用。 不用重复写,只要引用即可。

💡口诀:

“出口再出口,省心又高效。”


🍩6.2.4 导入空模块(只执行)

import './setup.js'

🧋比喻:

有些模块没有导出内容,但有“启动逻辑”要运行。 比如初始化系统、打扫卫生脚本。

只要导入它,JS 会自动执行文件内容!

💡例子:

// setup.js
console.log('系统初始化完成 ✅')

输出:

系统初始化完成 ✅

🌈6.3 传递默认值(第 49 页)

当你导入的模块中可能缺少内容时,可以使用默认值:

import { teaName = '默认奶茶' } from './tea.js'

🧋比喻:

某家分店菜单缺项没写“奶茶名”? 那系统自动填上“默认奶茶”。 让所有店都能顺利开业 😆


💫7. Generator 函数初体验(第 50 页)

终于登场!✨ 这是异步的底层魔法 —— 可以让函数「暂停」「继续」,就像播放电影一样!


🍹7.1 Generator 是什么?

📘 语法:

function* makeTea() {
  yield '开始煮茶 🍵'
  yield '加奶盖 🥛'
  yield '封口 ✅'
}
const gen = makeTea()
console.log(gen.next())
console.log(gen.next())
console.log(gen.next())
console.log(gen.next())

输出:

{ value: '开始煮茶 🍵', done: false }
{ value: '加奶盖 🥛', done: false }
{ value: '封口 ✅', done: false }
{ value: undefined, done: true }

🧋比喻:

Generator 就像一个「暂停剧场」🎬。

每次调用 next(), 就播放一幕奶茶制作过程, 到 yield 这里暂停,等下一次再继续!

💡口诀:

yield 是暂停键,next() 是播放键。”


☕7.2 yield 的返回机制

📘 例子:

function* orderProcess() {
  let tea = yield '下单成功 🧾'
  console.log('顾客选择:', tea)
}
const gen = orderProcess()
console.log(gen.next())     // 第一次执行,暂停在 yield
console.log(gen.next('珍珠奶茶'))

输出:

{ value: '下单成功 🧾', done: false }
顾客选择: 珍珠奶茶
{ value: undefined, done: true }

🧋比喻:

第一次暂停时,系统等顾客选奶茶。 第二次执行 next('珍珠奶茶'),把选择结果传回给程序!

💡口诀:

“第一次暂停发通知,第二次播放带参数。”


🌟 Generator 的核心用途

用途说明奶茶铺比喻
异步流程控制一步步执行任务奶茶制作分阶段
数据流管理逐步产生数据批量出奶茶
迭代器实现可自定义遍历逻辑依次制作多杯茶

🌸 小结(第 50 页)

模块作用奶茶铺记忆法
export / import模块导入导出分店配方卡
as重命名菜名改叫法
default主打产品招牌奶茶
* as全量导入整本菜单拿走
Generator可暂停函数奶茶分幕剧
yield暂停点等下一幕
next()播放键继续做茶

🌈 复习口诀:

模块进出口,名字可改口; Generator 来演戏,yield 说暂停!

🌸 第 51–55 页:Generator 深入 + 异步演进史


🧋7.1 Generator 再回顾(第 51 页)

Generator 是一种能 “暂停”又能“继续” 执行的函数。

📘 它最大的特点有:

  • 需要用 function* 定义;
  • 执行时返回一个「迭代器对象」;
  • 可以用 yield 暂停,用 next() 继续;
  • 每次调用 next(),函数从上次暂停处继续执行。

🌟示例 1:基础语法复习

function* makeTea() {
  yield '煮茶 🍵'
  yield '加奶 🥛'
  yield '封口 ✅'
}
​
const worker = makeTea()
console.log(worker.next())
console.log(worker.next())
console.log(worker.next())
console.log(worker.next())

输出:

{ value: '煮茶 🍵', done: false }
{ value: '加奶 🥛', done: false }
{ value: '封口 ✅', done: false }
{ value: undefined, done: true }

🧋比喻:

Generator 就像“奶茶机分步制作”: 第一次按按钮煮茶、第二次加奶、第三次封口。 每一步 yield 停下来等你再按下一次。

💡口诀:

yield 暂停,next() 启动。”


🌟示例 2:在 yield 中传参(第 52 页)

function* chatBot() {
  let name = yield '你叫什么名字呀?'
  yield `你好,${name}~要来一杯奶茶吗?`
}
​
const bot = chatBot()
console.log(bot.next().value)
console.log(bot.next('小可爱').value)

输出:

你叫什么名字呀?
你好,小可爱~要来一杯奶茶吗?

🧋比喻:

这是一个“对话式奶茶机” 🤖:

  • 第一次问问题(yield 暂停等待输入)
  • 第二次收到回答后继续运行

💡口诀:

yield 问问题,next() 给答案。”


🌟示例 3:for...of 遍历 Generator(第 52 页)

function* steps() {
  yield '备料 🧂'
  yield '打奶盖 🥛'
  yield '出杯 ✅'
}
​
for (let step of steps()) {
  console.log(step)
}

输出:

备料 🧂
打奶盖 🥛
出杯 ✅

🧋比喻:

就像店长检查奶茶工序: 用 for...of 可以一口气执行所有步骤,自动播放完每个 yield

💡口诀:

for...of 是全自动播放。”


☕️7.2 异步解决方案演进史(第 53–55 页)

这一页超重要!!🔥 几乎每次面试都会问:“你知道 Promise、async/await、Generator 的关系吗?”


🌈 阶段 1:Callback 回调地狱(第 53 页)

ajax('煮茶', function (tea) {
  ajax('加奶', function (milk) {
    ajax('封口', function () {
      console.log('奶茶完成 ✅')
    })
  })
})

🧋比喻:

这像是: “师傅要等茶煮好→再等奶盖→再封口”, 结果一层套一层,代码像楼梯绕来绕去!😵‍💫

💡问题:

太多嵌套 → 不好看、难维护。


🌈 阶段 2:Promise(第 54 页)

makeTea()
  .then(addMilk)
  .then(seal)
  .then(() => console.log('奶茶完成 ✅'))

🧋比喻:

把“嵌套楼梯”改成“流水线”, 每一步都排队执行,干净整齐! .then() 就是“下一步任务”。

💡优点:

可读性提升、链式结构更顺。


🌈 阶段 3:Generator + co 库(第 55 页)

async/await 诞生之前, 程序员用 Generator + co 库 实现类似“同步写异步”的效果。


🌟示例:

function* process() {
  const tea = yield getTea()
  const milk = yield getMilk()
  console.log('完成:', tea, milk)
}
​
co(process)

🧋比喻:

yield 像一个个“等待点”📍 每次自动等待任务完成后再继续下一步。

co 库就是“帮你自动点 next() 的小助手”🤖。

💡总结:

Generator = async/await 的爸爸! 它先提出了「暂停 → 等待 → 再继续」的思路, 后来被 async/await 优化成更好用的语法糖 🍬。


🌈 阶段 4:async / await(现代主流)

async function makeMilkTea() {
  const tea = await getTea()
  const milk = await getMilk()
  console.log('完成奶茶 ✅', tea, milk)
}

🧋比喻:

async/await 是“自动化奶茶机”版本的 Generator。 不用自己写 yieldnext(), 系统帮你自动完成暂停和恢复操作, 代码更干净、像在讲故事一样自然~


🌸 小结(第 55 页)

阶段技术特点奶茶铺比喻
1回调函数一层套一层手动接单,累死
2Promise链式调用自动流水线
3Generator + co可暂停函数半自动奶茶机
4async / await语法糖封装全自动智能奶茶机 🤖

🌈 一句话口诀记忆:

从回调到自动化,异步像开奶茶店升级: 手冲 → 流水线 → 半自动 → 全智能!🍹

🌸 第 56–60 页:Generator 实战 & Decorator 入门


🍵7.3.5 async/await(第 56 页)

这一页在总结 Generator 和 async/await 的关系。


📘 结论:

async/await 就是 Generator + 自动执行器(co库) 的语法糖 🍬

换句话说:

  • 以前写 Generator 要 yield + next() 控制;
  • 现在直接写 await 就能暂停;
  • 系统帮你自动管理“何时恢复执行”。

🌟例子:

async function makeTea() {
  const tea = await getTea()
  const milk = await getMilk()
  console.log('完成奶茶 ✅', tea, milk)
}

🧋比喻:

Generator 是“半自动奶茶机”,要人工一下一下按按钮(next())。 async/await 是“全自动奶茶机”,泡完茶自己加奶、自动封口。

你只要坐等奶茶出杯即可 😎。

💡口诀:

“Generator 是原理,async/await 是进化版。”


🧃7.4 Generator 的应用场景(第 57–58 页)

这部分超实用,讲的是:

Generator 除了暂停函数,还能干嘛?


🧋应用 1:实现自定义迭代器

function* countDown(n) {
  while (n > 0) {
    yield n--
  }
}
​
for (let num of countDown(3)) {
  console.log(num)
}

输出:

3
2
1

🧋比喻:

就像一个“倒计时奶茶制作器”! 每次 yield 一个数字,直到做完所有步骤。

💡口诀:

“Generator = 一口气做不完,暂停留待下次还。”


🧋应用 2:异步流程控制(老派神器)

function* getData() {
  const a = yield fetch('/api/tea')
  const b = yield fetch('/api/milk')
  console.log(a, b)
}

🧋比喻:

一步步取数据,就像煮茶 → 加奶 → 出杯。 每次 yield 等待上一步完成。

以前用这种写法管理异步,现在被 async/await 接管了。


🧋应用 3:状态管理

function* toggle() {
  let state = false
  while (true) {
    state = !state
    yield state
  }
}
const g = toggle()
console.log(g.next().value) // true
console.log(g.next().value) // false

🧋比喻:

这就像“开关灯”🔦—— 每次调用 next() 状态在 true/false 间切换。

可用于动画控制、UI 开关等场景。

💡口诀:

“Generator 不仅能暂停,还能记状态。”


🌈 8. ES6 中的 Decorator 装饰器(第 59–60 页)

终于到了新主角登场 🎀 Decorator(装饰器)是给 类或方法 加“额外功能”的语法糖。 就像游戏里给角色戴上“强化装备”✨。


📘8.1 介绍(第 59 页)

装饰器其实就是一个函数,用来“修改”类或类的方法。

  • 它以 @ 开头;
  • 作用在类定义或类方法上;
  • 在 TypeScript、Vue3、Nest.js 中经常能看到。

🌟例子:

function log(target) {
  console.log('被装饰的对象:', target)
}
​
@log
class MilkTea {}

🧋比喻:

@log 就像“奶茶师的监控摄像头 📸”, 每当创建奶茶机(类)时,它就记录日志。

所以装饰器常用于“记录、权限、计时”等场景。

💡口诀:

“@ 就是加 Buff —— 不改原逻辑,只加特效。”


☕8.2 用法分类(第 60 页)

装饰器可以用在:

  1. 类装饰器:装饰整个类;
  2. 方法装饰器:装饰类中的函数;
  3. 属性装饰器:装饰类的变量。

🌟类装饰器例子:

function addTimestamp(target) {
  target.prototype.timestamp = new Date().toLocaleString()
}
​
@addTimestamp
class Order {}
​
const o = new Order()
console.log(o.timestamp)

输出(例如):

2025/10/17 23:59:59

🧋比喻:

这像是“每杯奶茶打印时间标签 🕓”。 每次下单自动带时间戳,不用手动贴。

💡口诀:

“类装饰器加通用属性,像给所有奶茶贴出厂日期。”


☕方法装饰器例子(预告)

function log(target, name, descriptor) {
  const old = descriptor.value
  descriptor.value = function (...args) {
    console.log(`[LOG] 调用方法 ${name}`)
    return old.apply(this, args)
  }
}
​
class Shop {
  @log
  serveTea() {
    console.log('奶茶制作中...')
  }
}
new Shop().serveTea()

输出:

[LOG] 调用方法 serveTea
奶茶制作中...

🧋比喻:

@log 像“点单日志系统”, 每次顾客点单都会被自动记录下来 📋。

💡口诀:

“@前缀装饰器,悄悄加功能,不动原逻辑。”


🌸 小结(第 56–60 页)

知识点功能奶茶铺比喻
async/await简化异步全自动奶茶机
Generator可暂停函数分阶段泡茶
yield暂停点工序中断等信号
Decorator装饰类或方法奶茶机加 Buff
@log日志装饰器每单自动记录
@timestamp类装饰器打印出厂时间

🌈 复习口诀:

async 自动机,Generator 老父亲; Decorator 加 Buff,奶茶更高薪!🧋✨

🌸 第 61–65 页:Decorator 深入 & Set / Map 入门


🧋8.2.1 方法装饰器(第 61 页)

方法装饰器是用来 修改类中的方法行为 的。 就像在“奶茶制作函数”外面包一层“额外逻辑”。


🌟例子:

function log(target, name, descriptor) {
  const old = descriptor.value
  descriptor.value = function (...args) {
    console.log(`[LOG] 调用方法 ${name}`)
    return old.apply(this, args)
  }
}
​
class MilkTea {
  @log
  make() {
    console.log('制作奶茶中 🧋')
  }
}
​
new MilkTea().make()

输出:

[LOG] 调用方法 make
制作奶茶中 🧋

🧋比喻:

这个装饰器就像在“奶茶机按钮”外加了个摄像头 📸。 每次有人点单(执行方法),都会被自动记录下来。

💡口诀:

“@log 先插嘴,再放行。”


☕8.2.2 参数装饰器(第 62 页)

参数装饰器可以给函数的参数加标记或验证逻辑


🌟例子:

function required(target, name, index) {
  console.log(`方法 ${name} 的第 ${index} 个参数是必填的`)
}
​
class Shop {
  order(@required item) {
    console.log(`下单商品:${item}`)
  }
}
​
new Shop().order('珍珠奶茶')

输出:

方法 order 的第 0 个参数是必填的
下单商品:珍珠奶茶

🧋比喻:

这就像“点单系统”的必填校验: 顾客下单时必须选奶茶种类,否则系统警告 ⚠️。

💡口诀:

“@required 像表单必填项,忘填会被喊回来!”


🍰8.2.3 组合装饰器(第 63 页)

多个装饰器可以叠加使用,就像奶茶上面一层层配料叠加~


🌟例子:

function first(target) { console.log('加珍珠 🧋') }
function second(target) { console.log('加奶盖 🥛') }
​
@first
@second
class MilkTea {}

输出:

加奶盖 🥛
加珍珠 🧋

🧋比喻:

后写的装饰器先执行, 所以 @second 先动手加奶盖,@first 后来再撒珍珠~

💡口诀:

“上写下后加,像做多层奶茶。”


🍡8.3 使用注意事项(第 63 页)

📋装饰器要注意:

  1. 它们只是“语法糖”,本质上是函数;
  2. 执行顺序很重要(从下往上);
  3. 必须开启编译支持(在 TypeScript 或 Babel 配置中启用)。

🧋比喻:

想让奶茶机支持“自定义插件”,得先开启“开发者模式” 不然装饰器语法会报错。


☕ 9. ES6 新增的 Set 和 Map(第 64–65 页)

接下来是数据结构篇:

Set 是独一无二的奶茶订单表 Map 是带标签的点单记录簿


🧋9.1 Set:独一无二集合

Set 类似数组,但不允许重复元素。


🌟例子:

const teaSet = new Set([ '珍珠', '布丁', '椰果', '珍珠' ])
console.log(teaSet)

输出:

Set(3) { '珍珠', '布丁', '椰果' }

🧋比喻:

你点了两次“珍珠”,店员只算一次。 因为 Set 只保留唯一项,自动去重!

💡口诀:

“Set 去重不啰嗦。”


🍩9.1.1 常用方法

方法功能举例
add()添加元素set.add('奶茶')
delete()删除元素set.delete('珍珠')
has()是否存在set.has('椰果')
clear()清空集合set.clear()
size长度属性set.size

🧋比喻:

这就像店长的“奶茶原料表”:

  • add() 进货
  • delete() 清库存
  • has() 检查原料在不在
  • clear() 一键清仓

💡口诀:

“add 增,del 减,has 查,clear 清。”


🍯9.1.2 遍历 Set(第 65 页)

const set = new Set(['A', 'B', 'C'])
for (let item of set) {
  console.log(item)
}

输出:

A
B
C

🧋比喻:

for...of 就是逐个核对原料库存, 看每种奶茶配料是否齐全 ✅。


☕ 9.2 Map:键值对集合(预告)

Map 类似对象 Object,但键可以是任意类型(不只字符串)。

const shop = new Map()
shop.set('name', '可爱奶茶店')
shop.set('address', '月亮街 520 号')
console.log(shop.get('name'))

输出:

可爱奶茶店

🧋比喻:

Map 像“奶茶店的档案卡片盒”: 每张卡(key)都有对应信息(value), 而且 key 不限于文字,甚至可以是对象或函数!


🌸 小结(第 61–65 页)

模块功能奶茶铺记忆法
@log方法记录日志每次点单自动记账
@required参数必填验证点单表校验系统
@first / @second多层装饰器奶盖 + 珍珠叠加
Set唯一集合奶茶原料表
Map键值集合店铺信息卡

🌈 复习口诀:

装饰器加插件,Set 保唯一; Map 记店铺账,奶茶清又甜!🧋✨

🌸 第 66–70 页:Set 完整用法 + Map 键值对集合


🍵9.1.3 delete()(第 66 页)

const set = new Set(['奶茶', '果茶', '冰沙'])
set.delete('果茶')
console.log(set)

输出:

Set(2) { '奶茶', '冰沙' }

🧋解释: delete() 是删除指定元素。 就像你把“果茶”下架,从库存表中划掉它。

💡口诀:

“delete 是删除一项原料。”


🍰9.1.4 has()

set.has('奶茶') // true

🧋解释: 判断元素是否存在。 就像店长问:库存里还有“奶茶”吗? 系统回答:有 ✅。

💡口诀:

“has 就是问:‘有这货吗?’”


🍮9.1.5 clear()

set.clear()

🧋解释: 清空整个 Set。 就像月底盘点完,把所有库存表清空。

💡口诀:

“clear = 一键清仓。”


🍡9.1.6 遍历 Set(第 66–67 页)

const set = new Set(['A', 'B', 'C'])
for (let item of set) {
  console.log(item)
}

输出:

A
B
C

🧋解释: for...of 遍历 Set,就像你一个个检查仓库里的原料。


🌟forEach 遍历

set.forEach((value) => console.log(value))

🧋比喻: 这相当于你让“仓库助理”帮你一项项点名。

💡口诀:

“for...of 自己查,forEach 助理查。”


🌟数组互转技巧(超级实用!)

const arr = [...set]
const newSet = new Set(arr)

🧋解释: Set → 数组:加上 ... 展开符号。 数组 → Set:new 一下去重集合。

💡比喻:

像你把“奶茶原料清单”导出成 Excel,再导回仓库系统时自动去重。

💡口诀:

“三点展开去重忙,Set 数组互转香。”


🍯9.1.7 数学运算小技巧(第 67 页)

Set 还能玩“并集、交集、差集”:

let a = new Set([1, 2, 3])
let b = new Set([3, 4, 5])

// 并集
let union = new Set([...a, ...b])
// 交集
let intersect = new Set([...a].filter(x => b.has(x)))
// 差集
let diff = new Set([...a].filter(x => !b.has(x)))

🧋比喻:

  • 并集:A+B 两家奶茶店的菜单合并。
  • 交集:都有的饮品(比如都卖“珍珠奶茶”)。
  • 差集:只在 A 店有、B 店没有的限定款。

💡口诀:

“并就拼,交就筛,差就减。”


☕9.2 Map(第 68–70 页)

Map 是 键值对集合(key-value),类似对象 Object,但更灵活。


🌟9.2.1 初始化 Map

const shop = new Map([
  ['name', '小可爱奶茶店'],
  ['address', '月亮街 520 号']
])

🧋比喻:

Map 就像一个“奶茶档案卡盒”: 每张卡片有 key(属性)value(值)

💡口诀:

“Map 是对象的加强版——更聪明的记事本。”


🍵9.2.2 常用方法一览

方法说明比喻
set(key, value)添加/修改键值添加或更新奶茶信息
get(key)获取值查询某项信息
has(key)判断是否存在看某个档案是否有
delete(key)删除项删除某条记录
clear()清空所有项全部销毁
size项目数量档案总数

🌟9.2.3 set()

const shop = new Map()
shop.set('name', '小可爱奶茶屋')
shop.set('address', '天上人间路 66 号')

🧋解释:

像是往档案卡片里写入信息。 写两张卡:店名 + 地址。


🌟9.2.4 get()

console.log(shop.get('name')) // 小可爱奶茶屋

🧋解释:

查卡片上的内容,比如: “请问店名是什么?” 系统回答:“小可爱奶茶屋~💞”


🌟9.2.5 has()

shop.has('address') // true

🧋解释:

就像你问:“这张卡片上有没有‘地址’这项?” 有的~✅


🌟9.2.6 delete()

shop.delete('name')

🧋解释:

像把“店名”这张卡片撕掉。 数据立刻删除,干干净净。


🌟9.2.7 clear()

shop.clear()

🧋解释:

一键销毁全部档案。 整个奶茶店的资料都被清空了(慎用⚠️)。


🌈 小结(第 66–70 页)

知识点功能奶茶铺比喻
Set唯一集合原料库存表,自动去重
Map键值对集合店铺档案盒
add / delete / has / clearSet 操作进货、清货、查库存
set / get / has / clearMap 操作录档、查档、删档
并集 / 交集 / 差集数学集合菜单对比分析

🌸 复习口诀:

Set 去重不重复,Map 存档最靠谱; add 进货 has 查货,get 查询真舒服!🧋✨

🌸 第 71–75 页:WeakSet、WeakMap、Proxy 通俗讲解


☕9.2.8 遍历 Map(第 71 页)

const map = new Map([
  ['name', '小可爱奶茶店'],
  ['drink', '珍珠奶茶'],
  ['price', 18]
])

for (let [key, value] of map) {
  console.log(key, value)
}

输出:

name 小可爱奶茶店
drink 珍珠奶茶
price 18

🧋解释: Map 可以用 for...of 来循环出键和值。

就像你翻阅档案卡片盒 📇,一张张读出:“名字、饮品、价格”。

💡口诀:

“Set 只看值,Map 看键值。”


🍰9.3 WeakSet 与 WeakMap(第 72–74 页)

它们听起来像“Set”和“Map”的兄弟, 但其实是“短期关系”的版本。 适合“临时对象”,防止内存泄漏。


🌟9.3.1 WeakSet(第 72 页)

let ws = new WeakSet()
let cup = { name: '奶茶杯' }

ws.add(cup)
console.log(ws.has(cup)) // true

cup = null
// 对象被回收,WeakSet 自动清理

🧋解释: WeakSet 只接受对象类型, 并且不会阻止这些对象被垃圾回收(GC)

💡比喻:

你把「杯子」登记进“借用记录簿”, 但客人喝完丢掉了,系统会自动清空记录

📘区别:

普通 SetWeakSet
可存任意类型只能存对象
不会自动清理自动清理(防内存泄漏)
可遍历不可遍历

💡口诀:

“WeakSet 不强求,对象走就随它走~🍃”


🌟9.3.2 WeakMap(第 73–74 页)

let wm = new WeakMap()
let cup = { id: 1 }

wm.set(cup, '借出中')
console.log(wm.get(cup)) // 借出中

cup = null
// WeakMap 自动清除 key 对应的记录

🧋解释: WeakMap 的键只能是对象, 并且当这个对象没被引用时,它会自动删除那条数据。

💡比喻:

WeakMap 像“临时租赁档案”: 只要客人离开(对象被释放), 系统就自动把这条记录删掉。

📘区别:

普通 MapWeakMap
可用任意类型做 key只能用对象
可遍历不可遍历
不会自动清理自动清理内存
适合全局配置适合私密缓存

💡口诀:

“WeakMap 用来记短情,自动断联不占心 🫧”


🌈10. Proxy(第 75 页)

终于来到重量级主角——Proxy 代理对象


📘10.1 介绍

Proxy 可以理解为: “在对象和外部操作之间,加了一层中间人”。

当你访问、修改对象属性时, Proxy 可以拦截、修改、验证这些操作。


🌟例子:

const person = { name: '小可爱', age: 18 }

const proxy = new Proxy(person, {
  get(target, key) {
    console.log(`正在读取属性:${key}`)
    return target[key]
  },
  set(target, key, value) {
    console.log(`正在修改属性 ${key}${value}`)
    target[key] = value
  }
})

proxy.name      // 正在读取属性:name
proxy.age = 20  // 正在修改属性 age 为 20

🧋解释: Proxy 就像一个「奶茶店前台接待员」🧾:

  • 顾客想查信息(get)→ 前台汇报;
  • 顾客要改菜单(set)→ 前台登记修改。

💡口诀:

“对象自己不出面,全靠代理挡前线~”


🌟常见的 Proxy 拦截方法:

方法说明奶茶店比喻
get读取属性拦截客人问价钱,前台先看菜单
set修改属性拦截改菜单前必须审批
deleteProperty删除属性拦截删除菜单前核实确认
hasin 操作拦截查看某饮品是否在菜单上
ownKeys遍历属性拦截打印所有菜单项

🌟用途场景举例:

✅ 表单数据验证
const form = new Proxy({}, {
  set(obj, prop, value) {
    if (prop === 'age' && value < 0) {
      throw new Error('年龄不能是负数!')
    }
    obj[prop] = value
  }
})
form.age = 18   // ✅
form.age = -1   // ❌ 抛出错误

🧋比喻:

代理像“奶茶前台阿姨”, 不让你填错年龄、不让乱改菜单。

💡口诀:

“代理可监控,校验又轻松。”


🌸 小结(第 71–75 页)

知识点功能奶茶铺记忆法
WeakSet弱引用集合临时借杯登记簿
WeakMap弱引用键值表客人档案,自动清理
Proxy代理对象店铺前台,拦截一切请求

🌈 复习口诀:

WeakSet 存对象,WeakMap 记短情; Proxy 做前台,严防改菜单!🧋✨

🌸 第 76–80 页:Proxy + Reflect 深入讲解


☕10.2 用法(第 76 页)

前面学的 Proxy,只是“拦截”对象操作。 这页开始讲它的完整语法

let proxy = new Proxy(target, handler)
  • target:被代理的目标对象(比如菜单 menu
  • handler:代理逻辑配置(比如:get、set、delete 等操作)

💡比喻:

target 是奶茶店菜单, handler 是前台小姐姐的工作规章, Proxy 就是让小姐姐站到菜单前,接收所有操作。


🍵10.2.1 参数说明(第 76 页)

handler 拦截器对象

这个 handler 可以定义很多“钩子函数”,比如:

钩子函数拦截的操作奶茶铺类比
get()访问属性顾客问价格
set()设置属性修改菜单价
has()in 操作菜单上是否有某饮品
deleteProperty()删除属性删除菜单项
ownKeys()列出属性打印完整菜单

💡口诀:

“前台小姐姐:问、改、查、删,我都能拦住!”


☕10.2.3 Reflect(第 77 页)

Reflect 是 Proxy 的好搭档,它提供原始操作方法

Proxy 负责“拦”,Reflect 负责“干”。 两个合在一起,简直就是前台和后台完美联动!


🌟例子:

let person = { name: '小可爱', age: 18 }
​
let proxy = new Proxy(person, {
  get(target, key, receiver) {
    console.log(`获取属性:${key}`)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    console.log(`修改属性:${key}=${value}`)
    return Reflect.set(target, key, value, receiver)
  }
})
​
proxy.name
proxy.age = 20

输出:

获取属性:name
修改属性:age=20

🧋解释:

  • Proxy 拦住操作;
  • Reflect 代为执行;
  • 像小姐姐说:“等我问后台确认一下哦~✅”。

💡口诀:

“Proxy 拦截嘴,Reflect 动真手。”


🌈10.2.4 get() 深入(第 77–78 页)

let handler = {
  get(target, prop, receiver) {
    if (prop === 'price' && target[prop] > 50) {
      console.log('太贵啦!要不要考虑打个折?')
    }
    return Reflect.get(target, prop, receiver)
  }
}
​
let menu = { name: '草莓奶盖', price: 68 }
let proxy = new Proxy(menu, handler)
​
console.log(proxy.price)

输出:

太贵啦!要不要考虑打个折?
68

🧋比喻:

顾客问价格,前台偷偷看后台菜单: “咦,这杯奶茶太贵了吧!我提醒下老板要打折 🫢。”

💡口诀:

“Reflect 帮前台完成工作,Proxy 只负责提醒。”


🌟10.2.5 set()(第 78–79 页)

let handler = {
  set(target, prop, value) {
    if (prop === 'age' && value < 0) {
      throw new Error('年龄不能是负数!')
    }
    return Reflect.set(target, prop, value)
  }
}
​
let person = new Proxy({}, handler)
person.age = 20    // ✅
person.age = -1    // ❌ 抛错

🧋解释:

前台小姐姐在修改档案前先检查一遍, 如果填了奇怪的值(比如“负年龄”),直接打回。

💡口诀:

“Proxy 审核,Reflect 执行。”


☕10.2.6 deleteProperty()(第 79–80 页)

let obj = { tea: '珍珠奶茶', price: 18 }
​
let proxy = new Proxy(obj, {
  deleteProperty(target, prop) {
    console.log(`尝试删除属性 ${prop}`)
    return Reflect.deleteProperty(target, prop)
  }
})
​
delete proxy.price
console.log(obj)

输出:

尝试删除属性 price
{ tea: '珍珠奶茶' }

🧋解释:

删除菜单前,Proxy 会提醒你确认操作, Reflect 才真的从后台系统删除那一项。

💡比喻:

“前台小姐姐问:你确定要删掉‘价格’吗?好,那我通知后厨~”

💡口诀:

“Proxy 管嘴,Reflect 动手。”


🌈 小结(第 76–80 页)

知识点功能奶茶店比喻
Proxy拦截对象操作前台小姐姐接客
Reflect执行底层逻辑后台系统确认
get/set/delete拦截 取/改/删 操作前台代劳 + 后台落实

🧋复习口诀:

Proxy 拦截嘴,Reflect 动手腿; 一起配合好,对象更完美!🧋💪

🌸 第 81–84 页:Proxy 高级用法 & 实战场景


☕10.2.6 deleteProperty() 删除属性拦截(第 81 页)

"use strict";
const handler = {
  deleteProperty(target, key) {
    console.log(`尝试删除属性:${key}`);
    return Reflect.deleteProperty(target, key);
  }
};
​
const menu = { tea: "珍珠奶茶", price: 18 };
const proxy = new Proxy(menu, handler);
​
delete proxy.price;

输出:

尝试删除属性:price

🧋解释: 当你用 delete proxy.xxx 删除属性时,Proxy 会先拦截。 只有 Reflect.deleteProperty 执行后,才真的删掉那一项。

💡生活比喻:

顾客说:“这杯奶茶太贵了,把它从菜单删掉吧!” 前台小姐姐会先记录一笔日志(console.log),再通知后厨真正删掉。

💬 如果返回 false,则删除失败。

💡口诀:

“delete 前台先过目,Reflect 再执行删。”


🍰10.2.7 撤销代理 Proxy.revocable()(第 82 页)

const { proxy, revoke } = Proxy.revocable(target, handler)

🧋解释: 有时候,你创建的代理只想用一阵子,比如“临时工”模式。 那就可以用 Proxy.revocable()。 执行 revoke() 后,这个代理就失效了。

💡比喻:

“前台小姐姐是临时兼职,合同到期她就下班不干了~💼”

const { proxy, revoke } = Proxy.revocable({ name: "奶茶店" }, {});
console.log(proxy.name); // 奶茶店
revoke();
console.log(proxy.name); // ❌ 报错:Proxy 已被撤销

💡口诀:

“revocable 撤代理,合同一到立马离。”


☕10.3 使用场景(第 82–84 页)

终于来到 Proxy 的现实应用!🌈 这部分超实用,面试也很爱问!


🌟1️⃣ 数据校验(类型检查)

let numericDataStore = new Proxy({}, {
  set(target, key, value) {
    if (typeof value !== 'number') {
      throw new TypeError(`${key} 必须是数字类型`);
    }
    return Reflect.set(target, key, value);
  }
});
​
numericDataStore.count = 100;  // ✅
numericDataStore.count = "abc"; // ❌ 抛出错误

🧋解释:

Proxy 像一个“验货员”, 检查你存进仓库的数据是不是合格的“数字货”。 一旦你放进杂货(字符串),立刻报警 🚨

💡口诀:

“Proxy 做验货,类型不对就打回货。”


🌟2️⃣ API 权限控制(限制访问)

let api = {
  _apiKeys: ["123abc", "456def"],
  getUser(id) { console.log("获取用户", id); }
}
​
const RESTRICTED = ["_apiKeys"];
let secureAPI = new Proxy(api, {
  get(target, key, receiver) {
    if (RESTRICTED.includes(key)) {
      throw new Error(`${key} 是受保护的,禁止访问!`);
    }
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    if (RESTRICTED.includes(key)) {
      throw new Error(`${key} 不允许修改!`);
    }
    return Reflect.set(target, key, value, receiver);
  }
});

🧋解释:

这里 Proxy 像“奶茶店后台权限系统”☕ 你不能随便访问 核心配方(_apiKeys) , 否则系统会报错,防止泄露秘方。

💡比喻:

顾客可以点单(getUser), 但不能偷看配方(_apiKeys)。

💡口诀:

“机密属性加 Proxy,看得到不让动。”


🌟3️⃣ 响应式系统(Vue3 核心原理)

这就是最关键的一点:Vue3 的“响应式”就是基于 Proxy 实现的! 🧠

const queuedObservers = new Set();
​
function observe(fn) {
  queuedObservers.add(fn);
}
​
function observable(obj) {
  return new Proxy(obj, {
    set(target, key, value) {
      const result = Reflect.set(target, key, value);
      queuedObservers.forEach(fn => fn());
      return result;
    }
  });
}

🧋解释:

每次你改动 obj 的属性时,Proxy 都能捕捉到! 它会自动通知所有观察者(比如页面渲染函数)更新数据。

💡比喻:

就像奶茶库存系统: 你修改了“珍珠数量”,前台屏幕、采购系统、仓库清单都会立刻同步。

💡口诀:

“Proxy 做监控,数据变动秒同步。”


🌈 小结(第 81–84 页)

场景作用奶茶店类比
deleteProperty拦截删除属性删除菜单项时需确认
revocable临时代理,可撤销临时工合同制
数据校验检查输入类型验货员检查商品
API 控制限制访问私有属性防止偷看秘方
响应式系统数据联动更新修改库存自动更新前台显示

🧋复习口诀:

删前台看,撤合同完; 校数据防乱,控 API 护密串; Vue 响应靠 Proxy,一改全店都更新!💫