20251018-JavaScript八股文整理版(上篇)

🌸 第 1 页:JS 数据类型总览

🧠 一句话记忆:

JS 的数据类型分两大类: 基本类型(值类型) vs 引用类型(对象类型)

分类 子类型 存储位置
基本类型 Number, String, Boolean, Null, Undefined, Symbol, BigInt 栈内存(Stack)
引用类型 Object, Array, Function 堆内存(Heap)

📦 生活类比: 想象你的电脑桌上放两个地方:

  • "📏小抽屉"(栈内存)放简单小物件(值类型)
  • "📦大纸箱"(堆内存)放复杂的大玩意儿(对象)

小抽屉存的是实际的数值; 大纸箱存的是"标签"(引用),告诉你东西在哪个角落。


🌸 第 2 页:Number 类型

🧮 概念:

Number 表示数字,可以是整数、小数、正无穷 Infinity、负无穷 -Infinity、或者不是数的 NaN(Not a Number)。

💻 示例代码:

ini 复制代码
let a = 123;
let b = 1.23;
let c = Infinity;
let d = NaN;

💡 小笨蛋专属记忆法:

想象你在奶茶店当收银员:

  • 123 元:正常顾客付款;
  • 1.23 元:客人只买一小杯;
  • Infinity:有人要"无穷续杯",你无语;
  • NaN:客人拿树叶付款,系统:???

🌸 第 3 页:Undefined、Null、String

🌀 Undefined:

表示"变量声明了但没赋值"。

javascript 复制代码
let a;
console.log(a); // undefined

🍵 类比:你有一个空奶茶杯子,但里面还没倒奶茶。


🚫 Null:

表示"空对象,没有内容"。

ini 复制代码
let cup = null;
console.log(cup); // null

🍶 类比:杯子倒了奶茶但又被喝光了,里面是空的(杯子存在但内容没了)。


✨ String:

表示文本字符串,用 ' '" " 或 `` 包起来。

ini 复制代码
let name = '小可爱';
let desc = `今天学习 JavaScript`;

🍰 类比:你在奶茶杯上贴上标签写 "草莓味",这个标签就是字符串。


🌸 第 4 页:Boolean、Symbol

✅ Boolean:

只有两种取值:truefalse

ini 复制代码
let isCute = true;
let isTall = false;

🐰 类比:像开关一样,开灯/关灯,真/假。


🧩 Symbol:

表示唯一值,主要用于区分对象属性,防止命名冲突。

ini 复制代码
let id1 = Symbol('id');
let id2 = Symbol('id');
console.log(id1 === id2); // false

🎀 类比:你和别人都叫"小可爱",但身份证号不同------Symbol 就是那个身份证号。


🌸 第 5 页:引用类型(Object, Array, Function)

🧱 Object(对象):

一组键值对(key-value)

ini 复制代码
let person = {
  name: '小可爱',
  age: 18
};

🧋 类比:就像点奶茶的单子,写着「名字:小可爱」「口味:草莓味」。


🧮 Array(数组):

有序的值列表,用 [] 包住。

ini 复制代码
let drinks = ['奶茶', '果汁', '可乐'];

🍹 类比:一排奶茶杯排队站好,编号从 0 开始。


🧠 Function(函数):

封装可复用的逻辑代码。

javascript 复制代码
function sayHi(name) {
  console.log('Hi ' + name);
}
sayHi('小可爱');

🎤 类比:这是一个"喊人函数",输入名字,它就帮你喊出来。


🍬 总结口诀

"七值三引记心间,栈装小值堆装函。 数字布尔加字符串,空空未定各分担。 对象数组函数连,Symbol 唯一独自仙。"

🌼 第 6 页:函数与存储区别(值类型 vs 引用类型)

🧩 1.3.4 其他引用类型

代码示例:

bash 复制代码
function sum(a, b) {
  return a + b;
}
let result = sum(1, 2);

💬 解释: 这里定义了一个函数 sum,它像一个"小计算员"一样,你给他两个数 ab,他会算出和并告诉你结果。

📦 函数也是一种"引用类型",存在堆内存里。 👉 想象它是一个"奶茶机",机器说明书(函数体)放在堆里,调用时(执行)才会真的"打奶茶"。


🌸 第 7 页:存储区别

JavaScript 变量在底层的存储有两种:

  1. 基本类型存在"栈内存"(stack)
  2. 引用类型存在"堆内存"(heap)

🧠 1.4.1 基本类型

ini 复制代码
let a = 10;
let b = a;
b = 20;
console.log(a, b); // 10, 20

📖 解释:

  • 变量 a 存了一个数值 10
  • b = a 其实是复制了一份数值;
  • b 不影响 a

🍬 类比:

像你复制了一杯奶茶配方卡,自己改成多糖版,原来的不变。


🧱 1.4.2 引用类型

ini 复制代码
let obj1 = { name: '小可爱' };
let obj2 = obj1;
obj2.name = '小笨蛋';
console.log(obj1.name); // 小笨蛋

💬 解释:

  • obj1 是个对象,存在堆中;
  • obj2 = obj1 其实复制的是"地址标签";
  • obj2 的内容,其实两人共用一杯奶茶!💥

🍵 类比:

你和朋友共用一杯奶茶(对象),谁加糖,整杯都变甜。


🌸 第 8 页:堆栈结构图

这页有一张大图,展示了:

  • 栈内存(stack):存放变量名与引用地址;
  • 堆内存(heap):存放对象真实数据;
  • 箭头代表"引用关系"。

🧋 生活类比:

栈像你桌上的"备忘贴",写着「obj1 👉 柜子3号」; 堆像"奶茶仓库",柜子3号里放着真正的奶茶。 当你复制 obj2 = obj1,就是在贴一张新的备忘贴,但还是指向同一个柜子。


🌼 第 9 页:小结

重点回顾:

  1. 基本类型(栈内存):

    • 存值;
    • 拷贝的是值;
    • 互不影响。
  2. 引用类型(堆内存):

    • 存地址;
    • 拷贝的是引用;
    • 改一个,全体变化。

📦 口诀记忆:

"栈装值,堆装址,拷贝值不动,拷贝址同命。"


🌷 第 10 页:数据结构入门

🧭 2.1 什么是数据结构?

就是"存放数据的方式"。

举例:

  • 书架的摆法不同(分类 vs 随意堆);
  • 程序中的"数组、栈、队列"也各自有存放规则。

📚 2.2 数组(Array)

ini 复制代码
let arr = [1, 2, 3];

📦 特点:

  • 有序;
  • 可通过索引(编号)访问。 🍵 类比:像奶茶杯一排排放好,每个都有编号。

🧱 2.3 栈(Stack)

一种 后进先出(LIFO) 的数据结构。

📘 图中: 栈顶进 → 栈顶出。

🧋 类比:

奶茶杯子堆叠起来,你最后放上去的那杯(最上面的),会最先被取走。

arduino 复制代码
let stack = [];
stack.push('奶茶1');
stack.push('奶茶2');
stack.pop(); // 拿走奶茶2

🪜 2.4 队列(Queue)

一种 先进先出(FIFO) 的结构。

📘 图中: 队首出 → 队尾进。

🍹 类比:

排队买奶茶,先来的顾客先拿走,后来的慢慢排。

arduino 复制代码
let queue = [];
queue.push('客人A');
queue.push('客人B');
queue.shift(); // 客人A先走

🎯 超萌总结(第6~10页)

概念 记忆口诀 生活类比
基本类型 拷贝值,不连心 两张奶茶配方卡
引用类型 拷贝址,共命运 共喝一杯奶茶
后进先出 奶茶杯堆叠
队列 先进先出 顾客排队买奶茶

🍓 第 11 页:队列结构延伸(Queue ➜ Linked List)

🪜 队列回顾

图上显示「先进先出 (FIFO)」队列流程: 排队买奶茶 → 先来的顾客先拿走 → 新顾客从队尾入队。


🧵 2.5 链表(Linked List)

链表是一种"节点(Node)串起来"的结构。 每个节点保存:

  1. 当前的数据;
  2. 下一个节点的引用(next 指针)。

💻 示例理解:

ini 复制代码
let node1 = { value: 1, next: null };
let node2 = { value: 2, next: null };
node1.next = node2;

💬 解释:

  • node1 里放了数字 1;
  • 它的 next 指向 node2;
  • 这样就变成 "1 → 2" 的链式结构。

🍬 类比: 想象一串奶茶杯,每个杯子底部绑着一根绳子,绳子另一头连着下一个杯子。 要拿第 3 杯奶茶,得先摸第 1 杯 → 找到第 2 杯 → 再摸到第 3 杯。


🏷️ 2.6 字典(Map / Object)

字典是一种 "键值对存储" 结构。

bash 复制代码
let dict = {
  name: '小可爱',
  drink: '草莓奶茶'
};

📘 解释:

  • "name" 是 key;
  • "小可爱" 是 value。

🍵 类比: 就像点单系统的"清单表":

名称 奶茶类型
小可爱 草莓奶茶

你通过"名字(key)"找到对应的"奶茶(value)"。


🍪 2.7 集合(Set)

Set 是一种"不重复的值集合"。

sql 复制代码
let drinks = new Set(['奶茶', '果汁', '奶茶']);
console.log(drinks); // Set(2) { '奶茶', '果汁' }

💬 解释: Set 自动去重!同样的奶茶只保留一杯。

🍰 类比:

你在收银台点单系统里重复点"奶茶",系统会说:"重复啦,不用再加一杯!"


🌸 第 12 页:DOM 操作

🏗️ 3. DOM 是什么?

DOM(Document Object Model)是网页的树状结构 。 每个标签(如 <div><p><img>)都对应一个节点。

css 复制代码
<div>
  <p>Hello 小可爱</p>
</div>

📖 这其实是:

makefile 复制代码
父节点: div
子节点: p
文本: Hello 小可爱

🍓 类比: 网页就像一棵"奶茶店组织树":

  • 店长(div) ↳ 店员(p) ↳ 负责说"欢迎光临!"(文本内容)

🪄 第 13 页:DOM 操作常见方法

3.2 操作节点四大类:

  1. 创建(Create)
  2. 查找(Select)
  3. 修改(Update)
  4. 删除(Delete)

是不是很像奶茶店 CRUD 流程:

  • Create:开新奶茶;
  • Read:查看奶茶;
  • Update:改口味;
  • Delete:下架。

🧋 第 14 页:创建节点

🧱 3.2.1 创建节点

ini 复制代码
let p = document.createElement('p'); // 创建 <p>
p.innerText = '草莓奶茶';
document.body.appendChild(p);

📖 解释:

  1. createElement ------ 造一个新标签;
  2. innerText ------ 填入文字;
  3. appendChild ------ 把奶茶(元素)放到货架(页面)上。

🍹 类比:

你新做了一杯奶茶(创建节点), 在杯上写上口味(设置内容), 最后把它放到展示台(插入网页)。


✨ 第 15 页:修改、删除节点

🧾 修改属性

ini 复制代码
let img = document.querySelector('img');
img.setAttribute('src', 'milk-tea.jpg');

→ 改图片地址(换奶茶照片 📸)


🚮 删除节点

ini 复制代码
let p = document.querySelector('p');
p.remove(); // 删除 <p>

→ 把某段介绍(旧宣传语)删掉。


🌷 小结口诀(第 11~15 页)

概念 一句话记忆 生活类比
链表 节点串成链 奶茶杯用绳子连起来
字典 键值对查找 通过顾客名字查点单
集合 去重集合 重复点的奶茶只保留一杯
DOM 网页结构树 奶茶店组织架构图
CRUD 增删查改 奶茶上新、下架、改口味

🎯 一句总结送给你:

"链表串串连,字典查得全; 集合不重样,DOM搭舞台。"

🧋 第 16 页:DOM 的层级与节点关系

💡 DOM 节点层次图

图中展示了 "父节点(Parent)---子节点(Child)---兄弟节点(Sibling)" 的关系。

🍵 类比成奶茶店:

  • 店长(父节点)

    • 店员 A(子节点)
    • 店员 B(子节点)
    • 店员 A 和 B 是"兄弟节点"

🧱 获取父子关系的代码

ini 复制代码
let box = document.querySelector('.box');
let parent = box.parentNode;     // 父节点
let first = box.firstChild;      // 第一个子节点
let last = box.lastChild;        // 最后一个子节点
let next = box.nextSibling;      // 下一个兄弟节点

