🧩 第1页:请求失败时弹出 toast(热度:420)
题目:请求失败时自动弹出一个 toast,比如 HTTP 状态码为 401 时提醒用户。
💡 核心知识
axios拦截器(interceptor)可以统一处理请求或响应。- 如果响应的状态码是 401、403、500 等,就可以统一提示“请求失败啦~”。
🔍 代码讲解
axios.interceptors.response.use(
res => res,
err => {
if (err.response.status === 401) {
ElMessage.error('登录失效,请重新登录!');
}
return Promise.reject(err);
}
);
🔸 思路解析:
- axios.interceptors.response.use:这是响应拦截器,所有请求的响应都会经过这里。
- 判断状态码:
err.response.status是服务器返回的状态。 - 401 表示未登录或 token 失效。
- ElMessage.error 是 Element Plus 的消息提示(类似微信的小弹窗提示)。
🍼 生活类比:
你点外卖,服务员送错单(401),客服就给你发个 toast:“您的订单异常,请重新下单~”
🔑 记忆口诀:
“拦截错误——判断状态——发消息——打断返回。”
⚙️ 第2页:如何优化 if-else 嵌套(热度:310)
问题背景: if-else 嵌套太多像“洋葱层”,不优雅。
✨ 优化方法一:提前返回(early return)
function checkUser(user) {
if (!user) return '用户不存在';
if (!user.isLogin) return '请先登录';
return '欢迎回来!';
}
🧠 理解方式:
- 先排除错误的情况,早点 return。
- 让逻辑结构更清晰,不用层层缩进。
🍵 生活例子:
去咖啡店: 没带钱包 → 拒绝服务; 没会员卡 → 拒绝优惠; 否则 → 欢迎喝拿铁 ☕。
✨ 优化方法二:使用对象映射
const statusMap = {
200: '成功',
401: '未授权',
500: '服务器错误'
};
console.log(statusMap[401]); // 未授权
🧠 理解方式: 把一堆 if/else 换成查字典一样的对象映射。
📦 类比记忆:
if-else 像在翻资料,statusMap 就像“查字典”: 不用思考逻辑,直接索引取结果。
🧰 第3页:babel-runtime 的作用(热度:200)
💡 关键点
- babel 是 JavaScript 的“翻译官”,帮我们把新语法(比如 ES6)翻译成老浏览器能懂的 ES5。
- 但直接翻译会重复注入很多辅助函数,比如
_extends、_classCallCheck等。
✨ 解决方案
import 'babel-runtime/regenerator';
import { asyncToGenerator as _asyncToGenerator } from 'babel-runtime/helpers/asyncToGenerator';
📘 babel-runtime 的作用:
- 提供公共的辅助函数,不用重复注入。
- 减少打包体积,优化性能。
- 让 async/await、class 等语法能在旧浏览器中运行。
☕ 类比理解:
以前每个咖啡师(文件)都自己磨豆(辅助函数),很累。 现在统一放在“中央厨房”(babel-runtime),共享使用,轻量又高效。
📄 第4页:如何实现导出 PDF 文件(热度:173)
思路:前端生成页面截图或表格,再转成 PDF 文件。
✨ 常见方案
-
html2canvas + jsPDF
- html2canvas:把网页内容截图成图片;
- jsPDF:把图片塞进 PDF。
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
html2canvas(document.querySelector('#app')).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF();
pdf.addImage(imgData, 'PNG', 0, 0);
pdf.save('demo.pdf');
});
📘 原理类比:
把网页拍成照片(html2canvas),贴进 PDF 相册(jsPDF)里保存。
🎯 记忆口诀:
“先截图 → 再贴图 → 再保存。”
✨ 第5页:文字上显示动态背景(热度:100)
目标: 让文字表面闪烁或渐变动感,比如“招聘中✨”。
✨ 核心代码
.text {
background: linear-gradient(90deg, red, orange, yellow, green, blue, purple);
-webkit-background-clip: text;
color: transparent;
animation: move 2s linear infinite;
}
@keyframes move {
from { background-position: 0 0; }
to { background-position: 200% 0; }
}
🧠 解释:
linear-gradient造出彩色背景;background-clip: text让颜色“贴”在文字上;animation让背景动起来!
🎨 类比:
就像霓虹灯广告牌的文字在闪光流动, 背景在动,但文字本身没变色!
🧃 记忆法:
“贴背景 → 透明字 → 背景动。”
🧩 总结记忆表
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 1 | axios 拦截器 toast | 错误拦截,发消息 |
| 2 | 优化 if-else | 提前 return,查字典 |
| 3 | babel-runtime | 共享“辅助厨房” |
| 4 | 导出 PDF | 截图 → 贴图 → 保存 |
| 5 | 动态文字背景 | 贴背景 → 透明字 → 背景动 |
🧩 第6页:文本中有动画,如何保持同步(热度:100)
题目背景: 我们想让页面上的几个文字或图标“同时动起来”,比如“加载中…” 这三个点要一个接一个闪烁。
🪄 实现方案思路
① 用 setTimeout 控制时间差
function showDots() {
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log('.'.repeat(i + 1));
}, i * 500);
}
}
🔍 解释:
setTimeout延迟执行;- 每次延迟
i * 500毫秒; - 这样 0.5 秒显示一个点;
- 最终效果就是 “.” → “..” → “...”。
🍡 生活类比:
就像珍珠奶茶店老板在加料: 第1秒放1颗珍珠,第2秒放2颗,第3秒放3颗~节奏刚刚好。
② 使用 CSS 动画实现同步
@keyframes blink {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
.dot {
animation: blink 1s infinite;
}
📘 要点:
- 通过
@keyframes让透明度循环变化; animation-delay可以让多个元素有“错峰动画”。
🍬 生活类比:
三盏霓虹灯轮流闪烁,每盏灯都延迟一点亮起,看起来像“波浪灯”🌊。
💬 第7页:如何做好前端消息提示方案(热度:672)
场景: 表单提交、网络请求、操作成功/失败时,怎么让提示既及时又不烦人?
🌈 常见提示类型
- Toast 提示(轻量、自动消失) 👉 适合“操作成功”、“保存完成”等。
- Modal 弹窗(需要确认) 👉 适合“删除确认”、“重要提醒”等。
- Notification 通知(多信息持久显示) 👉 适合“系统消息”、“下载完成”等。
💡 原则
- 统一管理提示逻辑,避免到处写
ElMessage.error(); - 给每个接口统一错误处理机制;
- 不同类型提示分层次展示(轻提示不打断操作)。
🪄 生活类比:
想象你在公司:
- Toast = 前台小姐姐小声提醒你;
- Modal = 主管叫你确认;
- Notification = 公告栏贴出通知。
🌟 记忆口诀:
“轻事 Toast,重事 Modal,长事 Notification。”
⚙️ 第8页:如何优化线上用户反馈的响应(热度:631)
问题背景: 用户反馈系统 bug,说“点按钮没反应”; 前端怎么快速收集 & 还原现场?
🚀 优化方案
① 记录用户行为日志(前端埋点)
window.addEventListener('click', e => {
console.log('用户点击了:', e.target);
});
- 把关键操作记录下来,方便复现。
- 也可以上报到后端。
② 捕获错误
window.onerror = (msg, url, line, col, err) => {
console.log('前端异常:', msg);
};
📘 捕获 JS 错误、资源加载错误等,帮助快速定位。
③ 性能监控
Performance API可统计加载时间;LCP、FID、CLS等指标衡量页面体验。
🍰 生活类比:
就像开餐厅装了监控摄像头:
- 埋点 = 记录顾客点餐动作;
- 错误捕获 = 报警器响了知道出事;
- 性能监控 = 看厨房出餐速度。
🧠 记忆口诀:
“埋点留痕,异常报警,性能测速。”
📏 第9页:px 如何转换为 rem(热度:545)
重点知识: rem 是相对于 根节点 html 字体大小 的单位。
📘 举例:
html { font-size: 16px; }
div { width: 2rem; } /* => 2 × 16 = 32px */
👉 如果 html 字体变成 20px,div 就变成 40px。
🧮 自动换算方案
① 使用 postcss-pxtorem
安装:
npm install postcss-pxtorem --save-dev
配置:
postcss.config.js
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 16,
propList: ['*']
}
}
}
⚙️ 解释:
rootValue:1rem = 16px;- 所有样式里的 px 自动转成 rem。
🍩 生活类比:
你在做奶茶杯: 小杯(1rem=16px)→ 中杯(2rem=32px)→ 大杯(3rem=48px)。 一改比例,所有杯子都自动变大!
🧠 记忆口诀:
“根节点定比例,全局自动变。”
💎 第10页:flex 实现水平垂直居中(热度:481)
这题是经典 CSS 面试题。
🧩 代码示例
<div class="box">
<div class="inner">Hi~</div>
</div>
.box {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
background: pink;
}
🔍 解释:
display: flex开启弹性布局;justify-content: center→ 横向居中;align-items: center→ 纵向居中。
💡 类比记忆:
就像你抱着一个气球 🎈, 双手同时托住它的正中间——水平和垂直都稳稳居中!
🌸 复习表格总结
| 页码 | 主题 | 关键记忆点 |
|---|---|---|
| 6 | 同步动画 | 延迟 + 动画波浪灯 |
| 7 | 消息提示 | Toast/Modal/Notification 三层提醒 |
| 8 | 用户反馈 | 埋点 + 错误捕获 + 性能监控 |
| 9 | px→rem | 根节点定比例,全局自动变 |
| 10 | flex 居中 | 两个 center,气球正中 |
🧩 第11页:浏览器有缓存,但更换为 CDN 读资源时有缓存问题(热度:579)
🌰 场景描述
你上线了新版本,但用户还在加载旧的 CSS、JS 文件(比如 style.css?v=1.0 没更新),页面样式出问题。 为什么?因为浏览器缓存了旧文件,CDN 也缓存了一份。
🚀 解决思路
1️⃣ 加版本号(Query 参数法)
<script src="https://cdn.xxx.com/app.js?v=202510"></script>
📘 每次发新版本时修改版本号,浏览器会认为是新文件。
💡 口诀记:
改个版本号,缓存全失效~🚫
2️⃣ 文件名 Hash 化(构建工具自动做)
Webpack、Vite 都能在打包时生成:
app.34f2a1b.js
文件内容变了 → hash 值也变 → 文件名不同 → 不会读旧缓存。
🧠 生活类比:
就像外卖订单号换了,骑手不会再送旧的一份🍱。
3️⃣ CDN 缓存刷新
CDN 服务器也可能缓存了旧文件,可以在后台设置:
- 缓存时间(TTL);
- 或主动清理缓存(CDN 刷新 API)。
💡 口诀记:
前端改版本号,后端清缓存,天下太平 ✨
🍪 第12页:Cookie 可设置或不随请求携带(热度:533)
Cookie 是浏览器的小本子,记录登录状态、偏好等信息。 但不是所有请求都要带上 Cookie。
🧩 设置 cookie
document.cookie = "user=小可爱; path=/; Secure; SameSite=None";
path=/→ 所有路径都能访问;Secure→ 仅 HTTPS;SameSite=None→ 跨域也可带 Cookie。
🧩 axios 默认不带 Cookie?
axios.defaults.withCredentials = true;
如果跨域请求需要 Cookie(例如登录态验证),必须开启这个配置。
💡 生活类比:
“Cookie 就像出入证”
- 默认 axios 不带出入证;
- 开启
withCredentials就是让你带上工牌进入公司大门 🚪。
⚙️ 第13页:axios 是否可以取消请求(热度:532)
有时用户点了“搜索”按钮,然后又立刻改关键词,要取消前一个请求。
🧩 用 AbortController
const controller = new AbortController();
axios.get('/api/data', { signal: controller.signal });
// 取消请求
controller.abort();
🔍 解释:
AbortController控制一个信号;- 当调用
.abort(),axios 立刻终止请求; - 前端节省带宽,用户体验更好。
💡 生活类比:
就像你点外卖后又想改菜单,一键取消前单 🍔。
✅ 记忆口诀:
“控制器发信号,请求秒取消。”
🎨 第14页:前端如何实现鼠标悬停发光效果(热度:113)
这种题爱考 CSS 动效。
.btn {
background: linear-gradient(90deg, #ff7, #f0f);
transition: all 0.3s;
}
.btn:hover {
box-shadow: 0 0 20px #f0f;
}
✨ 解释:
transition设置过渡;:hover控制悬停时的状态;box-shadow让按钮“发光”。
🪩 生活类比:
鼠标就像聚光灯 🎇 一扫到按钮上,它就闪闪发光~“快点我呀!”
🧠 第15页:DOM 更新,如何判断元素是父元素还是子元素(热度:400)
🧩 代码讲解
function isParent(parent, child) {
return parent.contains(child);
}
contains() 是原生 DOM API。 返回 true 表示 parent 包含 child。
🍎 例子
const box = document.querySelector('.box');
const span = document.querySelector('.text');
console.log(box.contains(span)); // true
💡 生活类比:
“盒子里装着苹果🍎” →
box.contains(span)= true。
🧩 第15页扩展:判断一个对象是否为空对象(热度:546)
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
🔍 解释:
Object.keys(obj)会返回所有属性名数组;- 如果长度为 0 → 没有键 → 空对象。
💡 生活类比:
像打开冰箱看有没有菜: 有键名 = 有食材; 没键名 = 空冰箱 🥶。
✨ 总结表格
| 页码 | 知识点 | 关键口诀 |
|---|---|---|
| 11 | 浏览器 + CDN 缓存问题 | 改版本号、清 CDN |
| 12 | Cookie 跨域 | 开启 withCredentials |
| 13 | axios 取消请求 | 控制器信号,秒取消 |
| 14 | 鼠标悬停发光 | hover + shadow |
| 15 | 判断父子 & 空对象 | contains / Object.keys |
🧩 第16页:JS 如何判断“空”?(热度:640)
🧠 问题背景
我们常常需要判断一个变量是不是“空的”,比如:
- 空字符串
'' - 空数组
[] - 空对象
{} null、undefined- 空的
Map/Set - 数值
0、NaN
🧩 实现方法 ① —— 最基础版
function isEmpty(value) {
if (value === null || value === undefined) return true;
if (typeof value === 'string' && value.trim() === '') return true;
if (Array.isArray(value) && value.length === 0) return true;
if (value instanceof Map || value instanceof Set) return value.size === 0;
if (typeof value === 'object' && Object.keys(value).length === 0) return true;
return false;
}
💡 解释:
null / undefined→ 直接判空。- 字符串要去掉空格再判空。
- 数组判
length。 Map/Set判size。- 对象判
Object.keys().length。
🍡 生活类比
把“空”想成一个空奶茶杯 🧋
- 杯子不在(null)
- 杯子在但没装东西([]、{})
- 里面是空气或透明液('')
- 空杯架(Map、Set) 不论哪种情况,都该说它是空的。
🧠 记忆口诀
“五看一返真”: 看 null、看字串、看数组、看集合、看对象, 没东西 → 返回 true ✅
🎨 第17页:CSS 实现翻牌动画(热度:116)
✨ 题目:实现卡片翻转的 3D 效果。
🔧 代码讲解
.card {
perspective: 1000px; /* 景深 */
}
.inner {
transition: transform 0.6s;
transform-style: preserve-3d;
}
.card:hover .inner {
transform: rotateY(180deg);
}
.front, .back {
position: absolute;
backface-visibility: hidden; /* 背面隐藏 */
}
.back {
transform: rotateY(180deg);
}
🧠 讲解思路
perspective就像相机的景深;rotateY表示围绕 Y 轴翻转;backface-visibility是“背面遮挡”;transition让动画平滑。
🍬 生活类比:
就像你翻一张“会员卡” 💳: 正面写“奶茶半价”,反面写“使用规则”。 旋转 180° 时只看到一面,另一面被挡住。
💡 口诀记忆:
“景深定距,反面隐藏,Y 轴翻转,顺滑收场。”
⚙️ 第18页:flex: 1 代表什么?(热度:400)
🧠 解释核心
flex: 1 等价于:
flex: 1 1 0;
即:
flex-grow: 1(可扩张)flex-shrink: 1(可缩小)flex-basis: 0(初始宽度为0)
💡 代码举例
<div class="box">
<div class="item">A</div>
<div class="item">B</div>
</div>
.box { display: flex; }
.item { flex: 1; }
结果:A 和 B 平均分配空间!
🍰 生活类比:
想象有一根奶茶吸管被两个人“平均咬着喝”😂 谁都不多占,平分空间。
flex: 1就是“自动均分可用空间”。
🧠 记忆口诀
“Grow 展,Shrink 缩,Basis 零起步,一比一分家。”
💾 第19页:前端如何做代码逻辑梳理(热度:191)
🧩 面试场景
面试官问:“你项目中遇到复杂逻辑时,怎么梳理清楚的?”
🧠 答题逻辑
可以这样回答 👇:
1️⃣ 拆分模块: 把复杂功能拆成小任务(如登录模块、数据处理模块、UI 模块)。
2️⃣ 画流程图: 用流程线理清“输入→处理→输出”的逻辑。
3️⃣ 用伪代码草稿:
if (登录成功) {
加载首页();
} else {
弹窗提示();
}
4️⃣ 测试验证: 写单元测试或日志来验证每一步逻辑。
🍡 生活类比:
就像做奶茶流程: “接单 → 加冰 → 加茶 → 封膜 → 交付”。 一环扣一环,搞清楚顺序,奶茶才不翻车!
💡 口诀记忆:
“拆功能,画流程,写草稿,测输出。”
🧮 第20页(延伸补充内容)
还提到一些调试思路,比如:
- 利用
console.table()查看对象; - 用
debugger断点; - 逻辑复杂时打印关键变量;
- 用 ESLint / Prettier 统一代码风格;
- 模块职责分明,一个函数只干一件事。
🌈 总结复习卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 16 | 判断空 | 五看一返真 |
| 17 | 翻牌动画 | 景深定距,反面隐藏 |
| 18 | flex:1 | 一比一分家 |
| 19 | 逻辑梳理 | 拆、画、写、测 |
| 20 | 调试与规范 | 打印、断点、分模块 |
🧩 第21页:如何清理项目里多余的代码(热度:392)
💡 问题背景
项目越做越大,常常会有“没用的代码”——比如旧功能的 JS、CSS、图片文件。 这些代码不删会让打包变慢、体积变大。
✨ JS / TS 代码的清理方法
方法一:用 ESLint + Unused Imports 检查
// ❌ 未使用的变量
const name = '小可爱';
console.log('Hi!');
运行 ESLint 会提示:
'name' is defined but never used.
✅ 删除后变:
console.log('Hi!');
💡 记忆法
“ESLint 就像保洁阿姨,会告诉你哪些文件没人用~🧹”
方法二:Tree Shaking
Webpack、Vite 默认支持:
export function used() {}
export function unused() {}
只有 used() 被引用,unused() 会在打包时被自动删掉。
🍰 生活类比:
就像做奶茶时,只放顾客点的配料,没点的就不装进去~
✨ CSS 的清理方法
用 PurgeCSS / uncss
npm install purgecss --save-dev
它会扫描 HTML、JS 文件中用到的 class 名,只保留被用到的样式。
💡 口诀记忆
“JS 用 Tree Shaking,CSS 用 PurgeCSS。”
💬 第22页:前端图片优化方案(热度:199)
🧠 图片太大会拖慢网页速度,优化方法如下:
1️⃣ 格式优化
- 用
.webp、.avif,比.png、.jpg更小; - 可用
sharp或tinypng压缩。
2️⃣ 懒加载(Lazy Load)
<img src="preview.jpg" loading="lazy" />
浏览器只在图片出现在视口(可见区域)时才加载。
🍡 生活类比:
就像点外卖时,先上奶茶、后上小食,等顾客看到再上主餐——节省资源!
3️⃣ 响应式图片(适配不同屏幕)
<img srcset="small.jpg 480w, large.jpg 1200w" sizes="(max-width: 600px) 480px, 1200px" />
浏览器会自动选最合适的图片加载。
💡 口诀记忆
“换格式、懒加载、自适应。”
⚙️ 第23页:应用如何做版本发布(热度:247)
🚀 标准前端上线流程:
1️⃣ 版本号语义化 比如 1.0.0 → 主版本.次版本.修订版本 每次改动要打 tag(如 v1.0.3)。
2️⃣ CI/CD 自动化发布 比如 GitHub Actions 或 Jenkins:
steps:
- npm install
- npm run build
- docker build .
- docker push ...
自动打包、上传、发布。
3️⃣ 灰度发布 让一部分用户先体验,没问题再全量推送。
🍰 生活类比:
像奶茶店上新饮品: 先内部员工试喝(灰度),再全国推广(全量上线)!
💡 口诀记忆
“打 Tag → 自动化 → 灰度试喝。”
🪄 第24页:跨应用通信的方案(热度:280)
多个前端应用之间要互相“对话”,常见场景是微前端(比如 Qiankun)。
🌈 方案一:postMessage
主应用和子应用通过消息事件通信:
// 主应用
window.addEventListener('message', e => console.log(e.data));
// 子应用
window.parent.postMessage('你好主应用!', '*');
💬 类比:
像主店和分店打电话:“收到订单了吗?”📞
🌈 方案二:全局状态管理(EventBus / Redux / Pinia)
把数据放在共享仓库,所有应用都能取。
🌈 方案三:URL 参数共享
例如 ?token=abc,主应用跳转时带上参数。
💡 口诀记忆
“三种传话法:发消息、共享仓、带参数。”
🧠 第25页:【高频面试】Qiankun 是如何做 JS 隔离的(热度:228)
Qiankun 是微前端框架(Ant Group 出的),它能让多个独立前端项目在同一页面共存。
✨ 问题:JS 隔离是怎么实现的?
关键技术:Proxy + 沙箱机制
const sandbox = new Proxy(window, {
set(target, key, value) {
target[key] = value;
return true;
}
});
💡 Qiankun 在每个子应用启动时创建独立的“沙箱环境”, 让它的 window、document 等变量互不干扰。
🍡 生活类比:
想象每个奶茶店加盟店都能自己装修、配料、营业; 但总部规定“你不能动别人家的配方”,这样大家互不影响。
💬 记忆口诀:
“Proxy 做隔离,沙箱防串门。”
🧁 复习终极表
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 21 | 清理冗余代码 | ESLint 检查,Tree Shaking 清理 |
| 22 | 图片优化 | 换格式、懒加载、自适应 |
| 23 | 版本发布 | 打 Tag → 自动化 → 灰度试喝 |
| 24 | 跨应用通信 | 消息、仓库、参数 |
| 25 | Qiankun 隔离 | Proxy 沙箱防串门 |
🧩 第26页:【高频】说说浏览器和 Node 如何实现 JavaScript 模块化(热度:127)
🧠 一句话总结
前端模块化是为了解决“全局变量冲突”和“依赖混乱”问题。
🚀 浏览器端模块化演变历程
1️⃣ 早期:IIFE 模式(立即执行函数)
(function() {
var a = 1;
console.log(a);
})();
💡 解释: 用函数作用域把变量“包起来”,防止全局污染。 (但模块间没法直接导入导出,很不方便)
🍰 类比:
像奶茶店每个工位都有自己的柜子(变量),互不影响,但也拿不到别人的料 😂。
2️⃣ CommonJS(Node.js 使用)
// a.js
module.exports = { name: '小可爱' };
// b.js
const data = require('./a.js');
📘 特点:
- 同步加载(读文件时阻塞)
- 适合后端(本地读文件快)
3️⃣ AMD / CMD(前端时代)
浏览器不能同步加载,所以用了 异步加载方案:
define(['moduleA'], function(A) {
console.log(A);
});
🧠 区别:
- AMD(RequireJS) → 依赖前置;
- CMD(SeaJS) → 依赖就近。
🍡 类比:
点奶茶时,AMD 先点齐所有配料再做; CMD 是做到哪拿到哪,灵活一点。
4️⃣ ES Module(现代浏览器标准)
// a.js
export const name = '小可爱';
// b.js
import { name } from './a.js';
✨ 特点:
- 静态分析(编译时就能确定依赖)
- 异步加载、不阻塞
💡 口诀记忆
“IIFE 自己包、CommonJS 后端用、AMD 异步调、ESM 最现代。”
⚛️ 第27页:[React] 为什么不推荐用 index 做 key(热度:320)
🧠 问题核心
React 通过 key 来判断哪个元素需要更新、重用或删除。
{list.map((item, index) => <li key={index}>{item}</li>)}
💥 如果数组元素变化(如删除第一个), 后面的 index 全部改变,React 会“错误复用节点”。
🍬 举例理解 假设列表是:
['奶茶', '柠檬水', '绿茶']
删除第一个 → React 认为:
index=0 的节点还在,只是内容从“奶茶”改成“柠檬水”。
💣 结果:动画错乱、输入框错位。
✅ 解决方案:
<li key={item.id}>{item.name}</li>
💡 用唯一标识 id,而不是 index。
🍡 类比:
点单编号是唯一的,不能用“排队序号”当身份证,不然前后顾客信息串了!🤣
☕ 第28页:[React] Context 的作用(热度:420)
🧠 用来“跨层级”传数据,避免多层 props 传递。
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function Button() {
const theme = useContext(ThemeContext);
return <div>当前主题:{theme}</div>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Button />
</ThemeContext.Provider>
);
}
📘 解释:
createContext创建一个共享空间;Provider提供值;- 子组件用
useContext拿到。
🍩 生活类比:
就像“公司文化墙”:总部贴出主题“dark”,所有部门员工都能看到,不需要层层通知。
💡 口诀记忆
“Provider 提供,useContext 取用。”
🧠 第29页:前端如何实现埋点?(热度:199)
💡 什么是埋点?
埋点 = 收集用户行为日志(点击、浏览、错误)。
✨ 常见实现方式
1️⃣ 手动埋点
button.addEventListener('click', () => {
sendLog({ event: 'click_button', time: Date.now() });
});
👉 缺点:写太多会麻烦。
2️⃣ 自动埋点
通过代理或 hook 捕获事件:
window.addEventListener('click', e => {
console.log('用户点击了:', e.target);
});
或在 React 中重写组件生命周期。
3️⃣ 可视化埋点
用 SDK 或图形化工具(如 GrowingIO、神策),自动识别 DOM 元素。
🍰 生活类比:
埋点就像奶茶店装摄像头: 手动埋点 = 员工手动记笔记📒; 自动埋点 = 摄像头自动录制📷。
💡 口诀记忆
“手动记笔记,自动摄像头。”
⚙️ 第30页:当页面出现卡顿时该如何排查?(热度:310)
🚨 一句话总结
前端卡顿大多是“主线程被堵死”。
🔍 常见原因
- JS 执行太久(死循环、巨量计算);
- 图片太大;
- 网络慢;
- 频繁重绘 / 回流。
⚙️ 排查思路
① Chrome Performance
用 “Performance” 面板看哪段代码占用时间最多。
② 分帧优化
使用
requestAnimationFrame()分批执行任务。
function bigTask() {
let i = 0;
function run() {
if (i++ < 1000) {
console.log(i);
requestAnimationFrame(run);
}
}
run();
}
💡 解释: 把大任务切成很多小片段,分多帧执行。
🍡 类比:
一次做 1000 杯奶茶会爆炸 🧋💥, 分 10 次做,每次 100 杯就没问题。
💡 口诀记忆
“看性能 → 分批干 → 减重绘。”
🌈 终极复习表
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 26 | JS 模块化演进 | IIFE → CommonJS → AMD → ESM |
| 27 | React key | ID 唯一,不用 index |
| 28 | Context | Provider 提供,useContext 取 |
| 29 | 埋点实现 | 手动笔记 + 自动摄像 |
| 30 | 页面卡顿排查 | 看性能 → 分帧干 |
💬 最后一句彩蛋总结:
面试官问“你怎么优化性能?” 你就笑着说:“我不会让奶茶机一次做1000杯的~我用 requestAnimationFrame 分批煮!” 🍵✨
🧩 第31页:JS 超过 Number 最大值怎么处理?(热度:366)
💡 背景知识
JS 中普通的数字是 Number 类型,遵循 IEEE 754 双精度浮点数标准。 👉 最大值是:
Number.MAX_SAFE_INTEGER // 9007199254740991
超出这个值就可能失真。
📘 示例对比
console.log(9007199254740991 + 1); // 9007199254740992
console.log(9007199254740991 + 2); // 9007199254740992 ❌ 出错
第二个结果没变!因为浮点精度溢出了,JS 已经“懵了”。
✅ 解决方案
方法 1:BigInt
const big = 9007199254740991n + 2n;
console.log(big); // 9007199254740993n
🧠 BigInt:用 n 表示超大整数,精度完美保留。
方法 2:用字符串保存再计算(库支持)
可用 bignumber.js 或 decimal.js:
import BigNumber from 'bignumber.js';
const result = new BigNumber('9007199254740991').plus(2);
console.log(result.toString());
🍵 类比:
普通 Number 就像奶茶量杯,装到顶就溢出; BigInt 是大桶装,能无限倒进去 🪣。
💡 记忆口诀
“装不下用 BigInt,精度保留不溢出。”
📱 第32页:同一代码如何在 PC 打开是 Web 应用,在手机打开是 H5 应用?(热度:330)
🧠 思路
核心是根据设备类型区分“访问端”,然后按需跳转或自适应布局。
✨ 方法一:通过 UserAgent 判断
const isMobile = /Android|iPhone/i.test(navigator.userAgent);
if (isMobile) {
window.location.href = 'https://m.xxx.com';
} else {
window.location.href = 'https://www.xxx.com';
}
📘 navigator.userAgent:浏览器自动带的身份信息。
🍡 类比:
像门卫看身份证,一眼知道你是“移动端”还是“PC 端”👀。
✨ 方法二:响应式布局(推荐)
使用媒体查询(CSS):
@media (max-width: 768px) {
body { font-size: 14px; }
}
@media (min-width: 769px) {
body { font-size: 16px; }
}
💡 一套代码自动兼容不同屏幕。
🍰 类比:
像自适应奶茶杯:大杯机器自动换模具,小杯自动缩小。
🧠 口诀记忆:
“UA 判断 → 自动跳转; CSS 媒体 → 自适应。”
⚙️ 第33页:页面缩放造成窗口抖动的原因(热度:214)
💡 原因
- 使用
vh/vw、calc()等单位时,浏览器缩放计算精度不同; - 浏览器滚动条出现或消失时宽度变化;
- CSS 动画导致频繁重绘。
✅ 解决思路
- 使用固定像素(px)替代;
- 给滚动条预留空间;
- 减少频繁动画。
🍡 类比:
页面抖动就像桌上奶茶晃来晃去,因为桌子没固定好。 加个“防滑垫”(固定宽度)就稳了~✨
🧠 口诀记忆:
“缩放精度差,滚动条扰乱,动画多会抖。”
🧠 第34页:设计一个全站访问次数统计工具(热度:180)
🚀 思路流程
1️⃣ 每当用户访问时,调用接口记录一次。 2️⃣ 后端保存访问次数(如 Redis + MySQL)。 3️⃣ 前端请求显示总访问量。
💻 示例代码
// 前端:访问时请求统计接口
fetch('/api/visit', { method: 'POST' });
// 后端伪代码
let count = 0;
app.post('/api/visit', (req, res) => {
count++;
res.send({ total: count });
});
🍡 类比:
每进店一个顾客就让门口计数器 +1,最后告诉老板今天来了多少人 🧍♀️🧍♂️。
✅ 进阶优化
- 用 Redis 存(更快);
- 加上 IP 去重;
- 用
setInterval定时同步数据库。
💡 口诀记忆:
“请求 +1,Redis 暂存,定时写库。”
🧮 第35页:大文件上传了解多少?(热度:270)
💡 为什么要分片上传?
因为浏览器对单次上传文件大小有限制,大文件直接上传容易超时或中断。
🧩 分片上传思路
1️⃣ 前端切片
function sliceFile(file, size = 5 * 1024 * 1024) {
const chunks = [];
for (let i = 0; i < file.size; i += size) {
chunks.push(file.slice(i, i + size));
}
return chunks;
}
📘 解释: 把文件分成一片片小块(5MB 一块)。
2️⃣ 上传每个分片
chunks.forEach((chunk, index) => {
uploadChunk(chunk, index);
});
3️⃣ 服务端合并
后端接收所有分片,按顺序拼成完整文件。
🍰 生活类比:
上传大文件就像寄超大包裹 📦 分多次寄(分片上传),最后仓库把所有快递拼回去(服务端合并)。
⚙️ 进阶优化
- 支持断点续传;
- 每片加唯一 hash;
- 多线程并发上传。
💡 记忆口诀:
“切片寄快递,后台拼整箱。”
🌈 复习终极卡片
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 31 | 超大数精度 | BigInt 大桶装 |
| 32 | 一码多端 | UA 判断 + 响应式布局 |
| 33 | 页面抖动 | 滚动条扰乱 + 动画频繁 |
| 34 | 访问统计 | 请求 +1,Redis 暂存 |
| 35 | 大文件上传 | 切片寄快递,后台拼整箱 |
💬 收尾彩蛋:
如果面试官问“你遇到过哪些性能瓶颈?” 你就笑着说:“我把 2GB 文件分片上传、断点续传、秒传都搞过~像寄快递一样!” 📦✨
🧩 第36页:H5 如何解决移动端滑动卡顿问题(热度:410)
💡 背景
在移动端滑动页面或列表时,常会出现“卡顿、掉帧、不跟手”的问题。 原因通常是:浏览器重绘太频繁 或 JS 阻塞了主线程。
✅ 方法一:使用 will-change 提前优化绘制
.scroll-area {
will-change: transform;
}
💡 will-change 告诉浏览器:
“这块区域马上要动,你先准备好显卡缓存。”
🍰 类比:
就像奶茶师提前备好冰块、倒好茶底,一上单就能立刻做~❄️
✅ 方法二:使用 transform 替代 top/left
/* ❌ 会触发重排 */
element.style.top = '100px';
/* ✅ 推荐 */
element.style.transform = 'translateY(100px)';
因为 transform 不会改变布局,只影响显示层,性能更高。
✅ 方法三:硬件加速
.translate {
transform: translateZ(0);
}
加 translateZ(0) 或 translate3d(0,0,0) 会让浏览器启用 GPU 渲染。
🍡 类比:
手搅奶茶太慢?上电动搅拌机⚙️(GPU)提速!
✅ 方法四:节流与防抖
滚动事件太频繁时可加节流:
let timer = null;
window.addEventListener('scroll', () => {
if (timer) return;
timer = setTimeout(() => {
console.log('触发滚动逻辑');
timer = null;
}, 100);
});
🍬 记忆口诀:
“提前缓存显卡区,transform 不重排,节流防抖别乱来。”
⚙️ 第37页:站点一键换肤的实现方式有哪些?(热度:360)
💡 目标
切换主题(如“浅色 / 深色模式”)。
✅ 方案一:CSS 变量(最现代、推荐🔥)
:root {
--bg-color: #fff;
--text-color: #000;
}
.dark-theme {
--bg-color: #000;
--text-color: #fff;
}
div {
background: var(--bg-color);
color: var(--text-color);
}
切换时只改 <html> 或 <body> 的 class:
document.body.classList.toggle('dark-theme');
🍡 类比:
奶茶店换主题色:只改“杯子颜色模具”,不用重新调奶茶配方。
💡 优点:简单、实时切换、性能好。
✅ 方案二:Less/Sass 变量编译(老方案)
通过编译阶段替换颜色值,但要重新打包。
✅ 方案三:动态加载不同 CSS 文件
link.href = theme === 'dark' ? 'dark.css' : 'light.css';
适合多主题文件分离的场景。
✅ 方案四:React/Vue 动态状态切换
在全局状态(如 Vuex、Pinia、Context)中存主题变量,再动态绑定样式。
💡 记忆口诀:
“CSS 变量最灵活,Less 编译最传统,动态文件最直接。”
🎨 第38页:懒加载实现的核心原理(热度:299)
💡 定义
懒加载 = 延迟加载图片、组件等资源,等到“快出现”时才加载,节省流量和性能。
✅ 方法一:滚动事件监听(早期方案)
window.addEventListener('scroll', () => {
const img = document.querySelector('img');
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight) {
img.src = img.dataset.src;
}
});
📘 解释:
getBoundingClientRect()获取元素距离视口的距离;- 只有当图片快出现时,才赋值真实
src。
🍩 类比:
你滑到菜单才开始做奶茶,没看到菜单的就不浪费材料 💡。
✅ 方法二:IntersectionObserver(现代推荐)
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.src = e.target.dataset.src;
observer.unobserve(e.target);
}
});
});
observer.observe(document.querySelector('img'));
💡 浏览器原生提供接口,性能更高、代码更少!
🧠 记忆口诀:
“滚动监听早期法,IO 观察最优雅。”
📡 第39页:事件循环(Event Loop)与微任务宏任务(热度:470)
🧠 JS 执行机制
JS 是单线程运行的,任务分为两类:
- 宏任务(Macro Task) :
setTimeout、setInterval、I/O; - 微任务(Micro Task) :
Promise.then()、queueMicrotask。
✅ 代码演示
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
📘 输出顺序:
1
4
3
2
💡 原理:
- 主线程执行同步任务;
- 同步结束后执行“微任务队列”;
- 最后执行“宏任务队列”。
🍡 类比:
“先做现在的单(同步任务), 再处理便签备忘录(微任务), 最后处理新来的外卖订单(宏任务)。”
🧠 记忆口诀:
“同步先干完,微任立刻办,宏任排队慢。”
🧱 第40页:Promise 实现原理(热度:520)
💡 手写一个最小可用 Promise
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.callbacks = [];
const resolve = value => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.value = value;
this.callbacks.forEach(cb => cb(value));
};
executor(resolve);
}
then(onFulfilled) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
} else {
this.callbacks.push(onFulfilled);
}
}
}
📘 解释:
state表示状态(pending → fulfilled);resolve()改变状态;then()注册回调。
🍡 类比:
你点奶茶时,Promise 是“预订单”: 还没做好(pending) → 做好了(fulfilled) → 通知你来取(then 回调)。
🧠 记忆口诀:
“状态三变,回调先存,成功后触发。”
🌈 总结速记表
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 36 | 移动端流畅滚动 | 提前 GPU、transform 不重排 |
| 37 | 一键换肤 | CSS 变量最灵活 |
| 38 | 懒加载 | IO 观察最优雅 |
| 39 | 事件循环 | 同步先、微快、宏慢 |
| 40 | Promise 原理 | 状态三变,回调触发 |
💬 总结彩蛋:
面试官问“说说你了解的事件循环和异步机制?” 你就微笑说: “先上同步单,再处理微任务‘小票’,最后再接下一单外卖(宏任务)~”🍵😎
🧩 第41页:Less 变量作用域(热度:210)
💡 基础知识
Less 是一种 CSS 预处理器,可以使用变量、嵌套、函数等。
📘 示例
@color: red;
.box {
@color: blue;
color: @color; // blue
}
p {
color: @color; // red
}
🧠 解释:
- Less 的变量是 块级作用域;
- 在
.box里声明的@color只影响.box; - 外层
p仍然用外面的红色。
🍡 类比:
就像奶茶店里:
- 店长菜单写“默认口味:红豆奶茶🍓”;
- 但分店经理临时说“这杯做蓝莓味🫐”; 只在那杯里生效,其他杯不受影响!
💡 口诀记忆:
“块内变量只生效,外层依旧原味道。”
⚙️ 第42页:如何实现网页加载进度条(热度:270)
💡 实现原理
常见库是 NProgress,但也可以自己写。
✨ 方法一:用 NProgress(最简单)
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
router.beforeEach(() => NProgress.start());
router.afterEach(() => NProgress.done());
📘 解释:
start():进入页面时开始加载;done():页面加载完后关闭进度条。
✨ 方法二:原生实现(自定义)
let progress = 0;
const bar = document.querySelector('#progress');
function loadProgress() {
if (progress < 100) {
progress += 1;
bar.style.width = progress + '%';
requestAnimationFrame(loadProgress);
}
}
loadProgress();
🧠 解释: 用 requestAnimationFrame 让动画每帧更新,平滑不卡顿。
🍰 类比:
像奶茶制作台前的“订单进度条”: 你看到从 0% → 100%,奶茶终于做好咯!🧋
💡 口诀记忆:
“NProgress 快速挂,原生递增加动画。”
🖼️ 第43页:常见图片懒加载方式(热度:1001🔥)
💡 背景
图片太多会拖慢网页加载,懒加载只加载“快要出现在屏幕的图片”。
✅ 方法一:IntersectionObserver
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.src = e.target.dataset.src;
observer.unobserve(e.target);
}
});
});
document.querySelectorAll('img').forEach(img => observer.observe(img));
🧠 解释:
- 当图片进入视口(用户能看到)时,才真正加载;
dataset.src存储真实图片路径。
🍡 生活类比:
就像奶茶店的自动感应灯: 顾客走近(进入视口)→ 灯才亮(加载图片)💡。
✅ 方法二:滚动监听(早期)
window.addEventListener('scroll', () => {
const imgs = document.querySelectorAll('img');
imgs.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight) {
img.src = img.dataset.src;
}
});
});
💡 现在推荐用 IntersectionObserver,因为性能更好。
💡 口诀记忆:
“旧法滚动听,新法 IO 看。”
🍪 第44页:Cookie 构成部分与管理(热度:598)
🧠 Cookie 组成
name=value; expires=时间; path=/; domain=xxx.com; secure; HttpOnly
📘 各部分含义:
name=value:键值对;expires:过期时间;path:可访问路径;domain:作用域;secure:只在 HTTPS 下传输;HttpOnly:JS 不能访问,防 XSS 攻击。
✨ 设置 Cookie
document.cookie = "user=小可爱; path=/; max-age=3600";
🧠 解释:
max-age=3600表示 1 小时后过期;- Cookie 会自动随请求发送给同域服务器。
✨ 删除 Cookie
document.cookie = "user=; max-age=0";
🍰 类比:
Cookie 就像奶茶店的会员卡 🍵:
- 登录后发一张卡(set);
- 每次进店出示(发送);
- 过期或注销就剪卡(delete)。
💡 口诀记忆:
“发卡存身份,Secure 防偷听,HttpOnly 防偷窥。”
🌈 第45页总结表
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 41 | Less作用域 | 块内有效,外层不扰 |
| 42 | 网页进度条 | NProgress 快,原生帧动滑 |
| 43 | 图片懒加载 | 滚动听,新法看 |
| 44 | Cookie管理 | 发卡存身、防听防窥 |
| 45 | 浏览器优化 | 提前缓存、按需加载 |
💬 小彩蛋总结:
面试官问:“Cookie 为什么要加 HttpOnly?” 你就眨眨眼说: “就像我的奶茶会员卡,只能柜台系统刷,别人偷不走~😉”
🧩 第46页:浏览器缓存实现方式(热度:734)
💡 背景
浏览器缓存是前端性能优化的基础,它能让资源加载更快、节省带宽。 主要分两类:
🧠 一、强缓存(不发请求)
靠 响应头控制:
Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2025 07:28:00 GMT
📘 含义:
max-age=3600表示缓存 3600 秒;- 在这期间再次访问同一资源,浏览器直接读本地缓存,不发请求。
🍡 类比:
就像奶茶店老板说:“我这杯奶茶1小时内不用重新泡。” 顾客再次来,只拿“放在保温柜里的那杯”,快又省!
🧠 二、协商缓存(需验证)
当缓存过期,浏览器会发请求问服务器:“这资源变了吗?”
关键字段👇:
If-None-Match: "etag123"
If-Modified-Since: Wed, 21 Oct 2025 07:00:00 GMT
服务器返回:
304 Not Modified
意思是:“没变,用你本地的吧。”
🍰 类比:
顾客问老板:“这杯奶茶新换了吗?” 老板说:“没变!喝你冰箱那杯就行~”😆
💡 口诀记忆:
“强缓存不请求,协商要确认。”
⚙️ 第47页:DNS 解析了解多少(热度:712)
🧠 DNS 是什么?
DNS(Domain Name System)相当于“网络电话簿”。 它把网址(www.xxx.com)翻译成 IP 地址(1``92.168.x.x)。
✨ 解析流程
1️⃣ 浏览器缓存 → 2️⃣ 系统缓存(操作系统)→ 3️⃣ 本地 hosts 文件 → 4️⃣ DNS 服务器递归查询。
📘 举例说明:
访问 www.taobao.com:
- 先问浏览器:有缓存吗?
- 没有 → 问操作系统;
- 再没有 → 向本地 DNS(比如 8.8.8.8)查询;
- 本地 DNS 递归向根域名服务器、顶级域名服务器查询;
- 最后返回 IP 给浏览器缓存下来。
🍡 类比:
你要找“奶茶小可爱店”的地址: 先问自己记不记得(浏览器缓存)→ 问同事(系统缓存)→ 查电话簿(DNS)。
💡 口诀记忆:
“浏览器 → 系统 → 本地DNS → 根域名 → 目标。”
🧠 第48页:CDN 加速原理(热度:1789)
💡 CDN 是什么?
CDN(Content Delivery Network)= 内容分发网络 👉 把服务器资源(图片、视频、文件)放到离用户更近的节点。
✨ 工作流程
1️⃣ 用户访问网站; 2️⃣ DNS 把请求指向最近的 CDN 节点; 3️⃣ 节点缓存资源; 4️⃣ 下次访问,直接从该节点返回。
🧩 优点
- 加快访问速度;
- 降低源站压力;
- 提升稳定性(冗余备份) 。
🍰 生活类比:
你点奶茶时,系统会指派“离你最近的分店”接单, 这样奶茶送得快、店员也不累!🧋🚴♂️
💡 口诀记忆:
“就近分配,缓存复用。”
🧮 第49页:前端本地存储方案(热度:641)
🧠 常见三种
| 类型 | 容量 | 过期 | 特点 |
|---|---|---|---|
| localStorage | ~5MB | 永不过期 | 长期保存用户偏好 |
| sessionStorage | ~5MB | 关闭浏览器即失效 | 会话级 |
| cookie | 4KB | 可设定时间 | 会自动随请求发送 |
📘 示例
localStorage.setItem('theme', 'dark');
sessionStorage.setItem('tab', 'home');
document.cookie = "user=小可爱; max-age=3600";
🍡 类比:
- localStorage:会员卡(长期有效)
- sessionStorage:临时票(出门作废)
- cookie:门禁卡(每次自动打卡)
💡 口诀记忆:
“Local 长久留,Session 会话走,Cookie 自动带。”
⚙️ 第50页:什么是浏览器渲染(热度:1092)
🧠 渲染流程(重点!)
1️⃣ 解析 HTML → 生成 DOM 树 2️⃣ 解析 CSS → 生成 CSSOM 树 3️⃣ 合并 成 Render Tree(渲染树) 4️⃣ 布局(Layout) → 计算位置和大小 5️⃣ 绘制(Paint) → 把像素画出来 6️⃣ 合成(Composite) → GPU 合成最终画面
📘 示例:
<div style="color:red;">Hello</div>
👉 渲染顺序: HTML → DOM → CSSOM → 渲染树 → 绘制红色文字。
🍰 生活类比:
就像做奶茶的整个流程:
- 读菜单(HTML)
- 按配方准备材料(CSS)
- 组装出奶茶(Render Tree)
- 摆在杯台上(绘制)
- 最后封膜交付顾客(合成)✨
💡 口诀记忆:
“HTML+CSS 先合成,布局绘制出真容。”
🌈 复习总结卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 46 | 缓存机制 | 强缓存不请求,协商要确认 |
| 47 | DNS 解析 | 浏览器→系统→DNS→根→目标 |
| 48 | CDN 加速 | 就近分配,缓存复用 |
| 49 | 本地存储 | 长久留,会话走,Cookie自动带 |
| 50 | 浏览器渲染 | HTML+CSS→绘制出真容 |
💬 彩蛋总结:
面试官问你:“浏览器加载网页时都做了什么?” 你就微笑说: “先查DNS找店铺,再用CDN送奶茶,浏览器解析HTML和CSS一起冲出来的视觉盛宴~🧋✨”
🧩 第51页:直接在 window 上挂载变量有什么风险?(热度:872)
🧠 背景知识
在浏览器中,window 是全局对象。所有全局变量都会挂在它下面,比如:
var a = 1;
console.log(window.a); // 1
💣 问题来了:为啥不建议这么干?
① 容易命名冲突
window.data = "A";
window.data = "B"; // 被覆盖
📘 后加载的脚本可能会把前面的全局变量覆盖掉。
🍡 类比:
就像大家都往同一个冰箱放奶茶,结果最后互相挤掉对方的标签 😂。
② 安全风险高
恶意脚本能访问或修改全局变量,比如:
window.token = "abc123";
被其他脚本拿走 → 泄露敏感信息 ❌。
③ 影响可维护性
项目大了以后,没人知道哪些变量是全局的,一改就炸 ⚠️。
✅ 解决办法
- 封装在模块作用域中;
- 或使用
IIFE(立即执行函数); - 或使用 ES Module 的
import/export。
💡 口诀记忆:
“全局挂太多,命名冲突多;模块分一分,世界更安稳。”
🧭 第52页:常见 SEO 优化技巧(热度:888)
🧠 什么是 SEO?
SEO(Search Engine Optimization)即“搜索引擎优化”。 目标:让页面更容易被百度、Google 抓到并排在前面!
✅ 技术层优化
1️⃣ 语义化 HTML
<h1>前端八股文</h1>
<p>让你从小白到专家的学习路线</p>
👉 搜索引擎会重点识别 <h1>、<p>、alt 等内容。
2️⃣ 合理的 title / meta
<title>2025 前端面试题大全</title>
<meta name="description" content="最新前端八股文总结,包含 React/Vue/网络原理/算法场景题等内容。">
🍡 类比:
就像奶茶店门头写清楚“招牌珍珠奶茶”,搜索引擎更容易找到你。
3️⃣ SSR(服务端渲染)
- 前端 SPA 框架生成的页面内容是 JS 渲染出来的;
- 搜索引擎机器人看不到;
- SSR 让服务器提前生成完整 HTML,再返回给爬虫。
4️⃣ 静态化页面 / Sitemap / robots.txt 告诉搜索引擎哪些页面能爬、哪些不能。
💡 口诀记忆:
“语义标签要清晰,标题描述别省略,SSR 预渲染,爬虫笑嘻嘻。”
📱 第53页:小程序为什么会有两个线程?(热度:780)
🧠 微信小程序架构原理
一个小程序有两个主要线程:
1️⃣ 逻辑层(JS 线程) :处理数据、逻辑。 2️⃣ 视图层(WebView 线程) :渲染页面。
📘 为什么要分开?
- 提升性能;
- 防止 UI 卡顿;
- JS 冻结时页面仍能显示;
- 双线程用「数据通信」同步状态(通过
bridge)。
🍡 类比:
就像奶茶店:
- 前台小姐姐接单(视图层);
- 后厨小哥哥做奶茶(逻辑层); 两边靠“对讲机”沟通订单(bridge)。
💡 口诀记忆:
“逻辑管脑袋,视图管脸蛋,中间桥通信。”
⚙️ 第54页:Web 应用中如何处理请求超时或失败重试(热度:1093)
💡 背景
网络不稳定、接口异常时,不能让请求“挂着没反应”,要设置 超时 + 自动重试机制。
✅ 超时实现
function timeoutPromise(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject('Timeout!'), ms)
);
return Promise.race([promise, timeout]);
}
timeoutPromise(fetch('/api/data'), 5000)
.then(console.log)
.catch(console.error);
🧠 解释:
Promise.race():谁先完成用谁;- 如果请求超 5 秒没结果 → 抛出超时。
✅ 失败重试
function retry(fn, times = 3) {
return fn().catch(err => {
if (times <= 1) throw err;
console.log('重试中...');
return retry(fn, times - 1);
});
}
🍰 生活类比:
点奶茶三次:
- 第一次外卖员迷路;
- 第二次奶茶洒了;
- 第三次终于喝上了 😅。
💡 口诀记忆:
“Promise.race 控时限,失败三次再上前。”
🧠 第55页:HTML 的 data- 开头属性是什么?(热度:666)
💡 定义
HTML 提供了自定义属性方式: data-* 开头的都能用来存放自定义数据。
<div data-user="xiaokeai" data-level="vip">你好</div>
JS 获取:
const div = document.querySelector('div');
console.log(div.dataset.user); // xiaokeai
console.log(div.dataset.level); // vip
📘 解释:
- 所有
data-*属性会自动挂载到dataset对象; - 可在 JS 中安全读取,不影响 HTML 结构。
🍡 生活类比:
就像奶茶店在杯子底贴了“配料标签”:
data-sugar="50%"、data-temp="warm"顾客喝不见,但制作员知道怎么调 😋。
💡 口诀记忆:
“标签藏信息,dataset 全接听。”
🌈 第51~55页复习卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 51 | window全局风险 | 挤冰箱、撞名字、被偷走 |
| 52 | SEO优化 | 语义标签 + SSR 预渲染 |
| 53 | 小程序双线程 | 脑袋逻辑 + 脸蛋视图 |
| 54 | 请求超时重试 | race 控时限,retry 三次试 |
| 55 | data-*属性 | 杯底标签藏信息 |
💬 可爱收尾彩蛋:
面试官问你:“为什么小程序要两个线程?” 你眨眼笑说: “一个管脑袋(JS逻辑),一个管脸蛋(渲染UI), 不然一杯奶茶一边接单一边封膜就会撒出来啦~🧋✨”
🧩 第56页:移动端如何实现上拉加载、下拉刷新(热度:920)
💡 背景
这个功能在移动端页面(比如抖音、淘宝)非常常见。 当你滑到底部自动加载更多内容,往下拉则刷新当前页面。
✅ 核心思路
用 监听滚动事件 + 判断位置 来实现。
📘 代码核心逻辑
window.addEventListener('scroll', () => {
const scrollTop = document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const scrollHeight = document.documentElement.scrollHeight;
if (scrollTop + windowHeight >= scrollHeight - 50) {
console.log('触底了,加载更多数据!');
loadMore();
}
});
🧠 解释:
scrollTop:滚动距离;scrollHeight:页面总高度;windowHeight:可见高度;- 三者加起来 ≈ 总高度 → 到底部啦!
🍡 类比:
想象奶茶机的原料桶——倒快没料了(触底), 自动补货系统(loadMore)立刻加原料!
✅ 下拉刷新思路
监听 touchstart + touchmove + touchend:
let startY = 0;
document.addEventListener('touchstart', e => startY = e.touches[0].pageY);
document.addEventListener('touchend', e => {
if (e.changedTouches[0].pageY - startY > 100) {
console.log('下拉刷新触发!');
refresh();
}
});
🍰 类比:
顾客拿着杯子轻轻往下拉(下拉刷新), 奶茶机马上重启、重新冲泡一杯热的!🧋
💡 口诀记忆:
“滚动触底加,轻拉重泡刷。”
⚙️ 第57页:如何判断 DOM 元素是否在可视区域内(热度:846)
💡 为什么需要?
懒加载、动画触发、曝光埋点等功能都需要“知道元素是不是出现在屏幕上”。
✅ 方法一:getBoundingClientRect()
function isInViewPort(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
}
📘 解释:
- 通过元素的“边界坐标”判断是否在窗口内;
- 如果上下左右都在视口范围内 → 可见!
✅ 方法二:IntersectionObserver
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('元素出现了!');
}
});
});
observer.observe(document.querySelector('.target'));
🍡 类比:
就像奶茶店门口的感应门, 有人走近(元素进入视口)→ 门自动打开(触发回调)。
💡 口诀记忆:
“Rect测边距,IO最优雅。”
🎨 第58页:前端如何用 Canvas 生成带海报的分享图(热度:833)
💡 背景
朋友圈、小红书的“生成海报分享图”功能,本质上就是用 Canvas 绘图 + 图片导出。
✅ 核心步骤
① 获取 Canvas
<canvas id="poster" width="600" height="800"></canvas>
② 在 Canvas 上绘制内容
const canvas = document.getElementById('poster');
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, 600, 800); // 背景
const img = new Image();
img.src = 'avatar.jpg';
img.onload = () => {
ctx.drawImage(img, 50, 50, 100, 100);
ctx.font = '20px sans-serif';
ctx.fillText('小可爱奶茶店', 200, 100);
};
📘 解释:
fillRect()画背景;drawImage()画头像;fillText()写文字。
🍡 类比:
就像奶茶师傅在画一张“奶茶宣传海报”: 背景 → 图片 → 标题 → Logo,一层一层贴上去。
③ 导出图片
const imgUrl = canvas.toDataURL('image/png');
这就能导出一张 PNG 图片,直接分享。
💡 进阶技巧
- 可加二维码(
drawImage(qr, x, y, w, h)); - 可加水印、渐变背景;
- 可结合海报模板组件库(如 html2canvas、dom-to-image)。
💡 口诀记忆:
“Canvas画图层,toData导出图。”
🌈 第56~60页复习表
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 56 | 上拉加载/下拉刷新 | 滚到底补货,往下拉重泡 |
| 57 | 元素可视判断 | Rect测距,IO最优雅 |
| 58 | Canvas 海报生成 | 一层一层画,导出当分享 |
💬 小笨蛋彩蛋总结:
面试官问你:“上拉加载的原理是什么?” 你可以微笑说: “其实就像奶茶机原料快没了,我监听到了滚动触底事件,自动加料~不让顾客等!”🧋✨
🧩 第61页:如何通过大对象的可清除性存储大量数据(热度:1085)
💡 背景
localStorage 虽然方便,但有 5MB 限制,而且不会自动清理旧数据。 如果要存大量数据(例如缓存接口响应),我们要学会“能装又能清”的技巧!
✅ 方法一:封装带过期时间的 localStorage
function setLocalStorage(key, value, expire) {
const obj = {
data: value,
time: Date.now(),
expire // 毫秒
};
localStorage.setItem(key, JSON.stringify(obj));
}
function getLocalStorage(key) {
const val = localStorage.getItem(key);
if (!val) return null;
const obj = JSON.parse(val);
if (Date.now() - obj.time > obj.expire) {
localStorage.removeItem(key);
return null;
}
return obj.data;
}
📘 解释:
- 存的时候加上“时间戳”;
- 取的时候判断是否过期;
- 过期自动删除。
🍡 类比:
像奶茶店里的“珍珠存放罐”:
- 放进去时写上“生产时间 + 保质期”;
- 到时间自动丢掉,防止过期珍珠被端上桌!😆
✅ 方法二:使用 IndexedDB
- 异步、容量大(上百MB);
- 适合缓存大文件、图片、视频;
- 可配合
idb或dexie.js封装。
🍰 类比:
localStorage是小冰箱 🧊,IndexedDB是大型冷库 ❄️, 装得多、查得快、还能自动分类。
💡 口诀记忆:
“加时间戳会清理,大量数据用冷库。”
⚙️ 第62页:如果不使用 create-react-app,如何用 webpack 手搭 React 项目(热度:729)
这题超级容易加分 💯——面试官爱听你说“我手撸过 React 脚手架”。
✅ 第一步:初始化项目
npm init -y
npm install react react-dom
npm install webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin -D
🧠 解释:
- 安装 React + webpack;
- Babel 用来转译 JSX;
html-webpack-plugin自动生成 HTML。
✅ 第二步:配置 webpack
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: 'babel-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' })
],
devServer: { open: true }
};
🍡 类比:
这就像奶茶生产线的配置表:
entry:奶茶原料入口;output:封装好的成品位置;rules:加工标准(Babel 转译);plugins:额外操作(比如自动贴标签)。
✅ 第三步:配置 Babel
// .babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
👉 让 webpack 能识别 React 的 JSX 语法。
✅ 第四步:创建 React 入口文件
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
💡 总结类比:
- webpack:奶茶工厂;
- Babel:翻译机;
- React:奶茶配方;
html-webpack-plugin:前台装修师。
💡 口诀记忆:
“入口出厂设路径,Babel JSX 来翻译。”
🎨 第63页:CSS Module 与普通 CSS 区别(热度:688)
💡 问题场景
多人开发时,不同组件的类名容易互相污染。
✅ 普通 CSS 问题:
/* styles.css */
.title { color: red; }
<div className="title">A 组件</div>
<div className="title">B 组件</div>
👉 都变红了 ❌
✅ CSS Module 写法:
/* App.module.css */
.title { color: red; }
import styles from './App.module.css';
<div className={styles.title}>A 组件</div>
结果:每个类名被 webpack 自动加上唯一哈希,比如:
.title__abc123
🍡 类比:
普通 CSS 是“奶茶杯通用模具”,全店都染红; CSS Module 给每个分店加“门牌号”,互不影响。
💡 口诀记忆:
“加 module 避冲突,样式私有不乱组。”
🌈 第61~65页复习卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 61 | 本地大数据存储 | 加时间戳防过期,IndexedDB 放大库 |
| 62 | 手写 React 脚手架 | Webpack 奶茶厂,Babel 翻译机 |
| 63 | CSS Module 区别 | 加 module,私有化防污染 |
🧩 第66页:用 Node.js 实现一个命令行工具(热度:1732)
💡 背景
这题面试官爱问:“你会不会写 CLI 工具?” 其实就是做一个能在命令行运行的小程序,比如 vue create my-app 那种。
✅ 核心实现思路
用 Node.js 内置的 process 模块,获取命令行参数 + 输出结果。
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// 获取命令行参数
const args = process.argv.slice(2);
const [name] = args;
if (!name) {
console.log('请输入项目名');
process.exit(1);
}
// 创建项目目录
fs.mkdirSync(path.join(process.cwd(), name));
// 创建文件
fs.writeFileSync(`${name}/index.js`, 'console.log("Hello Node CLI!")');
console.log(`✅ 已成功创建项目 ${name}`);
🧠 解释:
process.argv:命令行输入参数;fs.mkdirSync():创建文件夹;process.cwd():当前路径。
🍡 类比:
想象你在奶茶工厂敲指令: “node make奶茶 shop1” → Node.js 马上帮你新建一个奶茶店文件夹,还自动写好“欢迎词”😆。
💡 口诀记忆:
“process拿参数,fs造文件。”
⚙️ 第67页:package.json 中 sideEffects 的作用(热度:229)
🧠 是啥?
sideEffects 是告诉 Webpack 哪些文件 不能被 Tree Shaking 删除。
✅ 代码解释
{
"sideEffects": false
}
表示整个项目都没有副作用文件,可以安全删除未使用的导出。
但如果有全局样式、polyfill 等,就得保留:
{
"sideEffects": ["*.css"]
}
📘 解释:
- Webpack Tree Shaking:去掉没用的代码;
sideEffects:告诉它哪些文件“虽然没被用到,但不能删”。
🍰 类比:
想象你打包奶茶时: “我只带喝的部分(JS函数),包装纸(CSS)看似没用,其实也要带上!”🧃
💡 口诀记忆:
“没副作用删干净,有全局样式别乱清。”
🧠 第68页:<script> 标签的类型和区别(热度:744)
💡 背景
HTML 里 <script> 标签是加载 JS 的关键,不同属性代表不同加载方式。
✅ 1️⃣ 普通 script
<script src="main.js"></script>
👉 默认会 阻塞渲染,加载完才执行。
✅ 2️⃣ async
<script src="main.js" async></script>
👉 异步加载,加载完立即执行(可能打乱顺序)。
✅ 3️⃣ defer
<script src="main.js" defer></script>
👉 异步加载,等 HTML 全部解析完才执行(顺序稳定)。
🍡 类比:
- 普通 script:奶茶师一定要等配料送到才能做;
async:谁配料先到先开做;defer:所有材料都到齐再一起做。
💡 口诀记忆:
“普通堵路,async乱序,defer稳序。”
🌐 第69页:为什么 SPA 应用要使用 hash 路由?(热度:681)
💡 背景
SPA(单页应用)只有一个 HTML 文件,怎么实现不同页面切换? → 用 # 哈希路由!
✅ 哈希原理
window.addEventListener('hashchange', () => {
console.log(location.hash); // #/home
});
📘 解释:
#后的内容不会发给服务器;- 浏览器感知变化但不刷新页面;
- JS 监听 hash,实现页面切换。
✅ History 路由(对比)
history.pushState({}, '', '/home');
需要后端配置重定向,否则刷新会 404。
🍡 类比:
哈希路由就像奶茶店菜单旁的小标签纸: 改动标签(#口味=芋圆奶茶)→ 味道切换,但厨房不重启。
💡 口诀记忆:
“Hash不刷新,History需配置。”
🎨 第70页:[React] 如何实现动态主题切换(热度:693)
💡 目标
实现一个按钮,能切换“亮色 / 暗色模式”。
✅ 方法一:CSS 变量 + Context 方案
:root {
--bg-color: #fff;
--text-color: #000;
}
.dark {
--bg-color: #000;
--text-color: #fff;
}
import React, { createContext, useState, useContext } from 'react';
const ThemeContext = createContext();
export default function App() {
const [theme, setTheme] = useState('light');
const toggle = () => setTheme(theme === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme }}>
<div className={theme}>
<h1>小可爱奶茶店</h1>
<button onClick={toggle}>切换主题</button>
</div>
</ThemeContext.Provider>
);
}
📘 解释:
- 用
Context管理主题; - 切换时改 class;
- CSS 自动应用不同变量。
🍡 类比:
就像奶茶店早上灯亮亮、晚上换成氛围灯; 点击“换灯”按钮,整个装修风格切换✨。
💡 口诀记忆:
“CSS变量换皮肤,Context传全局。”
🌈 第66~70页复习卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 66 | Node CLI 工具 | process拿参,fs造文件 |
| 67 | sideEffects | 没副作用删干净,有样式别乱清 |
| 68 | script区别 | 普通堵路,async乱序,defer稳序 |
| 69 | SPA哈希路由 | Hash不刷新,History需配置 |
| 70 | React换主题 | CSS变量换皮肤,Context传全局 |
💬 奶茶风总结:
面试官问:“为什么单页应用喜欢用 hash?” 你笑着说: “因为我换口味不想重启奶茶机呀~只改标签就能切换口味!”🧋😄
这一部分涵盖了面试超常问的几个硬核知识点: 👉 单点登录(SSO)、 👉 前端防爬虫与反爬机制、 👉 JS 深浅拷贝原理、 👉 前端性能优化与懒加载。
我来用「奶茶店生活类比 + 小白速记口诀」帮你轻轻松松拿下这页 💪🧋
🧩 第71页:单点登录(SSO)是什么,具体流程是怎样的?(热度:740)
💡 背景
SSO(Single Sign-On)单点登录: 一次登录,多系统共享登录状态。 常见于:阿里系、腾讯系的多应用系统。
✅ 核心思路
1️⃣ 用户访问 A 系统 → 未登录 2️⃣ A 跳转到 SSO 登录中心 3️⃣ 用户输入账号密码,SSO 验证成功后发一个「票据 token」 4️⃣ A 系统拿 token 去验证 → 成功登录 5️⃣ 之后访问 B 系统时,直接识别 token,无需再次登录
📘 图解说明(简化代码逻辑)
// 登录中心
if (username === 'xiaokeai' && password === '123456') {
const token = generateToken(username);
redirectToSystemA(token);
}
// 系统A验证
if (verifyToken(token)) {
localStorage.setItem('token', token);
console.log('登录成功');
}
🍡 生活类比:
你去“奶茶联盟”任意一家分店(系统A/B), 只要在总部(SSO中心)出示你的会员卡(token), 每家都能自动识别你是VIP~💳✨
💡 口诀记忆:
“登录一次,多店通吃。”
⚙️ 第72页:Web 前端如何防止别人爬取数据(热度:540)
💡 背景
很多网站会被爬虫程序抓取数据,我们要防爬虫保护接口与内容安全。
✅ 防爬手段一览
① 登录校验 + token
没有合法 token 的请求直接拒绝。
② UA 校验(User-Agent)
拦截常见爬虫标识(如 Python、Scrapy)。
③ 加密参数
前端传参前加密,例如:
const sign = md5(timestamp + secretKey);
④ 限制频率(Rate Limit)
同一 IP 频繁访问 → 暂时封禁。
⑤ 滑块 / 图形验证码
用视觉识别打断自动脚本。
🍰 类比:
你是奶茶店老板, 有个机器人天天来白喝奶茶(爬虫🤖)。 你于是:
- 要求出示会员卡(token);
- 换口令配方(加密);
- 加人脸验证(验证码)。
完美防爬!😎
💡 口诀记忆:
“验身份、加密参、防频刷、加验证码。”
🌐 第73页:用户访问页面后白屏,原因有哪些?怎么排查?(热度:690)
💡 白屏 = 页面没渲染成功
✅ 常见原因
1️⃣ 资源加载失败
- JS/CSS 路径错;
- CDN 挂了;
- 缓存文件 404。
2️⃣ 接口阻塞
- 接口请求未返回,页面卡死;
- 用
Promise.all未捕获错误。
3️⃣ 脚本报错
- 语法错误;
- 循环依赖;
- import 路径错误。
✅ 排查方法
- F12 → Console 看报错;
- Network 看是否资源未加载;
- 用性能面板(Performance)看渲染时间;
- 关键逻辑加 try-catch。
🍡 类比:
奶茶店点单后迟迟没出奶茶(白屏):
- 原料丢了(资源加载失败);
- 搅拌机卡住(接口卡住);
- 店员配方写错(JS 报错)。
💡 口诀记忆:
“先查资源,再查接口,最后看报错。”
🧠 第74页:【代码实现】JS 如何实现深浅拷贝(热度:906)
💡 背景
浅拷贝只复制第一层,深拷贝要连子对象一起复制。
✅ 浅拷贝
const obj = { name: '奶茶', info: { sweet: '50%' } };
const copy = { ...obj };
copy.name = '绿茶';
copy.info.sweet = '70%';
console.log(obj.info.sweet); // 70% ❌
📘 原因: 嵌套对象仍是“同一引用”。
✅ 深拷贝(JSON法)
const deepCopy = JSON.parse(JSON.stringify(obj));
📘 缺点:
- 不能拷贝函数、Symbol;
- 循环引用会报错。
✅ 深拷贝(递归法)
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
const result = Array.isArray(obj) ? [] : {};
for (let key in obj) {
result[key] = deepClone(obj[key]);
}
return result;
}
🍡 类比:
浅拷贝:只复制“奶茶菜单”,原料桶共用; 深拷贝:连原料桶都单独备一份,每家独立调味 🧋。
💡 口诀记忆:
“浅拷贝只换壳,深拷贝全备料。”
⚙️ 第75页:如何管理高频接口调用,有哪些核心思路?(热度:943)
💡 背景
当接口调用频繁(如搜索建议、滚动加载)时,要避免浪费和卡顿。
✅ 方法一:防抖(Debounce)
👉 多次触发只执行最后一次。
function debounce(fn, delay = 300) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
🍰 场景:输入框搜索建议。
✅ 方法二:节流(Throttle)
👉 一段时间内只执行一次。
function throttle(fn, delay = 300) {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn(...args);
flag = true;
}, delay);
};
}
🍰 场景:窗口滚动监听。
🍡 类比:
防抖:奶茶师等顾客说完完整订单再开始做(防止多做)。 节流:奶茶机 3 秒只能出一杯(防止超负荷)。
💡 口诀记忆:
“防抖等最后,节流限频率。”
🌈 第71~75页复习卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 71 | 单点登录 | 登录一次,多店通吃 |
| 72 | 防爬机制 | 验身份、加密参、防频刷、加验证码 |
| 73 | 白屏排查 | 查资源 → 接口 → 报错 |
| 74 | 深浅拷贝 | 浅拷贝换壳,深拷贝全备料 |
| 75 | 高频接口 | 防抖等最后,节流限频率 |
💬 小可爱记忆法总结:
面试官:“请讲讲你怎么做性能优化?” 你眨眼说: “我防抖节流一起上,让奶茶机既不浪费原料,也不卡壳~🧋✨”
🧩 第76页:vue-cli 都干了些什么?(热度:386)
💡 vue-cli 是啥?
vue-cli 就是 Vue 的“自动化脚手架工具”,帮你一键生成完整项目结构。
✅ 它做了哪些事?
1️⃣ 项目初始化 自动生成:
- src / public / package.json 等目录;
- vue.config.js 配置文件;
- webpack 打包配置。
2️⃣ 脚手架模板选择 你可以用:
vue create my-project
选择 Vue2 / Vue3 + TypeScript + Router + Vuex 等。
3️⃣ 集成工具链
- 自动配置 Babel、ESLint、PostCSS;
- 启动开发服务器(
npm run serve); - 热更新、构建优化。
🍡 类比:
你只需要说:“给我来一杯奶茶套餐A”, vue-cli 就自动:准备茶叶、冰块、杯子、封膜机,全都安排好~🤖✨
💡 口诀记忆:
“一键脚手架,配置都打包。”
⚙️ 第77页:JS 执行 100 万个任务,如何保证浏览器不卡死?(热度:806)
💡 背景
一次性执行巨量循环,会阻塞主线程,页面就“卡死”了。 要让任务“分批执行”,就像分批做奶茶 🍵。
✅ 方法一:分时处理(定时切片)
function processTasks(tasks) {
function run() {
const start = Date.now();
while (Date.now() - start < 50 && tasks.length) {
const task = tasks.shift();
doTask(task);
}
if (tasks.length) requestIdleCallback(run);
}
requestIdleCallback(run);
}
📘 解释:
- 一次执行 50ms 内任务;
- 用
requestIdleCallback在空闲时继续执行; - 不阻塞 UI。
🍡 类比:
一次做 100 万杯奶茶不可能! 改成“每分钟做几十杯”,客人不会卡住柜台等。😉
✅ 方法二:分帧执行(requestAnimationFrame)
每一帧(16ms)执行一小部分任务,和渲染同步。
💡 口诀记忆:
“分批切片,不让主线程爆炸。”
🌐 第78页:JS 中 fetch、head、body 有啥区别?(热度:420)
✅ fetch 是前端发网络请求的核心 API:
fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '小可爱' })
});
📘 说明:
headers:请求头(告诉服务器你是谁、传啥格式);body:请求体(要发送的数据);method:请求方式(GET、POST等)。
🍡 类比:
“headers 像奶茶杯上的标签🧋, body 就是奶茶内容, fetch 是外卖小哥 🚴 送它出去。”
💡 口诀记忆:
“fetch是外卖,header写标签,body放内容。”
🧠 第79页:ESLint 代码安全与规范是咋实现的?(热度:111)
💡 背景
ESLint 是前端的“代码警察 🚓”,负责扫描和规范代码格式。
✅ 工作原理:
1️⃣ 读取 .eslintrc 配置 2️⃣ 解析源码生成 AST(抽象语法树) 3️⃣ 遍历节点,匹配规则 4️⃣ 输出错误提示或自动修复
✅ 举个例子:
const name = "小可爱"
console.log(name)
💥 少了分号,ESLint 报:
Missing semicolon (semi)
🍡 类比:
ESLint 像店长巡视奶茶店: “吸管没放正!标签歪了!请修正!”😂
💡 口诀记忆:
“AST扫语法,规则控格式。”
🎨 第80页:动画控制原理是什么?用 JS 模拟一个动画帧(热度:354)
💡 背景
动画流畅的关键在于——每一帧的更新控制。
✅ 简单的动画示例
<div id="ball"></div>
<script>
let pos = 0;
function move() {
pos += 2;
ball.style.transform = `translateX(${pos}px)`;
if (pos < 200) requestAnimationFrame(move);
}
move();
</script>
📘 解释:
requestAnimationFrame会在浏览器下一帧调用;- 让动画与屏幕刷新率同步(更流畅);
- 比
setInterval更节能。
🍡 类比:
requestAnimationFrame就像奶茶师跟着节奏机做奶茶, 一拍一拍更新,不会乱速~🎵
💡 口诀记忆:
“动画用RAF,跟屏幕节奏走。”
🌈 第76~80页复习卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 76 | vue-cli作用 | 一键脚手架,配置都打包 |
| 77 | 百万任务不卡死 | 分批切片,不阻塞线程 |
| 78 | fetch区别 | headers标签,body内容 |
| 79 | ESLint原理 | AST扫语法,规则控格式 |
| 80 | 动画控制原理 | RAF跟帧走,不卡不卡 |
💬 小可爱总结时间:
面试官:“你怎么防止 JS 卡顿?” 你眨眼一笑说: “我分批执行任务,就像分批做奶茶,不会让顾客排成长龙🧋✨”
🧩 第81页:React 性能优化的经典方法(含 IntersectionObserver 懒加载示例)
💡 背景
当页面有很多图片或组件时,如果一次性全加载,会非常卡顿。 👉 所以我们要“懒加载”——只加载用户看到的部分。
✅ 方法一:IntersectionObserver 懒加载
class ImageLazy extends React.Component {
constructor(props) {
super(props);
this.state = { visible: false };
this.imgRef = React.createRef();
}
componentDidMount() {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
this.setState({ visible: true });
observer.disconnect();
}
});
observer.observe(this.imgRef.current);
}
render() {
const { src } = this.props;
return (
<img ref={this.imgRef} src={this.state.visible ? src : ''} alt="lazy" />
);
}
}
📘 解释:
IntersectionObserver是浏览器提供的“视口检测”API;- 当元素出现在屏幕视野中时触发;
- 我们就能在那一刻再加载图片,节省性能。
🍡 类比:
就像奶茶店老板只在有顾客靠近柜台时才开始泡茶, 远处没人就先不动锅~省时省料又聪明 😎。
💡 口诀记忆:
“进视口再加载,懒得聪明。”
✅ 其它常用优化方法
| 方法 | 说明 |
|---|---|
| shouldComponentUpdate / memo | 阻止无意义渲染 |
| React.lazy + Suspense | 按需加载组件 |
| 虚拟列表(Virtual List) | 渲染长列表时只展示可视区 |
⚙️ 第82页:[React] react-router 和 hash 路由区别(热度:434)
💡 先记一句话:
“Hash路由靠锚点,History路由靠API。”
✅ Hash 路由
http://localhost:3000/#/home
📘 特点:
- 改变
#后不会发请求; - 前端监听
hashchange事件即可; - 兼容性好,但丑。
✅ History 路由
http://localhost:3000/home
📘 特点:
- 通过
history.pushState实现; - 路径更优雅;
- 但刷新需要后端支持,否则 404。
🍡 类比:
Hash 路由像奶茶菜单的“编号标签”(#1、#2), History 路由像真正的“菜单路径” (/奶茶/珍珠)。
💡 口诀记忆:
“Hash快但丑,History美但难。”
🌐 第83页:HTML 的行内元素 vs 块级元素区别(热度:796)
| 类型 | 举例 | 特点 |
|---|---|---|
| 块级元素 | div、p、h1 | 独占一行,可设宽高 |
| 行内元素 | span、a、img | 不换行,只包内容 |
🍡 生活类比:
块级元素像一整张桌子 🍽️; 行内元素像桌上的奶茶杯 🧋, 桌子独占一行,奶茶杯可并排摆。
💡 口诀记忆:
“块级独行,行内并排。”
🧠 第84页:什么是 requestIdleCallback API?(热度:290)
💡 背景
当页面执行很多任务时,我们想在“浏览器空闲时”再执行一些不紧急任务。 → 这时用 requestIdleCallback!
✅ 示例代码:
requestIdleCallback(deadline => {
while (deadline.timeRemaining() > 0 && tasks.length) {
work(tasks.pop());
}
});
📘 解释:
- 浏览器空闲了才执行;
deadline.timeRemaining()表示本帧还能用的时间;- 不会阻塞用户交互。
🍡 类比:
奶茶师高峰期忙完主单后, 再利用空闲时间擦擦桌子、补吸管~ 这就是“空闲回调任务”!
💡 口诀记忆:
“主任务忙完再干活,空闲时间不浪费。”
⚙️ 第85页:总结页(复盘)
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 81 | React 懒加载 | 进视口再加载,懒得聪明 |
| 82 | React Router 区别 | Hash快但丑,History美但难 |
| 83 | 行内 vs 块级 | 桌子独行,奶茶杯并排 |
| 84 | requestIdleCallback | 忙完主活再补空 |
💬 小可爱总结一下:
面试官:“说说你在 React 项目里做过哪些性能优化?” 你:😏“我懒,但懒得聪明——懒加载、空闲执行、按需渲染全安排!”🧋✨
🧩 第86页:documentFragment 是什么?(热度:115)
💡 背景:
当我们想往页面里插入大量节点时,如果直接反复操作 DOM,会非常卡(因为频繁触发回流重绘)。
👉 所以浏览器给我们了一个“临时容器”:DocumentFragment。
✅ 举个例子
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `我是第 ${i} 个小div`;
fragment.appendChild(div);
}
document.body.appendChild(fragment);
📘 解释:
fragment是一个“迷你文档”,能装节点;- 它不在 DOM 树上;
- 当一次性 append 到页面时,浏览器只更新一次,大幅提速!
🍡 生活类比:
就像奶茶师先在托盘上准备好 10 杯奶茶 🧋, 再一次端上来,而不是一杯一杯跑十次柜台。💨
💡 口诀记忆:
“装好一托盘,再端上台面。”
⚙️ 第87页:git pull 和 git fetch 有什么区别?(热度:355)
✅ git fetch
只拉取远程更新,不会自动合并。
git fetch origin main
拉完后本地代码不会变,你可以自己对比、合并。
✅ git pull
= git fetch + git merge 直接拉取并合并代码。
git pull origin main
🍡 生活类比:
fetch就像“查看奶茶店菜单更新了啥”;pull则是“直接把新菜单贴上墙换掉旧的”。📜
💡 口诀记忆:
“fetch只看,pull拿来。”
🌈 第88页:前端如何做页面主题切换(热度:538)
💡 背景:
你想实现“深色模式 / 浅色模式”切换,或用户自定义主题色。
✅ 方法一:CSS 变量法
:root {
--bg-color: white;
--text-color: black;
}
.dark {
--bg-color: black;
--text-color: white;
}
document.documentElement.classList.add('dark');
📘 原理:
- CSS 变量实时生效;
- 不需刷新页面;
- 结合 JS 动态切换 class。
✅ 方法二:LocalStorage 记住用户偏好
const theme = localStorage.getItem('theme') || 'light';
document.body.classList.add(theme);
切换主题时:
localStorage.setItem('theme', theme);
🍡 生活类比:
就像顾客上次点“少糖珍珠奶茶”, 店员记下来(LocalStorage),下次自动照做~🧋✨
💡 口诀记忆:
“CSS变色,JS记忆。”
🧠 第89页:前端架构 - 如何保证系统稳定性(热度:566)
💡 背景:
在大型前端项目中,崩溃和白屏是大忌。系统稳定性靠设计保障。
✅ 常见方案
1️⃣ 错误监控
window.onerrortry/catchunhandledrejection→ 捕获 JS 异常和 Promise 错误。
2️⃣ 日志上报
window.onerror = function (msg, src, line, col, err) {
sendToServer({ msg, src, line, col, stack: err?.stack });
};
3️⃣ 性能监控
- 页面加载时间、接口响应时间;
- 可用 Performance API。
4️⃣ 熔断机制
- 网络异常时快速失败;
- 用兜底内容代替真实数据。
🍡 生活类比:
就像奶茶机坏了,系统会:
- 立刻报警(错误监控)
- 记录日志(日志上报)
- 临时卖瓶装奶茶(兜底方案)
💡 口诀记忆:
“监控报错,兜底保活。”
🌟 第90页复盘卡片
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 86 | DocumentFragment | 托盘装好再端上 |
| 87 | git pull vs fetch | fetch只看,pull拿来 |
| 88 | 主题切换 | CSS变色,JS记忆 |
| 89 | 稳定性设计 | 报错监控,兜底保活 |
💬 小可爱总结一下:
面试官问:“你做过性能优化和系统稳定性吗?” 你眨眼笑着说: “我托盘批量更新DOM、CSS变量秒换肤,出错还能兜底保活——绝不卡,也绝不崩 🧋✨!”
🧩 第91页:如何统计长任务时间、长任务执行次数(热度:489)
💡 背景
当页面“卡顿”或“掉帧”,其实是因为浏览器主线程在执行“长任务(Long Task) ”。 👉 所谓“长任务” = 执行时间 > 50ms 的任务(比如循环太久、加载太多图片)。
✅ 用 PerformanceObserver 监听长任务
const observer = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
console.log('长任务耗时:', entry.duration);
});
});
observer.observe({ entryTypes: ['longtask'] });
📘 解释:
PerformanceObserver是浏览器性能监控API;- 可以捕获页面中执行太久的任务;
entry.duration表示执行时间。
🍡 生活类比:
就像奶茶师每杯奶茶都要 50ms 制作时间, 如果他突然做一杯珍珠奶茶花了 500ms—— 说明他“忙太久”,让后面的单都排队了(页面卡顿)💦
💡 口诀记忆:
“50ms以上叫长任务,Performance来侦查。”
⚙️ 第92页:V8 里的 JIT 是什么?(热度:694)
💡 背景
JS 是解释型语言,但为了让执行速度快,V8 引擎用了“即时编译(JIT,Just-In-Time Compilation)”。
✅ 流程
1️⃣ 解析器(Parser) 把 JS 代码变成 AST(抽象语法树)
2️⃣ 解释器 Ignition 把 AST 转成字节码并执行
3️⃣ 编译器 TurboFan 监测“热代码”(执行频繁的部分),编译成本地机器码 → 下次执行更快 ⚡
🍡 生活类比:
一开始是“现做奶茶”(解释执行), 后来发现这款点得多,就提前备料(编译优化)。 所以第二次做更快~🧋💨
💡 口诀记忆:
“常跑的路铺柏油,常跑的代码编机器。”
🍪 第93页:用 JS 写一个 cookies 解析函数,输出第一个 key(热度:137)
✅ 示例代码
function parseCookie(cookieStr) {
return cookieStr.split(';')[0].split('=')[0];
}
📘 解释:
- cookie 是
"a=1; b=2; c=3"这种格式; - 用
split(';')按分号切割; - 取第一个,再按
=分成键值。
🍡 生活类比:
就像有三杯奶茶
a=珍珠奶茶; b=椰果奶茶; c=抹茶拿铁, 这段代码就是: “拿第一杯的口味名——珍珠奶茶!”🧋
💡 口诀记忆:
“按分号切,再拿第一个等号前。”
🎨 第94页:Vue 中 Scoped Styles 是怎么实现样式隔离的?(热度:244)
💡 背景
Vue 组件之间如果样式互相影响,很容易出问题。 <style scoped> 就是为了解决样式“串门”。
✅ 实现原理
<template>
<div class="box">你好呀</div>
</template>
<style scoped>
.box {
color: red;
}
</style>
👉 编译后变成:
<div class="box" data-v-123abc>你好呀</div>
<style>
.box[data-v-123abc] { color: red; }
</style>
📘 解释:
- Vue 编译器在每个组件的 DOM 上加上一个独特的
data-v-xxx; - CSS 选择器也带上这个标识;
- 不同组件的作用域互不干扰。
🍡 生活类比:
就像每个奶茶师的杯子都贴上专属编号 🧋 “这杯是A师傅的”,不会被别的师傅误放材料。✨
💡 口诀记忆:
“data-v标签,样式隔离。”
⚙️ 第95页:提升页面渲染性能的方法(热度:683)
✅ 常见方案
1️⃣ CSS 优化
- 避免过深的选择器;
- 尽量使用 class;
- 合理使用动画(GPU加速)。
2️⃣ JS 优化
- 减少 DOM 操作;
- 事件节流(throttle)与防抖(debounce);
- 拆分大任务(用 requestIdleCallback)。
3️⃣ 资源优化
- 图片懒加载;
- 代码按需加载;
- 静态资源 CDN。
4️⃣ 缓存机制
- HTTP 缓存;
- localStorage / indexedDB 存数据。
🍡 生活类比:
想让奶茶店运转更快:
- 材料分区摆放(CSS优化)
- 员工分工明确(JS优化)
- 原料提前备好(缓存优化)
💡 口诀记忆:
“CSS少嵌套,JS要节流,图片懒加载,缓存别落下。”
🧩 第96页(补充题):JS 中如何永久地阻止事件冒泡?(热度:269)
element.addEventListener('click', e => {
e.stopImmediatePropagation();
});
📘 解释:
stopPropagation():阻止继续向上冒泡;stopImmediatePropagation():连同同层事件监听器也阻止;- 相当于“彻底封死冒泡路径”。
🍡 生活类比:
顾客拍桌子点餐,你不仅拦住他, 连旁边想跟着点单的人也都噤声 😂。
💡 口诀记忆:
“Immediate 是彻底版的 stop。”
🌈 第91~95页复盘卡
| 页码 | 知识点 | 一句话记忆 |
|---|---|---|
| 91 | 统计长任务 | 超50ms算长,用Performance监控 |
| 92 | V8 JIT机制 | 热代码→机器码 |
| 93 | Cookie解析 | 分号切+等号取键 |
| 94 | Scoped样式隔离 | data-v独立域 |
| 95 | 性能优化 | 样式浅+节流懒+缓存存 |
💬 小可爱总结一句:
“我不光懂样式隔离,还能查长任务、做性能优化,连 JIT 都能顺口背~ 就像一杯珍珠奶茶,奶香顺滑不卡顿~🧋✨”
🧩 第96页:尾递归优化(Tail Call Optimization)
💡 背景
普通递归如果层数太深(比如 1万层),会导致栈溢出(stack overflow) 。 而“尾递归优化”能让编译器复用栈帧,不再堆积内存。
✅ 普通递归(会炸)
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
factorial(100000); // 💥 栈溢出!
因为每次都要等 factorial(n-1) 返回才能算乘积。
✅ 尾递归优化
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
factorial(100000); // ✅ 不会爆栈
📘 原理: 最后一步是直接“调用自身”,不会再创建新栈。
🍡 生活类比:
普通递归就像奶茶店排长队,每个顾客要等前一个做完; 尾递归像自助点单机——做完一杯直接覆盖上一个任务,不堆单!✨
💡 口诀记忆:
“最后一步是自己 → 不叠栈。”
🧩 Trampoline(蹦床)函数
当浏览器或 Node 不支持尾递归时,用“蹦床函数”模拟:
function trampoline(fn) {
while (typeof fn === 'function') {
fn = fn();
}
return fn;
}
它会循环执行函数而不是堆栈递归。
⚙️ 第97页:Promise 实现递归调用(热度:502)
✅ 示例
function asyncFactorial(n, acc = 1) {
return Promise.resolve().then(() => {
if (n <= 1) return acc;
return asyncFactorial(n - 1, n * acc);
});
}
📘 解释:
- 把递归放入异步队列;
- 让每次递归“放到下一轮事件循环”再执行;
- 避免同步爆栈。
🍡 生活类比:
就像奶茶师每做一杯都打卡一次休息,不会累趴。🧋💤
💡 口诀记忆:
“Promise 分段递归,不会炸栈。”
🎨 第98页:函数的防抖与节流(热度:554)
✅ 背景
当用户频繁触发事件(比如输入、滚动)时,要减少触发次数。
🧩 防抖(debounce)→ 等用户停下再执行
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
🍡 生活类比:
顾客不停点单(输入),店员会“等3秒没人再下单”~
🧩 节流(throttle)→ 固定时间间隔执行一次
function throttle(fn, delay) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= delay) {
fn.apply(this, args);
last = now;
}
};
}
🍡 生活类比:
店员每5秒钟接一单,多的都排队。
💡 口诀记忆:
“防抖等停下,节流限频率。”
🧠 第99页:TS项目中,如何使用 node_modules 里的自定义类型在 src 下复用?(热度:377)
✅ 背景
TypeScript 工程常常要跨模块复用类型定义。
✅ 做法
1️⃣ 在 node_modules/your-lib/index.d.ts 定义类型:
export interface User {
name: string;
age: number;
}
2️⃣ 在项目中使用:
import type { User } from 'your-lib';
3️⃣ 如果要共享本地类型: 在 tsconfig.json 中加:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@types/*": ["src/types/*"]
}
}
}
🍡 生活类比:
就像把“菜单模板”放在公共文件夹, 每个奶茶分店都能导入用,不用重写菜单。📋
💡 口诀记忆:
“类型提公库,路径配 alias。”
🛰️ 第100页:不同标签页间通信的方式(热度:401)
✅ 1️⃣ localStorage + storage事件
window.addEventListener('storage', e => {
console.log(e.key, e.newValue);
});
localStorage.setItem('msg', 'hello');
📘 解释:
- 改动 localStorage 时,其他标签页会触发
storage事件。
✅ 2️⃣ BroadcastChannel(推荐)
const channel = new BroadcastChannel('chat');
channel.postMessage('hi~');
channel.onmessage = e => console.log(e.data);
📘 解释:
- 多标签页共享一个“广播频道”,互发消息;
- 更方便、实时。
✅ 3️⃣ SharedWorker
适合复杂计算任务的通信。
🍡 生活类比:
多个奶茶窗口同时营业:
localStorage是公告栏;BroadcastChannel是对讲机;SharedWorker是中央调度台 🧋📡。
💡 口诀记忆:
“storage传公告,channel开对讲。”
🌈 第96~100页复盘卡
| 页码 | 主题 | 一句话记忆 |
|---|---|---|
| 96 | 尾递归优化 | 最后一步是自己,不叠栈 |
| 97 | Promise递归 | 分段异步,防爆栈 |
| 98 | 防抖节流 | 防抖等停,节流限频 |
| 99 | TS类型共享 | 类型提公库,路径配alias |
| 100 | 标签页通信 | 公告栏+对讲机模式 |
💬 小可爱总结:
“我不仅能写递归不炸,还能跨标签页聊天~ 用防抖节流节能省电,TypeScript 类型也井井有条~🧋✨”