🧩 第101页:浏览器中的多线程通信(核心概念)
💡 背景
JS 是单线程的,也就是说一次只能做一件事。 但浏览器为了更高效,会开“多个线程”来协作,比如:
| 线程 | 作用 |
|---|---|
| JS 主线程 | 执行脚本逻辑 |
| 渲染线程 | 负责绘制页面 |
| 网络线程 | 处理请求 |
| Worker 线程 | 跑耗时任务 |
| 定时器线程 | 管理 setTimeout 等定时操作 |
🍡 类比:
整个网页就像一个奶茶店 🍵 JS 主线程 = 前台点单员 Worker = 后厨冲奶茶的师傅 渲染线程 = 陈列师,负责摆好成品 大家并行合作,才能高效出单 💨
⚙️ 第102页:Web Worker 是什么?
💡 定义:
Web Worker 是浏览器提供的后台运行 JS 的方式。 让一些耗时任务(比如循环计算)在子线程中运行,不会卡主页面。
✅ 示例代码
主线程:
const worker = new Worker('worker.js');
worker.postMessage('开始制作奶茶');
worker.onmessage = e => {
console.log('收到回复:', e.data);
};
worker.js:
self.onmessage = e => {
console.log('子线程收到:', e.data);
self.postMessage('奶茶制作完成!');
};
📘 解释:
new Worker()开一个新线程;postMessage()负责通信;onmessage监听消息。
🍡 生活类比:
前台点单员(主线程)下单:“做杯珍珠奶茶!” 后厨(Worker)接单,做好后回喊:“好了!” 前台再接回来告诉顾客取餐 🧋。
💡 口诀记忆:
“主线程下单,Worker接单,消息来回传。”
🧠 第103页:Service Worker 是什么?
💡 定义
Service Worker 是运行在浏览器背后的“中间层脚本”。 它可以拦截请求、做缓存、离线访问,是 PWA(渐进式网页应用)的核心。
✅ 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(() => {
console.log('注册成功');
});
}
✅ 在 sw.js 中
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request).then(res => res || fetch(e.request))
);
});
📘 解释:
register()注册一个 worker;- 它会拦截请求;
- 先从缓存拿,如果没有再去 fetch。
🍡 生活类比:
像奶茶店有个仓库管理员(Service Worker)📦 顾客点奶茶时他会说:“我先看看仓库里有没有库存,有就不去冲新茶。”
💡 口诀记忆:
“缓存优先,离线也能喝奶茶。”
☕ 第104页:SharedWorker(共享 Worker)
💡 背景:
SharedWorker 是多个页面共享一个 Worker 实例。 它可以让不同标签页的数据互通。
✅ 示例
// main.js
const worker = new SharedWorker('shared.js');
worker.port.postMessage('hi');
// shared.js
onconnect = function (e) {
const port = e.ports[0];
port.onmessage = evt => {
console.log('收到:', evt.data);
port.postMessage('hello back');
};
};
📘 解释:
- 所有页面都能通过
port通信; - 共享同一个运行实例;
- 适合做多标签页同步。
🍡 生活类比:
多个奶茶店窗口共享一个“中央厨房” 👩🍳 不同店面都能向它要原料或同步库存~
💡 口诀记忆:
“SharedWorker:多页共享一锅茶。”
🧱 第105页:iframe + postMessage 通信
💡 背景
当父页面嵌套一个 iframe(或者不同源的网页)时,不能直接访问彼此变量。 但可以通过 window.postMessage() 来安全通信。
✅ 父页面
iframe.contentWindow.postMessage('你好子页', '*');
window.addEventListener('message', e => {
console.log('来自子页:', e.data);
});
✅ 子页面
window.addEventListener('message', e => {
console.log('父页发来的:', e.data);
e.source.postMessage('收到啦!', e.origin);
});
📘 解释:
postMessage()发消息;message事件监听;e.origin校验来源,防止安全问题。
🍡 生活类比:
父页面像奶茶总部总部,iframe 是加盟店。 他们不能直接拿对方账本,但可以互寄消息单(postMessage)📨
💡 口诀记忆:
“跨页通信靠postMessage,注意origin保安全。”
🌈 第101~105页复盘卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 101 | 浏览器多线程 | 奶茶店分工合作,线程各司其职 |
| 102 | Web Worker | 主线程下单,子线程做茶 |
| 103 | Service Worker | 缓存优先,离线可喝 |
| 104 | SharedWorker | 多页共享一锅茶 |
| 105 | iframe通信 | postMessage送纸条,origin保安全 |
💬 小可爱总结:
“我不仅懂多线程,还能解释每个‘奶茶师傅’干啥~ 主线程点单、Worker冲茶、Service Worker存货、SharedWorker协调、iframe寄信~ 一个网页的奶茶王国诞生啦🧋👑!”
🌟 第106页:React 不用 react-router 也能做导航!
💡 背景
我们平时用 react-router 来做页面切换,但其实只用 window.history 也能实现同样功能。
✅ 示例:
import React, { useState, useEffect } from 'react';
function App() {
const [page, setPage] = useState(window.location.pathname);
useEffect(() => {
window.addEventListener('popstate', () => {
setPage(window.location.pathname);
});
}, []);
const navigate = (path) => {
window.history.pushState({}, '', path);
setPage(path);
};
return (
<div>
<button onClick={() => navigate('/')}>首页</button>
<button onClick={() => navigate('/about')}>关于</button>
{page === '/' && <h1>主页内容</h1>}
{page === '/about' && <h1>关于我们</h1>}
</div>
);
}
📘 解释:
pushState():手动改变地址栏但不刷新;popstate事件:监听用户点“返回键”;- 用
useState控制页面显示内容。
🍡 生活类比:
react-router是“点奶茶菜单”的点单机, 而上面这段代码就是你自己做一个简易点单系统,点击就换内容,但不会整页刷新 💡。
💡 口诀记忆:
“pushState 改地址,popstate 监听回退。”
🧩 第107页:实现返回页面时回到之前滚动位置(热度:2484)
✅ 代码:
<a href="#part1">跳转到第1部分</a>
<a href="#part2">跳转到第2部分</a>
<div id="part1">第一部分</div>
<div id="part2">第二部分</div>
window.addEventListener('scroll', () => {
sessionStorage.setItem('scrollTop', document.documentElement.scrollTop);
});
window.addEventListener('load', () => {
const top = sessionStorage.getItem('scrollTop');
if (top) window.scrollTo(0, top);
});
📘 解释:
- 滚动时记下
scrollTop; - 页面重新进入时再
scrollTo()回原位置。
🍡 生活类比:
你去喝奶茶喝到一半出门接电话☕, 回来后还要从“刚喝到的那一口”继续喝—— 这就是记住上次滚动位置的逻辑!
💡 口诀记忆:
“滚动存 session,回来 scrollTo。”
⚙️ 第108页:如何让渲染十万条数据不卡?
💡 背景
如果直接用 for 一次性插入 10 万条,会阻塞主线程 → 页面卡死。 解决办法:分片渲染(分批次画)
✅ 示例:
function renderList(total) {
const once = 1000; // 每次渲染1000条
let done = 0;
function loop() {
requestAnimationFrame(() => {
const fragment = document.createDocumentFragment();
for (let i = 0; i < once && done < total; i++) {
const div = document.createElement('div');
div.textContent = done++;
fragment.appendChild(div);
}
document.body.appendChild(fragment);
if (done < total) loop();
});
}
loop();
}
renderList(100000);
📘 解释:
- 用
requestAnimationFrame每帧渲染一部分; documentFragment批量插入;- 页面不卡顿。
🍡 生活类比:
一次做 10 万杯奶茶肯定爆炸 💥 所以我们每次只做 1000 杯(分帧执行), 顾客会感觉出单流畅又快!🧋
💡 口诀记忆:
“分帧渲染 + DocumentFragment =不卡顿!”
⚙️ 第109页:webpack 打包时 hash 机制(热度:167)
💡 背景
webpack 打包出来的文件常带 hash,例如:
main.a3c4d5e.js
这是为了 缓存更新!
✅ 区别:
| 类型 | 含义 |
|---|---|
| hash | 整个项目改动都会变 |
| chunkhash | 按模块变化(推荐) |
| contenthash | 文件内容变化才变(最精准) |
output: {
filename: '[name].[contenthash].js'
}
🍡 生活类比:
每次奶茶配方改了(文件变),就换新编号防止顾客喝到旧货 🍶
contenthash就是 “只要原料(内容)换了,编号才换”~
💡 口诀记忆:
“contenthash 精准控缓存。”
💥 第110页:如何从 0 到 1 写渲染引擎(热度:404)
💡 概念
“渲染引擎”其实就是浏览器用来把 HTML/CSS/JS 转成画面的过程。
✅ 超简实现:
const vnode = {
tag: 'div',
children: [
{ tag: 'h1', text: 'Hello 小可爱' },
{ tag: 'p', text: '你也能写个渲染器~' }
]
};
function render(vnode) {
const el = document.createElement(vnode.tag);
if (vnode.text) el.textContent = vnode.text;
vnode.children?.forEach(child => el.appendChild(render(child)));
return el;
}
document.body.appendChild(render(vnode));
📘 解释:
- 模拟 Vue/React 的虚拟 DOM;
- 把对象结构递归生成真实 DOM。
🍡 生活类比:
想象你有个奶茶配方表 📋(虚拟DOM), 按配方一步步拿杯、加料、封膜 → 做出真实奶茶(真实DOM)
💡 口诀记忆:
“虚拟表 → 真杯子,一步步递归做。”
🌈 第106~110页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 106 | 自制路由 | pushState 改地址,popstate 监听回退 |
| 107 | 滚动记忆 | 滚动存 session,回来 scrollTo |
| 108 | 大数据渲染 | 分帧渲染不卡顿 |
| 109 | webpack hash | 内容改才换名 |
| 110 | 渲染引擎 | 虚拟DOM 变真页面 |
💬 小可爱总结:
“我现在能自己造路由、记滚动、不卡渲染、控缓存,还能写个小型渲染器! 谁还敢说我只是喝奶茶的小白,我是造奶茶机的工程师🧋⚙️!”
🌟 第111页:你是怎么从 0 到 1 搭建前端项目的?
💡 这是面试官最爱听的开放题,考你能否有体系地组织一个项目。
✅ 一般回答思路:
一、项目初始化
- 技术栈选择(Vue / React / Vite / TS)
- 初始化命令(
npm create vite@latest/npx create-react-app) - 版本控制(
git init)
🍡 生活类比:
就像开奶茶店前先选好“店型”和“装修风格”, 选 Vue 就像选珍珠奶茶系,选 React 像选冰沙系🍧。
二、工程化配置
- ESLint + Prettier(代码规范)
- husky + lint-staged(提交前检查)
- alias 路径别名(比如
@/components)
🍡 类比:
就像设置好“厨房规章”和“卫生检查”,防止师傅乱来乱倒奶茶😆。
三、性能优化
- 按需加载(懒加载路由)
- 图片压缩
- gzip 压缩 + CDN 缓存
🍡 类比:
提前备好珍珠、分时段冲茶,让店不堵不慢。
四、部署上线
- CI/CD 自动打包
- nginx 静态资源部署
🍡 类比:
就像建好门店 → 招牌点亮 → 开门迎客。
💡 口诀记忆:
“选栈 → 规范 → 优化 → 部署”,四步一条龙。
🧩 第112页:你如何用 TS 提升项目稳定性?
💡 重点在讲“TypeScript 的实战价值”。
✅ 1. 类型约束防止低级错误
function add(a: number, b: number): number {
return a + b;
}
🍡 解释:
TS 提前帮你“抓错单”,就像奶茶下单系统会阻止“草莓味珍珠奶茶”😂。
✅ 2. 类型推导 & 泛型
function echo<T>(arg: T): T {
return arg;
}
💡 泛型 = “模具可复用”,像奶茶店做杯子,可以装奶茶、也能装果汁。
✅ 3. keyof / typeof / infer 高级技巧
keyof:获取对象 keytypeof:从变量推类型infer:条件类型里自动推导
🍡 生活类比:
“keyof”像是菜单扫描器,自动知道所有奶茶口味。
💡 口诀记忆:
“静态约束防低错,泛型推导提复用。”
⚙️ 第113页:JS 的前向兼容性与后向兼容性
💡 含义:
- 前向兼容(Forward) :旧浏览器能跑新代码吗?❌(通常不行)
- 后向兼容(Backward) :新浏览器能跑旧代码吗?✅(通常可以)
✅ 示例
// 新语法:可选链
const name = user?.info?.name;
在旧浏览器中会报错 ❌,需要 Babel 转译。
🍡 生活类比:
新奶茶配方(新语法)老机器(旧浏览器)做不了,得“适配”成通用版~
💡 口诀记忆:
“旧能跑新叫前向,新能跑旧叫后向。”
🧠 第114页:浏览器和 Node 的性能优化区别
💡 两者环境不同 → 优化重点也不同。
| 领域 | 浏览器优化 | Node 优化 |
|---|---|---|
| 网络层 | CDN 缓存、HTTP/2 | 连接池、负载均衡 |
| 代码层 | 防抖节流、懒加载 | 异步IO、流处理 |
| 内存 | 垃圾回收监控 | 限制内存泄漏、stream 管理 |
🍡 生活类比:
浏览器是“前台点单员”(优化交互速度) Node 是“后厨冲茶员”(优化产能和并发)
💡 口诀记忆:
“前端省流量,后端拼并发。”
💥 第115页:Webpack 里 script 异步加载的原理(难点🔥)
✅ 背景
Webpack 打包出的 JS 会通过 <script> 标签插入 HTML。 这些脚本可能要异步加载,防止“阻塞渲染”。
✅ 示例
var script = document.createElement("script");
script.src = "chunk.js";
script.onload = resolve;
document.head.appendChild(script);
📘 解释:
createElement('script')→ 动态生成脚本;onload监听加载完成;- Webpack 内部其实是这样实现的模块懒加载。
🍡 生活类比:
当顾客点了新品“椰乳绿茶”,厨房没备料, Webpack 就是派个外卖骑手“去取那份脚本”,回来再执行 🛵。
💡 口诀记忆:
“异步加载靠 script,onload 通知执行。”
🌈 第111~115页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 111 | 项目搭建 | 选栈 → 规范 → 优化 → 部署 |
| 112 | TypeScript | 静态约束防错,泛型推导提复用 |
| 113 | 兼容性 | 旧能跑新叫前向,新能跑旧叫后向 |
| 114 | 性能优化 | 前端省流量,后端拼并发 |
| 115 | Webpack 异步加载 | 动态脚本 + onload 执行 |
💬 小可爱总结:
“我现在不仅能从 0 开店,还能写菜单、控库存、适配老顾客、优化出单、再加个异步外卖系统~ 奶茶帝国 CEO 就是我🧋👑!”
🌟 第116页:项目上线后,如何通知用户刷新当前页面?【热度:466】
💡 场景: 前端项目更新后,用户可能还在使用旧缓存(比如旧版本 JS)。 我们需要让他自动刷新或提示刷新,才能加载新版本!
✅ 方法一:版本号对比法
在项目打包时生成一个版本文件,比如 /version.json:
{
"version": "1.1.0"
}
然后在前端定时检查:
setInterval(async () => {
const res = await fetch('/version.json');
const { version } = await res.json();
if (version !== localStorage.getItem('version')) {
alert('有新版本啦,刷新加载最新内容!');
location.reload();
}
}, 60000);
📘 解释:
- 每 60 秒检查一次;
- 如果检测到服务器版本变了;
- 提醒用户刷新并 reload。
🍡 生活类比:
就像奶茶店出了新品口味(新版 JS), 但你手里菜单还旧,系统提醒你:“喂~菜单更新啦,刷新一下再点单吧!”✨
✅ 方法二:Service Worker 自动更新
在 PWA 中可以使用:
navigator.serviceWorker.register('/sw.js').then(reg => {
reg.onupdatefound = () => {
alert('检测到新版本!刷新后即可体验~');
window.location.reload();
};
});
💡 口诀记忆:
“版本号对比 → 手动提醒;Service Worker → 自动刷新。”
🧩 第117页:ESLint 代码安全的原理【热度:1111】
💡 ESLint 是用来“规范代码+避免低级错误”的工具。 像一个“奶茶店稽查员”,专门查:有没有偷懒的师傅😂。
✅ 工作原理:
- 解析代码生成 AST(抽象语法树) ;
- 按规则逐节点检查;
- 提示或修复不符合规范的地方。
module.exports = {
rules: {
'no-eval': 'error',
'eqeqeq': 'warn'
}
};
📘 解释:
no-eval: 禁止使用危险的eval;eqeqeq: 必须用===;'error'表示必须改,不改报错;'warn'只是提醒。
🍡 生活类比:
就像店长规定:
- ❌ “不准偷尝奶茶!”(no-eval)
- ⚠️ “糖分要精确控制哦!”(eqeqeq) ESLint 就是负责巡查这些行为的“奶茶巡逻官”!👮♀️
💡 口诀记忆:
“AST 看结构,规则查坏习惯。”
☕ 第118页:HTTP 是无状态协议,Web 如何保存用户登录?
💡 重点问题: HTTP 每次请求都是独立的,不记得你是谁。 要“记住你”,就得靠:Cookie、Session、Token、LocalStorage。
✅ Cookie + Session 方案
// 登录成功时
res.cookie('sessionId', 'abc123', { httpOnly: true });
- Cookie 存在浏览器;
- Session 存在服务器;
- 每次请求浏览器会自动带上 Cookie;
- 服务器用 Cookie 对应 sessionId 找到登录信息。
🍡 生活类比:
就像奶茶店发给顾客一张会员卡(Cookie), 店里保存着会员资料(Session)。 顾客下次来时出示卡号,店里一查就知道是谁啦~🎫
✅ Token + LocalStorage 方案
// 登录后返回 token
localStorage.setItem('token', data.token);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
- 无需服务器存状态;
- 前端保存 token;
- 每次请求带上它。
🍡 生活类比:
Token 就像“电子会员二维码”📱, 店里扫码即可识别你是谁,不需要查纸质卡~
💡 口诀记忆:
“Cookie-Session:店里有你档案; Token:你自己带二维码。”
⚙️ 第119页:Session、Cookie、Token 的区别总结
| 项目 | Cookie | Session | Token |
|---|---|---|---|
| 存储位置 | 浏览器 | 服务器 | 客户端 |
| 安全性 | 一般 | 较高 | 较高(JWT加密) |
| 适用场景 | 简单登录 | 服务端渲染 | 前后端分离 |
🍡 类比:
Cookie 是“会员卡”;Session 是“会员档案柜”;Token 是“扫码认证”。
💡 口诀记忆:
“Cookie 存浏览器,Session 存服务器,Token 存客户端。”
💥 第120页:JWT 登录机制(进阶)
const jwt = require('jsonwebtoken');
const token = jwt.sign({ user: '小可爱' }, 'secret', { expiresIn: '2h' });
// 验证阶段
jwt.verify(token, 'secret', (err, decoded) => {
if (err) console.log('无效token');
else console.log('欢迎回来', decoded.user);
});
📘 解释:
sign()生成带签名的加密 token;verify()验证合法性;- 无需在服务端存储,分布式更方便。
🍡 生活类比:
就像奶茶店的“数字会员码”扫码后就能识别你, 而店里不需要保存你的档案。
💡 口诀记忆:
“JWT 签名不存档,扫码即可查身份。”
🌈 第116~120页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 116 | 页面刷新 | 版本对比 + Service Worker |
| 117 | ESLint 原理 | AST 巡查坏习惯 |
| 118 | HTTP 登录保持 | Cookie + Session / Token |
| 119 | 登录机制对比 | 卡号 / 档案 / 扫码三兄弟 |
| 120 | JWT 登录 | 签名不存档,扫码查身份 |
💬 小可爱总结:
“我现在懂得让用户刷新菜单、代码自己巡查、还能分清会员卡和扫码登录~ 奶茶数字化运营我也能上岗了🧋💻!”
🌟 第121~122页:浏览器同源策略与跨域原理
💡 什么是同源?
浏览器安全规则规定: 协议 + 域名 + 端口 都相同 → 才是“同源”。
举例:
| 页面地址 | 请求地址 | 是否同源 |
|---|---|---|
| milk.com | milk.com | ✅ |
| milk.com | milk.com | ❌(协议不同) |
| milk.com | api.milk.com | ❌(域名不同) |
🍡 生活类比:
就像奶茶店“自家员工”才能进厨房。 不是同家店(不同源)的外卖员,想进来偷看秘方(访问资源)? ❌ 不允许!
💡 什么是跨域?
当你从一个源(比如前端 milk.com) 访问另一个源(比如后端 api.milk.com)时, 浏览器会拦截,提示: “跨域请求被阻止” 。
✅ 解决方案汇总(五大法宝)
1️⃣ JSONP(仅支持 GET)
<script src="http://api.milk.com/get?callback=showData"></script>
function showData(data){ console.log(data) }
利用 <script> 不受同源限制的特性。
🍡 记忆:
“偷偷塞数据进
<script>标签,让浏览器误以为是加载脚本。” (就像快递员塞小纸条进奶茶盒偷偷传话📦)
2️⃣ CORS(最常用🔥)
后端设置响应头允许跨域:
res.setHeader('Access-Control-Allow-Origin', 'https://milk.com');
📘 意思是“我允许来自 milk.com 的前端访问我”。
🍡 类比:
厨房老板贴公告:“允许指定合作奶茶店进入厨房拿原料。”
3️⃣ 代理转发(devServer Proxy)
// vite.config.js
server: {
proxy: {
'/api': {
target: 'https://api.milk.com',
changeOrigin: true
}
}
}
前端访问 /api,实际被代理转发到后端域名。
🍡 类比:
你点单给前台,前台帮你打电话给厨房~ 你自己没跨域,代理帮你完成。
4️⃣ Nginx 反向代理
location /api/ {
proxy_pass https://api.milk.com/;
}
📘 让请求在服务端转发,浏览器看不到跨域。
5️⃣ PostMessage / iframe 通信
用于前端页面之间跨域通信。
// 发送端
otherWindow.postMessage('你好', 'https://other.com');
// 接收端
window.addEventListener('message', (e)=>console.log(e.data));
🍡 类比:
两家奶茶店之间互传小纸条(postMessage)。
💡 口诀记忆:
“跨域五兄弟:JSONP、CORS、代理、Nginx、PostMessage。”
🌈 第123页:Cookie 跨域共享(浏览器安全核心题)
💡 问题: Cookie 默认只能在同源下发送。 要想跨域携带 Cookie,需要配置:
// 前端
axios.defaults.withCredentials = true;
// 后端
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Origin', 'https://milk.com');
🍡 类比:
奶茶会员卡默认只在“总部门店”能用。 如果要到“分店”通用,就得总部和分店都同意。
💡 口诀记忆:
“跨域带卡要双开:前端 withCredentials + 后端 Allow-Credentials。”
⚙️ 第124页:如何保证网页定时执行某个动作【热度:329】
✅ 普通轮询(setInterval)
setInterval(() => {
console.log('检查新订单');
}, 3000);
每 3 秒执行一次。
🍡 缺点: → 无论有没有新消息都在打扰(像外卖骑手每3秒敲门:来单了吗?😅)
✅ 优化版:长轮询(Long Polling)
function poll(){
fetch('/api/order').then(res => res.json()).then(data => {
console.log('新订单', data);
poll(); // 再次请求
});
}
poll();
🍡 特点:
一次请求挂起,直到有新数据再返回。 就像外卖员在门口“等单”,一旦有单立刻通知你!
✅ 超进阶:WebSocket(双向通信)
const ws = new WebSocket('wss://api.milk.com');
ws.onmessage = (msg)=>console.log('新订单', msg.data);
🍡 类比:
WebSocket 就像装了“对讲机📡”, 店铺和顾客实时说话,不用轮询问!
💡 口诀记忆:
“setInterval → 瞎问;Long Poll → 挂等;WebSocket → 实时聊。”
🚀 第125页:为什么 Vite 比 Webpack 快?【热度:382】
💡 面试高频! 关键点:Vite 用 ESBuild 预编译 + 按需加载。
✅ 原理对比
| 对比项 | Webpack | Vite |
|---|---|---|
| 启动方式 | 打包整个项目再启动 | 只加载用到的文件 |
| 构建工具 | Node (慢) | ESBuild (Go语言实现⚡) |
| HMR 热更新 | 全项目重新构建 | 精准模块更新 |
✅ Vite 配置示例
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()],
server: { port: 5173 }
})
🍡 生活类比:
Webpack 就像每次点单都要先把“整家奶茶原料”打包一遍才营业😵 Vite 则像是“现点现做”系统,只准备用户点到的那几杯~🧋
💡 口诀记忆:
“Vite 现点现做,Webpack 全家打包。”
🧋第121~125页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 121 | 同源策略 | 同协议+域名+端口才同源 |
| 122 | 跨域方案 | JSONP、CORS、代理、Nginx、PostMessage |
| 123 | Cookie 跨域 | 前端 withCredentials + 后端 Allow-Credentials |
| 124 | 定时机制 | 问(setInterval)、等(Long Poll)、聊(WebSocket) |
| 125 | Vite 原理 | ESBuild + 按需加载 = 现点现做的奶茶系统 |
💬 小可爱总结:
“我现在不仅能跨店传单、会员卡通用、自动报新单、 还能比别人快启动十倍——简直是奶茶界的 Vite 之神⚡🧋!”
🧋第126页:Vite的内部机制和Webpack的核心区别
💡 面试高频题:“Vite为什么比Webpack快?”
Vite 的“快”=两点:
1️⃣ 开发阶段用原生 ES Module
它不需要像 Webpack 那样先“打包整个项目”,而是“按需加载”。
// 浏览器直接加载
import { createApp } from './main.js'
🍡 生活类比:
Webpack 就像工厂先打包 100 种奶茶原料再开门卖;
Vite 则是:顾客点啥,现调哪种,不浪费时间~
2️⃣ ESBuild 编译快
Vite 用 Go 写的
esbuild编译依赖,速度是 JS 打包器的 10~100 倍。
3️⃣ 热更新(HMR)更精准
Webpack HMR 重建整个模块依赖;
Vite HMR 只替换变动的那一小段文件!
💡 口诀记忆:
“Vite 按需加热,Webpack 整锅重煮。”
⚙️第127页:列出所有浏览器下的存储方案
💡 存储=浏览器“记忆小仓库” 按大小与用途分:
| 类型 | 作用 | 容量 | 特点 |
|---|---|---|---|
| Cookie | 登录凭证 | ~4KB | 会随请求发送 |
| LocalStorage | 持久存储 | ~5MB | 永久保存 |
| SessionStorage | 临时缓存 | ~5MB | 页面关闭清空 |
| IndexedDB | 数据库 | 上百MB | 结构化存储 |
| CacheStorage | 离线缓存 | 很大 | 可离线用 |
🍡 生活类比:
Cookie:小票 LocalStorage:储物柜 SessionStorage:当天寄存 IndexedDB:仓库数据库 CacheStorage:整套冷藏柜
💡 口诀:
“Cookie 小票,Local 储物,Session 临寄,Index 仓库,Cache 冷藏。”
🚀第128页:JS 执行 100 万个任务,如何保证不卡顿?【热度:806】
💡 问题核心:浏览器是单线程,如果一次执行太多任务会卡UI。 所以要用“分片执行 / 微任务优化”的技巧。
✅ 方法1:切片执行(分批执行)
function process(list) {
if (!list.length) return
const chunk = list.splice(0, 1000)
chunk.forEach(item => console.log(item))
setTimeout(() => process(list), 0)
}
process(bigArray)
📘 原理:
- 一次只处理 1000 个任务;
- 处理完用
setTimeout让出主线程; - 下一轮再继续。
🍡 类比:
一次别煮 1000 杯奶茶,分批煮100杯一波; 每做完一批休息1秒,店不会爆炸~💨
✅ 方法2:使用 requestIdleCallback
function process(list) {
requestIdleCallback(() => {
const chunk = list.splice(0, 1000)
chunk.forEach(doSomething)
if (list.length > 0) process(list)
})
}
📘 requestIdleCallback 让浏览器在“空闲时”执行任务。
🍡 类比:
就像你在奶茶高峰期忙时不插队,等没客人时再补库存。
💡 口诀记忆:
“切片 + 空闲执行 =不卡顿。”
🧠第129页:Git 命令 commit 背后发生了什么?
💡 面试官考察你是否“懂 Git 原理”,而不仅会背命令。
✅ 当你执行:
git commit -m "update"
实际上发生了3步:
1️⃣ 生成对象(object)
Git 会把当前修改打包成一个 blob 对象(像文件快照)。
2️⃣ 写入树(tree)
表示文件结构。
3️⃣ 更新 HEAD 指针
让 HEAD 指向这个新的 commit。
🍡 类比:
每次提交就像“打包好一批奶茶订单(快照)”, 并记录到“订单历史(commit log)”中。
✅ git reset 和 git revert
| 命令 | 行为 | 类比 |
|---|---|---|
reset | 回到某个旧版本,抹掉之后的 | “撕掉后几页订单记录”📄 |
revert | 创建一个反向提交 | “补一份反单记录,不删历史”📜 |
💡 口诀记忆:
“commit 记快照,reset 撕历史,revert 补反单。”
🧩第130页:Git 提交记录是怎么储存的?【热度:160】
git log --oneline
Git 的历史本质上是:
一个有向无环图(DAG)
每个 commit 节点都指向它的“父节点”:
A -> B -> C -> D
🍡 类比:
每天奶茶店关账时,把当天账本盖章、并签上昨天那页的编号; 形成一条不可篡改的历史链📚。
💡 口诀记忆:
“Git 历史是一条盖章链。”
🌈 第126~130页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 126 | Vite 核心 | 按需加载 + esbuild 极速编译 |
| 127 | 浏览器存储 | 小票储物临寄仓库冷藏 |
| 128 | 卡顿优化 | 分片执行 + 空闲时补活 |
| 129 | Git 提交 | commit 打快照,reset 撕历史 |
| 130 | Git 原理 | 历史是一条盖章链(DAG) |
💬 小可爱总结:
“我现在不仅能写不卡页面的前端,还能解释 Git 背后的大脑结构, 就像懂原理的奶茶店长,不光能调饮,还能修机器!🧋💪”
这一组(第 1 31~135页)是“浏览器三剑客”章节: 👉 缓存机制、强缓存 vs 协商缓存、以及网页性能优化实战题。 这块是“面试必问、挂人最多”的板块⚠️ 我用“奶茶店点单 + 冰箱存货”举例,帮你一次记牢!
🧋第131页:浏览器缓存机制(核心)
💡 什么是缓存?
浏览器缓存(Cache)是浏览器在本地保存资源副本(HTML、CSS、JS、图片…), 以便下次访问时直接从本地读取,提高加载速度。
🍡 生活类比:
就像奶茶店提前备好珍珠、椰果。 有人点单时,直接取现货,不用现煮!⏱️
✅ 缓存的两大类型
| 类型 | 检查点 | 是否重新请求服务器 | 举例 |
|---|---|---|---|
| 强缓存 | 看 HTTP 响应头的过期时间(Expires / Cache-Control) | ❌ 不访问服务器 | 直接用本地存的文件 |
| 协商缓存 | 询问服务器文件是否更新(If-Modified-Since / ETag) | ✅ 可能访问 | 若没改,返回 304 |
🧩 第132页:强缓存详解(Cache-Control)
✅ 方式一:HTTP 头 Cache-Control
Cache-Control: max-age=31536000
意思是这个资源在一年内(31536000 秒)都有效。 浏览器直接用本地缓存。
🍡 类比:
就像奶茶店写着“珍珠保质期 1 年”, 一年内直接取,不用重煮!
✅ 方式二:Expires(旧方法)
Expires: Wed, 21 Oct 2025 07:28:00 GMT
是个绝对时间(容易因为时区不准被弃用), 所以推荐用 Cache-Control。
🍡 类比:
“珍珠有效期到明天早上7点”, 但时区搞错,就全坏掉啦🤣。
💡 口诀记忆:
“强缓存靠 Cache-Control,时间没过不用问。”
🧠 第133页:协商缓存详解(If-Modified-Since / ETag)
当强缓存过期,浏览器会发请求问服务器:
“这杯奶茶配方更新了吗?”
如果没更新,服务器返回:
304 Not Modified
浏览器继续用本地版本。
✅ 协商缓存两种机制
1️⃣ If-Modified-Since / Last-Modified
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
服务器比对文件修改时间决定是否返回新文件。
2️⃣ ETag / If-None-Match
ETag: "abc123"
If-None-Match: "abc123"
根据文件的唯一 hash 判断是否改变。
🍡 类比:
店长给奶茶配方打上编号(ETag)。 顾客下次点单说:“我上次喝的是编号abc123的,变了吗?” 店长一看:一样,直接拿库存的!
💡 口诀记忆:
“强缓存看时间,协商缓存比编号。”
☕ 第134页:面试高频题——如何清除浏览器缓存?
💡 有时候部署新版本,但用户一直看到旧页面,就是缓存在捣乱。
✅ 解决方法:
方式一:文件名加版本号
<script src="main.js?v=1.0.1"></script>
🍡 类比:
奶茶配方更新了?就换个“新标签”:珍珠奶茶 v2!
方式二:HTTP 头禁用缓存
Cache-Control: no-cache, no-store, must-revalidate
📘 这会告诉浏览器:
“别信旧货,必须重新拿。”
🍡 类比:
店长下令:旧珍珠全倒掉,重新煮!🔥
💡 口诀记忆:
“改名强制更新,或直接清仓重来。”
🚀 第135页:web 系统性能优化(热度:789)
💡 面试必考点——“你怎么优化网页加载速度?”
我帮你拆成 5 类优化策略👇:
✅ 一、资源层面优化
- 压缩 JS / CSS(Webpack、Vite 自动支持)
- 图片懒加载(
<img loading="lazy">) - 使用 CDN 分发静态资源
🍡 类比:
奶茶工厂把原料提前装好分店配送,就近拿货!
✅ 二、网络层优化
- HTTP/2 多路复用;
- 使用缓存策略(强缓存 + 协商缓存);
- 开启 Gzip 压缩。
🍡 类比:
外卖订单打包成一趟车送出,不一单一趟~🚚
✅ 三、渲染层优化
- 避免长任务阻塞;
- 使用虚拟列表(Virtual List);
- 懒加载组件。
🍡 类比:
一次只展示用户眼前的奶茶,不用把后厨所有奶茶都端出来🤣
✅ 四、代码层优化
- Tree-Shaking 去除无用代码;
- 按需引入(import only what you use);
- 减少 DOM 操作。
🍡 类比:
只做顾客点的那杯奶茶,不多浪费原料。
✅ 五、用户体验层优化
- 骨架屏(Skeleton);
- 预加载首屏资源;
- 提前展示 loading 状态。
🍡 类比:
顾客一进门就看到“制作中动画”,心理更舒服~😆
💡 口诀记忆:
“压缩资源、合并请求、按需加载、懒加载、加骨架。”
🌈 第131~135页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 131 | 缓存机制 | 先看本地,再问服务器 |
| 132 | 强缓存 | 看时间,直接用 |
| 133 | 协商缓存 | 比编号,看有无改 |
| 134 | 清除缓存 | 改名或禁用 |
| 135 | 性能优化 | 压缩+懒加载+骨架屏 |
💬 小可爱总结:
“我现在能解释为什么网页加载快、还能识别缓存陷阱。 就像奶茶总经理,既懂冷链仓储又懂出餐优化🧋💻!”
🌟第136页:JS 为什么要有事件循环(Event Loop)?
💡 先理解背景
JS 是单线程语言,也就是:
同一时间只能干一件事。
但网页上有:
- 计时器;
- 网络请求;
- DOM 操作;
- 用户点击…
如果都顺序执行,页面就会卡死 😵。
🍡 生活类比:
奶茶店只有一个小哥在做事(JS主线程), 顾客点单太多时,他必须学会“排队+插空做”。
✅ 于是浏览器搞出一个“事件循环系统”
流程如下:
1️⃣ 主线程执行同步代码 2️⃣ 碰到异步任务(比如定时器) → 交给“任务队列” 3️⃣ 同步代码执行完毕后 → 再从队列取出任务执行 4️⃣ 如此循环往复
📘 这就叫 Event Loop(事件循环)
🍡 类比:
就像奶茶店:
- 主线程是奶茶师;
- 异步任务是“等待泡茶的订单”;
- 泡好后再“叫号执行”;
- 不断循环接单、出单。
🍋第137页:宏任务(MacroTask) vs 微任务(MicroTask)
💡 异步任务有两种“优先级”:
| 类型 | 举例 | 执行时机 |
|---|---|---|
| 宏任务 | setTimeout、setInterval、script | 每一轮事件循环的开始 |
| 微任务 | Promise.then、queueMicrotask | 当前宏任务执行完后立即执行 |
✅ 执行顺序口诀:
“同步先执行,接着跑微任务,最后宏任务。”
🌰 举个例子(超经典题)
console.log('A')
setTimeout(() => console.log('B'))
Promise.resolve().then(() => console.log('C'))
console.log('D')
🧠 执行顺序: 1️⃣ 同步任务:A、D 2️⃣ 微任务(Promise):C 3️⃣ 宏任务(setTimeout):B
👉 输出顺序:A D C B
🍡 类比:
主线点单(同步)先做 → 顺手清洗奶茶杯(微任务) → 等泡茶机滴完后再取(宏任务)。
🧋第138页:再看 Promise + async/await 顺序题
💡 async/await 其实是“Promise 的语法糖”, 它会让“异步代码看起来像同步”,但内部还是事件循环那一套。
🌰 举例
async function test() {
console.log('1')
await Promise.resolve()
console.log('2')
}
test()
console.log('3')
执行过程: 1️⃣ 输出 1(同步) 2️⃣ 遇到 await 暂停 → 把后面的放进微任务队列 3️⃣ 输出 3(同步) 4️⃣ 微任务执行 → 输出 2
👉 结果:1 3 2
🍡 类比:
“做奶茶做到一半(await 泡茶)→ 转去接别的单 → 茶泡好后回来继续做。”
✅ Promise 嵌套题再进阶
console.log('A')
setTimeout(() => console.log('B'))
Promise.resolve().then(() => {
console.log('C')
Promise.resolve().then(() => console.log('D'))
})
console.log('E')
🧠 执行顺序:
1️⃣ 同步任务:A、E 2️⃣ 第一轮微任务:C 3️⃣ 新创建的微任务:D 4️⃣ 最后宏任务:B
👉 输出:A E C D B
🍡 口诀:
“同步完 → 当前所有微任务清空 → 再执行下一波宏任务。”
⚙️第139页:浏览器执行机制总结图(我帮你口述成脑图)
🧠 执行顺序逻辑如下:
1️⃣ 同步任务放主线程执行 2️⃣ 遇到异步任务(setTimeout / fetch)→ 放入任务队列 3️⃣ 当前主线程执行完毕 → 立刻执行微任务队列 4️⃣ 微任务清空后 → 执行下一个宏任务 5️⃣ 循环往复
📘 内存口诀:
“宏开局,微穿插,主线程不空转。”
🍡 生活总比喻:
奶茶师(主线程):
- 先把眼前的订单做完(同步);
- 顺手洗洗杯子(微任务);
- 再接下一单顾客(宏任务);
- 如此循环一整天☕。
🧠第140页:前端异步机制应用场景(实战)
💡 常考题:
“为什么在 Vue / React 里修改数据后,用 setTimeout 才能拿到更新结果?”
因为 DOM 更新放在微任务或下一轮宏任务中执行。
this.count++
console.log(this.$el.innerText) // 旧值
setTimeout(() => console.log(this.$el.innerText)) // 新值
🍡 类比:
就像你刚下奶茶订单,前台还没更新订单号。 得等一轮循环后,系统才刷新出来。
✅ 小延伸:requestAnimationFrame
它在下一帧渲染前执行,比 setTimeout 更精确:
requestAnimationFrame(() => {
console.log('页面即将刷新,执行动画')
})
🍡 类比:
就像奶茶师在出杯前最后打奶盖!
🌈 第136~140页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 136 | Event Loop 原理 | JS 像单线程排单系统 |
| 137 | 宏任务 vs 微任务 | 同步 → 微任务 → 宏任务 |
| 138 | Promise 顺序题 | await 会暂停并放入微任务队列 |
| 139 | 执行机制 | “宏开局,微穿插” 循环跑 |
| 140 | 实战应用 | DOM 更新要等下一轮循环 |
💬 小可爱总结:
“我现在知道 JS 就像奶茶店排队机: 顾客(任务)排队、泡茶机(异步)空闲插单、 还得看微任务先洗杯再上新单!🧋⚡”
🧋第141页:浏览器渲染机制(Reflow / Repaint)
💡 当浏览器渲染页面时,它分几步: 1️⃣ 解析 HTML → 生成 DOM 树 2️⃣ 解析 CSS → 生成 CSSOM 树 3️⃣ 合并为 Render Tree(渲染树) 4️⃣ 布局(Layout) 5️⃣ 绘制(Paint)
🍡 生活类比:
就像奶茶厂:
- HTML:菜单结构;
- CSS:奶茶外观样式;
- Render Tree:制作清单;
- Layout:排好奶茶杯的位置;
- Paint:真正画出每杯奶茶的样子。
✅ 重绘(Repaint)与重排(Reflow)
| 类型 | 触发条件 | 成本 | 举例 |
|---|---|---|---|
| 重绘 | 改变颜色、不影响布局 | 低 | 改变 color |
| 重排 | 改变尺寸或位置 | 高 | 改变 width / margin |
🍡 类比:
“重绘”= 换杯贴纸; “重排”= 整个奶茶货架要重摆!💦
⚠️ 优化技巧
1️⃣ 减少逐项修改样式:用 classList.add() 一次性更新。 2️⃣ 读写 DOM 分离(避免交替触发重排)。 3️⃣ 使用 requestAnimationFrame() 做动画。
🍡 口诀记忆:
“少动货架,多贴标签。”
🧠第142页:防抖(debounce)与节流(throttle)
💡 这俩是面试高频考点,核心区别:
| 名称 | 场景 | 执行规律 | 类比 |
|---|---|---|---|
| 防抖 | 输入框搜索、按钮点击 | 停止触发后再执行 | “等顾客不点了才开始做奶茶”🧋 |
| 节流 | 滚动事件、窗口 resize | 固定间隔执行 | “每隔 1 秒出一杯奶茶”⏱️ |
✅ 防抖(debounce)实现:
function debounce(fn, delay) {
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, arguments), delay)
}
}
📘 原理解释:
- 每次触发事件都清除上一次定时器;
- 只有最后一次触发后过了 delay 时间才真正执行。
🍡 类比:
顾客疯狂喊单“我要珍珠奶茶!我要椰果奶茶!” 店员:等你不喊了,我再开始做~😆
✅ 节流(throttle)实现:
function throttle(fn, delay) {
let last = 0
return function() {
const now = Date.now()
if (now - last > delay) {
fn.apply(this, arguments)
last = now
}
}
}
📘 原理解释:
- 每隔固定时间(delay)只执行一次。
🍡 类比:
无论顾客喊多快,我每隔 2 秒只做一杯奶茶。 节奏稳定,不慌乱!
💡 口诀记忆:
“防抖等停,节流限频。”
⚙️第143页:Promise、async、await 性能优化(结合防抖节流)
function requestData(type) {
return new Promise((resolve) => {
setTimeout(() => resolve(`数据类型:${type}`), 1000)
})
}
async function load() {
const result = await requestData('奶茶原料')
console.log(result)
}
load()
🍡 类比:
点单系统(Promise)负责排队, 店员(await)等待奶茶完成后才打包出单。
💡 和防抖节流结合应用:
搜索框输入时,用
debounce包裹请求函数, 这样只有用户输入停止后,才真正去请求服务器。
🧩第144页:Webpack 构建优化
Webpack 就像一个奶茶原料打包厂。 原始文件太多(HTML、JS、CSS、图片), 要打包成“体积小、加载快”的成品包。
✅ 常见优化方法
1️⃣ 代码分割(Code Splitting)
import(/* webpackChunkName: "about" */ './about.js')
将不同页面的代码分块,只加载需要的部分。
🍡 类比:
每个分店只配送本店常用原料,不全店统一一车拉。
2️⃣ Tree-Shaking
删除未使用代码。
export function use() {}
export function unused() {}
// 只引入use时,unused不会被打包。
🍡 类比:
仓库只发出点单里的原料,不浪费!
3️⃣ 懒加载(Lazy Loading)
const page = () => import('./Home.vue')
页面用到时才加载模块。
🍡 类比:
顾客点抹茶拿铁时才开抹茶罐,不提前开盖。
4️⃣ 缓存优化
output: {
filename: '[name].[contenthash].js'
}
文件名带 hash,可避免浏览器缓存老资源。
🍡 类比:
新奶茶版本要贴新日期标签,防止顾客喝到旧配方。
💡 口诀记忆:
“拆包懒加载,摇树去冗余,改名防旧货。”
🌈第145页:生产环境构建技巧
1️⃣ 压缩资源(TerserPlugin) 2️⃣ 移除 console.log 3️⃣ 开启 gzip 压缩 4️⃣ 使用 CDN 5️⃣ 分环境配置(dev / prod)
🍡 类比:
奶茶厂开分店时:
- 减少包装体积(压缩)
- 去掉废话标签(console)
- 提前送货(CDN)
- 按城市口味(环境配置)
🌈 第141~145页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 141 | 渲染机制 | 重绘换贴纸,重排搬货架 |
| 142 | 防抖节流 | 防抖等停,节流限频 |
| 143 | async/await 实战 | 等Promise泡奶茶出单 |
| 144 | Webpack 优化 | 拆包摇树懒加载 |
| 145 | 构建优化 | 压缩去冗,CDN 分发 |
💬 小可爱总结:
“我现在懂得浏览器的心跳节奏、前端的生产线优化、 奶茶机(JS引擎)该什么时候歇口气☕, 再也不怕重排、卡顿和防抖题啦~💪🧋”
🍵第146页:虚拟 DOM(Virtual DOM)
💡 是什么?
虚拟 DOM 是用 JS 对象 来描述真实 DOM 的结构。 当数据变化时,不用立即操作真实 DOM, 而是先在虚拟 DOM 里对比(Diff),最后一次性更新。
🌰 举例:
const vdom = {
tag: 'div',
props: { class: 'cup' },
children: [
{ tag: 'span', children: ['珍珠奶茶'] }
]
}
🍡 类比:
虚拟 DOM 就像“奶茶制作清单”。 不用每换一颗珍珠都重新开盖搅拌,而是 先在清单上标注变化 → 最后统一制作。
✅ 优点
1️⃣ 减少频繁 DOM 操作,提高性能。 2️⃣ 支持跨平台(Web、Native、SSR)。 3️⃣ 更易于实现 diff 对比。
💡 口诀记忆:
“先写账单后出餐,一次性上齐。”
🧠第147页:Diff 算法(核心优化逻辑)
Diff 算法的目标是:
“快速找出虚拟 DOM 新旧差异,最小代价更新真实 DOM。”
🌰 简化示例
<ul>
<li key="A">奶茶A</li>
<li key="B">奶茶B</li>
<li key="C">奶茶C</li>
</ul>
若新列表为:
<li key="B">奶茶B</li>
<li key="A">奶茶A</li>
<li key="D">奶茶D</li>
Diff 会发现:
- B 还在 → 复用;
- A 移动;
- C 删除;
- D 新增。
🍡 类比:
老板比较新旧菜单:
- 还在卖的保留(复用)
- 不卖的划掉(删除)
- 新奶茶加进去(新增)
- 顺序调整(移动)
💡 React / Vue 的优化规则: 1️⃣ 只比较同层节点。 2️⃣ 用 key 提高效率(不然可能重建整个列表)。
✅ 口诀记忆:
“同层对比,看 key 识别。”
⚙️第148页:Fiber 架构(React 16 之后的重构)
💡 Fiber 的核心思想是:
“把一次性的大更新任务切成小块,分阶段执行。”
📘 为什么需要 Fiber?
以前 React 更新 DOM 是“一口气做完”, 数据量大时就会造成卡顿 ❌。 Fiber 让它可以中断 / 恢复,保持页面流畅 ✅。
🌰 类比:
奶茶师以前做单都是从头到尾不停(一次性任务)。 现在有了 Fiber:
- 可以“分段制作”;
- 中途接待顾客(优先级高的任务);
- 再回来继续做剩下的奶茶。
✅ Fiber 的特征
1️⃣ 可中断 / 恢复。 2️⃣ 任务优先级(高优先先执行)。 3️⃣ 时间切片(分帧渲染)。
💡 口诀记忆:
“大单切小段,忙中可插单。”
🧋第149页:OAuth 2.0 授权机制(热度 210)
💡 OAuth 2.0 是一种授权协议(不是认证), 它允许“第三方应用”安全地访问用户资源。
🌰 举例:
你用「微信登录小程序」, 小程序并不会拿到你的密码, 而是通过“授权令牌 token”访问微信数据。
✅ 核心流程
1️⃣ 用户登录授权页; 2️⃣ 第三方拿到授权码(code); 3️⃣ 服务器用 code 向认证服务器换取 token; 4️⃣ 用 token 请求资源(如用户信息); 5️⃣ token 过期需刷新。
fetch('https://auth-server.com/token', {
method: 'POST',
body: {
code: 'abc123',
client_id: 'app123',
client_secret: 'xxx',
redirect_uri: 'http://myapp.com/callback'
}
})
🍡 类比:
用户扫码授权,就像给奶茶店一个「限时会员卡」。 店员拿卡刷一次 → 获取顾客偏好信息。 卡过期要重新领~
💡 口诀记忆:
“凭卡授权,不交密码。”
🧩第150页:单点登录(SSO)与常见鉴权方式
💡 单点登录 SSO 是什么?
用户在一个系统登录后,其他系统自动登录。
🌰 场景:
公司内部:
- 登录「企业门户」→ 自动登录「考勤系统」「OA」「CRM」。
🍡 类比:
员工进公司刷一次工卡, 办公室门、茶水间、打印室全都能进!
✅ 实现方式
方案一:Cookie + Session
浏览器携带 cookie 校验身份。 适合同域系统。
方案二:JWT(JSON Web Token)
const token = jwt.sign({ user: 'xiaokeai' }, 'secret', { expiresIn: '2h' })
Token 存在前端(localStorage / header), 服务端只校验签名,不存状态。
🍡 类比:
发一个加密的“工牌”,任何门禁都能识别。
✅ 常见登录鉴权方式
| 鉴权方式 | 特点 | 适用场景 |
|---|---|---|
| Session | 简单、安全高 | 同域 |
| Token(JWT) | 无状态、易扩展 | 跨域 / 微服务 |
| OAuth2 | 第三方授权 | 微信、GitHub 登录 |
| SSO | 统一登录 | 企业级内部系统 |
💡 口诀记忆:
“SSO 一次通行,JWT 无状态,OAuth 授权卡。”
🌈 第146~150页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 146 | 虚拟 DOM | 清单先记,最后出餐 |
| 147 | Diff 算法 | 同层对比,看 key 识别 |
| 148 | Fiber 架构 | 大单切小段,插单不卡顿 |
| 149 | OAuth2 | 凭授权卡,不交密码 |
| 150 | SSO & 鉴权 | 一卡通行,全站互信 |
💬 小可爱总结:
“我现在懂了!原来 React 的更新机制像流水线分段生产, 登录授权又像企业发工卡~ 以后面试官问 OAuth、Diff、Fiber、SSO,我能像倒奶茶一样顺滑讲出来🧋💪。”
🥤第151页:跨域 Cookie 与请求行为
💡 题目:
“假如在域名 A 请求域名 B 时,B 想设置 Cookie,浏览器会不会保存?”
✅ 背景知识
Cookie 有两个关键属性:
domain:控制作用域;sameSite:防止跨站攻击。
默认规则下,
A 网站发请求到 B 网站,B 网站返回的 Cookie 不会被浏览器保存。
因为这涉及安全问题(防止 CSRF 攻击)。
🌰 举例:
fetch('https://b.com/api', {
method: 'POST',
credentials: 'include'
})
服务端设置:
Set-Cookie: token=123; SameSite=None; Secure
🍡 要点:
SameSite=None允许跨域携带;Secure必须 HTTPS 才能生效;credentials: 'include'告诉浏览器:请求要带 Cookie。
🍵 类比说明:
就像奶茶加盟店(b.com)要在总部(a.com)的电脑里留会员卡(Cookie)。 总部要明确同意(include),总部网络要安全(HTTPS), 加盟店还得开口说“我允许跨店存会员卡”(SameSite=None)。
✅ 口诀记忆:
“跨域留卡要双同意,HTTPS 保安全。”
⚙️第152页:Vite 和 Webpack 区别(热度 530)
💡 核心区别总结
| 对比项 | Vite | Webpack |
|---|---|---|
| 打包原理 | 基于 ES Module(原生 import) | 基于 bundle 打包 |
| 启动速度 | 秒级(不打包直接运行) | 慢(先构建) |
| 构建工具 | esbuild(Go 语言) | webpack 自身(JS) |
| 热更新 | 局部更新 | 全量刷新 |
| 适合场景 | 开发环境快 | 生产环境稳定 |
🍡 类比:
Webpack 像传统奶茶厂: 先一次性混好所有原料 → 再开业。
Vite 像智能点单机: 点哪杯现做哪杯,效率高但需要新设备支持(现代浏览器)。
✅ 口诀记忆:
“Vite 快如闪电,Webpack 稳如老厂。”
🧋第153页:封装请求函数(含错误处理)
这部分是必考的“工程题”~ 面试官常说:“封装一个请求函数,并处理错误。”
✅ 代码拆解
export async function request(url, options = {}) {
const res = await fetch(url, { ...options })
if (!res.ok) throw new Error('网络异常')
return await res.json()
}
📘 加强版(含超时 + 拦截器):
export function request(url, options, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject('请求超时'), timeout)
)
])
}
🍡 类比:
顾客点奶茶时,如果 5 秒内没做出来(超时), 系统直接取消订单,防止顾客生气!💢
✅ 异常捕获
try {
const data = await request('/api/order')
console.log(data)
} catch (e) {
console.error('请求失败:', e)
}
🍡 类比:
就像奶茶店老板设置“5分钟内没做完就退款”的自动规则。
💡 口诀记忆:
“超时退单,错误拦截。”
🧠第154页:前端如何处理请求超时(timeout)
💡 背景: fetch 默认没有超时机制! 必须自己实现。
✅ 方法 1:Promise.race()
Promise.race([
fetch('/api'),
new Promise((_, reject) => setTimeout(() => reject('timeout'), 3000))
])
谁先完成谁赢,fetch 太慢就判超时。
✅ 方法 2:AbortController(推荐)
const controller = new AbortController()
setTimeout(() => controller.abort(), 3000)
fetch('/api', { signal: controller.signal })
.catch(err => console.error('请求被中止:', err))
🍡 类比:
老板说:“这杯奶茶三秒没出杯就取消订单。” 店员听到信号立刻丢弃制作过程。
💡 口诀记忆:
“race 抢先判,abort 手动断。”
☕第155页:axios 请求封装最佳实践
✅ 封装思想
import axios from 'axios'
const instance = axios.create({
baseURL: '/api',
timeout: 5000
})
// 请求拦截
instance.interceptors.request.use(config => {
config.headers.Authorization = localStorage.getItem('token')
return config
})
// 响应拦截
instance.interceptors.response.use(
res => res.data,
err => {
if (err.response?.status === 401) alert('请重新登录')
return Promise.reject(err)
}
)
🍡 类比:
axios 就像奶茶总部系统:
- 请求拦截器 = 点单前自动检查会员卡;
- 响应拦截器 = 出单后检查奶茶是否过期。
✅ 优点
- 统一错误处理;
- 自动附带 token;
- 可配置重试逻辑。
💡 口诀记忆:
“axios 总部控单,自动验卡报错。”
🌈 第151~155页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 151 | 跨域 Cookie | 跨域留卡要双同意 |
| 152 | Vite vs Webpack | 新厂快,老厂稳 |
| 153 | 请求封装 | 超时退单,错误拦截 |
| 154 | 超时处理 | race 抢先判,abort 手动断 |
| 155 | axios 拦截器 | 总部控单,统一验卡 |
💬 小可爱总结:
“我现在懂啦!跨域像奶茶跨店发会员卡, fetch 超时像顾客取消订单,axios 拦截像总部验卡。 原来每个机制都能和奶茶店流程对应上~甜又专业!🧋✨”
🧠第156页:Node.js 如何充分利用多核 CPU?(热度:725)
💡 背景
Node.js 是单线程的(只有一个主线程跑 JS)。 那问题来了:我的服务器有 8 核 CPU,为啥 Node 只吃一核?🤔
✅ 解决方法:Cluster 模块
Node 提供 cluster 模块,可以创建多个子进程(worker) 共享同一个端口。
import cluster from 'cluster'
import os from 'os'
import http from 'http'
if (cluster.isPrimary) {
const cpus = os.cpus().length
for (let i = 0; i < cpus; i++) cluster.fork() // 创建多个子进程
} else {
http.createServer((req, res) => {
res.end('多核并发OK')
}).listen(3000)
}
🍡 生活类比:
以前只有一个奶茶师(Node主线程)在干活。 cluster 模块让你雇了 8 个师傅(子进程)同时做奶茶! 顾客再多也不怕堵单!💪
✅ 口诀记忆:
“单线程调度,多师傅干活。”
🍵第157页:后端一次性返回海量数据,前端怎么优化?
💡 场景: 后端返回 10 万条订单数据,直接渲染会导致页面卡死!🫠
✅ 解决方案:
1️⃣ 分页加载(分页器)
👉 只请求当前页数据。 🍡 类比:奶茶工厂每次只做一批,不会一次全做。
2️⃣ 懒加载(Lazy Load)
👉 用户滚动到底部时再加载下一批。 🍡 类比:顾客一边喝一边再点下一杯。
3️⃣ 虚拟列表(Virtual List)
👉 只渲染屏幕范围内的内容。 代码核心逻辑是计算出“可视区域”的数据 slice。
const visibleData = data.slice(start, end)
🍡 类比:
货架上其实只有几杯“假展示奶茶”, 顾客往下看时,后面的奶茶才偷偷替换出来~
4️⃣ Web Worker 多线程
👉 把数据处理放到后台线程,避免主线程阻塞。 🍡 类比:让后厨去切珍珠,前台奶茶师继续接单不耽误!
✅ 口诀记忆:
“分页取数、懒加载、只渲染看得见、重活甩后台。”
⚙️第158页:为什么前端性能越来越慢?(热度:625)
💡 原因主要有四类问题👇
🧩 一、资源体积太大
- JS 包太多、图片太大、依赖臃肿。
🧋像点奶茶时,每杯都塞满布丁珍珠椰果芋圆——太重了当然慢!
🧩 二、渲染层问题
- 重排重绘频繁;
- DOM 节点太多;
- CSS 选择器复杂。
💡 比喻:奶茶店货架太挤,每次换个招牌都要全店重摆。
🧩 三、网络瓶颈
- 没做缓存;
- 未启用 gzip;
- CDN 配置不当。
🚚 像奶茶原料都要从总仓调货,路远当然慢。
🧩 四、逻辑阻塞
- 同步循环太久;
- 大计算任务阻塞主线程;
- 未使用 Web Worker。
🍡 类比:
前台收银员自己去仓库拿料,柜台全堵死。
✅ 优化方向总结: 1️⃣ 减包体积(Tree Shaking、按需加载); 2️⃣ 优化渲染(虚拟列表、防抖节流); 3️⃣ 加缓存(CDN、localStorage); 4️⃣ 多线程(Web Worker);
💡 口诀记忆:
“轻装上阵、快交互、加缓存、多线程。”
🧋第159页:页面加载速度优化(性能优化思维)
💡 面试官最爱问这题:
“你如何系统性地优化一个网站的性能?”
我们可以分三层思考:网络层、渲染层、代码层。
🌍 网络层优化
| 方向 | 技巧 |
|---|---|
| 减少请求数 | 合并资源、雪碧图、懒加载 |
| 提升传输速率 | 开启 gzip / brotli、HTTP/2 |
| 减少传输体积 | 压缩代码、删除 console |
| 使用缓存 | CDN + 本地缓存策略 |
🧠 渲染层优化
| 方向 | 技巧 |
|---|---|
| DOM 操作 | 批量处理、虚拟 DOM |
| 动画优化 | 使用 requestAnimationFrame |
| 图片优化 | 懒加载、WebP 格式 |
| 预加载 | <link rel="preload"> 提前拉资源 |
💻 代码层优化
| 方向 | 技巧 |
|---|---|
| 减少计算量 | 防抖节流 |
| 分片执行 | requestIdleCallback |
| 代码分割 | 动态 import() |
| 异步加载 | import + await |
🍡 生活类比:
像经营奶茶店提速:
- 网络层 = 原料运输快;
- 渲染层 = 摆奶茶架高效;
- 代码层 = 店员分工明确。
✅ 口诀记忆:
“网速快、渲染省、代码轻。”
☕第160页:前端日志监控与 SDK 设计思路(热度:755)
💡 日志监控 = 网站“黑匣子”。 能帮你发现:用户卡顿、接口报错、崩溃原因。
✅ 核心思路
1️⃣ 日志采集
window.onerror = (msg, url, line, col, err) => {
send({ msg, url, line, col, stack: err.stack })
}
🍡 类比:奶茶机一出错就自动上报总部。
2️⃣ 性能数据监控
使用 PerformanceObserver 监听:
- 页面加载时间;
- 首屏渲染;
- 请求耗时。
3️⃣ 用户行为埋点
document.addEventListener('click', e => {
report({ type: 'click', target: e.target.tagName })
})
🍡 类比:总部统计顾客最常点哪杯奶茶。
4️⃣ 上报机制
- 批量上报:节省带宽;
- 离线缓存:断网后补发;
- 采样策略:减少噪声。
💡 口诀记忆:
“出错即报、性能监听、点啥上报、断网补发。”
🌈 第156~160页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 156 | Node 多核 | 多师傅干活并发爽 |
| 157 | 大数据渲染 | 只渲染可视,后台切珍珠 |
| 158 | 性能变慢原因 | 轻装快交互 |
| 159 | 性能优化体系 | 三层提速法 |
| 160 | 日志监控 | 出错即报、点击埋点 |
💬 小可爱总结:
“我现在完全懂了~Node多线程就像雇多师傅; 性能优化像奶茶店升级流水线; 日志监控就像总部装摄像头📹。 一整套性能体系串起来,好喝又稳~🧋✨”
🍵第161页:前端日志埋点 SDK 设计思路
💡 SDK 是什么? 就是“工具包”或“监控插件”——比如你的网页想监控用户行为(点击、停留、报错等), 就得封装一个 SDK 自动帮你收集上报。
✅ SDK 核心流程
1️⃣ 数据采集(点了什么、在哪个页面) 2️⃣ 数据上报(通过接口发给服务器) 3️⃣ 上报策略(节流、防抖、批量、断网补发) 4️⃣ SDK 打包发布(npm、cdn)
🍡 生活类比:
奶茶总部要监控全国分店的点单情况。 SDK 就是装在每家店里的“智能收银机插件”, 自动记录顾客点单、报错、等待时间,一键上报总部。
💡 口诀记忆:
“采数据,上报送,总部洞察靠打通。”
✨ SDK 初始化示例
import { Tracker } from './tracker.js'
const tracker = new Tracker({ url: 'https://server.com/report' })
tracker.send({ event: 'click', user: 'xiaokeai' })
这里的 Tracker 就像奶茶店的“上报机器人”, url 是汇报总部的接口地址。
🧠第162页:SDK 核心逻辑代码讲解
class Tracker {
constructor(options) {
this.url = options.url
}
send(data) {
navigator.sendBeacon(this.url, JSON.stringify(data))
}
}
🔹 navigator.sendBeacon: 浏览器内置的“异步上报通道”, 即使页面关闭也能把数据发出去,不会堵主线程。
🍡 类比:
奶茶店关门时还能自动上传营业数据,不会丢失。
✅ 改进版:加缓存 + 批量上报
this.cache.push(data)
if (this.cache.length > 5) this.flush()
每次攒 5 条数据再上传,就像集中发货省成本。
💡 口诀记忆:
“批量上报更省流,Beacon 关门不掉包。”
⚙️第163页:节流防抖 + 用户行为埋点上报
💡 用户操作频繁,比如疯狂点击按钮,就可能造成上报风暴。 所以要加「节流」和「防抖」。
✅ 防抖(Debounce)
👉 等操作停止一段时间后再执行。
function debounce(fn, delay) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => fn(...args), delay)
}
}
🍡 类比:
顾客连续说“我要奶茶!我要奶茶!我要奶茶!” 店员等他安静 3 秒后才开始做。
✅ 节流(Throttle)
👉 固定间隔执行一次。
function throttle(fn, interval) {
let last = 0
return (...args) => {
const now = Date.now()
if (now - last >= interval) {
fn(...args)
last = now
}
}
}
🍡 类比:
奶茶店 10 秒才接一次订单,哪怕顾客催得飞起,也得排队。
💡 口诀记忆:
“防抖等安静,节流限频率。”
☕第164页:在 React / Vue 中埋点上报实现
🧩 React 实现
useEffect(() => {
const handleClick = () => tracker.send({ event: 'click' })
window.addEventListener('click', handleClick)
return () => window.removeEventListener('click', handleClick)
}, [])
📘 解释:
useEffect相当于页面“加载完安装监听器”,return清理监听器,防止内存泄漏。
🍡 类比:
店员上班时打开摄像头监听顾客行为,下班关掉。
🧩 Vue 实现
onMounted(() => {
window.addEventListener('click', () => tracker.send({ event: 'click' }))
})
onUnmounted(() => {
window.removeEventListener('click', ...)
})
📘 Vue 与 React 类似: 生命周期钩子里注册和注销事件。
🍡 类比:
Vue 店长上班安装摄像头,关门自动拆除。
💡 口诀记忆:
“React 用 useEffect,Vue 用 onMounted。”
🔐第165页:Token 鉴权机制(热度:942)
💡 Token 就像网站发给用户的“通行证”。 比传统 Cookie 更轻便、安全。
✅ Token 的三种类型
| 类型 | 特点 | 适用场景 |
|---|---|---|
| Access Token | 短期访问令牌 | 普通登录 |
| Refresh Token | 用于续签新 Token | 长登录 |
| ID Token | 认证用户身份 | OAuth2 / OpenID |
🌰 举例说明
登录成功后,后端返回:
{
"access_token": "abc123",
"refresh_token": "xyz456"
}
前端保存 access_token 到 header:
Authorization: Bearer abc123
当 access_token 过期,自动用 refresh_token 换新。
🍡 类比:
你办了奶茶店会员卡(access_token), 到期后拿身份证(refresh_token)续卡。
✅ Token 的验证方式
1️⃣ 签名验证(JWT) 2️⃣ Redis 校验(集中式) 3️⃣ 混合模式(登录态 + JWT)
💡 口诀记忆:
“短卡进店,长卡续期,签名验真。”
🌈 第161~165页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 161 | SDK 架构 | 智能插件帮总部监控 |
| 162 | 数据上报 | Beacon 异步 + 批量节省 |
| 163 | 防抖节流 | 防抖等停,节流限频 |
| 164 | 前端埋点 | React useEffect,Vue onMounted |
| 165 | Token 机制 | 短卡进店,长卡续期 |
💬 小可爱总结:
“我懂啦~SDK 就像奶茶总部的监控小助手, Beacon 帮忙送消息,节流防抖防过劳; React 和 Vue 负责不同店的监听, Token 就是会员卡~续期、验真都有逻辑🧋✨!”
🧋第166页:Token 与 Cookie 的区别与结合使用
💡 这是超级经典面试题:
“Token 和 Cookie 有什么区别?什么时候用?能一起用吗?”
✅ 一、两者核心区别
| 对比项 | Cookie | Token |
|---|---|---|
| 存储位置 | 浏览器内部自动管理 | localStorage / sessionStorage |
| 携带方式 | 自动随请求发送 | 需手动放在 header 里 |
| 跨域支持 | 不友好(受同源策略限制) | 可跨域 |
| 安全性 | 易被 CSRF 攻击 | 相对更安全 |
| 使用场景 | 老式登录状态保持 | 前后端分离、移动端、API 接口认证 |
🍡 生活类比:
Cookie 像“自动会员卡”——每次点奶茶自动出示; Token 像“电子二维码通行证”——你得手动出示,但更灵活,跨店也能用!
✅ 口诀记忆:
“Cookie 自动随身带,Token 手动通行跨店快。”
✅ 二、能不能一起用?
👉 可以!最佳实践是两者结合: 1️⃣ 服务端签发 Token; 2️⃣ Token 存进 Cookie; 3️⃣ 每次请求浏览器自动带上; 4️⃣ 后端验证 Token 签名合法性。
🍡 类比:
奶茶总部(后端)给顾客一个带签名的“电子会员卡”, 存进顾客手机的钱包(Cookie),每次扫码(请求)就能验证真伪。
✅ 三、安全建议
- Cookie 要加
HttpOnly(防止被 JS 读取); - Token 要加签名验证(防伪造);
- 使用 HTTPS(防窃听);
- 设置过期时间 + 刷新机制。
🍡 类比:
会员卡:要有签名、防盗刷、设有效期,还得走加密通道~
💡 口诀记忆:
“Cookie 防偷读,Token 防伪造。”
🧠第167页:前端权限控制与系统架构设计(热度:329)
💡 面试常问:
“你在项目中是怎么设计权限系统的?”
✅ 一、常见权限模型
| 模型 | 说明 | 举例 |
|---|---|---|
| RBAC(角色控制) | 用户分角色,角色授予权限 | 管理员可删帖,普通用户只能看 |
| ABAC(属性控制) | 根据属性动态判断 | 部门=技术 && 时间<18:00 才能访问 |
| PBAC(策略控制) | 按规则脚本化执行 | 拥有 tag=VIP 时开放某模块 |
🍡 类比:
奶茶总部:
- 店长(Admin)能改菜单;
- 员工(Staff)只能打奶盖;
- 顾客(User)只能下单;
- VIP 客户有“隐藏菜单权限”。
✅ 口诀记忆:
“角色定权限,属性加条件,策略最灵活。”
✅ 二、前端权限体系设计
1️⃣ 登录时获取权限数据:
const userInfo = await getUserInfo()
store.roles = userInfo.roles
2️⃣ 路由动态加载:
router.addRoute({
path: '/admin',
meta: { roles: ['admin'] }
})
3️⃣ 按钮级别权限控制:
<button v-if="roles.includes('admin')">删除</button>
🍡 类比:
前端页面就是奶茶菜单, 后台根据“职位”决定显示哪些按钮。 店长能改价,员工只能点单。
✅ 安全提示:
权限控制要后端兜底。 前端只是“展示层过滤”, 真正权限校验必须在服务器做。
🍡 类比:
你前台菜单隐藏了“改价按钮”,但只要后厨不验身份,别人照样能改价!🚨
💡 口诀记忆:
“前端限展示,后端真守门。”
⚙️第168页:系统防御策略总结(配合前端权限)
💡 这页是“系统安全合辑”:前后端都该配合的守护法。
✅ 常见防御手段
1️⃣ 登录态校验(Token + Refresh Token) 2️⃣ 接口签名(加密验证) 3️⃣ 接口限流(防暴力请求) 4️⃣ 数据脱敏(掩码显示手机号) 5️⃣ 权限兜底(后端最终审核)
🍡 生活类比:
奶茶店:
- 限制每人点单次数(限流);
- 奶茶配方做模糊展示(脱敏);
- 点单时核对会员卡签名(签名验证)。
✅ 口诀记忆:
“签名防伪造,限流防滥刷,脱敏护隐私。”
🧋第169页:低代码(Low-Code)平台架构设计(热度:399)
💡 题目:
“请你说说低代码平台的底层原理。”
✅ 一、核心思想
低代码平台 = 拖拽组件 + 动态渲染 + 元数据驱动。
✅ 二、底层原理
1️⃣ 每个页面、组件都转成 JSON 描述:
{
"type": "Button",
"props": { "text": "提交", "color": "blue" }
}
2️⃣ 前端渲染引擎解析 JSON → 生成真实界面; 3️⃣ 后端保存这份 JSON,随时可还原页面。
🍡 类比:
奶茶店总部提供“DIY 奶茶机”: 你只要拖拽杯子、添加珍珠、命名口味,系统就能自动生成奶茶配方。
✅ 三、关键模块
- 可视化拖拽编辑器;
- 组件库管理;
- 渲染引擎;
- 数据绑定;
- 权限与发布系统。
🍡 类比:
像搭乐高积木一样搭页面,每块积木都有定义、参数和权限。
✅ 口诀记忆:
“拖拽搭积木,数据控页面。”
🧠第170页:低代码平台的运行机制(继续)
💡 低代码平台怎么运行生成的页面?
✅ 一、Schema 渲染流程
1️⃣ 用户操作界面保存 JSON; 2️⃣ 渲染引擎读取 JSON; 3️⃣ 按组件库规则动态 createElement(); 4️⃣ 最终生成可交互页面。
function render(schema) {
const el = document.createElement(schema.type)
Object.assign(el, schema.props)
document.body.append(el)
}
🍡 类比:
就像总部拿到奶茶配方表(JSON),根据配方一步步还原那杯奶茶。
✅ 二、运行时绑定
- 双向绑定数据;
- 动态事件注入;
- 异步请求绑定;
- 权限动态控制。
🍡 类比:
你修改奶茶甜度 → 系统自动更新配方 → 同步所有门店配料。
💡 口诀记忆:
“Schema 定蓝图,引擎造真机。”
🌈 第166~170页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 166 | Cookie vs Token | 自动卡 vs 通行证,结合最稳 |
| 167 | 权限控制设计 | 前端限展示,后端真守门 |
| 168 | 系统防御策略 | 签名防伪造,限流防滥刷 |
| 169 | 低代码原理 | 拖拽搭积木,数据控页面 |
| 170 | Schema 渲染 | 蓝图生成真机,动态绑定活起来 |
💬 小可爱总结:
“我现在完全能想象~Cookie 是自动会员卡,Token 是跨店通行证; 权限系统像奶茶总部审批菜单; 低代码平台就是 DIY 奶茶机——配方是 JSON,渲染就是泡奶茶!🧋✨”
🧋第171页:[低代码平台] 架构设计思路(热度:263)
💡 题目问的是:
“低代码平台的整体分层架构是怎么设计的?”
✅ 一、三层架构思路(超经典!)
| 层级 | 说明 | 比喻 |
|---|---|---|
| 1️⃣ 设计层(Editor) | 拖拽页面、可视化配置 | 奶茶师在调配台上拖拖拽拽选配料 |
| 2️⃣ 运行层(Runtime) | 执行渲染逻辑、事件绑定 | 把配方转成真正的奶茶 |
| 3️⃣ 存储层(Schema/Server) | 保存 JSON、生成模板 | 把奶茶配方存在总部系统里 |
🍡 生活类比:
想象你在“智能奶茶机”上设计饮品(设计层), 点“生成”后机器实际调奶(运行层), 系统自动保存配方(存储层), 以后想喝同款,只要再调出来即可!
✅ 口诀记忆:
“设计层搞编辑,运行层管展示,存储层留配方。”
✨ 代码例子:渲染器简化版
function render(schema) {
const el = document.createElement(schema.type)
Object.assign(el, schema.props)
document.body.append(el)
}
拿到一份配方(schema)→ 根据描述(props)→ 动态生成对应元素。
🍡 就像总部拿到饮品 JSON 配方后,一键让机器调好奶茶。
🧠第172页:[Webpack] 构建性能优化思路(热度:1,163)
💡 面试常问:
“Webpack 项目太慢了,怎么提速?”
✅ 一、性能优化的八大思路:
| 分类 | 优化手段 | 类比 |
|---|---|---|
| 构建速度 | 缓存、并行、多线程 | 多人同时煮奶茶 |
| 体积优化 | 压缩、Tree Shaking | 去掉多余糖浆 |
| 资源加载 | 代码分割、懒加载 | 顾客来了再现煮 |
| 缓存策略 | hash 文件名 | 每杯奶茶贴上时间标签 |
| 打包范围 | exclude node_modules | 不重复煮原料 |
| loader 提速 | thread-loader、cache-loader | 并行打奶盖 |
| 分环境构建 | 开发 / 生产 | 打样奶茶 vs 批量出货 |
| 分包策略 | splitChunks | 不同口味独立打包 |
🍡 生活类比总结:
Webpack 就像奶茶生产流水线,
- Tree Shaking:去掉没人点的口味
- 多线程 Loader:几台奶盖机一起打
- hash 缓存:每次出货都贴上批次号
✅ 口诀记忆:
“去重、分包、多线、贴签、懒加载。”
⚙️第173页:Webpack Loader 与 Plugin 优化
Webpack 的核心是两个引擎:
Loader(加工原料) + Plugin(打包增强器)
✅ Loader 优化技巧
1️⃣ 减少匹配范围:
{
test: /.js$/,
use: 'babel-loader',
exclude: /node_modules/
}
🍡 不加工“外厂原料”(node_modules)。
2️⃣ 使用缓存:
use: ['cache-loader', 'babel-loader']
🍡 奶茶机缓存上次调好的比例,下次不用重配。
✅ Plugin 优化技巧
const TerserPlugin = require('terser-webpack-plugin')
optimization: {
minimize: true,
minimizer: [new TerserPlugin({ parallel: true })]
}
🍡 让“打包小哥”同时开多台打包机,并行压缩代码~💪
✅ 口诀记忆:
“Loader 减范围,Plugin 多线程。”
🍵第174页:Webpack 缓存与分包策略
✅ 一、缓存策略(hash)
output: {
filename: '[name].[contenthash].js'
}
当内容没变,hash 不变,浏览器可用老缓存。
🍡 类比:
奶茶贴标签
[红豆奶茶.批号.2025-10-20],没变就别重煮。
✅ 二、分包策略(SplitChunks)
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: { test: /node_modules/, name: 'vendors', chunks: 'all' }
}
}
}
将第三方库(node_modules)单独打包成 vendors.js。 避免业务代码改动导致第三方库缓存失效。
🍡 类比:
“珍珠、奶盖、红茶”分开煮,方便复用~
✅ 口诀记忆:
“缓存靠标签,分包防浪费。”
☕第175页:Webpack 构建提速实战技巧
💡 最终面试 killer 问法:
“Webpack 打包很慢,你具体会做哪些优化?”
✅ 一键提速清单
| 方向 | 手段 |
|---|---|
| 🧩 缓存优化 | cache-loader、babel 缓存 |
| ⚙️ 并行编译 | thread-loader、Terser parallel |
| 🚀 缩小范围 | exclude node_modules |
| 📦 分包提速 | splitChunks 动态拆分 |
| 🧠 按需加载 | import() 懒加载组件 |
| 🧹 精简代码 | Tree Shaking、去掉 console |
| 🧴 CDN 加速 | 第三方库外链化 |
| 🪄 模式区分 | dev = 快,prod = 优化后上线 |
🍡 生活类比:
你要开奶茶连锁工厂:
- 把常用原料单独冷藏(缓存);
- 多个奶茶师一起煮(并行);
- 不重复煮“老配方”(exclude);
- 不用时不提前泡(懒加载)。
✅ 口诀记忆:
“多线快打包,缓存防复煮。”
🌈 第171~175页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 171 | 低代码三层架构 | 设计拖拽 → 渲染执行 → 保存配方 |
| 172 | Webpack 优化总览 | 八路提速法,像奶茶厂流水线 |
| 173 | Loader & Plugin 优化 | Loader 减范围,Plugin 并行干 |
| 174 | 缓存 + 分包 | hash 贴标签,拆包防浪费 |
| 175 | 实战提速清单 | 多线程 + 懒加载 + Tree Shaking |
💬 小可爱总结:
“我懂了~Webpack 优化其实就像开奶茶工厂! Tree Shaking 是‘去掉没人点的口味’, cache-loader 是‘保留老配方’, splitChunks 是‘分锅煮珍珠奶盖’, 一条流水线多线程一开,速度嗖嗖的~🧋💨”
🍵第176页:Webpack 打包优化代码实战(上)
💡 我们先看 Webpack 的优化配置结构。 这页主要教你如何配置 plugins、loader、缓存、分包 等。
✅ 一、基础配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash].js', // 加 hash,方便浏览器缓存
clean: true, // 每次构建前清空 dist
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
],
};
📘 解释:
entry: 入口文件;output: 输出文件;filename的[contenthash]就像“奶茶批次号”,内容不变就不重做;clean: true表示“出新奶茶前先清洗机器”;HtmlWebpackPlugin自动帮你把 JS 注入 HTML 文件。
🍡 类比记忆:
每次出货前清洗奶茶机,打包好的奶茶(JS 文件)贴上批次号,顾客拿到最新口味不串味!
🧠第177页:CSS 提取与压缩
💡 打包 CSS 也能提速!我们用 MiniCssExtractPlugin + css-minimizer-webpack-plugin。
✅ CSS 提取配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' })],
};
📘 解释:
MiniCssExtractPlugin.loader: 把 CSS 单独“拉出来”;- 这样 JS 和 CSS 各走各的,互不干扰。
🍡 类比记忆:
JS 是“奶茶液体”,CSS 是“包装膜”,现在分开生产,互不拖慢。
✅ CSS 压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
optimization: {
minimizer: [new CssMinimizerPlugin()],
}
压缩体积,让奶茶杯盖更紧致 💅。
⚙️第178页:JS 压缩 + 多线程并行打包
💡 JS 文件大,编译慢?那就开“多台机器”打包!
✅ TerserPlugin 并行压缩
const TerserPlugin = require('terser-webpack-plugin');
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // 多线程压缩!
}),
],
};
🍡 类比:
以前一台奶茶机挤珍珠,现在十台一起挤,效率翻十倍 🧋💨。
✅ 二次导出
export { default as Sugar } from './Sugar.js'
export { default as Milk } from './Milk.js'
Tree Shaking 时能更精准删除没用模块。 就像只保留“奶茶机常用口味”,丢掉没人点的“香芋奶茶 2008 限定版”。
☕第179页:Preload / Prefetch 原理与应用
💡 浏览器优化加载速度的两把“神器”:
preload和prefetch。
✅ preload:立刻加载,优先级高
<link rel="preload" href="/main.js" as="script">
🍡 类比:
看到顾客走近奶茶店,立刻开始提前煮茶。
✅ prefetch:空闲时加载,优先级低
<link rel="prefetch" href="/about.js" as="script">
🍡 类比:
现在客人少,先偷偷备一批珍珠。下次有顾客来就不慌。
💡 区别口诀:
“Preload 马上做,Prefetch 有空做。”
✅ Webpack 配置中用法:
output: {
filename: '[name].[contenthash].js'
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
preload: ['main.js'],
prefetch: ['about.js']
})
]
自动帮你生成 preload/prefetch 标签。
🧋第180页:Tree Shaking 原理与配置
💡 Tree Shaking = 摇掉没用的代码, 就像“把多余的奶茶配料摇走,只留核心原料”。
✅ 基础配置
optimization: {
usedExports: true,
}
📘 意思是:标记出哪些导出被使用。
✅ 结合 ESModule 实现
export function drink() {}
export function waste() {}
import { drink } from './milk.js'
编译时发现
waste()没被引用 → 自动删除它!
🍡 类比记忆:
你菜单上 10 款奶茶,只要顾客点了 3 款,其它都不煮,省时又省料。
✅ 更高级的压缩版
const TerserPlugin = require('terser-webpack-plugin')
optimization: {
minimize: true,
minimizer: [new TerserPlugin()]
}
Tree Shaking + 压缩双管齐下,打包体积直接减半!
🍡 类比:
奶茶配方精简完再浓缩一遍,完美~🧋✨
🌈 第176~180页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 176 | 基础打包配置 | 批次号 hash,打包清洁机 |
| 177 | CSS 优化 | 分离包装膜,压缩更轻盈 |
| 178 | JS 优化 | 多机器并行挤珍珠,导出更干净 |
| 179 | 预加载优化 | preload 马上做,prefetch 空时做 |
| 180 | Tree Shaking | 摇掉没点的奶茶,精简高效 |
💬 小可爱总结:
“我懂啦~Webpack 优化就像开奶茶工厂: 多机器挤珍珠(多线程)、分锅煮奶茶(分包)、 预热备料(preload/prefetch)、 还要摇掉没人点的口味(Tree Shaking)~ 打包就能像做奶茶一样快又香甜💨🧋!”
🧋第181页:Webpack 模块热更新(HMR)原理
💡 题目问:
“Webpack 的热更新是怎么实现的?为什么它能不刷新页面就更新代码?”
✅ 一、什么是 HMR?
HMR(Hot Module Replacement)= 模块热替换。 就是在你改了前端文件后,浏览器不用整个刷新页面, 而是“只更新变动的部分模块”🔥。
🍡 生活类比:
想象你的奶茶机在运行时:
- 以前:每次换口味都要关机 + 清洗 + 重启(全刷新)。
- 现在:智能奶茶机支持 “热切换口味” ,只替换糖浆,不停机(HMR)。
✅ 二、原理机制
1️⃣ Webpack-dev-server 开启一个 WebSocket 通信; 2️⃣ 一旦文件变化,Webpack 通知浏览器:
“嘿,奶茶糖浆配方变了!” 3️⃣ 浏览器下载新模块(新的 JS chunk); 4️⃣ 替换掉旧模块,不刷新整页。
✅ 三、关键配置
devServer: {
hot: true, // 启用热更新
}
🍡 类比记忆:
hot: true= “奶茶机支持边调味边出杯!”
💬 口诀记忆:
“WebSocket 通信换模块,热更新不重启。”
☕第182页:Webpack 构建流程精讲
💡 题目问:
“Webpack 的打包流程是什么?从入口到输出经历了哪些阶段?”
✅ 一、流程概览
| 阶段 | 作用 | 类比 |
|---|---|---|
| 1️⃣ 初始化 | 读取配置文件 | 开机读取奶茶配方表 |
| 2️⃣ 编译模块 | 分析依赖(import、require) | 看哪些奶茶原料要准备 |
| 3️⃣ 模块转换 | 用 Loader 处理各种文件 | 加热、搅拌、打奶盖 |
| 4️⃣ 输出资源 | 生成 bundle 文件 | 装杯、封膜、打标签 |
✅ 二、示例代码
module.exports = {
entry: './src/index.js',
output: { filename: 'bundle.js' },
module: {
rules: [{ test: /.css$/, use: ['style-loader', 'css-loader'] }],
},
};
🍡 类比:
JS 是主料,CSS 是辅料, Loader 是奶茶机的“加工器”。
💬 口诀记忆:
“读配置 → 分依赖 → 转文件 → 出成果。”
🧠第183页:IndexedDB 原理与使用
💡 题目问:
“IndexedDB 和 LocalStorage 有什么区别?它的底层原理是什么?”
✅ 一、区别总结
| 对比项 | LocalStorage | IndexedDB |
|---|---|---|
| 存储大小 | 约 5MB | 可存几十 MB 或更多 |
| 数据类型 | 只能字符串 | 可存对象、数组、二进制 |
| 操作方式 | 同步 | 异步(基于事件) |
| 使用场景 | 简单配置 | 离线应用、缓存大数据 |
🍡 生活类比:
LocalStorage 就像奶茶店前台的“小抽屉”,放几张优惠券还行; IndexedDB 就像后厨仓库,能存珍珠、红豆、芋圆(大量数据)。
✅ 二、代码示例
let request = indexedDB.open('MilkTeaDB', 1)
request.onsuccess = e => console.log('连接成功')
request.onupgradeneeded = e => {
let db = e.target.result
db.createObjectStore('orders', { keyPath: 'id' })
}
📘 解释: 1️⃣ 打开数据库; 2️⃣ 如果不存在就创建; 3️⃣ 设置主键存结构化数据。
🍡 类比记忆:
开仓库(open),建货架(createObjectStore),放订单数据(orders)。
💬 口诀记忆:
“Local 小抽屉,Indexed 大仓库。”
⚙️第184页:浏览器的存储机制总结
💡 这页是复盘大合集,考点多、但逻辑清晰。
✅ 一、存储类型对比
| 类型 | 特点 | 场景 |
|---|---|---|
| Cookie | 小、自动携带、用于登录 | 会话保持 |
| LocalStorage | 简单 key-value,5MB | 配置缓存 |
| SessionStorage | 临时存储,关页消失 | 临时状态 |
| IndexedDB | 结构化数据 | 离线存储 |
| Cache Storage | 网络请求缓存 | PWA 离线应用 |
🍡 类比:
- Cookie = 自动会员卡
- LocalStorage = 前台抽屉
- IndexedDB = 后厨仓库
- CacheStorage = 物流仓储系统
✅ 口诀记忆:
“Cookie 自动带,Local 存配置,Indexed 放大货,Cache 缓接口。”
🧋第185页:Webpack Chunk 原理与拆分策略
💡 题目问:
“Webpack 如何划分 chunk?项目中如何控制它?”
✅ 一、什么是 Chunk?
Chunk = 一块打包产物(JS 文件)。
🍡 类比:
奶茶工厂的“装奶茶杯”:每杯就是一个 chunk。
✅ 二、如何拆分 chunk?
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: { test: /node_modules/, name: 'vendors', chunks: 'all' }
}
}
}
📘 解释:
- 第三方库(node_modules)单独拆出去;
- 避免业务逻辑改动时 vendor 缓存失效;
- 提高浏览器复用率。
🍡 类比记忆:
把“珍珠”、“奶盖”、“红茶”分开包装。顾客下次点单时,直接复用珍珠~
✅ 三、异步加载 chunk
function loadAbout() {
import('./about.js').then(module => module.default())
}
这就是“懒加载”:只有当顾客点“关于我们奶茶”时,才现煮那杯。
💬 口诀记忆:
“同步打主包,异步拆分煮。”
🎨第185页下:Canvas vs SVG
💡 面试问:
“为什么大多数图表库(ECharts 等)用 Canvas 而不是 SVG?”
✅ 一、根本区别
| 对比项 | Canvas | SVG |
|---|---|---|
| 绘制方式 | 位图(像素级) | 矢量图(节点级) |
| 性能 | 快,适合大量元素 | 慢,适合少量元素 |
| 交互性 | 需手动绑定 | 自带节点事件 |
| 放大缩小 | 模糊 | 清晰 |
🍡 类比:
- Canvas = 奶茶照片(像素图),一次画完,改动要重画;
- SVG = 奶茶线稿(矢量图),每个部分都是独立可控的。
✅ 总结口诀:
“Canvas 性能强,SVG 精细美。”
🌈 第181~185页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 181 | HMR 热更新 | 热切换口味不停机 |
| 182 | 构建流程 | 读配置→分依赖→加工→出成果 |
| 183 | IndexedDB | 小抽屉 vs 大仓库 |
| 184 | 存储体系 | Cookie 登录,Indexed 缓大货 |
| 185 | Chunk 拆分 + Canvas | 分锅煮奶茶,Canvas 快速渲染 |
💬 小可爱总结:
“我现在懂了~Webpack 热更新就像奶茶机能换口味不重启; 浏览器存储就像奶茶店的前台抽屉 + 后厨仓库; 而 ECharts 用 Canvas,是因为它出奶茶更快不掉帧!🧋💨”
🌸 第186页:Canvas vs SVG 的性能对比
💡 面试问:
“Canvas 和 SVG 有什么区别?为什么 ECharts 选择用 Canvas?”
✅ 一、性能对比
| 项目 | Canvas | SVG |
|---|---|---|
| 绘制方式 | 像素画布,一次性画好 | 每个图形是节点,可单独操作 |
| 性能 | 适合大量图形 | 适合少量、精细操作 |
| 重绘 | 改一点要重画全部 | 改谁就改谁 |
| 清晰度 | 放大会糊 | 放大仍清晰(矢量) |
🍡 类比:
- Canvas = 一张奶茶海报(像素图),改配料要重画整张。
- SVG = 奶茶插画稿(每个元素独立),换个珍珠颜色不影响其他部分。
✅ 二、为什么 ECharts 选 Canvas?
1️⃣ Canvas 性能更高,能快速绘制上千个点; 2️⃣ SVG 节点太多时会卡爆浏览器; 3️⃣ 对交互不敏感的图表(柱状图、折线图)Canvas 性价比高。
💬 口诀记忆:
“SVG 精细慢,Canvas 粗犷快。”
🧠 第187页:Vite 为什么比 Webpack 快?
💡 面试问:
“Vite 为什么构建速度比 Webpack 快这么多?”
✅ 一、核心区别:打包方式不同!
| 项目 | Webpack | Vite |
|---|---|---|
| 处理方式 | 先打包再运行 | 先运行再按需加载 |
| 启动速度 | 慢 | 快 |
| 原理 | 把所有代码打成 bundle | 利用 ESModule 动态导入 |
| 适合场景 | 大型项目,兼容性好 | 开发环境快如闪电 ⚡ |
🍡 生活类比:
- Webpack:开奶茶店前,要先煮好全部口味。
- Vite:有顾客点哪款,再现煮那一杯。
✅ 二、Vite 的技术原理
1️⃣ 开发时用原生 ESM(import/export) ; 2️⃣ 用 esbuild(Go 写的编译器) 处理依赖,比 JS 写的 webpack 快几十倍; 3️⃣ 生产构建时,仍用 Rollup 打包,保证体积小。
💬 口诀记忆:
“Webpack 先打包再跑,Vite 现点现煮超快。”
⚙️ 第188页:常用的 Webpack Plugin
💡 面试问:
“说几个你常用的 Webpack 插件(Plugin),并解释它们的作用。”
| 插件 | 作用 | 类比 |
|---|---|---|
| HtmlWebpackPlugin | 自动生成 HTML 并注入 JS | 自动帮奶茶贴标签 |
| DefinePlugin | 定义全局常量 | 定义店名、LOGO |
| MiniCssExtractPlugin | 分离 CSS 文件 | 分开做奶茶液体和杯膜 |
| TerserWebpackPlugin | 压缩 JS | 把奶茶打包成迷你杯 |
| CleanWebpackPlugin | 构建前清空输出目录 | 清洗奶茶机再出新货 |
🍡 类比记忆:
Plugin 就像奶茶工厂的“自动化工具组”: 有的清洗机器、有的贴标签、有的装杯压缩。
💬 口诀记忆:
“Html 贴标签,Define 定品牌,Terser 压缩装小杯。”
🧋 第189页:常用的 Webpack Loader
💡 Loader 是用来“加工原料”的,Plugin 是“辅助打包”的。
| Loader | 作用 | 类比 |
|---|---|---|
| babel-loader | 把 ES6 转旧版 JS | 翻译新配方让旧奶茶机也能懂 |
| css-loader | 解析 CSS 文件 | 把样式融入奶茶液体 |
| style-loader | 把 CSS 注入页面 | 把奶茶倒进杯子里 |
| file-loader | 处理图片、字体 | 打包奶茶海报图 |
| url-loader | 小文件转 base64 | 小图直接融进奶茶里 |
💬 口诀记忆:
“Babel 翻译配方,Style 注入奶茶。”
🧠 第190页(上):React 如何解决状态共享?
💡 面试问:
“多个组件要共享状态怎么办?”
✅ 一、三种方式
| 方法 | 特点 | 类比 |
|---|---|---|
| 1️⃣ 父组件传 props | 简单但层层传递 | 奶茶店老板一层层传指令 |
| 2️⃣ Context | 跨组件共享 | 店长广播通知所有员工 |
| 3️⃣ Redux / Zustand / Jotai | 独立全局状态管理 | 中控电脑直接分配任务 |
✅ 二、Context 用法:
const UserContext = createContext();
function App() {
return (
<UserContext.Provider value="小可爱">
<Header />
</UserContext.Provider>
)
}
function Header() {
const name = useContext(UserContext)
return <h1>欢迎回来,{name}~</h1>
}
🍡 类比记忆:
Provider = 店长发通知 useContext = 员工接收广播
💬 口诀记忆:
“Props 一层层传,Context 全店广播。”
💻 第190页(下):React 性能优化
💡 面试问:
“React 性能优化有哪些手段?”
✅ 一、常见优化手段
| 优化手段 | 含义 | 类比 |
|---|---|---|
| React.memo | 组件 props 不变就不重渲染 | 一杯奶茶口味没变不重做 |
| useMemo | 缓存计算结果 | 缓存珍珠煮好时间 |
| useCallback | 缓存函数引用 | 记住上次调奶茶动作 |
| 虚拟列表 | 渲染可视区域 | 一次只展示 10 杯奶茶,不全摆出来 |
| 懒加载(lazy) | 延迟加载组件 | 客人点哪杯再现做那杯 |
🍡 类比记忆:
React 优化就是“懒得重煮”:
- 不变的不做
- 重复的缓存
- 看得见的先出
💬 口诀记忆:
“记住上次煮的,别重复出杯。”
🌈 第186~190页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 186 | Canvas vs SVG | SVG 精致慢,Canvas 粗犷快 |
| 187 | Vite vs Webpack | Webpack 先打包,Vite 现点现煮 |
| 188 | 常用 Plugin | Html 贴标签,Terser 压缩装小杯 |
| 189 | 常用 Loader | Babel 翻译配方,Style 注入奶茶 |
| 190 | React 状态共享与优化 | Props 传递累,Context 广播快 |
💬 小可爱总结:
“Webpack 是‘老式奶茶厂’,Vite 是‘自动奶茶机’, Plugin 是‘工厂配件’,Loader 是‘原料加工机’, React.memo 就是‘不变不重煮’, 用这一套比喻我都能秒懂啦~🧋✨!”
🍵 第191页:Vue 中 v-if 和 v-for 的优先级问题
💡 面试问:
“为什么不推荐在同一个标签上同时使用
v-if和v-for?”
✅ 解释
Vue 解析模板时: 1️⃣ v-for 优先级比 v-if 高。 2️⃣ 所以会先循环再判断。
👉 比如:
<li v-for="item in list" v-if="item.show">
{{ item.name }}
</li>
相当于:
list.map(item => {
if (item.show) return item
})
🍡 问题: 如果你的 list 很大(比如 1000 条),那每次都得遍历 + 判断,白白浪费性能。
✅ 正确写法:
<li v-for="item in showList" :key="item.id">
{{ item.name }}
</li>
computed: {
showList() {
return this.list.filter(item => item.show)
}
}
🍡 类比记忆:
就像奶茶店有 1000 张订单,先过滤出“已付款”的再做,不要边做边查是不是付款。
💬 口诀记忆:
“先过滤后循环,不边走边判断。”
🧋 第192页:静态资源缓存的方式
💡 面试问:
“静态资源缓存有哪些方式?浏览器是怎么判断文件要不要重新加载的?”
✅ 一、缓存类型
| 类型 | 特点 | 类比 |
|---|---|---|
| 强缓存 | 直接用本地缓存,不发请求 | “昨天泡好的珍珠,今天直接用” |
| 协商缓存 | 要问一下服务器能不能用旧的 | “打电话问仓库:昨天的珍珠还能用吗?” |
✅ 二、强缓存
靠两个响应头控制:
Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2025 07:28:00 GMT
🍡 浏览器逻辑:
在 3600 秒内,不去请求服务器,直接用缓存。
✅ 三、协商缓存
靠 ETag / Last-Modified 机制:
ETag: "abc123"
If-None-Match: "abc123"
📘 过程: 1️⃣ 浏览器请求文件; 2️⃣ 服务器比对 ETag; 3️⃣ 一样则返回 304(不用传内容)。
🍡 类比记忆:
ETag 就是“珍珠批次号”。 如果批次没变,就不再做新奶茶,直接用旧的。
💬 口诀记忆:
“强缓存不问,协商缓存问一下。”
⚙️ 第193页:SPA 单页面路由实现原理
💡 面试问:
“SPA(单页应用)的路由跳转不刷新页面,原理是什么?”
✅ 一、核心思想:
SPA 其实是“前端自己控制页面切换”,浏览器并没有重新请求页面。
✅ 二、两种路由模式:
| 模式 | 原理 | URL 表现 | 类比 |
|---|---|---|---|
| hash 模式 | 监听 window.onhashchange | /home#about | 奶茶订单后面贴标签 |
| history 模式 | 通过 history.pushState | /about | 修改账单但不通知服务器 |
✅ 三、hash 模式实现:
window.addEventListener('hashchange', () => {
console.log(location.hash)
})
✅ 四、history 模式实现:
history.pushState({}, '', '/about')
window.onpopstate = () => {
console.log(location.pathname)
}
🍡 类比记忆:
hash 模式像是在奶茶杯上贴标签; history 模式是换菜单但不告诉老板。
💬 口诀记忆:
“Hash 改标签,History 改菜单。”
🧠 第194页:Axios 的原理与封装机制
💡 面试问:
“Axios 的实现原理是什么?为什么浏览器和 Node 都能用?”
✅ 一、Axios 的架构
Axios 其实是对原生请求(XMLHttpRequest / http 模块)的封装。 内部结构像“中间层工厂”,有拦截器、适配器、配置合并等模块。
✅ 二、核心执行流程
axios({
url: '/api/milk',
method: 'get'
})
执行步骤如下: 1️⃣ 合并配置; 2️⃣ 请求拦截器; 3️⃣ 适配器发请求(XHR / Node HTTP); 4️⃣ 响应拦截器; 5️⃣ 返回结果。
✅ 三、拦截器示例
axios.interceptors.request.use(config => {
console.log('准备发请求', config)
return config
})
🍡 类比记忆:
- 请求拦截器 = 点单员检查奶茶口味是否合规
- 响应拦截器 = 出杯员检查奶茶质量是否正常
💬 口诀记忆:
“发前查一遍,收后看一眼。”
🧋 第195页:前端如何预警 Web 应用的请求异常
💡 面试问:
“如果 Web 页面请求失败,你怎么监控并报警?”
✅ 一、try-catch + 上报
try {
await axios.get('/api/milk')
} catch (err) {
reportError(err)
}
用 try-catch 捕获错误,调用
reportError()上报。
✅ 二、全局捕获
window.onerror = function(message, source, line, col, error) {
reportError(error)
}
✅ 三、Promise 异常捕获
window.addEventListener('unhandledrejection', e => {
reportError(e.reason)
})
🍡 类比记忆:
奶茶机坏了?立刻报警灯闪烁,记录错误日志发给维修站。
💬 口诀记忆:
“异常不慌,全局上报。”
🌈 第191~195页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 191 | v-if vs v-for | 先过滤后循环,别边做边查 |
| 192 | 缓存机制 | 强缓存不问,协商缓存问一下 |
| 193 | SPA 路由原理 | Hash 改标签,History 改菜单 |
| 194 | Axios 原理 | 发前查一遍,收后看一眼 |
| 195 | 错误上报机制 | 奶茶机坏立刻报警灯闪 |
💬 小可爱总结:
“我现在懂啦~Vue 的
v-if要先筛单再循环, 浏览器缓存就像奶茶批次号判断要不要重煮, SPA 的路由换菜单不刷新店面, Axios 发单前后都查一遍, 奶茶机坏了还得立刻报警灯闪~🧋💨”
🌸 第196页:前端如何监控网页性能?
💡 面试问:
“前端如何检测网页加载速度、白屏时间、首屏时间、接口耗时?”
✅ 一、核心思路:性能监控要测“三个时间”
| 名称 | 含义 | 类比 |
|---|---|---|
| 白屏时间 | 页面开始渲染前的空白时间 | 顾客进店到看到第一个奶茶广告的时间 |
| 首屏时间 | 用户看到首屏主要内容的时间 | 顾客看到菜单的时间 |
| 总加载时间 | 页面全部资源加载完 | 奶茶原料全上架完的时间 |
✅ 二、如何获取这些时间?
👉 通过浏览器提供的 Performance API
window.addEventListener('load', () => {
const t = performance.timing
console.log('白屏时间:', t.responseStart - t.navigationStart)
console.log('首屏时间:', t.domContentLoadedEventEnd - t.navigationStart)
console.log('总加载时间:', t.loadEventEnd - t.navigationStart)
})
🍡 类比记忆:
responseStart= 第一批奶茶广告上架时间domContentLoaded= 菜单可看时间loadEventEnd= 所有原料齐备
💬 口诀记忆:
“白屏测首图,首屏测菜单,全载测完仓。”
🧋 第197页:如何监控前端接口耗时?
💡 面试问:
“怎么统计前端请求的响应时间和错误率?”
✅ 一、最直接的办法:封装 Axios 拦截器
axios.interceptors.request.use(config => {
config.meta = { startTime: Date.now() }
return config
})
axios.interceptors.response.use(
res => {
const duration = Date.now() - res.config.meta.startTime
console.log(`接口 ${res.config.url} 耗时: ${duration}ms`)
return res
},
err => {
console.error(`接口 ${err.config.url} 请求失败`)
return Promise.reject(err)
}
)
🍡 类比记忆:
就像奶茶点单系统:
- 记录下“开始做”的时间(startTime)
- 客人拿到奶茶后算耗时(duration)
- 失败单自动报警。
💬 口诀记忆:
“下单记时,出杯算秒。”
☕ 第198页:前端实现网页截图的方式
💡 面试问:
“如何在前端网页上实现截图功能?”
✅ 一、核心方案:html2canvas
import html2canvas from 'html2canvas'
html2canvas(document.querySelector('#app')).then(canvas => {
const img = canvas.toDataURL('image/png')
document.body.appendChild(canvas)
})
✅ 二、原理
html2canvas 会遍历 DOM,把每个元素绘制成 Canvas,再转成图片。
🍡 类比记忆:
就像给奶茶店大厅拍一张照片📸,不是“拷贝”,而是“画出来的复制品”。
💬 口诀记忆:
“DOM 画成 Canvas,Canvas 变成照片。”
✅ 应用场景:
- 导出网页快照(报表);
- 用户反馈时截图;
- 在线签名、电子表单。
📸 第199页:H5 拍照上传功能实现
💡 面试问:
“H5 页面怎么实现‘点击按钮拍照并上传’?”
✅ 一、实现思路:
HTML + JS + 设备调用 API。
✅ 二、HTML 结构
<input type="file" accept="image/*" capture="camera">
这行代码的关键在 capture="camera", 📱 它告诉浏览器:“唤起手机相机进行拍照上传”。
✅ 三、JS 处理逻辑
const input = document.querySelector('input')
input.onchange = e => {
const file = e.target.files[0]
const formData = new FormData()
formData.append('photo', file)
fetch('/upload', {
method: 'POST',
body: formData
}).then(res => res.json())
}
🍡 类比记忆:
用户点按钮 = “打开前置摄像头”
FormData= 奶茶打包机fetch('/upload')= 把奶茶上传到云厨房 ☁️
💬 口诀记忆:
“打开相机,FormData 打包,fetch 上传。”
☁️ 第200页:H5 拍照 + React 封装示例(进阶版)
这页给了 React 封装的写法,让你在项目中能直接复用👇
import React, { useRef, useState } from 'react'
export default function App() {
const inputRef = useRef(null)
const [photo, setPhoto] = useState(null)
const handleTakePhoto = () => inputRef.current.click()
const handleChange = e => {
const file = e.target.files[0]
const url = URL.createObjectURL(file)
setPhoto(url)
}
return (
<div>
<input
ref={inputRef}
type="file"
accept="image/*"
capture="camera"
onChange={handleChange}
style={{ display: 'none' }}
/>
<button onClick={handleTakePhoto}>📷 拍照上传</button>
{photo && <img src={photo} alt="预览" style={{ width: 200 }} />}
</div>
)
}
🍡 类比记忆:
useRef= 备用相机按钮;useState= 存放拍好的奶茶图;URL.createObjectURL= 快速预览本地奶茶;fetch上传后才真正“出厂”。
💬 口诀记忆:
“Ref 触发,State 存图,URL 预览。”
🌈 第196~200页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 196 | 性能监控 | 白屏测首图,首屏测菜单,全载测完仓 |
| 197 | 接口耗时统计 | 下单记时,出杯算秒 |
| 198 | 网页截图 | DOM 画成 Canvas,Canvas 变成照片 |
| 199 | 拍照上传(H5) | 打开相机,FormData 打包,fetch 上传 |
| 200 | React 拍照封装 | Ref 触发相机,State 存图预览 |
💬 小可爱总结:
“现在我懂了~ 性能监控像记录奶茶制作时长, 截图是给奶茶拍照存档📸, 拍照上传是让顾客直接上传自拍照, 一整套监控+上传闭环搞定了!⚙️🧋”
🧋第201页:React 下拉刷新功能实现
💡 面试问:
“怎么在 React 页面里实现‘下拉刷新’功能?(比如移动端网页向下拉,自动重新加载内容)”
✅ 一、用的库
npm install react-pull-to-refresh
这行命令安装了一个第三方库,名字叫 react-pull-to-refresh。 它帮我们处理复杂的“手势下拉 + 松手回弹 + 触发刷新”逻辑。
✅ 二、代码实现
import React from 'react'
import PullToRefresh from 'react-pull-to-refresh'
function App() {
const handleRefresh = () => {
return new Promise(resolve => {
setTimeout(() => {
console.log('页面刷新完成!')
resolve()
}, 2000)
})
}
return (
<PullToRefresh onRefresh={handleRefresh}>
<div>🍹 下拉刷新一下吧~</div>
</PullToRefresh>
)
}
export default App
🧠 解释(小白友好版)
🍡 当用户往下拉网页时,PullToRefresh 组件检测到这个“手势动作”, 👉 就会自动调用 handleRefresh()。
而 handleRefresh() 返回一个 Promise,
- 只有当 Promise 执行完(也就是
resolve()被调用), - 才代表“刷新结束,界面可以恢复啦~”。
💬 类比:
把网页想象成奶茶店的菜单墙🍧。 “下拉刷新”就像店长用力扯下旧菜单、贴上新菜单。 贴新菜单前要等后台准备好数据(那段
setTimeout模拟的等待时间)。
💡 口诀记忆:
“下拉→触发→等 Promise 结束 → 页面更新。”
🧠第202~204页:如何修改第三方 npm 包?
💡 面试问:
“npm 安装的第三方包不符合需求,我要改源码怎么办?” 这个问题其实考察你会不会用 monorepo + 本地包链接机制(npm link / workspace) 。
✅ 一、思路总结
我们不能直接去 node_modules 里改代码(因为一重新安装包就被覆盖)。 正确姿势是👇
💡 “拷贝出来 → 改源码 → 建本地包 → 项目用自己改的版本。”
✅ 二、详细步骤(一步步讲)
1️⃣ 安装第三方包
npm install example-package
假设你要改的包叫 example-package。
2️⃣ 新建一个项目
mkdir my-project && cd my-project
3️⃣ 拷贝原包源码
cp -r node_modules/example-package packages/example-package
把它复制到自己的 packages 文件夹中。 就像你把别人奶茶的配方抄下来,放到自己仓库。
4️⃣ 修改 package.json
{
"dependencies": {
"example-package": "file:packages/example-package"
}
}
这里的 "file:" 表示: 👉 “别去 npm 下载包,用我本地这个版本!”
5️⃣ 进入包目录开始修改
code ./packages/example-package
你可以像改自己项目一样调试这个库。
6️⃣ 重新安装依赖并测试
cd my-project
npm install
确保现在项目用的就是你本地修改后的版本。
7️⃣ 如果改好了要发布:
cd packages/example-package
npm publish
或者发布到你的私有 npm 仓库:
npm publish --registry http://your-private-registry.com
🍡 类比记忆:
- 从别人那拿到“奶茶秘方(第三方包)”
- 抄一份到自己后厨(
packages)- 改口味(修改源码)
- 不再买外面的原料(
file:指向自己)- 好喝就开分店(
npm publish)
💬 口诀记忆:
“抄到本地改口味,file 路径连自家。”
🧠 补充:pnpm / yarn 的更优做法(现代方案)
现在很多项目是 monorepo 结构,比如:
my-project/
├── packages/
│ └── example-package/
├── web/
└── package.json
可以用 workspace 管理:
{
"workspaces": ["packages/*", "web"]
}
这样修改完包,运行一次 npm install,项目自动更新依赖。
🍡 类比:
就像“总店与分店共用一个仓库”,配方更新立刻同步所有奶茶分店。
🌈 第201~204页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 201 | React 下拉刷新 | 下拉→Promise 结束→刷新完 |
| 202 | 改 npm 包原因 | 官方包不合口味 |
| 203 | 改法 | 拷贝源码→file 路径→npm install |
| 204 | 进阶方案 | 用 workspace 管理更方便 |
💬 小可爱总结:
“原来下拉刷新就是网页版的‘奶茶菜单更新系统’🍵, 而改 npm 包就像我不想用别人家的配方, 要在自己厨房改口味再卖出去~ file 指向自己,workspace 一改全同步, 学会这套我也是前端奶茶厂 CTO 啦~💼🧋!”