📖 解释:

  • parentNode:找上级;
  • firstChild:找第一个孩子;
  • lastChild:找最后一个孩子;
  • nextSibling:找下一个同级。

🍬 类比:

就像你在员工名单上查"谁是我的上司 / 第一个同事 / 下一个排班同事"。


🌷 第 17 页:修改节点内容

3.2.2 修改节点内容

JS 中修改标签里的内容常见 3 种写法:

ini 复制代码
div.innerText = '草莓奶茶';
div.textContent = '波霸奶茶';
div.innerHTML = '<p>新品:🍈哈密瓜奶绿</p>';

📖 区别:

  • innerText:只改文字;
  • textContent:更快,也改文字;
  • innerHTML:能直接插入 HTML 标签。

🍹 类比:

innerText:换菜单文字 textContent:换更快的菜单 innerHTML:整张新菜单贴上去(含图文排版)


🌸 第 18 页:插入节点 / 移除节点

🧩 插入节点

ini 复制代码
let p = document.createElement('p');
p.innerText = '新品上线啦!';
document.body.appendChild(p);

💬 就像你新做一杯奶茶 ➜ 放上货架展示。


🧺 插入指定位置

ini 复制代码
let parent = document.querySelector('.menu');
let item = document.createElement('li');
item.innerText = '抹茶拿铁';
parent.insertBefore(item, parent.firstChild);

📘 解释: insertBefore 表示插在某个节点前面。 🍵 类比:

你要把新品"抹茶拿铁"插到菜单第一个位置。


🧹 删除节点

ini 复制代码
let node = document.querySelector('p');
node.remove();

🍋 类比:

不好卖的奶茶,直接下架扔掉。


🍑 第 19 页:节点属性与样式

🧾 获取属性

ini 复制代码
let img = document.querySelector('img');
console.log(img.getAttribute('src'));

💬 获取 <img> 的"src"属性值。


🧱 修改属性

less 复制代码
img.setAttribute('src', 'new-tea.jpg');

💬 换新图啦~(把"草莓奶茶"照片换成"抹茶奶茶")。


🧍‍♀️ 修改样式

ini 复制代码
let box = document.querySelector('.box');
box.style.color = 'pink';
box.style.background = 'white';

🍓 类比:

店员换新制服:粉色字体 + 白色背景。 (也就是通过 JS 改 CSS)


☀️ 第 20 页:BOM(浏览器对象模型)

🌎 什么是 BOM?

BOM(Browser Object Model)是浏览器提供的"窗口级对象",让你能操作整个浏览器,而不只是网页。

🌐 包括这些核心对象:

对象 功能
window 整个浏览器窗口本身
location 当前网页的 URL 地址
navigator 浏览器信息(比如 Chrome、Edge)
screen 屏幕大小、分辨率
history 浏览记录(前进、后退)

🪟 4.1 window 对象

所有 JS 全局变量其实都是挂在 window 上的。

javascript 复制代码
window.alert('欢迎光临小可爱奶茶店!');

🍵 类比:

"window" 就像整家奶茶店的大玻璃窗------ 你能通过它"观察整个店",还能在上面贴公告牌。


🎀 小笨蛋也能秒懂总结(第16~20页)

知识点 记忆口诀 奶茶店类比
父子兄弟节点 DOM家谱图 店长和店员层级
修改节点内容 三种方式改菜单 文字、HTML、整页菜单
插入/删除节点 append / remove 上架新品 / 下架旧款
属性与样式 get/set/style 看/改奶茶照片和外观
BOM 操作浏览器 整个奶茶店的"外部系统"

🎯 一句话助记:

"DOM管店内布局,BOM管整家店; 节点有家谱,样式换新颜。"

🌸 第 21 页:BOM 继续讲解(window)

🪟 4.2 window 对象

ini 复制代码
var a = 10;
console.log(window.a); // 10

💬 解释: 所有的全局变量,默认都挂在 window 这个"老大对象"上。 → 你定义的 a 实际上等价于 window.a

🍵 类比:

整个奶茶店的"总控台"是 window, 所有设备(冰箱、奶茶机、灯光)都登记在 window 上。 你说 a = 10,就像往总控台上新增了一个"新开关"。


🧾 window 常见方法

javascript 复制代码
window.alert('新品上架啦!');
window.confirm('确定要删除这杯奶茶吗?');
window.prompt('请输入您最爱的奶茶口味:');
方法 功能 奶茶店类比
alert() 弹出提示 公告板贴出"新品上架"通知
confirm() 确认操作 问顾客"确定要退单吗?"
prompt() 获取输入 让顾客填写"口味偏好"

🌬️ 窗口操作

javascript 复制代码
window.open('https://milk-tea.com');
window.close();

🍰 类比:

你点开一个新的"分店网页"(open), 再关掉(close)就像拉下卷帘门。


🍓 第 22 页:location 对象

负责记录、读取和修改"当前网址信息"。

ini 复制代码
console.log(location.href); // 当前网址
location.href = 'https://www.taobao.com'; // 跳转页面

🍬 类比:

"location" 是你奶茶店的"地址牌"。

  • 看地址牌(href) = 查看现在在哪个店;
  • 换地址牌 = 搬去新店(跳转页面)。

🧭 location 属性总结:

属性 含义 举例
href 完整地址 https://milk.com/page.html
hostname 主机名 milk.com
pathname 路径 /page.html
protocol 协议 https:
search 查询参数 ?id=123

🍵 小笨蛋记忆口诀:

"协议头、主机名、路径尾,问号后面参数配。"


🌼 第 23 页:navigator 对象

记录浏览器本身的信息,比如用的是 Chrome、Safari 还是 Edge。

arduino 复制代码
console.log(navigator.userAgent);

输出示例:

ini 复制代码
Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 Chrome/122.0 Safari/537.36

🍹 类比:

navigator 就像奶茶店的"前台员工", 你问他:"你是哪家店的?" 他回答:"Chrome 奶茶旗舰店,版本 122。"


属性 含义
appName 浏览器名称
appVersion 版本号
language 浏览器语言
platform 操作系统平台
userAgent 浏览器完整信息

🍓 类比:

这些属性就像员工证件:姓名、编号、语言、工号。


🧋 第 24 页:screen 和 history

4.5 screen(屏幕)

存储的是屏幕尺寸、可视宽高等信息。

arduino 复制代码
console.log(screen.width, screen.height);

🍵 类比:

"screen" 就像你奶茶店的橱窗大小: 橱窗越大(屏幕宽),能展示的奶茶越多。


4.6 history(浏览记录)

记录你访问过的网页,可以前进或后退。

scss 复制代码
history.back(); // 后退一页
history.forward(); // 前进一页

🍰 类比:

history 就是"奶茶回忆录"📖。

  • back() 回到上一杯喝过的奶茶;
  • forward() 去尝下一杯。

🍑 第 25 页:== 与 === 的区别

💡 区别一览表:

运算符 含义 特点
== 相等(值相等) 自动类型转换
=== 全等(值与类型都相等) 不会转换类型

🧠 举例讲透:

ini 复制代码
1 == '1'   // true(字符串自动转数字)
1 === '1'  // false(类型不同)
null == undefined // true
null === undefined // false

🍬 类比:

== 就像"模糊匹配"------你说"草莓",我也当成"草莓奶茶"; === 是"严格匹配"------只有"草莓奶茶"才算对,不含糊。


☕ 记忆口诀:

"双等宽松聊恋爱,三等谨慎签合同。"

(意思是: == 可能因为类型转换"将就"一下, === 必须类型、数值都一模一样,像签合同一样严谨。)


🎀 小笨蛋也能懂的总结(第21~25页)

模块 核心功能 奶茶店类比
window 浏览器全局对象 总控台
location 地址栏 门牌号
navigator 浏览器信息 店员身份证
screen 屏幕信息 橱窗尺寸
history 浏览记录 奶茶回忆录
== vs === 比较规则 模糊恋爱 vs 严谨婚姻

🌟 一句话记忆:

"window 全局掌控,location 换店导航; navigator 报身份,screen 看橱窗,history 查奶茶过往。 双等将就爱,三等要真心。" ❤️

🧋第 26 页:== 和 === 再复习

🍰 图里的核心区分

  • ==宽松比较,会自动转换类型。
  • ===严格比较,类型和值都必须相同。
  • != / !==:对应"不相等"和"非全等"。

📖 举个例子:

ini 复制代码
1 == '1'    // true  字符串自动转数字
1 === '1'   // false 类型不同

🍬 类比:

== 就像店员说:"草莓味?草莓奶茶也行~" === 就像严格审查:"必须草莓奶茶加波霸,否则不算!"


🌸第 27 页:5.1 关于类型转换

🧮 JS 在比较时会自动"帮你转换类型"

ini 复制代码
console.log(1 == true);  // true
console.log(0 == false); // true
console.log('' == false); // true

💡 JS 的脑回路是这样的:

  • true → 转成数字 1
  • false → 转成数字 0
  • 空字符串 → 也是 0

所以这些结果都是真的。

🍹 类比:

顾客点单时,服务员会自动脑补: "他说'一杯',那肯定是数量 1;他说'不要',就是 0;啥都不说就是空(0)。"


💥 常见坑点示例

arduino 复制代码
console.log([] == false); // true
console.log([] == ![]);   // true

解释:

  • [] 转换成数字 → 0;
  • ![] 是 false;
  • 0 == 0 → true。

🍵 类比:

空杯子和没杯子,在店员眼里都"算空"~ 结果就被认成一样。


🍑第 28 页:5.2 空字符串转换 & 5.3 对象转换

空字符串的比较

ini 复制代码
console.log('' == 0); // true
console.log('' === 0); // false

💬 '' 会自动变成数字 0 → 所以 == 觉得一样,=== 说类型不同。

🍰 类比:

"0 元优惠券"和"空白优惠券",服务员模糊地认为一样(==); 但经理查账会说:不一样的单据(===)。


对象比较

ini 复制代码
let a = {};
let b = {};
console.log(a == b); // false
console.log(a === b); // false

💬 解释: 对象比较时,看的是引用地址,不是内容。 a、b 两个对象在不同的内存位置。

🍵 类比:

你做了两杯一模一样的奶茶,但放在不同桌上, 看起来一样,其实"杯号"不同。


🌷第 29 页:5.4 小结 & 6.1 typeof

💡 小结口诀:

"值类型比值,引用比址; == 会转换,=== 不客气。"


🧭 6.1 typeof 操作符

用来判断 数据类型

csharp 复制代码
typeof 123           // 'number'
typeof '奶茶'        // 'string'
typeof true          // 'boolean'
typeof undefined     // 'undefined'
typeof Symbol()      // 'symbol'
typeof null          // ❗ 'object'(历史遗留 bug)
typeof {}            // 'object'
typeof []            // 'object'
typeof function(){}  // 'function'

🍬 总结成一句话:

typeof 可以看出"是什么口味"的奶茶, 但遇到空杯(null)会认错成"奶茶杯(object)"😂


📦 小笨蛋速记表:

typeof 结果 小故事
123 number 数字奶茶编号
'奶茶' string 奶茶标签
true boolean 点单开关
undefined undefined 还没做好的奶茶
null object ❗ 空杯(被误认成对象)
[] object 奶茶组合盒
function function 奶茶制作机

🌼第 30 页:6.2 instanceof

判断一个对象是否是某个构造函数的实例。

javascript 复制代码
let arr = [];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true

💬 原理: instanceof 是沿着原型链往上查,看是否能找到对应的构造函数。

🍰 类比:

店员小明是"饮品部(Array)"的员工, 但饮品部又属于"总店(Object)"。 所以:

  • 是 Array 的实例 ✅
  • 也是 Object 的一员 ✅

☕ typeof vs instanceof 区别总结:

比较方式 用于 返回 特点
typeof 基本类型 字符串 判断简单类型(除了 null)
instanceof 引用类型 布尔值 判断对象属于哪种"家族"

🍹 口诀记忆:

"typeof 查口味,instanceof 查家族。"


🎀 小可爱总结(第26~30页)

知识点 一句话记忆 奶茶店例子
== / === 宽松恋爱 vs 严谨婚姻 "差不多"也算 vs 必须完全一致
类型转换 JS 自动脑补 空杯 ≈ 没杯
typeof 看口味 判断奶茶类型
instanceof 查家族 判断是饮品部还是甜品部

💡 超级总结口诀

"双等将就爱,三等要真心; typeof 看奶茶口味,instanceof 查员工部门; 值比值,址比址,空杯容易出事。"

