🌸 第 1–5 页:var、let、const 的区别详解
🧋一、整体概念先感受一下
你可以把 var、let、const 想成三种“放原料的柜子”:
| 关键字 | 作用范围 | 特点 | 比喻 |
|---|---|---|---|
var | 函数作用域 | 会“提前声明” | 老式开放柜子(谁都能乱翻) |
let | 块级作用域 | 不会提前声明,不能重复声明 | 独立储物格(进门前要钥匙) |
const | 块级作用域 | 声明后不能改 | 锁死的储物柜(封印配方) |
🧃1.1 var:老前辈,开放式“公共奶茶柜”
📘特点:
- 会变量提升(hoisting) 意思是:即使你还没声明变量,JS 也会先“默默创建”它。
- 作用域是函数级(function scope) 在函数里有效,不受花括号影响。
- 可以重复声明
🍬代码解释:
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:聪明的“小分区储物柜”
📘特点:
- 不能重复声明。
- 有 块级作用域。
- 声明前不能访问(会报错)。
- 没有变量提升。
🍬示例:
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:恒定不变的“密封配方柜”
📘特点:
- 声明必须初始化。
- 不可重新赋值。
- 块级作用域。
- 适合存放“不会变的常量”。
🍬示例:
const brand = "喜茶"
brand = "奈雪" // ❌ 报错:Assignment to constant variable
🧋比喻:
const是“店长专用密封柜”, 里头存放的是“招牌奶茶秘方”, 谁都改不了——除非拆柜子!
🍭但注意:
const menu = { drink: "绿茶" }
menu.drink = "奶茶" // ✅ 可以修改对象内容
🧋比喻:
const锁的是“柜子”,不是“柜子里的原料”~ 所以对象属性还能改,但不能换整个对象。
🍩1.4 小结:三者的区别表格
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数级 | 块级 | 块级 |
| 变量提升 | ✅ 有 | ❌ 无 | ❌ 无 |
| 可重复声明 | ✅ | ❌ | ❌ |
| 暂时性死区 | ❌ | ✅ | ✅ |
| 是否可修改 | ✅ | ✅ | ❌(值不可改) |
🧃口诀总结记忆:
🧋 “var 老好人,啥都能干;let 小心翼翼,只在小格;const 店长专用,动不得!”
🧋1️⃣ 为什么 var 会“变量提升”?
🧠 核心原理: 在 JavaScript 执行前,解释器会先“预扫描”代码,把所有用 var 声明的变量提前创建。 但注意!只声明提前,不赋值提前。
console.log(drink) // undefined
var drink = '奶茶'
相当于:
var drink
console.log(drink)
drink = '奶茶'
🧋生活类比:
想象你在开店前,店长已经提前把所有“奶茶柜子”装好了标签(比如“珍珠”“红茶”), 只是柜子里还没装东西。 所以当你开门营业(代码执行)前去看柜子时——它确实在,但还是空的!☁️
🧩面试总结句:
var的变量提升,是因为 JavaScript 引擎在编译阶段就创建了变量声明的“存储空间”, 而赋值操作则留到执行阶段才进行。
🧋2️⃣ let 和 const 的“暂时性死区”是什么?
🧠 概念: 在代码块 {} 内,如果用 let 或 const 声明变量,在声明之前访问它,就会报错。 这个“声明前禁止访问”的区域,就叫 暂时性死区(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...of、entries()、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('乌龙奶茶')
🧋比喻:
箭头函数就像自动封口奶茶机—— 不需要太多人工操作(
function、return), 一键制作,干净利落✨!
🍡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 和 -0(Object.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/await是 Promise 的语法糖。 它让异步代码像写同步代码一样顺滑 ✨。
🌟例子一:用 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 | 浏览器端 | 异步加载(适合网页) |
| CommonJS | Node.js | 同步加载(适合服务器) |
| ES Module | ES6 原生方案 | 官方统一标准 |
🧋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。 不用自己写
yield、next(), 系统帮你自动完成暂停和恢复操作, 代码更干净、像在讲故事一样自然~
🌸 小结(第 55 页)
| 阶段 | 技术 | 特点 | 奶茶铺比喻 |
|---|---|---|---|
| 1 | 回调函数 | 一层套一层 | 手动接单,累死 |
| 2 | Promise | 链式调用 | 自动流水线 |
| 3 | Generator + co | 可暂停函数 | 半自动奶茶机 |
| 4 | async / 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 页)
装饰器可以用在:
- 类装饰器:装饰整个类;
- 方法装饰器:装饰类中的函数;
- 属性装饰器:装饰类的变量。
🌟类装饰器例子:
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 页)
📋装饰器要注意:
- 它们只是“语法糖”,本质上是函数;
- 执行顺序很重要(从下往上);
- 必须开启编译支持(在 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 / clear | Set 操作 | 进货、清货、查库存 |
| set / get / has / clear | Map 操作 | 录档、查档、删档 |
| 并集 / 交集 / 差集 | 数学集合 | 菜单对比分析 |
🌸 复习口诀:
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) 。
💡比喻:
你把「杯子」登记进“借用记录簿”, 但客人喝完丢掉了,系统会自动清空记录。
📘区别:
| 普通 Set | WeakSet |
|---|---|
| 可存任意类型 | 只能存对象 |
| 不会自动清理 | 自动清理(防内存泄漏) |
| 可遍历 | 不可遍历 |
💡口诀:
“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 像“临时租赁档案”: 只要客人离开(对象被释放), 系统就自动把这条记录删掉。
📘区别:
| 普通 Map | WeakMap |
|---|---|
| 可用任意类型做 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 | 删除属性拦截 | 删除菜单前核实确认 |
has | in 操作拦截 | 查看某饮品是否在菜单上 |
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,一改全店都更新!💫