20251020-前端场景题(上篇)

133 阅读54分钟

🧩 第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);
  }
);

🔸 思路解析:

  1. axios.interceptors.response.use:这是响应拦截器,所有请求的响应都会经过这里。
  2. 判断状态码err.response.status 是服务器返回的状态。
  3. 401 表示未登录或 token 失效
  4. 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 的作用:

  1. 提供公共的辅助函数,不用重复注入。
  2. 减少打包体积,优化性能。
  3. 让 async/await、class 等语法能在旧浏览器中运行。

类比理解

以前每个咖啡师(文件)都自己磨豆(辅助函数),很累。 现在统一放在“中央厨房”(babel-runtime),共享使用,轻量又高效。


📄 第4页:如何实现导出 PDF 文件(热度:173)

思路:前端生成页面截图或表格,再转成 PDF 文件。

✨ 常见方案

  1. 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; }
}

🧠 解释:

  1. linear-gradient 造出彩色背景;
  2. background-clip: text 让颜色“贴”在文字上;
  3. animation 让背景动起来!

🎨 类比

就像霓虹灯广告牌的文字在闪光流动, 背景在动,但文字本身没变色!

🧃 记忆法

“贴背景 → 透明字 → 背景动。”


🧩 总结记忆表

页码知识点一句话记忆
1axios 拦截器 toast错误拦截,发消息
2优化 if-else提前 return,查字典
3babel-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)

场景: 表单提交、网络请求、操作成功/失败时,怎么让提示既及时又不烦人?


🌈 常见提示类型

  1. Toast 提示(轻量、自动消失) 👉 适合“操作成功”、“保存完成”等。
  2. Modal 弹窗(需要确认) 👉 适合“删除确认”、“重要提醒”等。
  3. 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 可统计加载时间;
  • LCPFIDCLS 等指标衡量页面体验。

🍰 生活类比:

就像开餐厅装了监控摄像头:

  • 埋点 = 记录顾客点餐动作;
  • 错误捕获 = 报警器响了知道出事;
  • 性能监控 = 看厨房出餐速度。

🧠 记忆口诀:

“埋点留痕,异常报警,性能测速。”


📏 第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用户反馈埋点 + 错误捕获 + 性能监控
9px→rem根节点定比例,全局自动变
10flex 居中两个 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
12Cookie 跨域开启 withCredentials
13axios 取消请求控制器信号,秒取消
14鼠标悬停发光hover + shadow
15判断父子 & 空对象contains / Object.keys

🧩 第16页:JS 如何判断“空”?(热度:640)

🧠 问题背景

我们常常需要判断一个变量是不是“空的”,比如:

  • 空字符串 ''
  • 空数组 []
  • 空对象 {}
  • nullundefined
  • 空的 Map / Set
  • 数值 0NaN

🧩 实现方法 ① —— 最基础版

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;
}

💡 解释:

  1. null / undefined → 直接判空。
  2. 字符串要去掉空格再判空。
  3. 数组判 length
  4. Map/Setsize
  5. 对象判 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翻牌动画景深定距,反面隐藏
18flex: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 更小;
  • 可用 sharptinypng 压缩。

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 在每个子应用启动时创建独立的“沙箱环境”, 让它的 windowdocument 等变量互不干扰。


🍡 生活类比:

想象每个奶茶店加盟店都能自己装修、配料、营业; 但总部规定“你不能动别人家的配方”,这样大家互不影响。


💬 记忆口诀:

“Proxy 做隔离,沙箱防串门。”


🧁 复习终极表

页码知识点一句话记忆
21清理冗余代码ESLint 检查,Tree Shaking 清理
22图片优化换格式、懒加载、自适应
23版本发布打 Tag → 自动化 → 灰度试喝
24跨应用通信消息、仓库、参数
25Qiankun 隔离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)

🚨 一句话总结

前端卡顿大多是“主线程被堵死”。


🔍 常见原因

  1. JS 执行太久(死循环、巨量计算);
  2. 图片太大;
  3. 网络慢;
  4. 频繁重绘 / 回流。

⚙️ 排查思路

① Chrome Performance

用 “Performance” 面板看哪段代码占用时间最多。

② 分帧优化

使用 requestAnimationFrame() 分批执行任务。

function bigTask() {
  let i = 0;
  function run() {
    if (i++ < 1000) {
      console.log(i);
      requestAnimationFrame(run);
    }
  }
  run();
}

💡 解释: 把大任务切成很多小片段,分多帧执行。

🍡 类比:

一次做 1000 杯奶茶会爆炸 🧋💥, 分 10 次做,每次 100 杯就没问题。


💡 口诀记忆

“看性能 → 分批干 → 减重绘。”


🌈 终极复习表

页码知识点一句话记忆
26JS 模块化演进IIFE → CommonJS → AMD → ESM
27React keyID 唯一,不用 index
28ContextProvider 提供,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.jsdecimal.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)

💡 原因

  1. 使用 vh/vwcalc() 等单位时,浏览器缩放计算精度不同;
  2. 浏览器滚动条出现或消失时宽度变化;
  3. 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)setTimeoutsetInterval、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

💡 原理:

  1. 主线程执行同步任务;
  2. 同步结束后执行“微任务队列”;
  3. 最后执行“宏任务队列”。

🍡 类比:

“先做现在的单(同步任务), 再处理便签备忘录(微任务), 最后处理新来的外卖订单(宏任务)。”


🧠 记忆口诀:

“同步先干完,微任立刻办,宏任排队慢。”