🧋 第 31 页:instanceof 实战讲解

javascript 复制代码
function Car() {}
let myCar = new Car();
​
console.log(myCar instanceof Car);     // true
console.log(myCar instanceof Object);  // true

💡解释:

  • myCar 是用 Car 这个构造函数造出来的。
  • 所以 myCarCar 的实例;
  • 而所有对象最终都继承自 Object,因此它也是 Object 的实例。

🍰类比:

"Car" 是【奶茶制作机的蓝图】; myCar 是【做好的那台机器】; 所有机器都属于【设备部(Object)】。


🪜 instanceof 的工作原理图

它会从对象的原型链一路往上查:

对象的 __proto__ → 构造函数的 prototype → 再往上找直到 Object.prototype。

📦类比:

就像从"店员"往上查找: 店员 → 店长 → 区经理 → 总公司。 如果在任一层找到了"身份证模板",那就返回 true。


🌸 第 32 页:6.3 复制(拷贝)

有时候我们想"复制一个对象",但是对象存在"引用传递"问题:

ini 复制代码
let obj1 = { name: '小可爱' };
let obj2 = obj1;
obj2.name = '小笨蛋';
console.log(obj1.name); // 小笨蛋

💬解释: obj2 并不是新的对象,只是复制了引用地址。 改一个,另一个也变。

🍹类比:

你把"奶茶配方表"的链接发给别人,他改了糖量,你的配方也跟着变~😱


✨浅拷贝与深拷贝

拷贝方式 含义 常见方法
浅拷贝 只复制第一层 Object.assign()...展开符
深拷贝 连内部对象也复制 JSON.parse(JSON.stringify(obj))

🍵类比:

浅拷贝就像"复制菜单目录"; 深拷贝是"连奶茶配方都重新抄一份"。


🧠 第 33 页:7. JavaScript 原型与原型链

🧩 原型(Prototype)是啥?

每个函数在创建时,JS 都会自动给它加一个属性 prototype。 这个属性指向一个对象,这个对象里保存着"共享的属性和方法"。

举例:

javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log('Hi,我是 ' + this.name);
};
​
let p1 = new Person('小可爱');
p1.sayHi(); // 输出:Hi,我是 小可爱

🍬解释:

  • Person 是"模板"(构造函数);
  • prototype 是模板的"说明书";
  • p1 是根据模板造出的"员工";
  • 员工会自动带上说明书里的技能。

🍓类比:

你是"奶茶培训学校校长(Person)"; 每个毕业生(p1、p2)都学会相同技能 sayHi(), 因为都读了一样的"培训手册(prototype)"。


🧋 第 34 页:原型链(Prototype Chain)

javascript 复制代码
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

💡解释:

JS 会一层层往上查找属性,直到最顶层 Object.prototype

🍵类比:

小可爱(p1) → 奶茶学院(Person) → JS 总部(Object) → 没上级了(null)。


🧭 原型链查找机制

当你访问 p1.sayHi 时,JS 会:

  1. 先查 p1 自己有没有;
  2. 没有就查它的原型;
  3. 再没有就一直往上找,直到顶层。

🍹类比:

员工不会调奶茶机?那去问店长; 店长不会?问区域经理; 再不会?问总部; 总部都没教?那就"undefined"了 😆。


🌷 第 35 页:可视化理解原型链

代码示例:

javascript 复制代码
function Person() {}
let person1 = new Person();
​
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true

🧩解释:

  • 构造函数的 prototype 有个 constructor 属性指回自己;
  • 实例的 __proto__ 指向构造函数的 prototype。

🍰类比:

奶茶培训学院(Person)有一本教材(prototype), 教材上注明"作者:Person"; 学生(person1)的说明书(proto)引用了这本教材。


💡 小笨蛋超记口诀(第31~35页)

概念 一句话总结 奶茶店类比
instanceof 判断家族关系 员工属于哪个部门
浅拷贝 复制目录 只抄菜单标题
深拷贝 完整复制 连奶茶配方都重写
prototype 模板说明书 培训手册
原型链 一层层往上找 员工 → 店长 → 总公司

🎀 超萌终极口诀

"instanceof 查家谱, 浅深拷贝别混淆; prototype 是培训书, 原型链上层层找。"

🧋第 36 页:原型链的全图理解

图上是一张"大型 JS 家族树",主要说明:

  • 所有函数(包括构造函数)都是 Function 的实例。
  • 所有对象的最终祖宗都是 Object.prototype
  • 顶端是 null ------ 没有再往上的人了。

🧱 你可以这样理解层级:

角色 说明 类比
Person 构造函数(奶茶学院) 教你做奶茶
person 实例对象(学员) 毕业的奶茶师
Person.prototype 原型(教材) 学员共享技能
Object.prototype JS 的终极祖宗 总部培训手册

📘 示例代码:

javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log('你好,我是 ' + this.name);
};
​
let p1 = new Person('小可爱');
p1.sayHi(); // 输出:你好,我是 小可爱

📖 解释:

  • p1 自己没有 sayHi
  • JS 会顺着原型链去找 → 找到 Person.prototype
  • 如果还没有,就再去 Object.prototype
  • 一直到顶端才停止。

🍓 类比:

奶茶师(p1)不知道做某种新口味 → 去查学院教材(prototype); 教材里没写?再问总公司(Object)!


🌸第 37 页:原型链的关系验证

javascript 复制代码
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

📚 结论:

  • 原型链是一个"逐级上溯"的结构;
  • null 表示没有再往上的祖先。

🍵 类比:

小可爱(p1) → 奶茶学院(Person) → 总部(Object) → 天花板(null)。


🪜 小笨蛋记忆口诀:

"对象有原型,原型也有原型, 原型尽头是 null,总公司没人管。"


🍰第 38 页:作用域链的理解

JS 中作用域是"变量能被访问到的范围"。

📖 分三类:

  1. 全局作用域:所有地方都能访问。
  2. 函数作用域:只在函数内部有效。
  3. 块级作用域letconst{} 内有效。

🍹 类比:

  • 全局作用域:奶茶总部公告,所有分店都能看;
  • 函数作用域:某家分店的员工守则;
  • 块级作用域:当天的临时活动通知。

📘 示例

javascript 复制代码
let name = '总部'; // 全局
​
function branch() {
  let name = '奶茶店A'; // 局部
  console.log(name);
}
branch();  // 奶茶店A
console.log(name); // 总部

🍬 解释: 内部作用域可以访问外部,但外部不能反过来访问内部。


☕第 39 页:作用域链机制

当 JS 访问变量时,会:

  1. 先在当前作用域找;
  2. 找不到就往上一级;
  3. 一直查到全局;
  4. 如果都没有,就报错。
ini 复制代码
let a = '总部';
function shop() {
  let a = '奶茶店';
  function employee() {
    console.log(a);
  }
  employee();
}
shop(); // 奶茶店

🍓 类比:

员工问"今天喝什么口味?" 自己分店有菜单 → 就用分店的; 没有才去问总部。


⚙️ 块级作用域

ini 复制代码
if (true) {
  let x = 10;
}
console.log(x); // ❌ 报错

💬 解释: let 定义的变量只在 {} 内可用。

🍵 类比:

活动期间的"买一送一券", 离开当天活动就作废。


🧠第 40 页:作用域综合演示

ini 复制代码
var x = 10;
function foo() {
  console.log(x);
  var x = 20;
}
foo(); // undefined

💬 解释:

  • JS 在执行前会"提升变量声明(hoisting)",
  • 所以 var x 在函数里被提前声明但未赋值;
  • 输出时它是 undefined

🍰 类比:

店员问:"今天有草莓吗?" 仓库(函数)先登记了草莓这件事(声明)但还没送货(赋值), 所以此时"库存为空"。


🎀 小笨蛋终极总结(第36~40页)

概念 一句话记忆 奶茶店比喻
原型链 层层继承 员工 → 店长 → 总部
prototype 共用技能本 培训教材
作用域 变量可见范围 告示适用范围
作用域链 一层层查找变量 从店员问到总部
变量提升 先登记后发货 草莓先记账还没送到

🍓 超萌口诀收尾!

"原型链上查家谱,作用域里找口味; 局部外部层层叠,未赋先提是空杯。"

🧋第 41 页:作用域链回顾

来看这段代码👇

ini 复制代码
var a = 2;
function foo() {
  var b = 3;
  function bar() {
    var c = 4;
    console.log(a + b + c);
  }
  bar();
}
foo(); // 输出 9

💬 解释: JS 查找变量时是"从内往外"找的。 bar() → 先看自己有没有 abc。 找不到 a → 去 foo() 外层 → 再找全局。

🍵 类比:

店员(bar)要做奶茶:

  • 先看自己柜台有没有材料;
  • 没有就去门店仓库(foo);
  • 再不行就打电话总部(全局)。

🌱 图示说明

图上展示:

  • 每个函数都有自己的"作用域气泡";
  • 外层函数就是"上一级仓库";
  • 它们连接起来形成 作用域链(Scope Chain)

🍬 口诀:

"函数套函数,作用域像洋葱; 从内往外剥,总能找到葱。"


🌸第 42 页:作用域链图解(办公楼模型)

图里是一个"高楼层结构":

  • 每层代表一个作用域;
  • 顶楼是全局作用域;
  • 内层函数越深 → 楼层越高。

📖 小笨蛋记法:

想象变量是一杯奶茶 🍹 你在 7 楼想找奶茶,先看自己桌上有没有; 没有就去 6 楼 → 5 楼 → 一直找到 1 楼总部。


📘 代码解释

ini 复制代码
var x = 10;
function outer() {
  var y = 20;
  function inner() {
    var z = 30;
    console.log(x + y + z);
  }
  inner();
}
outer(); // 输出 60

📍执行顺序:

  1. inner 自己的作用域(z = 30)
  2. outer 的作用域(y = 20)
  3. 全局作用域(x = 10)

🍰 类比:

店员(inner)要做"特调奶茶", 自己柜台有糖浆(z), 从楼下仓库拿茶叶(y), 最后总部提供牛奶(x)。


☀️第 43 页:作用域链小结

级别 名称 范围
1 全局作用域 所有函数外部
2 函数作用域 每个函数独立
3 块级作用域 {} 内生效(let/const)

💡 JS 在"定义函数时"就决定好作用域,而不是运行时才决定。 👉 这就是 "词法作用域(Lexical Scope)"

🍹 类比:

店员入职那天,公司就决定了他能进哪些仓库。 不是等他去拿材料时才临时决定的。


🧠第 44 页:this 对象(灵魂考点登场)

this 就是当前执行环境下的对象, 取决于"函数被谁调用",而不是定义时。

💻 基础例子:

ini 复制代码
var name = '总部';
function sayHi() {
  console.log(this.name);
}
​
const shop = {
  name: '奶茶店A',
  sayHi: sayHi
};
​
sayHi();       // 输出:总部
shop.sayHi();  // 输出:奶茶店A

📖 解释:

  • 第一行 sayHi() 是由全局环境调用 → this 指向 window
  • 第二行 shop.sayHi() 是由对象调用 → this 指向 shop

🍓 类比:

同一句话 "欢迎光临~"

  • 在总部喊:代表总部欢迎;
  • 在分店喊:代表分店欢迎。 所以 this 取决于"谁喊的"!

🌷第 45 页:this 指向的 4 条核心规则

💡 1. 默认绑定(普通函数调用)

scss 复制代码
function show() {
  console.log(this);
}
show(); // 浏览器中输出 window

👉 没有指定对象,this 默认是全局对象(浏览器里是 window)。

🍹 类比:

没写发件人名字,系统默认认为是"总部"发的。


💡 2. 隐式绑定(对象调用)

javascript 复制代码
const tea = {
  name: '奶茶A',
  getName() {
    console.log(this.name);
  }
};
tea.getName(); // 奶茶A

👉 调用者是 tea 对象 → this = tea。

🍰 类比:

店员(this)属于哪个分店,就说那家店的口味。


💡 3. 显式绑定(call/apply/bind)

javascript 复制代码
function greet() {
  console.log('Hi, ' + this.name);
}
​
const shop = { name: '奶茶店B' };
greet.call(shop); // Hi, 奶茶店B

💬 .call() 手动指定 this。 🍬 类比:

像你强制让总部的广播系统播放"奶茶店B"的口号。


💡 4. new 绑定(构造函数)

