From 25be1a4fca8b6a8287f2f8cab05776910a625384 Mon Sep 17 00:00:00 2001 From: Jaydon Date: Fri, 24 Jul 2026 03:27:19 +0800 Subject: [PATCH] =?UTF-8?q?docs(js=5Fnotes):=20explain=20hoisting=20+=20TD?= =?UTF-8?q?Z=20in=20ch10;=20reword=20=E9=94=9A=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 10-functions.js: add hoisting/TDZ section (why names are registered but not touchable; var silent-undefined vs const/let TDZ error) + runnable demo; reword 锚点 to plainer phrasing - my-mistakes.md: same 锚点 rewording in entry N --- js_notes/00-concepts/my-mistakes.md | 2 +- js_notes/10-functions.js | 58 ++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/js_notes/00-concepts/my-mistakes.md b/js_notes/00-concepts/my-mistakes.md index 3907e07..9ce9827 100644 --- a/js_notes/00-concepts/my-mistakes.md +++ b/js_notes/00-concepts/my-mistakes.md @@ -140,7 +140,7 @@ const add = (a, b) => a + b // ✅ 无 {}:右边是表达式(值),自动 const add = (a, b) => { return a + b } // ✅ 有 {}:块里必须手写 return ``` - **现象**:函数默默返回 `undefined`,**不报错**(最难查) -- **根源**:锚点是 **`=>` 右边期望一个「表达式」(能算出值的东西)**。 +- **根源**:核心一句 —— **`=>` 右边期望一个「表达式」(能算出值的东西)**。 - 表达式(`a+b`)=【有值】→ 直接返回;能赋值 `const x = a+b` ✅ - 代码块(`{...}`)=【一堆动作,不产值】→ 不能赋值 `const x = {a+b}` ❌ 报错 - 所以 `{ a+b }` 里那个 `a+b` 被算出来**却被丢弃**,函数无 return → undefined diff --git a/js_notes/10-functions.js b/js_notes/10-functions.js index 6e0ba72..0716d0a 100644 --- a/js_notes/10-functions.js +++ b/js_notes/10-functions.js @@ -21,6 +21,29 @@ // 区别(初学先记一条):声明会被"提升"(hoisting),定义前就能调用; // 表达式不会,必须先定义后调用。现代代码更常用【表达式 + 箭头函数】。 // +// ── 什么叫"提升(hoisting)"?⭐ ── +// 一句话说透:JS 真正执行前,会先扫一遍代码,把某些声明"吊"到所在作用域的顶端。 +// 所以你能在"字面写的位置之前"就用到它。hoisting = 吊起、提起。 +// • 函数声明 function f(){} → 【整个函数】(名字+函数体)被提到顶 +// → 定义前调用 ✅ 能用 +// • 函数表达式 const f = function(){} / 箭头 → 只有【变量名】被登记, +// "= 赋值"留在原地不动 → 定义前调用 ❌ 报错(见下) +// +// ── "名字提了却不让碰",提它有啥用?⭐⭐(易困惑)── +// 提升的用途【不是】让你提前用,而是另外两件事: +// 用途一:一进作用域就把所有声明"登记"好 → "这个名字归本作用域"从第一行 +// 起就板上钉钉,不会前半段指外层、后半段指内层(名字归属一致)。 +// 用途二:const/let 提升后标记为"未初始化,禁止访问",这段区间叫 +// 【TDZ 暂时性死区】。你若在赋值前就用它 → 【当场报错】 +// ReferenceError: Cannot access 'x' before initialization。 +// 对照老式 var:var 也提升,但预设值是 undefined → 提前用【静默给 undefined】, +// 把 bug 藏起来。const/let 是在这个教训上改的:保留提升(用途一), +// 但把"提前读到 undefined"升级成"提前读就报错"(用途二)。 +// → 一句话:提升负责"提前登记名字",TDZ 负责"用早了就当场拦下"。 +// +// 实用建议:别去利用提升在定义前调用函数(代码会难读)。养成"先定义后用" +// 的习惯,提升对你就几乎无感 —— 而 const+箭头本来就必须先定义后用。 +// // ── 2. 箭头函数 => ⭐⭐(pi/TS 代码里到处是)──────── // const add = (a, b) => a + b // 无 {}:自动 return // const add = (a, b) => { return a + b } // 有 {}:必须自己 return @@ -39,7 +62,7 @@ // 代码块只是"一堆动作",本身不产出值 → JS 不猜你要交啥 → 必须自己 return,否则返回 undefined。 // 一句话:=> 后给"值(表达式)"就自动还;开了"{代码块}"就得自己 return。 // -// ⚠️ 推论(同一锚点):对象字面量 { } 也以 { 开头 → 会撞上"代码块"这条特判! +// ⚠️ 由上面那句核心推出:对象字面量 { } 也以 { 开头 → 会撞上"代码块"这条特判! // 想直接返回对象,用 () 裹住,强行回到"表达式"语境: // const make = (name) => { name: name } // ❌ { 被当代码块 → 返回 undefined // const make = (name) => ({ name: name }) // ✅ () 逼它当表达式 → 返回对象 @@ -97,6 +120,15 @@ const multiply = function (a, b) { } console.log(multiply(4, 5)) // 20 +// ── 2.5 提升 hoisting:声明能"提前用",表达式不能 ⭐ ── +console.log(hoisted()) // works! ← 在定义【上面】就调用,函数声明被整体提升,能用 +function hoisted() { + return 'works!' +} +// 对照:const/箭头是表达式,提前用会报错(TDZ)。取消注释验证: +// console.log(notYet()) // ❌ ReferenceError: Cannot access 'notYet' before initialization +// const notYet = () => 'nope' + // ── 3. 箭头函数:三种写法,越来越短 ⭐ ───────────── const addArrow1 = (a, b) => { return a + b // 带 {} 要自己写 return @@ -156,24 +188,48 @@ console.log(applyTwice(inc, 5)) // 7 ← inc(inc(5)) = 7 // 练习 A:写一个【函数声明】叫 double,接收一个数字,返回它的两倍。 // 调用 double(8) 并打印(应为 16) +function double(num) { + return num * 2 +} +console.log(double(8)) // 练习 B:把练习 A 改写成【箭头函数】版本(单表达式、自动 return), // 存进 const doubleArrow,调用并打印 +const doubleArrow = num * 2 + // 练习 C:写一个箭头函数 greetUser(name), // 给 name 一个默认值 'guest',返回 `Hello, xxx`。 // 分别在"传名字"和"不传"两种情况下调用并打印 +(name = 'guest') => `Hello, ${name}` + // 练习 D:写一个函数 max2(a, b),返回两个数里较大的那个。 // 要求用到 return 提前结束(想想练习示范 5 的 firstPositive)。 // 用 max2(3, 9) 和 max2(10, 2) 各测一次 +function max2(a, b) { + if ( a >= b) { + return a + } + return b +} + + // 练习 E(动脑):下面这段为什么打印 undefined?先想再动手改好它, // 让它正确返回并打印 20。(提示:看 return) // const triple = (n) => { n * 3 } // console.log(triple(...)) ← 你来补,并修好 triple +const triple = (n) => {return n * 3 } +console.log(triple(20/3)) // 练习 F(挑战,连接下一章):写一个高阶函数 repeat(fn, times), // 它把传进来的函数 fn 连续调用 times 次(每次打印一句话即可)。 // 例:repeat(() => console.log('hi'), 3) 应打印三行 hi。 // 提示:times 次 → 用循环;fn 是"传进来的函数",直接 fn() 就能调用 + +const fn = () => {console.log("执行一次fn函数")} +const repeat = (fn, times) => { + if (times === 0) return + return fn(fn, times -1) +}