🧱 第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事件循环同步先、微快、宏慢
40Promise 原理状态三变,回调触发

💬 总结彩蛋:

面试官问“说说你了解的事件循环和异步机制?” 你就微笑说: “先上同步单,再处理微任务‘小票’,最后再接下一单外卖(宏任务)~”🍵😎

🧩 第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页总结表

页码主题一句话记忆
41Less作用域块内有效,外层不扰
42网页进度条NProgress 快,原生帧动滑
43图片懒加载滚动听,新法看
44Cookie管理发卡存身、防听防窥
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

  1. 先问浏览器:有缓存吗?
  2. 没有 → 问操作系统;
  3. 再没有 → 向本地 DNS(比如 8.8.8.8)查询;
  4. 本地 DNS 递归向根域名服务器、顶级域名服务器查询;
  5. 最后返回 IP 给浏览器缓存下来。

🍡 类比:

你要找“奶茶小可爱店”的地址: 先问自己记不记得(浏览器缓存)→ 问同事(系统缓存)→ 查电话簿(DNS)。


💡 口诀记忆:

“浏览器 → 系统 → 本地DNS → 根域名 → 目标。”


🧠 第48页:CDN 加速原理(热度:1789)

💡 CDN 是什么?

CDN(Content Delivery Network)= 内容分发网络 👉 把服务器资源(图片、视频、文件)放到离用户更近的节点。


✨ 工作流程

1️⃣ 用户访问网站; 2️⃣ DNS 把请求指向最近的 CDN 节点; 3️⃣ 节点缓存资源; 4️⃣ 下次访问,直接从该节点返回。


🧩 优点

  • 加快访问速度
  • 降低源站压力
  • 提升稳定性(冗余备份)

🍰 生活类比:

你点奶茶时,系统会指派“离你最近的分店”接单, 这样奶茶送得快、店员也不累!🧋🚴‍♂️


💡 口诀记忆:

“就近分配,缓存复用。”


🧮 第49页:前端本地存储方案(热度:641)

🧠 常见三种

类型容量过期特点
localStorage~5MB永不过期长期保存用户偏好
sessionStorage~5MB关闭浏览器即失效会话级
cookie4KB可设定时间会自动随请求发送

📘 示例

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缓存机制强缓存不请求,协商要确认
47DNS 解析浏览器→系统→DNS→根→目标
48CDN 加速就近分配,缓存复用
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页复习卡

页码主题一句话记忆
51window全局风险挤冰箱、撞名字、被偷走
52SEO优化语义标签 + SSR 预渲染
53小程序双线程脑袋逻辑 + 脸蛋视图
54请求超时重试race 控时限,retry 三次试
55data-*属性杯底标签藏信息

💬 可爱收尾彩蛋:

面试官问你:“为什么小程序要两个线程?” 你眨眼笑说: “一个管脑袋(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最优雅
58Canvas 海报生成一层一层画,导出当分享

💬 小笨蛋彩蛋总结:

面试官问你:“上拉加载的原理是什么?” 你可以微笑说: “其实就像奶茶机原料快没了,我监听到了滚动触底事件,自动加料~不让顾客等!”🧋✨

🧩 第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);
  • 适合缓存大文件、图片、视频;
  • 可配合 idbdexie.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 翻译机
63CSS 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.jsonsideEffects 的作用(热度: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页复习卡

页码主题一句话记忆
66Node CLI 工具process拿参,fs造文件
67sideEffects没副作用删干净,有样式别乱清
68script区别普通堵路,async乱序,defer稳序
69SPA哈希路由Hash不刷新,History需配置
70React换主题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页复习卡

页码主题一句话记忆
76vue-cli作用一键脚手架,配置都打包
77百万任务不卡死分批切片,不阻塞线程
78fetch区别headers标签,body内容
79ESLint原理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页:总结页(复盘)

页码主题一句话记忆
81React 懒加载进视口再加载,懒得聪明
82React Router 区别Hash快但丑,History美但难
83行内 vs 块级桌子独行,奶茶杯并排
84requestIdleCallback忙完主活再补空

💬 小可爱总结一下:

面试官:“说说你在 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.onerror
  • try/catch
  • unhandledrejection → 捕获 JS 异常和 Promise 错误。

2️⃣ 日志上报

window.onerror = function (msg, src, line, col, err) {
  sendToServer({ msg, src, line, col, stack: err?.stack });
};

3️⃣ 性能监控

  • 页面加载时间、接口响应时间;
  • 可用 Performance API。

4️⃣ 熔断机制

  • 网络异常时快速失败;
  • 用兜底内容代替真实数据。

🍡 生活类比:

就像奶茶机坏了,系统会:

  • 立刻报警(错误监控)
  • 记录日志(日志上报)
  • 临时卖瓶装奶茶(兜底方案)

💡 口诀记忆:

“监控报错,兜底保活。”


🌟 第90页复盘卡片

页码主题一句话记忆
86DocumentFragment托盘装好再端上
87git pull vs fetchfetch只看,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监控
92V8 JIT机制热代码→机器码
93Cookie解析分号切+等号取键
94Scoped样式隔离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尾递归优化最后一步是自己,不叠栈
97Promise递归分段异步,防爆栈
98防抖节流防抖等停,节流限频
99TS类型共享类型提公库,路径配alias
100标签页通信公告栏+对讲机模式

💬 小可爱总结:

“我不仅能写递归不炸,还能跨标签页聊天~ 用防抖节流节能省电,TypeScript 类型也井井有条~🧋✨”