ini 复制代码
function Person(name) {
  this.name = name;
}
const p = new Person('小可爱');
console.log(p.name); // 小可爱

👉 使用 new 时,this 会绑定到"新建的对象"上。

🍵 类比:

"new" 就像开了一个新分店,新店就成了 this!


💫 小笨蛋专属总结(第41~45页)

概念 一句话总结 奶茶店类比
作用域 变量能访问的区域 奶茶仓库范围
作用域链 多层作用域查找 从分店查到总部
词法作用域 定义时确定范围 入职时发的权限卡
this 谁调用指向谁 谁喊"欢迎光临"就代表谁
call/apply/bind 手动指定 this 强制让总部喊出分店的口号
new 绑定 构造函数绑定 开新分店,新 this 诞生!

🎀 终极口诀

"作用域链像仓库,层层往上找; this 看谁喊话,谁喊谁代表。 new 开新店,call 改台词, JS 虽抽象,其实奶茶味十足~🍹"

🧋第 46 页:this 的四种绑定方式复习

这几页主要讲的就是------ this 到底是谁?取决于"谁喊话"!

你可以想成:this 是函数执行时的"身份牌" 。 谁在调用函数,this 就代表谁!


🍰 9.2.2 隐式丢失(this 丢了)

ini 复制代码
function show() {
  console.log(this.name);
}
​
var name = '总部';
const obj = { name: '分店A', show };
​
obj.show();      // 分店A
const f = obj.show;
f();             // 总部(this 丢失)

💬解释:

  • obj.show() → 调用者是 obj,所以 this = obj;
  • f() → 函数单独被调用,就回到默认绑定 → this = window。

🍹类比:

奶茶店员工(show)平时在分店上班(this = 分店A)。 但有一天他辞职在家做兼职(脱离 obj),那就成了自由人(this = window)。


🌸第 47 页:隐式丢失的陷阱

javascript 复制代码
var name = '总部';
const shop = {
  name: '奶茶店B',
  getName() {
    console.log(this.name);
  }
};
​
setTimeout(shop.getName, 1000); // 输出:总部

💡原因: setTimeout 里的回调函数是"普通调用", 不是由 shop 直接调用的,this 丢了!

🍬类比:

总部让你"1秒后喊出你的名字", 结果你忘了自己在哪家分店工作,就喊"总部!"😂

✅ 解决办法:

scss 复制代码
setTimeout(() => shop.getName(), 1000);

👉 箭头函数不改变 this(后面讲)


☕第 48 页:new 绑定(构造函数)

ini 复制代码
function Tea(name) {
  this.name = name;
}
let cup = new Tea('草莓奶茶');
console.log(cup.name); // 草莓奶茶

💬解释:

  • 使用 new 时,JS 会自动创建一个新对象;
  • 并把 this 绑定到这个新对象上;
  • 最后自动返回它。

🍵类比:

"new" 就像开了一家新分店。 新分店会挂着自己的门牌(this),独立存在。


🧠第 49 页:箭头函数中的 this

dart 复制代码
const shop = {
  name: '分店C',
  show: () => {
    console.log(this.name);
  }
};
shop.show(); // undefined(不是分店C!)

💬解释: 箭头函数不会创建自己的 this, 它的 this 是"外层作用域"的 this。

🍬类比:

箭头函数是"实习生"------没有自己的工牌, 它用的是外层经理的工牌。 如果外层是总部环境 → 它拿的是总部的工牌。


📘 正确写法

javascript 复制代码
const shop = {
  name: '分店C',
  show() {
    console.log(this.name);
  }
};
shop.show(); // 分店C ✅

🍓第 50 页:事件绑定中的 this

javascript 复制代码
document.querySelector('button').addEventListener('click', function() {
  console.log(this); // <button> 元素本身
});

💡解释:

  • 普通函数绑定事件 → this 指向触发事件的 DOM 元素。

🍵类比:

顾客(click)点了哪一杯奶茶(button), this 就代表那杯奶茶本身。


⚡箭头函数事件绑定

javascript 复制代码
document.querySelector('button').addEventListener('click', () => {
  console.log(this); // window
});

💬箭头函数没有自己的 this,所以 this 来自外层(通常是 window)。

🍹类比:

实习生负责点单(箭头函数), 但他没有"杯子编号"概念, 所以直接指到总部去了(window)。

🧋第 51 页:this 四种绑定规则复习

上一章(第 45~50 页)讲了基础,现在这页讲的是:

✅ 默认绑定 ✅ 隐式绑定 ✅ 显式绑定(call/apply/bind) ✅ new 绑定


🧱 1️⃣ 默认绑定(普通函数)

javascript 复制代码
function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 输出 2(浏览器中是 window.a)

💬 没有任何修饰的函数调用 → this 默认指向全局对象。

🍵 类比:

如果没人明确点名是谁喊话,就默认认为是"总部"发的通知。


🧱 2️⃣ 隐式绑定(对象调用)

javascript 复制代码
function foo() {
  console.log(this.a);
}
var obj = { a: 2, foo: foo };
obj.foo(); // 输出 2

💬 谁点的 .foo(),this 就指向谁。 🍰 类比:

"奶茶店 A" 员工喊"我最棒" → 那代表奶茶店 A,不是总部。


🧱 3️⃣ 显式绑定(call/apply/bind)

javascript 复制代码
function foo() {
  console.log(this.a);
}
var obj = { a: 2 };
foo.call(obj); // 输出 2

💬 call() / apply() / bind() 可以强制修改 this。 🍓 类比:

总部(window)拿着麦克风说:"请奶茶店 A 来喊这句话!" → 强行切换发言人。


🧱 4️⃣ new 绑定

ini 复制代码
function Foo(a) {
  this.a = a;
}
var bar = new Foo(2);
console.log(bar.a); // 2

💬 使用 new 创建新对象时:

  1. 创建一个新空对象;
  2. 把 this 绑定到这个新对象;
  3. 自动返回它。

🍹 类比:

"new" 就像开了一家新的奶茶分店 🍵, 新店(this)有自己独立的库存和菜单。


🌸第 52 页:箭头函数中的 this

ini 复制代码
function outer() {
  return () => {
    console.log(this.a);
  };
}
var obj = { a: 2 };
var foo = outer.call(obj);
foo(); // 输出 2

💬 箭头函数不会自己绑定 this,它会继承外层的 this。

🍰 类比:

箭头函数像"学徒", 没有自己的工作证(this),只能用师傅的。


📘 坑点对比

ini 复制代码
var a = 10;
var obj = {
  a: 20,
  fn: () => {
    console.log(this.a);
  }
};
obj.fn(); // 输出 10

💬 因为箭头函数的 this 是定义时绑定,不是运行时! 所以这里 this 是 window,而不是 obj。

🍵 类比:

学徒(箭头函数)不是听当前店长的,而是永远听"总部"的命令。😂


☀️第 53 页:new 与普通函数的区别

ini 复制代码
function Foo(name) {
  this.name = name;
}
let a = Foo('奶茶A');      // 普通调用
let b = new Foo('奶茶B');  // 构造调用

💬 区别:

调用方式 this 指向 返回值
普通函数调用 全局对象(window) undefined
new 调用 新建对象 这个对象本身

🍓 类比:

普通调用:总部代工,产品归总部。 new 调用:开新分店,产品归新店。


🧠第 54 页:new 操作符做了什么

ini 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const person1 = new Person('Tom', 20);

💡 new 操作符做的 4 件事:

1️⃣ 创建一个新空对象 {}; 2️⃣ 把新对象的原型指向构造函数的 prototype; 3️⃣ 把构造函数的 this 绑定到新对象上; 4️⃣ 如果函数没有返回其他对象,则自动返回这个新对象。

🍵 类比:

"new" = 新开奶茶店:

  1. 建新店(空对象)
  2. 店长培训(绑定 prototype)
  3. 店内设置菜单、装修(赋值 this)
  4. 新店开始营业(返回实例)

📘 代码演示

ini 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayHi = function() {
  console.log('Hi, 我是 ' + this.name);
};
​
const p1 = new Person('小可爱', 18);
p1.sayHi(); // Hi, 我是 小可爱

🍰 类比:

新分店(p1)开业 → 会使用总部的"招呼语模板(sayHi)"。


🌷第 55 页:可视化理解 new 流程

图中展示 new 的四个阶段(总结成奶茶故事版)👇:

阶段 JS 行为 奶茶类比
创建新对象 新奶茶店开张
连接原型 给店铺挂上总部培训手册
绑定 this 店长接管店铺
返回对象 店铺正式营业 🎉

🎀 小可爱总结(第51~55页)

概念 一句话记忆 奶茶店类比
this 默认绑定 谁都没喊就是总部 默认 window
this 隐式绑定 谁点的谁说话 obj.fn()
this 显式绑定 强制切换发言人 call/apply
箭头函数 没自己 this,继承外层 学徒听师傅的
new 操作符 新建对象并绑定 新开奶茶店

💡 终极口诀

"谁喊谁代表,call 改发言人; new 开新店铺,箭头跟师傅干; 四步造分店,prototype 挂菜单。"

🧋第 56 页:手写 new 操作符(模拟实现)

💻 new 是什么?

当我们执行:

ini 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}
let p = new Person('小可爱', 18);

JS 内部其实做了 4 步事👇:

1️⃣ 创建一个空对象 {} 2️⃣ 把这个对象的原型指向 Person.prototype 3️⃣ 绑定 this 到这个新对象上,并执行构造函数 4️⃣ 如果构造函数没有返回对象,则返回这个新建对象

🍵 类比:

"new" 就像 新开一家奶茶分店: ① 先建店面(空对象) ② 给它挂上总部的培训手册(prototype) ③ 店长(this)负责装修和配置菜单 ④ 没意外就开门营业(返回对象)


🧱 手写 new 代码实现

javascript 复制代码
function myNew(fn, ...args) {
  const obj = {};                     // ① 创建空对象
  obj.__proto__ = fn.prototype;       // ② 链接原型
  const result = fn.apply(obj, args); // ③ 执行构造函数并绑定 this
  return result instanceof Object ? result : obj; // ④ 返回对象
}

🍰 类比解释:

  • obj 是"新开的奶茶店";
  • obj.__proto__ = fn.prototype 让它能用总部的配方书;
  • fn.apply(obj, args) 店长装修菜单;
  • 最后返回新开的奶茶店实例。

🌸第 57 页:call / apply / bind 的作用与区别

方法 是否立即执行 参数传递方式 作用
call ✅ 立即执行 参数一个个传 改变 this
apply ✅ 立即执行 参数数组传 改变 this
bind ❌ 不立即执行 参数一个个传 返回新函数

🍬 类比:

  • call:立刻喊总部广播宣传。📢
  • apply:让总部一次性播放多个分店的公告。📜
  • bind:先录好音,等需要时再播放。🎙️

🧱 例子演示(第57页)

javascript 复制代码
function greet(gift) {
  console.log(`${this.name} 收到 ${gift}`);
}
​
const person = { name: '小可爱' };
​
greet.call(person, '草莓奶茶'); // 小可爱 收到 草莓奶茶
greet.apply(person, ['波霸奶茶']); // 小可爱 收到 波霸奶茶
​
const newFn = greet.bind(person, '珍珠奶茶');
newFn(); // 小可爱 收到 珍珠奶茶

🍵 记忆口诀:

call 是"单点直呼";apply 是"套餐传递";bind 是"预约执行"。


☀️第 58 页:手写 call、apply、bind 实现

✨ 手写 call

ini 复制代码
Function.prototype.myCall = function (context, ...args) {
  context = context || window;
  context.fn = this;       // 暂时把函数放进 context 里
  const result = context.fn(...args);
  delete context.fn;       // 清除函数
  return result;
};

🍹 类比:

你(this)临时借用了别的奶茶店的广播系统(context), 播完广告(函数执行)后再拔掉插头(删除 fn)。


✨ 手写 apply

ini 复制代码
Function.prototype.myApply = function (context, args) {
  context = context || window;
  context.fn = this;
  const result = args ? context.fn(...args) : context.fn();
  delete context.fn;
  return result;
};

📘 区别:apply 的参数是数组形式传入。


✨ 手写 bind

javascript 复制代码
Function.prototype.myBind = function (context, ...args) {
  const fn = this;
  return function (...innerArgs) {
    return fn.apply(context, [...args, ...innerArgs]);
  };
};

🍰 类比:

你把总部的"广告词"录成语音(返回新函数), 以后店长随时播放(延迟执行)。


