docs(js_notes): complete ch11 higher-order functions (map/filter/reduce)

- 11-higher-order-functions.js: chapter + exercises A-F done; E/F cleaned
  into / traces (^ is XOR not power; [a,b] is comma operator not slice;
  reduce without initial value avoids leading space)
- my-mistakes: add P (^ vs **) and Q (comma operator vs slice), both
  'runs but wrong'; explain comma operator; update quick-lookup table
- README: mark ch11 done
pull/1034/head
Jaydon 5 days ago
parent edfff2a6e4
commit a0f8e5371e

@ -161,6 +161,29 @@ const make = (name) => ({ name: name }) // ✅ () 逼它当表达式 → 返回
里面的 `name:` 又被当成**标签语句**(JS 给循环起名字的老语法,`名字:`),于是静默返回 undefined
- **教训**:箭头函数**直接返回对象**,用 `()` 把对象裹起来,逼回"表达式"语境。
## P. 用 `^` 当乘方(其实是按位异或)⭐(能跑不报错)
📍来源:`../11-higher-order-functions.js` 练习 E
```js
8 ** 2 // 64 ✅ 乘方用 **(或 n*n)
8 ^ 2 // 10 ❌ ^ 是"按位异或(XOR)",不是乘方!8^2=10、10^2=8
```
- **现象**:算出个莫名其妙的数,**不报错**(最难发现),和平方毫无关系
- **教训**:JS 的乘方是 `**`;`^` 是位运算(异或)。从数学/别的语境迁移来的人常栽。
## Q. `arr[a, b]` 当成切片(其实是逗号运算符,取单个)⭐
📍来源:`../11-higher-order-functions.js` 练习 F
```js
s[1, s.length-1] // ❌ 逗号运算符只取最后一个值 → 等于 s[s.length-1],取【单个字符】
s.slice(1) // ✅ 切片要用 .slice(见 04 章)
```
- **现象**:想切一段,却只得到一个字符
- **教训**:JS 没有 `[start, end]` 切片语法;`[]` 里的逗号是"逗号运算符"。
逗号运算符 `a, b`:**从左到右全都算一遍,但整个表达式的值 = 最后一个**
(`(1,2,3)` 的值是 `3`)。所以 `s[1, len-1]` = `s[len-1]`,取单个字符。
切字符串/数组一律用 `.slice(start, end)`。日常几乎用不到逗号运算符,认得它别踩坑即可。
---
## 🔁 高频报错 → 秒查表
@ -175,3 +198,4 @@ const make = (name) => ({ name: name }) // ✅ () 逼它当表达式 → 返回
| 屏幕啥都没有 | 忘了 console.log / 没接住返回值 | D、E |
| 从某行起全报错 | 引号没配对 | I |
| 函数返回 `undefined`(不报错) | `{}` 里忘 return / 箭头返回对象没加 `()` | N、O |
| 算出莫名其妙的数(不报错) | `^` 当乘方(实为异或)/ `[a,b]` 当切片 | P、Q |

