You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
30-Days-Of-JavaScript/js_notes/11-higher-order-functions.js

161 lines
8.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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