🧠第 59 页:call / apply / bind 的区别表(总结页)

方法 是否立即执行 参数格式 返回结果
call ✅ 是 单个参数 结果
apply ✅ 是 数组 结果
bind ❌ 否 单个参数 新函数

📍小笨蛋口诀:

"call 立刻喊,apply 队形喊,bind 等会儿喊。"


🌷第 60 页:总结 + 延伸思考

💡 面试高频问:

"请你实现一个简单的 bind() 函数。"

你只要记得:

  1. bind 不会立刻执行;
  2. 它返回一个新函数;
  3. 新函数执行时 this 指向固定对象;
  4. 还能预置参数。

📘 面试示例:

javascript 复制代码
Function.prototype.simpleBind = function(context, ...args) {
  const fn = this;
  return function(...innerArgs) {
    return fn.apply(context, args.concat(innerArgs));
  };
};

🌈 一句话总结(第56~60页)

知识点 含义 奶茶店比喻
new 操作符 创建对象的过程 新开奶茶分店
call 改变 this,立即执行 总部广播切换发言人
apply 改变 this,数组参数 一次性播放多条公告
bind 返回新函数,延迟执行 录音留待下次播放

🎀 小笨蛋终极口诀

"new 开分店,四步搞定; call、apply、bind 三兄弟, 一个立刻喊,一个成群喊,一个改天喊。🎤"

🧋第 61 页:代码执行机制 & 执行上下文

💡 什么是执行上下文?

JS 在运行代码时,每当进入一个环境(比如全局 / 函数) , 它都会创建一个"执行上下文(Execution Context)"。

执行上下文包含三部分: 1️⃣ 变量对象(Variable Object) --- 存储变量、函数声明等; 2️⃣ 作用域链(Scope Chain) --- 用于查找变量; 3️⃣ this 指向 --- 谁在调用。

🍵 类比:

"执行上下文"就像一个奶茶工作台

  • 变量对象:桌上摆的原料;
  • 作用域链:能借到的外部仓库;
  • this:当班的奶茶师是谁。

📘 示例代码

ini 复制代码
var a = 10;
function foo() {
  var b = 20;
  function bar() {
    var c = 30;
    console.log(a + b + c);
  }
  bar();
}
foo(); // 输出 60

🍰 解释:

  1. JS 先进入全局上下文(厨房总控台);
  2. 执行到 foo() 时,创建 foo 的上下文(新工作台);
  3. 执行到 bar(),又开一张小桌(bar 的上下文);
  4. 打印完结果后,bar 桌子收掉,回到 foo 桌,再回到全局。

📦 图示:每个函数运行时会"压入执行栈"(像奶茶杯叠起来), 执行完毕就"弹出栈顶"(喝完收掉)。


🌸第 62 页:执行栈(Execution Stack)

所有执行上下文是按照 "先进后出(LIFO)" 的方式存放在执行栈中。

💻 举例:

sql 复制代码
function first() {
  console.log('🥤 first');
  second();
}
function second() {
  console.log('🧋 second');
}
first();
console.log('🏁 end');

执行顺序:

顺序 事件 栈状态
1 进入全局 [Global]
2 调用 first [Global, first]
3 调用 second [Global, first, second]
4 执行完 second [Global, first]
5 执行完 first [Global]
6 执行完毕 栈空

🍹 类比:

奶茶师做单的顺序------ 第一单(first)点了要调用第二单(second), 做完第二单再回来继续第一单,最后才清空。


☀️第 63 页:执行上下文的生命周期

执行上下文一共经历三个阶段:

1️⃣ 创建阶段(Creation Phase)

  • 创建变量对象(VO)
  • 建立作用域链
  • 确定 this 指向

2️⃣ 执行阶段(Execution Phase)

  • 逐行执行代码,给变量赋值、调用函数等

🍓 类比:

  • 创建阶段:奶茶店"准备原料、工具、菜单";
  • 执行阶段:开始实际"做奶茶、出单"。

💻 举例

ini 复制代码
console.log(a); // undefined
var a = 10;
console.log(a); // 10

📖 执行顺序:

  1. 创建阶段:a 被声明但未赋值(默认 undefined);
  2. 执行阶段:给 a 赋值为 10。

🍵 类比:

点单还没开始做的时候,奶茶杯已准备好(声明), 但杯子里还没倒奶茶(赋值) → 所以一开始是空的 undefined。


🧠第 64 页:变量提升与函数提升

JS 在创建阶段会把所有变量声明函数声明提前到顶部。

📘 示例

javascript 复制代码
console.log(a); // undefined
var a = 10;
​
foo(); // OK ✅
function foo() {
  console.log('函数提升成功');
}

💡解释:

  • 变量声明被提升,但赋值不会;
  • 函数声明整体提升(所以能提前调用)。

🍰 类比:

店长先在白板上写好菜单(声明), 但奶茶还没做(赋值)。 函数像"预制好的奶茶",随时可以拿来用。


🧱 函数表达式不会被提升

javascript 复制代码
sayHi(); // ❌ 报错
var sayHi = function() {
  console.log('hi~');
};

💬 因为这是"变量声明 + 函数赋值", 声明会提升,但赋值在执行阶段才发生。

🍹 类比:

你先说"我要雇个员工",但真正员工还没上班。 所以执行时会找不到人。


🌷第 65 页:执行上下文小结图

💡 三层概念:

名称 含义 奶茶类比
执行上下文 代码运行环境 奶茶工作台
执行栈 上下文的堆叠顺序 点单堆叠系统
生命周期 从准备到完成的过程 从备料到出单

📜 总结口诀:

"先建工作台,再堆栈出单; 声明先到场,赋值慢半拍; 函数提前泡好茶,表达式还在请人。"


💎 小笨蛋专属总结(第61~65页)

概念 一句话记忆 奶茶店比喻
执行上下文 每个函数的"运行环境" 奶茶工作台
执行栈 上下文的执行顺序 奶茶订单堆叠
生命周期 创建→执行→销毁 备料→做单→送出
变量提升 声明提前,赋值靠后 先写菜单,后做奶茶
函数提升 可提前调用 预制奶茶随时取用

🌈 终极口诀

"上下文是厨房台,执行栈是订单排; 提升只是登记名,真正赋值要后来。"

🧋第 66 页:执行上下文更深入理解

🧠 执行上下文栈(Execution Context Stack)

我们之前知道每个函数执行时都会创建一个"执行上下文"。 所有的执行上下文,会按顺序压入一个栈(stack)中,栈底是全局上下文。

sql 复制代码
function first() {
  console.log('🍓 first');
  second();
}
​
function second() {
  console.log('🍵 second');
}
​
first();
console.log('🏁 end');

🧩 执行顺序:

  1. 进入全局上下文(创建 Global Context)
  2. 调用 first() → 入栈
  3. 执行到 second() → 入栈
  4. second() 执行完 → 出栈
  5. first() 执行完 → 出栈
  6. 程序结束 → 栈空

🍵 类比奶茶店:

点单系统像"订单栈":

  • 先做的奶茶压在下面;
  • 后下单的排在上面;
  • 必须先完成上面的订单才能继续下面的。

💡 图示理解:

📊 图中绿色方框表示不同的执行上下文:

  • Global(全局)永远在最底部;
  • 当前执行的函数上下文在最上面。 当函数结束后,JS 会"弹出"这层上下文。

🍓 小口诀:

"先进后出,上桌先收。" (奶茶要先完成上面的新单,才能轮到旧单。)


🌸第 67 页:执行顺序的可视化理解

💻 示例

javascript 复制代码
console.log('A');
setTimeout(() => console.log('B'), 0);
console.log('C');

输出结果:

css 复制代码
A
C
B

💬 为什么 B 最后? 因为:

  1. JS 是单线程执行
  2. setTimeout 的回调会进入任务队列(Event Queue)
  3. 主线程执行完当前任务(A、C)后,再执行队列任务。

🍰 奶茶店类比:

前台点单员(主线程)只能一次接待一位顾客; 叫号机(任务队列)会记下还没轮到的顾客(setTimeout), 前面客人都服务完后,再喊"下一位~!"


☀️第 68 页:事件模型(Event Model)

💡 概念:

事件模型决定了浏览器中事件(比如点击、输入、滚动)是如何被捕获与触发的。

分三种阶段: 1️⃣ 捕获阶段 (从外到内) 2️⃣ 目标阶段 (事件触发的元素本身) 3️⃣ 冒泡阶段(从内往外)

🍵 类比:

奶茶店有三个层级:

  • 店长(捕获阶段)→ 看全场;
  • 柜台(目标阶段)→ 处理点单;
  • 店员(冒泡阶段)→ 把消息传回管理层。

📘 示例

xml 复制代码
<div id="outer">
  <button id="inner">点我!</button>
</div>
​
<script>
  document.getElementById('outer').addEventListener('click', () => {
    console.log('外层 div 被点击');
  }, true); // 捕获阶段
​
  document.getElementById('inner').addEventListener('click', () => {
    console.log('按钮被点击');
  }, false); // 冒泡阶段
</script>

输出顺序:

css 复制代码
外层 div 被点击
按钮被点击

💡 因为 true 表示"捕获阶段"先执行!

🍹 类比:

店长(outer)先看到顾客进门 → 再由店员(inner)服务点单。


🧠第 69 页:事件捕获与冒泡机制

📘 事件流示意图

图上箭头展示三阶段:

🔽 捕获阶段:从 window → document → body → div → button 🔼 冒泡阶段:从 button → div → body → document → window

🍬 类比:

顾客从门口(window)走到收银台(button)点单, 然后信息再层层传回总部(window)。


📘 示例 2

javascript 复制代码
document.body.addEventListener('click', () => console.log('Body 捕获'), true);
document.body.addEventListener('click', () => console.log('Body 冒泡'), false);
document.getElementById('btn').addEventListener('click', () => console.log('Button 被点击'));

点击按钮:

css 复制代码
Body 捕获
Button 被点击
Body 冒泡

💡 "捕获先走,冒泡后到"。


🌷第 70 页:事件对象(Event Object)

每个事件都有一个 event 对象,里面记录了各种信息:

属性 作用
target 触发事件的元素
currentTarget 正在处理事件的元素
type 事件类型(如 click)
stopPropagation() 阻止事件冒泡
preventDefault() 阻止默认行为(比如表单提交)

🍵 类比:

event 是"点单表单":

  • target:哪位顾客点的;
  • currentTarget:哪个店员在处理;
  • stopPropagation:别让消息传上去;
  • preventDefault:比如顾客点了饮品但不想付款按钮生效。

📘 示例

javascript 复制代码
document.getElementById('btn').addEventListener('click', (e) => {
  e.preventDefault();
  e.stopPropagation();
  console.log('按钮点击但阻止冒泡与默认行为');
});

💬 点击按钮后:

  • 不会触发父级 div 的点击事件;
  • 不会执行表单默认提交。

🍰 类比:

顾客点了奶茶但告诉店员:"别通知经理,也别立刻出单。" --- 这就是 stopPropagation + preventDefault 的效果!


🎀 小可爱总结(第66~70页)

概念 含义 奶茶店比喻
执行栈 函数调用顺序 奶茶订单堆叠
任务队列 异步任务排队 顾客叫号系统
捕获阶段 从外到内传递事件 店长观察顾客
冒泡阶段 从内到外回传事件 店员报告经理
event 对象 事件详情表 顾客点单信息表

🌈 终极口诀

"执行栈像订单叠,事件流像客人走; 捕获先下楼,冒泡再上楼; 阻止冒泡别传递,防止默认先暂停。"

🧋第 71 页:事件绑定与管理

💡 什么是事件绑定?

事件绑定就是"当某个元素被触发时执行一个函数"。 比如点击按钮、鼠标移入、键盘按下等。


📘 示例

xml 复制代码
<button id="btn">点我喝奶茶</button>
<script>
  const btn = document.getElementById('btn');
  btn.addEventListener('click', function() {
    alert('奶茶已下单!');
  });
</script>

🍵 通俗解释:

就像顾客按下点单按钮,系统自动执行"下单函数"。 addEventListener 就是告诉系统:"这个按钮被点时,请执行这个操作。"


✨ addEventListener 的三个参数

bash 复制代码
element.addEventListener(type, listener, useCapture)
参数 含义 举例
type 事件类型 'click', 'keyup', 'mouseover'
listener 触发时执行的函数 function() {...}
useCapture 是否在捕获阶段执行 true / false