@ -0,0 +1,161 @@
// 11 · 高阶函数 + 回调(Higher-order functions & callbacks)
// 学练复习一体:概念总结 → 示范 → 轮到你
// 跑法:Quokka / 右上角 ▷ / node js_notes/11-higher-order-functions.js
// 示范区带预期输出;轮到你区只给要求,自己写
//
// ═════════════════════ 概念总结 ═════════════════════
//
// 你在第 10 章练习 F 已经摸到门了:repeat(fn, times) 把【函数当参数传进去】再调用。
// 这一章把这个能力用到最常见的地方:处理数组。这三个方法(map/filter/reduce)是
// pi/TS 真实代码里【出现频率最高】的东西,是读懂真实代码的第一道门槛。
//
// ── 0. 两个词:高阶函数 & 回调 ⭐ ────────────────
// • 回调函数(callback):被你【传给别人、由别人来调用】的那个函数。
// • 高阶函数(higher-order function):【接收函数当参数】或【返回一个函数】的函数。
// map/filter/reduce/forEach 都是高阶函数;你传给它们的箭头函数就是回调。
// [1,2,3].map(n => n * 2)
// └─ map 是高阶函数 └─ n => n*2 是回调(map 会对每个元素调用它)
// 💡 这正是 agent/pi 的核心套路:你注册"工具函数",框架在合适时机回调它们。
//
// ── 1. map:每个元素做变换,返回【等长】新数组 ⭐⭐ ──
// const nums = [1, 2, 3]
// nums.map(n => n * 2) // [2, 4, 6] ← 一进一出,长度不变
// 回调的返回值 = 新数组对应位置的元素。不改原数组(返回新的)。
// 常用:从对象数组里"抽一列":
// users.map(u => u.name) // ['Tom', 'Jerry']
//
// ── 2. filter:挑出【回调返回 true】的元素 ⭐⭐ ──
// const nums = [1, 2, 3, 4]
// nums.filter(n => n % 2 === 0) // [2, 4] ← 回调返回 true 的才留下
// 回调要返回【布尔】(条件);返回 true 保留,false 丢弃。长度可能变短。不改原数组。
//
// ── 3. reduce:把整个数组【归并成一个值】⭐⭐(最难,最强)──
// nums.reduce((acc, n) => acc + n, 0)
// └acc └当前元素 └初始值
// • acc(accumulator 累加器):跨每一轮"攒结果"的容器,上一轮的返回值传给下一轮
// • 第二个参数 0 是 acc 的【初始值】(强烈建议总是写上)
// 过程(求和 [1,2,3],初始 0):
// 轮1 acc=0, n=1 → 返回 1
// 轮2 acc=1, n=2 → 返回 3
// 轮3 acc=3, n=3 → 返回 6 ← 最终结果
// reduce 能做 map/filter 能做的一切(求和、找最大、计数、拼接…),但先把上面三个用熟。
//
// ── 4. 其他常用高阶方法(顺带认识)──────────────
// forEach(fn) 只遍历做事,【没有返回值】(想要新数组用 map,别用 forEach)
// find(fn) 返回【第一个】让回调为 true 的元素(找不到 undefined)
// some(fn) 只要【有一个】满足 → true
// every(fn) 【全部】满足 → true
//
// ── 5. 链式调用(chaining)⭐ ────────────────────
// map/filter 返回新数组 → 可以接着点下一个方法,像流水线:
// nums.filter(n => n % 2 === 1).map(n => n * 10)
// 先挑出奇数,再各 ×10。真实代码里到处是这种"过滤→变换"的链。
//
// ═════ 要点回顾 ═════
// 1. 高阶函数=接收/返回函数;回调=被传进去、由别人调用的函数 ⭐
// 2. map:变换,等长新数组(回调返回值=新元素)⭐⭐
// 3. filter:筛选,回调返回布尔,true 才留 ⭐⭐
// 4. reduce:归并成一个值,(acc, n) => ...,别忘初始值 ⭐⭐
// 5. forEach 无返回值;find/some/every 各司其职
// 6. map/filter 返回新数组 → 可链式;都不改原数组 ⭐
//
// ═════════════════════ 示范 ═════════════════════
const nums = [1, 2, 3, 4, 5]
// ── 1. map:每个 ×2,等长新数组 ⭐ ─────────────────
console.log(nums.map((n) => n * 2)) // [ 2, 4, 6, 8, 10 ]
console.log(nums) // [ 1, 2, 3, 4, 5 ] ← 原数组没变
// ── 2. filter:只留偶数 ⭐ ─────────────────────────
console.log(nums.filter((n) => n % 2 === 0)) // [ 2, 4 ]
// ── 3. reduce:求和(初始值 0)⭐ ──────────────────
console.log(nums.reduce((acc, n) => acc + n, 0)) // 15
// 看清过程:把每一轮打印出来
nums.reduce((acc, n) => {
console.log(` acc=${acc}, n=${n}${acc + n}`) // 0,1→1 / 1,2→3 / 3,3→6 / 6,4→10 / 10,5→15
return acc + n
}, 0)
// ── 4. forEach:只做事,无返回值 ──────────────────
nums.forEach((n) => process.stdout.write(n + ' ')) // 1 2 3 4 5
console.log('← forEach 遍历')
console.log(nums.forEach((n) => n)) // undefined ← forEach 没有返回值!
// ── 5. find / some / every ───────────────────────
console.log(nums.find((n) => n > 3)) // 4 ← 第一个大于 3 的
console.log(nums.some((n) => n > 4)) // true ← 有一个大于 4
console.log(nums.every((n) => n > 0)) // true ← 全部大于 0
// ── 6. 链式:先筛奇数,再各 ×10 ⭐ ─────────────────
console.log(nums.filter((n) => n % 2 === 1).map((n) => n * 10)) // [ 10, 30, 50 ]
// ── 7. 对象数组(呼应 07 章,真实数据形状)⭐ ──────
const users = [
{ name: 'Tom', age: 20 },
{ name: 'Jerry', age: 18 },
]
console.log(users.map((u) => u.name)) // [ 'Tom', 'Jerry' ] ← 抽一列
console.log(users.filter((u) => u.age >= 20)) // [ { name: 'Tom', age: 20 } ] ← 挑成年
// ═════════════════════ 轮到你 ═════════════════════
// (只给要求,自己写代码)
// 练习 A(map):有 const prices = [10, 20, 30]
// 用 map 给每个价格打八折(×0.8),打印新数组([8, 16, 24])
const prices = [10, 20, 30]
console.log(prices.map(n => n * 0.8))
// 练习 B(filter):有 const ages = [12, 18, 25, 9, 30]
// 用 filter 挑出所有 >= 18 的,打印([18, 25, 30])
const ages = [12, 18, 25, 9, 30]
console.log(ages.filter(n => n >= 18))
// 练习 C(reduce):有 const nums = [3, 8, 1, 10, 5]
// 用 reduce 算出它们的总和,打印(27)
// 提示:回调是 (acc, n) => ...,初始值给 0
const nums_ = [3, 8, 1, 10, 5]
console.log(nums_.reduce((acc, n) => acc + n, 0))
// 练习 D(map + 对象数组):有下面这个 products
// const products = [
// { name: '苹果', price: 5 },
// { name: '香蕉', price: 3 },
// ]
// 用 map 得到一个【只含名字】的数组 ['苹果', '香蕉'],打印
const products = [
{ name: '苹果', price: 5 },
{ name: '香蕉', price: 3 },
]
console.log(products.map(product => product.name))
// 练习 E(链式,动脑):还是上面的 nums = [3, 8, 1, 10, 5]
// 先用 filter 挑出偶数,再用 map 把它们各自平方,打印。
// 提示:两个方法链着写,filter(...).map(...)
// ❌ ^ 在 JS 是"按位异或"不是乘方:8^2=10、10^2=8 → 得 [10, 8],和平方无关
// console.log(nums_.filter(n => n % 2 === 0).map(n => n^2))
// ✅ 乘方用 **(或 n*n)
console.log(nums_.filter(n => n % 2 === 0).map(n => n**2)) // [ 64, 100 ]
// 练习 F(reduce 进阶,挑战):有 const words = ['I', 'love', 'js']
// 用 reduce 把它们拼成一句话 'I love js'(单词间加空格)。
const words = ['I', 'love', 'js']
// ❌ 初始值给 "":第一轮 acc="" → " I",开头多一个空格 → 结果 " I love js"
// let sentence = words.reduce((acc, n) => `${acc} ${n}`, "")
// ❌ 想切掉开头空格却写错:sentence[1, ...] 的逗号是"逗号运算符",只取最后一个值,
// 等于 sentence[9] → 取【单个字符】's',不是切片(切片要用 .slice)
// console.log(sentence[1, sentence.length-1])
// 🔸 能救回来但不优雅:事后 slice 掉开头空格
// console.log(sentence.slice(1))
// ✅ 从源头解决:不给初始值 → reduce 拿第一个词当初始 acc、从第二个开始拼 → 开头无空格
const sentence = words.reduce((acc, n) => `${acc} ${n}`)
console.log(sentence) // I love js

@ -61,7 +61,7 @@
### 三、逻辑与函数(pi 前置 · 核心)
- [x] `09-loops` 循环
- [x] `10-functions` 函数(声明/表达式、箭头、参数、返回、作用域、提升/TDZ)
- [ ] `11-higher-order-functions` 高阶函数 + 回调(map / filter / reduce)← 读 TS 必备
- [x] `11-higher-order-functions` 高阶函数 + 回调(map / filter / reduce、链式)← 读 TS 必备
- [ ] `12-destructuring-spread` 解构与展开 ← pi 源码里到处是,读代码前置
- [ ] `13-closures` 闭包(理解 agent 状态、回调捕获)

Loading…
Cancel
Save