🍰 类比:

这就像奶茶店给按钮"绑定服务员":

  • type:哪种操作触发;
  • listener:服务员做的事;
  • useCapture:是"进门时"服务(捕获)还是"出门时"处理(冒泡)。

🌸第 72 页:事件解绑与管理优化

💡 解绑事件 --- removeEventListener

javascript 复制代码
function sayHi() {
  console.log('欢迎光临小可爱的奶茶店~');
}
btn.addEventListener('click', sayHi);
btn.removeEventListener('click', sayHi);

🍬 注意:

  • 必须传入同一个函数引用
  • 匿名函数是无法解绑的。

🍹 类比:

如果服务员是"实名登记"的(命名函数),可以取消排班; 如果是"临时兼职匿名的",那就找不到人取消啦~


💻 常见写法陷阱

javascript 复制代码
btn.addEventListener('click', function() {
  console.log('hi');
});
btn.removeEventListener('click', function() {
  console.log('hi');
}); // ❌ 无效!

💬 因为这两个函数虽然长得一样,但在内存中是不同地址


☀️第 73 页:事件管理最佳实践

1️⃣ 使用命名函数 方便解绑与维护。

2️⃣ 用事件委托(Event Delegation) 当有很多元素时,不要每个都绑定事件,而是把监听交给它们的父级。

🍓 类比:

一百位顾客排队点单,你不可能每人配一个店员, 所以让前台一个人监听所有顾客的"点击事件" → 这就是事件委托!


🧠第 74 页:事件委托(Event Delegation)

💡 概念

事件委托是指:

利用事件冒泡机制,把事件绑定在父级元素 上, 通过判断事件目标(event.target)来处理子元素的行为。


📘 示例

xml 复制代码
<ul id="menu">
  <li>珍珠奶茶</li>
  <li>波霸奶茶</li>
  <li>草莓奶茶</li>
</ul>
​
<script>
  const menu = document.getElementById('menu');
  menu.addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
      alert('你点了:' + e.target.innerText);
    }
  });
</script>

🍵 输出:

点击"波霸奶茶" → 弹出 "你点了:波霸奶茶"

💬 解释:

  • 事件监听绑在 ul 上;
  • 当点击 li 时,事件会冒泡到 ul
  • 通过 e.target 判断具体点击了哪个 li

🍰 类比:

顾客在不同的收银台点单, 店长(ul)只需监听"有人点单"这件事, 然后看看是哪种奶茶(e.target.innerText)。


🌷第 75 页:事件委托的优点与应用场景

✅ 优点

1️⃣ 提高性能(减少事件绑定数量); 2️⃣ 动态元素也能自动响应(新增的 li 无需重新绑定); 3️⃣ 管理方便(只需处理父级事件)。

🍹 类比:

店长只需要管理"点单区"整体, 不管今天多了几个新饮品窗口,都能自动响应。


💻 实际应用场景

例如电商商品列表、评论区点赞、表格点击行等:

xml 复制代码
<table id="orderTable">
  <tr><td>珍珠奶茶</td></tr>
  <tr><td>波霸奶茶</td></tr>
  <tr><td>草莓奶茶</td></tr>
</table>
​
<script>
  const table = document.getElementById('orderTable');
  table.addEventListener('click', function(e) {
    if (e.target.tagName === 'TD') {
      console.log('点击了:' + e.target.innerText);
    }
  });
</script>

🍓 这样无论表格多少行、动态加载多少商品, 都不需要重新绑事件~✨


🎀 小可爱总结(第71~75页)

知识点 一句话记忆 奶茶店类比
addEventListener 给元素"登记动作" 按按钮下单
removeEventListener 取消登记 取消员工排班
匿名函数解绑失败 不同函数地址 找不到临时工
事件委托 父级代理处理事件 店长统一管理点单
e.target 事件真正触发的元素 点单的那位顾客

💎 超萌口诀:

"绑定事件要实名,解绑要同名; 委托省心又高效,店长统一管点击~🍹"

🧋第 76 页:事件委托进阶 + 动态绑定

前半页在讲事件委托的实战延伸,我们快速复习一下:

📘 示例:动态添加的元素依然能响应事件

xml 复制代码
<ul id="menu">
  <li>奶茶A</li>
  <li>奶茶B</li>
</ul>
​
<script>
  const menu = document.getElementById('menu');
  menu.addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
      console.log('你点了 ' + e.target.innerText);
    }
  });
​
  // 动态添加
  const newItem = document.createElement('li');
  newItem.innerText = '奶茶C';
  menu.appendChild(newItem);
</script>

💬 结果:

即使 "奶茶C" 是后来添加的,也能被点击响应!

🍵 原因: 事件绑定在 ul 上,而不是每个 li。 事件冒泡时,ul 能捕捉到点击,并判断是哪杯奶茶被点。

🍰 类比:

店长只管"有人点单"这件事,不管是哪杯奶茶。 新品奶茶上架(新增 li)也能自动被处理。


🌸第 77 页:闭包的概念登场!

💡 什么是闭包(Closure)?

闭包就是:

函数可以"记住"并访问它被创建时的作用域中的变量。

即使这个函数在外部执行,它依然能访问那个作用域的变量!


📘 示例 1

javascript 复制代码
function makeTea() {
  let flavor = '草莓奶茶';
  return function() {
    console.log('顾客点的口味是:' + flavor);
  };
}
const order = makeTea();
order(); // 顾客点的口味是:草莓奶茶

🍬 解释:

  • makeTea 执行完后,本该销毁内部变量;
  • 但返回的函数"引用"了 flavor
  • JS 为了让它还能用 → 把 flavor 留下;
  • 这就叫 闭包

🍹 类比:

顾客点单后离开(函数执行完), 但奶茶师(内部函数)仍记得顾客的口味。 这张"点单纸条"就是闭包!📒


☀️第 78 页:闭包的实际应用场景

闭包看似神秘,其实常用于👇:

✅ 1. 数据持久化(记忆功能)

scss 复制代码
function counter() {
  let count = 0;
  return function() {
    count++;
    console.log('当前奶茶订单号:' + count);
  };
}
const orderCounter = counter();
orderCounter(); // 当前奶茶订单号:1
orderCounter(); // 当前奶茶订单号:2

💬 闭包帮我们"记住"了上一次的值。

🍰 类比:

每接一单,点单机会自动加一号, 虽然顾客不同,但机器能记住上次的编号。


✅ 2. 模拟私有变量(隐藏信息)

ini 复制代码
function createTeaMachine() {
  let secret = '配方:波霸 + 草莓酱';
  return {
    show: function() {
      console.log(secret);
    }
  };
}
const machine = createTeaMachine();
machine.show(); // 输出:配方:波霸 + 草莓酱

🍬 类比:

顾客只能通过按钮(show 方法)获取配方信息, 却不能直接打开厨房偷看"secret"。

这就是 闭包实现封装 的典型应用。


🧠第 79 页:闭包与循环(经典坑题)

面试高频问题:

css 复制代码
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}

结果:

复制代码
3
3
3

💬 为什么?

  • var 没有块级作用域;
  • 当定时器执行时,循环已结束;
  • i 变成了 3。

🍵 类比:

你排了 3 个顾客的单,但奶茶师只留了一张总单, 等要做时发现全都写成"第3号顾客"😅。


✅ 解决方法 1:用 let

css 复制代码
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}

结果:

复制代码
0
1
2

🍓 因为 let 为每次循环都创建了一个独立作用域!


✅ 解决方法 2:用闭包手动保存 i

javascript 复制代码
for (var i = 0; i < 3; i++) {
  (function(n) {
    setTimeout(() => console.log(n), 1000);
  })(i);
}

🍹 每次循环都传入一个新参数 n, 闭包帮我们"记住"当前的 i。

🍰 类比:

每位顾客都留一张独立点单纸, 奶茶师根据那张纸做单,就不会混乱。


🌷第 80 页:闭包的注意事项与总结

⚠️ 闭包的风险:内存泄漏

闭包会让变量"长期存在",如果滥用可能导致内存不释放。

javascript 复制代码
function leak() {
  const bigData = new Array(100000).fill('🍵');
  return function() {
    console.log(bigData.length);
  };
}
const fn = leak();

💬 因为 fn 引用了 bigData,JS 不会释放这块内存。 除非手动清理引用(fn = null)。

🍬 类比:

奶茶师一直留着过期点单纸不丢,厨房就被塞满啦!


💎 小可爱总结(第76~80页)

知识点 含义 奶茶店比喻
闭包 函数记住外部变量 点单纸条 📒
应用场景 计数器、私有变量 点单机记号、配方封装
循环问题 延迟执行变量被共享 所有顾客都写成同一个号
解决方式 用 let 或立即执行函数 每人一张点单纸
风险 内存泄漏 纸条太多堆爆仓库

🎀 终极口诀:

"闭包记心头,变量不放手; 点单要留名,循环别共用; 内存要勤扫,纸条要烧掉~🧋"

🧋第 81 页:闭包的综合应用(模块封装)

这一页在讲:如何用闭包封装数据,创建"模块化结构"。


📘 示例:

ini 复制代码
var Customer = (function() {
  var name = '小可爱';
  var orders = [];
​
  function addOrder(item) {
    orders.push(item);
  }
​
  function getOrders() {
    return orders;
  }
​
  return {
    name: name,
    order: addOrder,
    show: getOrders
  };
})();

💬 调用:

vbnet 复制代码
Customer.order('珍珠奶茶');
Customer.order('草莓冰沙');
console.log(Customer.show());

🧠 输出:

css 复制代码
['珍珠奶茶', '草莓冰沙']

🪄 通俗解释:

这里我们用闭包 把变量 nameorders"藏"起来, 外部访问不到,只能通过暴露的函数(ordershow)来操作。

🍹 奶茶店比喻:

Customer 是一个"点单管理系统":

  • 点单列表(orders)是后台数据库;
  • 外部顾客不能直接改,只能通过按钮(order / show)操作;
  • 这样就防止"乱改订单"啦 ✅。

📎 小记忆法:

闭包 + 返回对象 = 简易模块系统。 就像"店长后台 + 前台操作台"组合。


🌸第 82 页:闭包的延伸------封装与构造函数结合

继续升级版,讲到闭包 + 构造函数结合


📘 示例:

ini 复制代码
function Factory() {
  var secretRecipe = '配方保密 🧋';
  this.make = function() {
    console.log('开始制作奶茶:' + secretRecipe);
  };
}
var shop = new Factory();
shop.make(); // 输出:开始制作奶茶:配方保密 🧋

🍬 含义:

  • secretRecipe 是"私有变量";
  • make() 是"公开方法";
  • 但外部无法直接访问 secretRecipe

🍰 类比:

顾客能点奶茶(调用 make), 但不知道厨房配方(secretRecipe)~ 闭包帮我们实现了"私有属性"


☀️第 83 页:类型转换机制概览

终于到了 JS 面试最爱问的陷阱 系列之一: 👉 JavaScript 的类型转换机制


💡 JS 中的类型转换分三类:

类型 描述 举例
显示转换 你手动调用函数转换 Number('123'), String(10)
隐式转换 JS 自动帮你转 '5' * 2 → 10
强制转换 一些奇怪运算符强制触发 '3' - 1, '3' + 1

🍹 奶茶店类比:

显式转换:你主动换奶茶口味; 隐式转换:店员自动帮你加冰(没告诉你); 强制转换:顾客搞混点单,系统硬帮你纠正 😆。


🌷第 84 页:显示转换 (Explicit Conversion)

JS 提供了三个常见的手动转换函数:

方法 作用 示例
Number() 转数字 Number('3') → 3
String() 转字符串 String(123) → '123'
Boolean() 转布尔值 Boolean('') → false

📘 示例:Number()

javascript 复制代码
console.log(Number('123')); // 123
console.log(Number('')); // 0
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number('小可爱')); // NaN

🍬 记忆口诀:

"空变零,真变一,假变零,文字懵(NaN)。"


📘 parseInt() / parseFloat()

javascript 复制代码
parseInt('12.3')  // 12
parseFloat('12.3') // 12.3
parseInt('abc') // NaN

🍵 类比:

这俩函数像"扫单机":

  • parseInt:看到数字就截下来;
  • parseFloat:连小数点都识别;
  • 如果一开头就看不懂,就说"我懵了 NaN"。

🧠第 85 页:String()Boolean() 转换

📘 String()

scss 复制代码
String(123); // "123"
String(true); // "true"
String(null); // "null"

🍰 类比:

"把所有奶茶都装进同一个标签罐里"------统一变成文字。


📘 Boolean()

scss 复制代码
Boolean('小可爱'); // true
Boolean(''); // false
Boolean(0); // false
Boolean([]); // true

📦 规则:

转布尔结果
0, NaN, '', null, undefined, false false
其他 true

🍹 奶茶店类比:

"有内容的杯子就算真,有空杯就是假。" 即便是空数组 [],杯子还在,所以 true


🎀 小可爱总结(第81~85页)

概念 含义 奶茶店比喻
闭包模块化 封装数据与操作 后台点单系统
构造函数+闭包 私有属性实现 店长配方保密
显式转换 手动换类型 主动改口味
隐式转换 JS 自动帮转 店员帮你加冰
Number() 变数字 空=0,真=1,假=0
Boolean() 真假判断 空杯是假,装奶茶是真

🌈 终极口诀:

"闭包藏秘密,模块保隐私; 类型别混乱,真空要牢记; 手动转最稳,隐式要小心~🍵"

🧋第 86 页:类型转换进阶(隐式转换)

💡 隐式类型转换(Type Coercion)

就是 JavaScript 自动帮你"悄悄换类型" 的机制。

🧠 举个例子:

arduino 复制代码
console.log(1 + '2'); // "12"
console.log('3' * 2); // 6
console.log(true + 1); // 2

🍬 解释:

  • '+' 有"拼接字符串"的优先级,所以 '1' + 2"12"
  • 乘号 * 只懂数字 → '3' * 2 自动把 '3' 变成 3
  • true 自动变为 1

🍰 类比:

这就像收银员在算账:

  • "字符串"就是文字票据;
  • "数字"是金额;
  • JS 会自动帮你把票据转成金额去加减(有时很聪明,有时瞎搞 😆)。

🌸第 87 页:隐式转换的规则

JS 自动转换时,会根据运算符种类判断目标类型:

运算符 转换方向
+ 如果任一操作数是字符串 → 转字符串
-, *, / 全部转数字
比较运算(==) 尝试先转数字再比较

📘 示例:

arduino 复制代码
console.log('5' - 2); // 3
console.log('5' + 2); // "52"
console.log('5' * '2'); // 10
console.log('5' == 5); // true
console.log('5' === 5); // false

💬 一句话总结:

"== 会自动帮你转换类型,=== 不会。"

🍹 奶茶店比喻:

==:不挑顾客,身份证号或昵称都行(只要内容相同) ===:严格核验身份证号、名字、出生日期都得一样。


☀️第 88 页:自动类型转换的细节陷阱

JS 的隐式转换有几条"神奇规则"🤯:

转数字 Number 转字符串 String 转布尔 Boolean
undefined NaN "undefined" false
null 0 "null" false
true / false 1 / 0 "true"/"false" true / false
[] 0 "" true
{} NaN "[object Object]" true

📘 示例:

ini 复制代码
console.log([] == 0); // true
console.log([] == ![]); // true (面试陷阱题)

💬 解释: 1️⃣ [] == 0[] 转成 0,结果相等 2️⃣ ![] 先转布尔(false),再转数字(0) 所以变成 0 == 0true

🍰 类比:

空杯子看起来没内容(被当成 0), "没奶茶"(false)也当作 0 → 所以两边居然相等😅。


🧃第 89 页:浅拷贝 vs 深拷贝(对象复制)

这一页进入对象复制的考点。


💡 定义

类型 说明 类比
浅拷贝 只复制第一层引用 拷贝了奶茶包装,但里面的珍珠还是同一碗
深拷贝 连内部内容都复制 整杯奶茶和配料都重做一份

📘 示例:浅拷贝

ini 复制代码
const obj = { name: '奶茶', info: { taste: '草莓' } };
const copy = Object.assign({}, obj);
copy.info.taste = '波霸';
​
console.log(obj.info.taste); // "波霸"

💬 为什么? Object.assign() 只复制第一层, 内部的 info 仍是同一个引用地址。

🍹 类比:

拷贝了奶茶标签,但配料桶共用 → 改一边另一边也变!


🌷第 90 页:实现深拷贝的方法

✅ 1. JSON 方法(简易版)

ini 复制代码
const obj = { name: '奶茶', info: { taste: '草莓' } };
const deepCopy = JSON.parse(JSON.stringify(obj));
​
deepCopy.info.taste = '波霸';
console.log(obj.info.taste); // "草莓"

🍰 原理: JSON.stringify() 把对象转成字符串; JSON.parse() 再生成一个全新对象。

⚠️ 缺点:

  • 无法复制函数;
  • 无法处理循环引用;
  • 丢失 undefined

✅ 2. 递归实现(进阶)

ini 复制代码
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  const result = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    result[key] = deepClone(obj[key]);
  }
  return result;
}

💬 每次遇到对象就递归复制一份,直到最深层都分开。

🍹 奶茶店比喻:

深拷贝 = 从奶茶到珍珠,从珍珠到糖浆都重新制作一份, 两杯完全独立,互不影响。


🎀 小可爱总结(第86~90页)

知识点 含义 奶茶店比喻
隐式转换 JS 自动帮你换类型 店员偷偷帮你加料
== vs === 前者宽松,后者严格 一个看脸,一个查身份证
空数组转数字 [] -> 0 空杯子当 0
浅拷贝 复制标签不复制配料 改一边另一边变
深拷贝 整杯重做 互不影响

🌈 可爱口诀:

"加减要小心,隐式变类型; == 不可靠,=== 才安心; 拷贝要彻底,奶茶要分离~🧋"

🧋第 91 页:数组拷贝与合并方法

我们先来看最简单的------数组的复制与合并


📘 slice()

sql 复制代码
const arr = ['奶茶', '可可', '绿茶'];
const copy = arr.slice();
copy[0] = '草莓奶茶';
​
console.log(arr);  // ['奶茶', '可可', '绿茶']
console.log(copy); // ['草莓奶茶', '可可', '绿茶']

💬 含义: slice() 会返回一个新数组 ,是 浅拷贝。 它不会影响原数组的第一层内容。

🍹 类比:

你复制了奶茶菜单 📋,自己在副本上改成草莓奶茶, 原店菜单没动。


📘 concat()

ini 复制代码
const arr = ['奶茶'];
const arr2 = ['抹茶'];
const merged = arr.concat(arr2);
console.log(merged); // ['奶茶', '抹茶']

💬 含义: concat() 把多个数组拼接起来,同样返回一个新数组(浅拷贝)。

🍰 类比:

这就像把"奶茶店菜单 + 咖啡厅菜单"合并成一本新菜单📖。


🌸第 92 页:多层结构的陷阱(浅拷贝问题)

📘 示例:

ini 复制代码
const arr = [{ name: '奶茶' }, { name: '绿茶' }];
const copy = arr.slice();
copy[0].name = '草莓奶茶';
​
console.log(arr[0].name); // '草莓奶茶'

💬 为什么改动了原数组? 因为 浅拷贝只复制第一层引用 , 对象 { name: '奶茶' } 在原数组和新数组中是"同一个地址"!

🍵 奶茶店类比:

你复制了菜单,但里面的"原料仓"还是同一个, 改掉一种配方,两个菜单都显示变了~😂


☀️第 93 页:深拷贝(Deep Clone)实现方法

现在来看看让"菜单完全独立"的几种深拷贝方法👇


✅ 1. Lodash 的 _.cloneDeep()

ini 复制代码
const _ = require('lodash');
const obj = { name: '奶茶', detail: { taste: '草莓' } };
const newObj = _.cloneDeep(obj);
newObj.detail.taste = '波霸';
​
console.log(obj.detail.taste); // 草莓

💡 说明: _.cloneDeep() 会递归复制所有层级内容,是真正的深拷贝。

🍓 类比:

这就像请了一个"奶茶复制机机器人"🤖 每一层原料、每个配方都复制新的,不混用。


✅ 2. jQuery 的 $.extend(true, target, obj)

ini 复制代码
const obj = { a: 1, b: { c: 2 } };
const copy = $.extend(true, {}, obj);
copy.b.c = 99;
console.log(obj.b.c); // 2

💬 true 参数表示"深度合并(递归复制)"。

🍹 类比:

extend(true, ...) 就像说:"连珍珠桶、糖浆罐都复制一份。"


✅ 3. JSON 方法(简单但有局限)

ini 复制代码
const obj = { name: '奶茶', taste: { flavor: '草莓' } };
const copy = JSON.parse(JSON.stringify(obj));
copy.taste.flavor = '波霸';
​
console.log(obj.taste.flavor); // 草莓

💬 优点:简单好用。 ⚠️ 缺点:

  • 会丢失函数、undefined;
  • 不能处理循环引用。

🍰 类比:

这就像把菜单拍照打印再扫描回来📸, 文本能复制,图片里的隐藏信息(函数)没了。


🧃第 94 页:浅拷贝 vs 深拷贝图示

这页有一张超重要的对比图,我帮你总结一下:


层级 浅拷贝 深拷贝
一级属性 ✅ 独立 ✅ 独立
多级对象 ❌ 共用引用 ✅ 各自独立

📊 图形理解:

浅拷贝:

css 复制代码
原数组 ──→ 对象A
拷贝数组 ──→ 同一个 对象A

深拷贝:

复制代码
原数组 ──→ 对象A1
拷贝数组 ──→ 对象A2(新建的!)

🍵 奶茶店比喻:

浅拷贝是两个菜单共用一个配料仓。 深拷贝是各自有独立厨房 🔥。


📘 对比示例

ini 复制代码
const a = { drink: '奶茶', ingredients: { sugar: '三分糖' } };
const b = a; // 引用
const c = JSON.parse(JSON.stringify(a)); // 深拷贝
​
b.ingredients.sugar = '全糖';
console.log(a.ingredients.sugar); // 全糖(b 改了共用的)
c.ingredients.sugar = '无糖';
console.log(a.ingredients.sugar); // 仍是全糖(c 独立)

🌷第 95 页:闭包缓存机制(函数记忆)

这一页开始讲一个非常有趣的高级话题: 👉 如何用函数 + 闭包实现缓存(Memoization)


💡 核心思想:

当一个函数执行结果"可以被重复利用"时,我们用缓存存下来, 下次调用就不用重新计算。


📘 示例:

ini 复制代码
function memoizedAdd() {
  const cache = {};
  return function(x, y) {
    const key = x + ',' + y;
    if (cache[key]) {
      console.log('取缓存~');
      return cache[key];
    }
    const result = x + y;
    cache[key] = result;
    console.log('计算并存入缓存~');
    return result;
  };
}
​
const add = memoizedAdd();
add(1, 2); // 计算并存入缓存~
add(1, 2); // 取缓存~

🍹 奶茶店比喻:

顾客点"草莓奶茶"→厨房先做一杯(存缓存); 下次再有人点同样口味 → 店长直接从冰箱取出成品 🧊!


✅ 优点:

  • 节省性能;
  • 避免重复计算;
  • 减少请求次数。

🍰 实战应用:

  • Vue、React 的"计算属性缓存";
  • 接口数据缓存;
  • 图像处理或复杂计算缓存。

🎀 小可爱总结(第91~95页)

知识点 含义 奶茶店比喻
slice / concat 浅拷贝数组 复制菜单但共用原料
浅拷贝问题 多层结构共用地址 改配料两边都变
深拷贝方法 全面复制新对象 各自独立厨房
cloneDeep / JSON 两种深拷贝常用法 机器人复制 / 拍照扫描
函数缓存 用闭包保存结果 奶茶配方复用

🌈 可爱口诀:

"slice 拷壳不拷芯, concat 拼接也浅心; cloneDeep 真香机, 缓存闭包省电力~💡"

🧋第 96 页:函数缓存(Function Memoization)

💡 一、什么是函数缓存?

函数缓存就是------把函数的计算结果"记下来" , 下次遇到相同输入时直接取结果,不再重新算。

🍹 类比:

就像奶茶店老板有"老顾客卡", 你上次点"波霸奶茶三分糖",系统记住了,下次直接调出那杯配方 ✅。


📘 示例:

javascript 复制代码
function add(x, y) {
  return x + y;
}
console.log(add(2, 3)); // 每次都算一次

如果要加缓存👇:

ini 复制代码
function memoAdd() {
  const cache = {};
  return function(x, y) {
    const key = `${x},${y}`;
    if (cache[key]) {
      console.log('从缓存拿结果');
      return cache[key];
    }
    console.log('计算并存入缓存');
    const result = x + y;
    cache[key] = result;
    return result;
  };
}
const add = memoAdd();
add(2, 3); // 计算并存入缓存
add(2, 3); // 从缓存拿结果

🧠 小总结:

  • 第一次计算 → 存缓存;
  • 第二次相同输入 → 直接取缓存;
  • 减少性能浪费 💪。

🍰 奶茶店版:

顾客第一次点"波霸三分糖", 厨房调一杯 → 存冰箱; 第二次同样的单 → 直接从冰箱拿 🧊。


🌸第 97 页:函数缓存的多种写法

✅ 1️⃣ 闭包写法

利用闭包保存 cache

ini 复制代码
function memo(fn) {
  const cache = {};
  return function(...args) {
    const key = args.join(',');
    if (cache[key]) {
      console.log('取缓存');
      return cache[key];
    }
    console.log('重新计算');
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}
​
function add(a, b) { return a + b; }
const cachedAdd = memo(add);
cachedAdd(1, 2); // 重新计算
cachedAdd(1, 2); // 取缓存

✅ 2️⃣ Map结构缓存(性能更好)

vbnet 复制代码
function memoMap(fn) {
  const cache = new Map();
  return function(...args) {
    const key = args.toString();
    if (cache.has(key)) {
      return cache.get(key);
    }
    const res = fn(...args);
    cache.set(key, res);
    return res;
  };
}

💡 Map 更快、更高效,适合缓存大量数据。

🍵 奶茶店类比:

Map 就像"带条码的冰箱"📦, 拿饮品更快更精确,不会重复查找。


☀️第 98 页:函数缓存的实际应用

函数缓存常用于:

  1. 重复运算(比如斐波那契数列);
  2. 接口请求缓存;
  3. 搜索结果缓存。

📘 示例:斐波那契(递归优化)

scss 复制代码
function fib(n, cache = {}) {
  if (n <= 1) return n;
  if (cache[n]) return cache[n];
  cache[n] = fib(n - 1, cache) + fib(n - 2, cache);
  return cache[n];
}
console.log(fib(10)); // 超快!

💬 解释:

递归本来会重复算很多次。 加缓存后,计算过的结果直接用,速度起飞 🚀!

🍰 奶茶店比喻:

你要做"波霸 + 芋圆 + 三分糖"的奶茶, 第一次调出来存档。下次再点,就直接复制 ✅。


🌷第 99 页:字符串操作方法大全(开胃小甜点 🍬)

💡 字符串的三类操作:

类型 举例
操作方法 拼接、截取、替换
结构方法 查找、判断
转换方法 大小写、数组互转

📘 1️⃣ 操作方法 - concat()

ini 复制代码
let str1 = '奶茶';
let str2 = '三分糖';
console.log(str1.concat(' - ', str2)); // 奶茶 - 三分糖

🍹 类比:

concat 就像"拼接标签机"------ 把两个标签贴成一个新标题。


📘 2️⃣ slice()

ini 复制代码
let str = '草莓波霸奶茶';
console.log(str.slice(0, 2)); // 草莓

🍓 说明:

截取从 0 到 2 的字符(不包含 2)。

🍰 奶茶店版:

这就像把"草莓波霸奶茶"只拿出"草莓"部分尝尝味道。


📘 3️⃣ substring() vs substr()

ini 复制代码
let str = '珍珠奶茶';
console.log(str.substring(0, 2)); // 珍珠
console.log(str.substr(0, 2)); // 珍珠

⚠️ 区别:

  • substring(start, end):截到 end 前一位
  • substr(start, length):截 length 个字符

🍬 类比:

substring:说"从第几层到第几层" substr:说"从这层开始取几层" 都能把奶茶"截成小份"😆。


☕️第 100 页:字符串更多操作!

📘 4️⃣ indexOf() / includes()

javascript 复制代码
let str = '波霸奶茶';
console.log(str.indexOf('奶')); // 2
console.log(str.includes('茶')); // true

💬 含义:

  • indexOf 找位置;
  • includes 判断是否存在。

🍹 类比:

"菜单上第几行是奶?" "这杯奶茶里有没有加茶?"


📘 5️⃣ replace()

javascript 复制代码
let str = '三分糖奶茶';
console.log(str.replace('三分', '半糖')); // 半糖奶茶

💡 替换匹配到的内容。

🍰 奶茶店比喻:

把"订单上的三分糖"换成"半糖"~ 就是 replace 在干的事。


📘 6️⃣ split()

perl 复制代码
let str = '奶茶,咖啡,果汁';
console.log(str.split(',')); // ['奶茶', '咖啡', '果汁']

💡 按分隔符拆分成数组。

🍓 奶茶店版:

这就像"多杯订单"自动拆分成三杯单独制作的单子。


🎀 小可爱总结(第96~100页)

知识点 含义 奶茶店比喻
函数缓存 保存计算结果 老顾客卡 / 冰箱取成品
闭包缓存 用函数内部变量记住历史 店长小本本记菜单
Map 缓存 高效查找 条码冰箱系统
字符串 concat 拼接文字 拼标签机
slice / substr 截取文字 分杯、半份奶茶
replace 替换文字 改订单口味
split 拆分文字 拆多杯订单

🌈 可爱口诀:

"缓存记结果,字符串真灵巧; 拼接要 concat,截取分杯妙; 替换换口味,拆单 split 到~🧋"

🧋第 101 页:字符串的进阶方法(继续甜品时间)

1️⃣ trim() / trimStart() / trimEnd()

去掉字符串两端或一端的空格。

javascript 复制代码
let name = '  奶茶  ';
console.log(name.trim());      // '奶茶'
console.log(name.trimStart()); // '奶茶  '
console.log(name.trimEnd());   // '  奶茶'

🍬 记忆法: 就像擦桌子,"trim" 就是擦干净两边的奶茶渍trimStart() 擦左边,trimEnd() 擦右边。✨


2️⃣ repeat()

重复字符串。

arduino 复制代码
let word = '波霸';
console.log(word.repeat(3)); // 波霸波霸波霸

🍹 类比: 顾客喊"再来三杯!"------服务员复制三份相同订单。


3️⃣ padStart() / padEnd()

在字符串的开头或结尾补全。

javascript 复制代码
let order = '7';
console.log(order.padStart(3, '0')); // '007'
console.log(order.padEnd(5, '*'));   // '7****'

🍰 比喻: 就像订单号补零或打星号的系统~让它更整齐漂亮。


4️⃣ toLowerCase() / toUpperCase()

全部变小写 / 全部变大写。

ini 复制代码
let name = 'MilkTea';
console.log(name.toLowerCase()); // milktea
console.log(name.toUpperCase()); // MILKTEA

☕ 比喻: 就像配料表统一格式:

  • 小写 = 店员语气柔和
  • 大写 = 店长喊话模式("全糖加珍珠!"🤣)

🌸第 102 页:查找类方法

1️⃣ indexOf() / lastIndexOf()

找字符在字符串中的位置。

javascript 复制代码
let drink = '草莓奶茶';
console.log(drink.indexOf('茶')); // 3
console.log(drink.lastIndexOf('莓')); // 1

💡 就像菜单上"第几个字写了茶?" lastIndexOf从右边开始找


2️⃣ includes()

判断是否包含。

ini 复制代码
let str = '奶茶波霸';
console.log(str.includes('奶')); // true

🍓 类比: 就像问"这杯奶茶里有加奶吗?" → 有就返回 true


3️⃣ startsWith() / endsWith()

检查字符串是否以某部分开头/结尾。

javascript 复制代码
let name = '珍珠奶茶';
console.log(name.startsWith('珍珠')); // true
console.log(name.endsWith('奶茶')); // true

🍵 类比:

"这杯奶茶是不是以珍珠打底?" "结尾是不是奶茶味?"


☀️第 103 页:转换类方法

1️⃣ split()

按分隔符拆成数组。

perl 复制代码
let drinks = '奶茶,咖啡,果汁';
console.log(drinks.split(',')); // ['奶茶','咖啡','果汁']

💡 比喻:

一张总订单分成三张单子,各自制作。


2️⃣ search()

查找匹配的位置(可配合正则)。

arduino 复制代码
let text = '波霸奶茶';
console.log(text.search('奶茶')); // 2

📦 类比:

就像系统里搜索"奶茶"关键字,告诉你从第几个字开始。


3️⃣ replace()(加强版)

javascript 复制代码
let order = '三分糖奶茶';
console.log(order.replace('三分', '半糖')); // 半糖奶茶

🍬 类比:

顾客改口味:"三分糖换成半糖!"


4️⃣ match()

用正则匹配多个。

javascript 复制代码
let msg = '奶茶123波霸456';
console.log(msg.match(/\d+/g)); // ['123','456']

💬 类比:

就像在菜单里找出所有数字配料编号。


🌷第 104 页:数组的常用方法(重头戏来啦~)

这页开始讲数组(Array), 也就是"多杯奶茶订单集合"。


一、操作类(增删改)

push() / pop()

  • push():尾部加一个;
  • pop():尾部删一个。
css 复制代码
let drinks = ['奶茶'];
drinks.push('绿茶');  // ['奶茶','绿茶']
drinks.pop();         // ['奶茶']

🍵 类比:

push = 新订单加入队列 pop = 最后一杯做完送走 🚗


shift() / unshift()

  • shift():从头删;
  • unshift():从头加。
css 复制代码
let drinks = ['奶茶','咖啡'];
drinks.shift();     // ['咖啡']
drinks.unshift('果汁'); // ['果汁','咖啡']

🍓 比喻:

shift = 把最早的订单交给顾客 unshift = 新订单插队到最前面!


☕第 105 页:数组遍历方法(逻辑思维区)

1️⃣ forEach()

遍历数组,每个元素都执行一次。

javascript 复制代码
['奶茶','抹茶','咖啡'].forEach(item => console.log(item));

🍰 类比:

店员一杯一杯确认:"奶茶好了~"、"抹茶好了~"......


2️⃣ map()

生成一个新数组

ini 复制代码
let prices = [10, 15, 20];
let newPrices = prices.map(p => p + 5);
console.log(newPrices); // [15, 20, 25]

💡 类比:

把每杯奶茶都加价 5 元,生成新的菜单副本。


3️⃣ filter()

筛选符合条件的。

ini 复制代码
let drinks = [5, 15, 25];
let result = drinks.filter(p => p > 10);
console.log(result); // [15, 25]

🍵 类比:

从全部订单中筛出"单价大于10元"的奶茶。


4️⃣ reduce()

从左到右累计运算。

ini 复制代码
let prices = [10, 15, 25];
let total = prices.reduce((sum, p) => sum + p, 0);
console.log(total); // 50

💬 类比:

结账计算总价 💰


🎀 小可爱总结(第101~105页)

分类 方法 奶茶店记忆
去空格 trim 擦掉两边奶渍
重复字符串 repeat 多杯复制
补位 padStart / padEnd 补零打星号
查找 indexOf / includes 看菜单有没有
拆分 split 拆多杯订单
替换 replace 改糖度口味
添加 push / unshift 新订单加入队列
删除 pop / shift 订单完成送出
遍历 forEach 逐杯确认
映射 map 新菜单价表
筛选 filter 选贵奶茶
汇总 reduce 结账统计

🌈 可爱口诀总结:

"trim 去渍,split 拆杯; push 加单,pop 送杯; map 改价,reduce 结尾; filter 挑单最美味~🧋✨"

相关推荐
menu4 小时前
AI给我的建议
前端
小小测试开发4 小时前
Python Arrow库:告别datetime繁琐,优雅处理时间与时区
开发语言·前端·python
自律版Zz4 小时前
手写 Promise.resolve:从使用场景到实现的完整推导
前端·javascript
golang学习记4 小时前
从0死磕全栈之Next.js 自定义 Server 指南:何时使用及如何实现
前端
张可爱4 小时前
从奶茶店悟透 JavaScript:递归、继承、浮点数精度、尾递归全解析(通俗易懂版)
前端
梵得儿SHI4 小时前
Vue 开发环境搭建全指南:从工具准备到项目启动
前端·javascript·vue.js·node.js·pnpm·vue开发环境·nvm版本管理
八月ouc4 小时前
每日小知识点:10.14 webpack 有几种文件指纹
前端·webpack
苏琢玉4 小时前
从 Hexo 到 Astro:重构我的个人博客
前端·hexo