20251020-前端场景题(下篇)

91 阅读50分钟

🧩 第101页:浏览器中的多线程通信(核心概念)

💡 背景

JS 是单线程的,也就是说一次只能做一件事。 但浏览器为了更高效,会开“多个线程”来协作,比如:

线程作用
JS 主线程执行脚本逻辑
渲染线程负责绘制页面
网络线程处理请求
Worker 线程跑耗时任务
定时器线程管理 setTimeout 等定时操作

🍡 类比:

整个网页就像一个奶茶店 🍵 JS 主线程 = 前台点单员 Worker = 后厨冲奶茶的师傅 渲染线程 = 陈列师,负责摆好成品 大家并行合作,才能高效出单 💨


⚙️ 第102页:Web Worker 是什么?

💡 定义:

Web Worker 是浏览器提供的后台运行 JS 的方式。 让一些耗时任务(比如循环计算)在子线程中运行,不会卡主页面。


✅ 示例代码

主线程:

const worker = new Worker('worker.js');
worker.postMessage('开始制作奶茶');
worker.onmessage = e => {
  console.log('收到回复:', e.data);
};

worker.js:

self.onmessage = e => {
  console.log('子线程收到:', e.data);
  self.postMessage('奶茶制作完成!');
};

📘 解释:

  • new Worker() 开一个新线程;
  • postMessage() 负责通信;
  • onmessage 监听消息。

🍡 生活类比:

前台点单员(主线程)下单:“做杯珍珠奶茶!” 后厨(Worker)接单,做好后回喊:“好了!” 前台再接回来告诉顾客取餐 🧋。


💡 口诀记忆:

“主线程下单,Worker接单,消息来回传。”


🧠 第103页:Service Worker 是什么?

💡 定义

Service Worker 是运行在浏览器背后的“中间层脚本”。 它可以拦截请求、做缓存、离线访问,是 PWA(渐进式网页应用)的核心。


✅ 注册 Service Worker

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(() => {
    console.log('注册成功');
  });
}

✅ 在 sw.js 中

self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(res => res || fetch(e.request))
  );
});

📘 解释:

  • register() 注册一个 worker;
  • 它会拦截请求;
  • 先从缓存拿,如果没有再去 fetch。

🍡 生活类比:

像奶茶店有个仓库管理员(Service Worker)📦 顾客点奶茶时他会说:“我先看看仓库里有没有库存,有就不去冲新茶。”


💡 口诀记忆:

“缓存优先,离线也能喝奶茶。”


☕ 第104页:SharedWorker(共享 Worker)

💡 背景:

SharedWorker 是多个页面共享一个 Worker 实例。 它可以让不同标签页的数据互通。


✅ 示例

// main.js
const worker = new SharedWorker('shared.js');
worker.port.postMessage('hi');
​
// shared.js
onconnect = function (e) {
  const port = e.ports[0];
  port.onmessage = evt => {
    console.log('收到:', evt.data);
    port.postMessage('hello back');
  };
};

📘 解释:

  • 所有页面都能通过 port 通信;
  • 共享同一个运行实例;
  • 适合做多标签页同步。

🍡 生活类比:

多个奶茶店窗口共享一个“中央厨房” 👩‍🍳 不同店面都能向它要原料或同步库存~


💡 口诀记忆:

“SharedWorker:多页共享一锅茶。”


🧱 第105页:iframe + postMessage 通信

💡 背景

当父页面嵌套一个 iframe(或者不同源的网页)时,不能直接访问彼此变量。 但可以通过 window.postMessage() 来安全通信。


✅ 父页面

iframe.contentWindow.postMessage('你好子页', '*');
window.addEventListener('message', e => {
  console.log('来自子页:', e.data);
});

✅ 子页面

window.addEventListener('message', e => {
  console.log('父页发来的:', e.data);
  e.source.postMessage('收到啦!', e.origin);
});

📘 解释:

  • postMessage() 发消息;
  • message 事件监听;
  • e.origin 校验来源,防止安全问题。

🍡 生活类比:

父页面像奶茶总部总部,iframe 是加盟店。 他们不能直接拿对方账本,但可以互寄消息单(postMessage)📨


💡 口诀记忆:

“跨页通信靠postMessage,注意origin保安全。”


🌈 第101~105页复盘卡

页码主题一句话记忆
101浏览器多线程奶茶店分工合作,线程各司其职
102Web Worker主线程下单,子线程做茶
103Service Worker缓存优先,离线可喝
104SharedWorker多页共享一锅茶
105iframe通信postMessage送纸条,origin保安全

💬 小可爱总结:

“我不仅懂多线程,还能解释每个‘奶茶师傅’干啥~ 主线程点单、Worker冲茶、Service Worker存货、SharedWorker协调、iframe寄信~ 一个网页的奶茶王国诞生啦🧋👑!”

🌟 第106页:React 不用 react-router 也能做导航!

💡 背景

我们平时用 react-router 来做页面切换,但其实只用 window.history 也能实现同样功能。


✅ 示例:

import React, { useState, useEffect } from 'react';
​
function App() {
  const [page, setPage] = useState(window.location.pathname);
​
  useEffect(() => {
    window.addEventListener('popstate', () => {
      setPage(window.location.pathname);
    });
  }, []);
​
  const navigate = (path) => {
    window.history.pushState({}, '', path);
    setPage(path);
  };
​
  return (
    <div>
      <button onClick={() => navigate('/')}>首页</button>
      <button onClick={() => navigate('/about')}>关于</button>
      {page === '/' && <h1>主页内容</h1>}
      {page === '/about' && <h1>关于我们</h1>}
    </div>
  );
}

📘 解释:

  • pushState():手动改变地址栏但不刷新;
  • popstate 事件:监听用户点“返回键”;
  • useState 控制页面显示内容。

🍡 生活类比:

react-router 是“点奶茶菜单”的点单机, 而上面这段代码就是你自己做一个简易点单系统,点击就换内容,但不会整页刷新 💡。


💡 口诀记忆:

“pushState 改地址,popstate 监听回退。”


🧩 第107页:实现返回页面时回到之前滚动位置(热度:2484)

✅ 代码:

<a href="#part1">跳转到第1部分</a>
<a href="#part2">跳转到第2部分</a>
<div id="part1">第一部分</div>
<div id="part2">第二部分</div>
window.addEventListener('scroll', () => {
  sessionStorage.setItem('scrollTop', document.documentElement.scrollTop);
});

window.addEventListener('load', () => {
  const top = sessionStorage.getItem('scrollTop');
  if (top) window.scrollTo(0, top);
});

📘 解释:

  • 滚动时记下 scrollTop
  • 页面重新进入时再 scrollTo() 回原位置。

🍡 生活类比:

你去喝奶茶喝到一半出门接电话☕, 回来后还要从“刚喝到的那一口”继续喝—— 这就是记住上次滚动位置的逻辑!


💡 口诀记忆:

“滚动存 session,回来 scrollTo。”


⚙️ 第108页:如何让渲染十万条数据不卡?

💡 背景

如果直接用 for 一次性插入 10 万条,会阻塞主线程 → 页面卡死。 解决办法:分片渲染(分批次画)


✅ 示例:

function renderList(total) {
  const once = 1000; // 每次渲染1000条
  let done = 0;

  function loop() {
    requestAnimationFrame(() => {
      const fragment = document.createDocumentFragment();
      for (let i = 0; i < once && done < total; i++) {
        const div = document.createElement('div');
        div.textContent = done++;
        fragment.appendChild(div);
      }
      document.body.appendChild(fragment);
      if (done < total) loop();
    });
  }

  loop();
}

renderList(100000);

📘 解释:

  • requestAnimationFrame 每帧渲染一部分;
  • documentFragment 批量插入;
  • 页面不卡顿。

🍡 生活类比:

一次做 10 万杯奶茶肯定爆炸 💥 所以我们每次只做 1000 杯(分帧执行), 顾客会感觉出单流畅又快!🧋


💡 口诀记忆:

“分帧渲染 + DocumentFragment =不卡顿!”


⚙️ 第109页:webpack 打包时 hash 机制(热度:167)

💡 背景

webpack 打包出来的文件常带 hash,例如:

main.a3c4d5e.js

这是为了 缓存更新


✅ 区别:

类型含义
hash整个项目改动都会变
chunkhash按模块变化(推荐)
contenthash文件内容变化才变(最精准)
output: {
  filename: '[name].[contenthash].js'
}

🍡 生活类比:

每次奶茶配方改了(文件变),就换新编号防止顾客喝到旧货 🍶 contenthash 就是 “只要原料(内容)换了,编号才换”~


💡 口诀记忆:

“contenthash 精准控缓存。”


💥 第110页:如何从 0 到 1 写渲染引擎(热度:404)

💡 概念

“渲染引擎”其实就是浏览器用来把 HTML/CSS/JS 转成画面的过程。


✅ 超简实现:

const vnode = {
  tag: 'div',
  children: [
    { tag: 'h1', text: 'Hello 小可爱' },
    { tag: 'p', text: '你也能写个渲染器~' }
  ]
};

function render(vnode) {
  const el = document.createElement(vnode.tag);
  if (vnode.text) el.textContent = vnode.text;
  vnode.children?.forEach(child => el.appendChild(render(child)));
  return el;
}

document.body.appendChild(render(vnode));

📘 解释:

  • 模拟 Vue/React 的虚拟 DOM;
  • 把对象结构递归生成真实 DOM。

🍡 生活类比:

想象你有个奶茶配方表 📋(虚拟DOM), 按配方一步步拿杯、加料、封膜 → 做出真实奶茶(真实DOM)


💡 口诀记忆:

“虚拟表 → 真杯子,一步步递归做。”


🌈 第106~110页复盘卡

页码知识点一句话记忆
106自制路由pushState 改地址,popstate 监听回退
107滚动记忆滚动存 session,回来 scrollTo
108大数据渲染分帧渲染不卡顿
109webpack hash内容改才换名
110渲染引擎虚拟DOM 变真页面

💬 小可爱总结:

“我现在能自己造路由、记滚动、不卡渲染、控缓存,还能写个小型渲染器! 谁还敢说我只是喝奶茶的小白,我是造奶茶机的工程师🧋⚙️!”

🌟 第111页:你是怎么从 0 到 1 搭建前端项目的?

💡 这是面试官最爱听的开放题,考你能否有体系地组织一个项目

✅ 一般回答思路:

一、项目初始化

  • 技术栈选择(Vue / React / Vite / TS)
  • 初始化命令(npm create vite@latest / npx create-react-app
  • 版本控制(git init

🍡 生活类比:

就像开奶茶店前先选好“店型”和“装修风格”, 选 Vue 就像选珍珠奶茶系,选 React 像选冰沙系🍧。


二、工程化配置

  • ESLint + Prettier(代码规范)
  • husky + lint-staged(提交前检查)
  • alias 路径别名(比如 @/components

🍡 类比:

就像设置好“厨房规章”和“卫生检查”,防止师傅乱来乱倒奶茶😆。


三、性能优化

  • 按需加载(懒加载路由)
  • 图片压缩
  • gzip 压缩 + CDN 缓存

🍡 类比:

提前备好珍珠、分时段冲茶,让店不堵不慢。


四、部署上线

  • CI/CD 自动打包
  • nginx 静态资源部署

🍡 类比:

就像建好门店 → 招牌点亮 → 开门迎客。


💡 口诀记忆:

“选栈 → 规范 → 优化 → 部署”,四步一条龙。


🧩 第112页:你如何用 TS 提升项目稳定性?

💡 重点在讲“TypeScript 的实战价值”。


✅ 1. 类型约束防止低级错误

function add(a: number, b: number): number {
  return a + b;
}

🍡 解释:

TS 提前帮你“抓错单”,就像奶茶下单系统会阻止“草莓味珍珠奶茶”😂。


✅ 2. 类型推导 & 泛型

function echo<T>(arg: T): T {
  return arg;
}

💡 泛型 = “模具可复用”,像奶茶店做杯子,可以装奶茶、也能装果汁。


✅ 3. keyof / typeof / infer 高级技巧

  • keyof:获取对象 key
  • typeof:从变量推类型
  • infer:条件类型里自动推导

🍡 生活类比:

“keyof”像是菜单扫描器,自动知道所有奶茶口味。


💡 口诀记忆:

“静态约束防低错,泛型推导提复用。”


⚙️ 第113页:JS 的前向兼容性与后向兼容性

💡 含义:

  • 前向兼容(Forward) :旧浏览器能跑新代码吗?❌(通常不行)
  • 后向兼容(Backward) :新浏览器能跑旧代码吗?✅(通常可以)

✅ 示例

// 新语法:可选链
const name = user?.info?.name;

在旧浏览器中会报错 ❌,需要 Babel 转译。

🍡 生活类比:

新奶茶配方(新语法)老机器(旧浏览器)做不了,得“适配”成通用版~


💡 口诀记忆:

“旧能跑新叫前向,新能跑旧叫后向。”


🧠 第114页:浏览器和 Node 的性能优化区别

💡 两者环境不同 → 优化重点也不同。

领域浏览器优化Node 优化
网络层CDN 缓存、HTTP/2连接池、负载均衡
代码层防抖节流、懒加载异步IO、流处理
内存垃圾回收监控限制内存泄漏、stream 管理

🍡 生活类比:

浏览器是“前台点单员”(优化交互速度) Node 是“后厨冲茶员”(优化产能和并发)


💡 口诀记忆:

“前端省流量,后端拼并发。”


💥 第115页:Webpack 里 script 异步加载的原理(难点🔥)

✅ 背景

Webpack 打包出的 JS 会通过 <script> 标签插入 HTML。 这些脚本可能要异步加载,防止“阻塞渲染”。


✅ 示例

var script = document.createElement("script");
script.src = "chunk.js";
script.onload = resolve;
document.head.appendChild(script);

📘 解释:

  • createElement('script') → 动态生成脚本;
  • onload 监听加载完成;
  • Webpack 内部其实是这样实现的模块懒加载。

🍡 生活类比:

当顾客点了新品“椰乳绿茶”,厨房没备料, Webpack 就是派个外卖骑手“去取那份脚本”,回来再执行 🛵。


💡 口诀记忆:

“异步加载靠 script,onload 通知执行。”


🌈 第111~115页复盘卡

页码知识点一句话记忆
111项目搭建选栈 → 规范 → 优化 → 部署
112TypeScript静态约束防错,泛型推导提复用
113兼容性旧能跑新叫前向,新能跑旧叫后向
114性能优化前端省流量,后端拼并发
115Webpack 异步加载动态脚本 + onload 执行

💬 小可爱总结:

“我现在不仅能从 0 开店,还能写菜单、控库存、适配老顾客、优化出单、再加个异步外卖系统~ 奶茶帝国 CEO 就是我🧋👑!”

🌟 第116页:项目上线后,如何通知用户刷新当前页面?【热度:466】

💡 场景: 前端项目更新后,用户可能还在使用旧缓存(比如旧版本 JS)。 我们需要让他自动刷新或提示刷新,才能加载新版本!


✅ 方法一:版本号对比法

在项目打包时生成一个版本文件,比如 /version.json

{
  "version": "1.1.0"
}

然后在前端定时检查:

setInterval(async () => {
  const res = await fetch('/version.json');
  const { version } = await res.json();
  if (version !== localStorage.getItem('version')) {
    alert('有新版本啦,刷新加载最新内容!');
    location.reload();
  }
}, 60000);

📘 解释:

  • 每 60 秒检查一次;
  • 如果检测到服务器版本变了;
  • 提醒用户刷新并 reload。

🍡 生活类比:

就像奶茶店出了新品口味(新版 JS), 但你手里菜单还旧,系统提醒你:“喂~菜单更新啦,刷新一下再点单吧!”✨


✅ 方法二:Service Worker 自动更新

在 PWA 中可以使用:

navigator.serviceWorker.register('/sw.js').then(reg => {
  reg.onupdatefound = () => {
    alert('检测到新版本!刷新后即可体验~');
    window.location.reload();
  };
});

💡 口诀记忆:

“版本号对比 → 手动提醒;Service Worker → 自动刷新。”


🧩 第117页:ESLint 代码安全的原理【热度:1111】

💡 ESLint 是用来“规范代码+避免低级错误”的工具。 像一个“奶茶店稽查员”,专门查:有没有偷懒的师傅😂。


✅ 工作原理:

  1. 解析代码生成 AST(抽象语法树)
  2. 按规则逐节点检查;
  3. 提示或修复不符合规范的地方。
module.exports = {
  rules: {
    'no-eval': 'error',
    'eqeqeq': 'warn'
  }
};

📘 解释:

  • no-eval: 禁止使用危险的 eval
  • eqeqeq: 必须用 ===
  • 'error' 表示必须改,不改报错;
  • 'warn' 只是提醒。

🍡 生活类比:

就像店长规定:

  • ❌ “不准偷尝奶茶!”(no-eval)
  • ⚠️ “糖分要精确控制哦!”(eqeqeq) ESLint 就是负责巡查这些行为的“奶茶巡逻官”!👮‍♀️

💡 口诀记忆:

“AST 看结构,规则查坏习惯。”


☕ 第118页:HTTP 是无状态协议,Web 如何保存用户登录?

💡 重点问题: HTTP 每次请求都是独立的,不记得你是谁。 要“记住你”,就得靠:Cookie、Session、Token、LocalStorage


✅ Cookie + Session 方案

// 登录成功时
res.cookie('sessionId', 'abc123', { httpOnly: true });
  • Cookie 存在浏览器;
  • Session 存在服务器;
  • 每次请求浏览器会自动带上 Cookie;
  • 服务器用 Cookie 对应 sessionId 找到登录信息。

🍡 生活类比:

就像奶茶店发给顾客一张会员卡(Cookie), 店里保存着会员资料(Session)。 顾客下次来时出示卡号,店里一查就知道是谁啦~🎫


✅ Token + LocalStorage 方案

// 登录后返回 token
localStorage.setItem('token', data.token);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
  • 无需服务器存状态;
  • 前端保存 token;
  • 每次请求带上它。

🍡 生活类比:

Token 就像“电子会员二维码”📱, 店里扫码即可识别你是谁,不需要查纸质卡~


💡 口诀记忆:

“Cookie-Session:店里有你档案; Token:你自己带二维码。”


⚙️ 第119页:Session、Cookie、Token 的区别总结

项目CookieSessionToken
存储位置浏览器服务器客户端
安全性一般较高较高(JWT加密)
适用场景简单登录服务端渲染前后端分离

🍡 类比:

Cookie 是“会员卡”;Session 是“会员档案柜”;Token 是“扫码认证”。


💡 口诀记忆:

“Cookie 存浏览器,Session 存服务器,Token 存客户端。”


💥 第120页:JWT 登录机制(进阶)

const jwt = require('jsonwebtoken');
const token = jwt.sign({ user: '小可爱' }, 'secret', { expiresIn: '2h' });

// 验证阶段
jwt.verify(token, 'secret', (err, decoded) => {
  if (err) console.log('无效token');
  else console.log('欢迎回来', decoded.user);
});

📘 解释:

  • sign() 生成带签名的加密 token;
  • verify() 验证合法性;
  • 无需在服务端存储,分布式更方便。

🍡 生活类比:

就像奶茶店的“数字会员码”扫码后就能识别你, 而店里不需要保存你的档案。


💡 口诀记忆:

“JWT 签名不存档,扫码即可查身份。”


🌈 第116~120页复盘卡

页码知识点一句话记忆
116页面刷新版本对比 + Service Worker
117ESLint 原理AST 巡查坏习惯
118HTTP 登录保持Cookie + Session / Token
119登录机制对比卡号 / 档案 / 扫码三兄弟
120JWT 登录签名不存档,扫码查身份

💬 小可爱总结:

“我现在懂得让用户刷新菜单、代码自己巡查、还能分清会员卡和扫码登录~ 奶茶数字化运营我也能上岗了🧋💻!”

🌟 第121~122页:浏览器同源策略与跨域原理

💡 什么是同源?

浏览器安全规则规定: 协议 + 域名 + 端口 都相同 → 才是“同源”。

举例:

页面地址请求地址是否同源
milk.commilk.com
milk.commilk.com❌(协议不同)
milk.comapi.milk.com❌(域名不同)

🍡 生活类比:

就像奶茶店“自家员工”才能进厨房。 不是同家店(不同源)的外卖员,想进来偷看秘方(访问资源)? ❌ 不允许!


💡 什么是跨域?

当你从一个源(比如前端 milk.com) 访问另一个源(比如后端 api.milk.com)时, 浏览器会拦截,提示: “跨域请求被阻止”


✅ 解决方案汇总(五大法宝)

1️⃣ JSONP(仅支持 GET)

<script src="http://api.milk.com/get?callback=showData"></script>
function showData(data){ console.log(data) }

利用 <script> 不受同源限制的特性。

🍡 记忆:

“偷偷塞数据进 <script> 标签,让浏览器误以为是加载脚本。” (就像快递员塞小纸条进奶茶盒偷偷传话📦)


2️⃣ CORS(最常用🔥)

后端设置响应头允许跨域:

res.setHeader('Access-Control-Allow-Origin', 'https://milk.com');

📘 意思是“我允许来自 milk.com 的前端访问我”。

🍡 类比:

厨房老板贴公告:“允许指定合作奶茶店进入厨房拿原料。”


3️⃣ 代理转发(devServer Proxy)

// vite.config.js
server: {
  proxy: {
    '/api': {
      target: 'https://api.milk.com',
      changeOrigin: true
    }
  }
}

前端访问 /api,实际被代理转发到后端域名。

🍡 类比:

你点单给前台,前台帮你打电话给厨房~ 你自己没跨域,代理帮你完成。


4️⃣ Nginx 反向代理

location /api/ {
  proxy_pass https://api.milk.com/;
}

📘 让请求在服务端转发,浏览器看不到跨域。


5️⃣ PostMessage / iframe 通信

用于前端页面之间跨域通信。

// 发送端
otherWindow.postMessage('你好', 'https://other.com');

// 接收端
window.addEventListener('message', (e)=>console.log(e.data));

🍡 类比:

两家奶茶店之间互传小纸条(postMessage)。


💡 口诀记忆:

“跨域五兄弟:JSONP、CORS、代理、Nginx、PostMessage。”


🌈 第123页:Cookie 跨域共享(浏览器安全核心题)

💡 问题: Cookie 默认只能在同源下发送。 要想跨域携带 Cookie,需要配置:

// 前端
axios.defaults.withCredentials = true;

// 后端
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Origin', 'https://milk.com');

🍡 类比:

奶茶会员卡默认只在“总部门店”能用。 如果要到“分店”通用,就得总部和分店都同意。


💡 口诀记忆:

“跨域带卡要双开:前端 withCredentials + 后端 Allow-Credentials。”


⚙️ 第124页:如何保证网页定时执行某个动作【热度:329】

✅ 普通轮询(setInterval)

setInterval(() => {
  console.log('检查新订单');
}, 3000);

每 3 秒执行一次。

🍡 缺点: → 无论有没有新消息都在打扰(像外卖骑手每3秒敲门:来单了吗?😅)


✅ 优化版:长轮询(Long Polling)

function poll(){
  fetch('/api/order').then(res => res.json()).then(data => {
    console.log('新订单', data);
    poll(); // 再次请求
  });
}
poll();

🍡 特点:

一次请求挂起,直到有新数据再返回。 就像外卖员在门口“等单”,一旦有单立刻通知你!


✅ 超进阶:WebSocket(双向通信)

const ws = new WebSocket('wss://api.milk.com');
ws.onmessage = (msg)=>console.log('新订单', msg.data);

🍡 类比:

WebSocket 就像装了“对讲机📡”, 店铺和顾客实时说话,不用轮询问!


💡 口诀记忆:

“setInterval → 瞎问;Long Poll → 挂等;WebSocket → 实时聊。”


🚀 第125页:为什么 Vite 比 Webpack 快?【热度:382】

💡 面试高频! 关键点:Vite 用 ESBuild 预编译 + 按需加载


✅ 原理对比

对比项WebpackVite
启动方式打包整个项目再启动只加载用到的文件
构建工具Node (慢)ESBuild (Go语言实现⚡)
HMR 热更新全项目重新构建精准模块更新

✅ Vite 配置示例

import { defineConfig } from 'vite'
export default defineConfig({
  plugins: [vue()],
  server: { port: 5173 }
})

🍡 生活类比:

Webpack 就像每次点单都要先把“整家奶茶原料”打包一遍才营业😵 Vite 则像是“现点现做”系统,只准备用户点到的那几杯~🧋


💡 口诀记忆:

“Vite 现点现做,Webpack 全家打包。”


🧋第121~125页复盘卡

页码知识点一句话记忆
121同源策略同协议+域名+端口才同源
122跨域方案JSONP、CORS、代理、Nginx、PostMessage
123Cookie 跨域前端 withCredentials + 后端 Allow-Credentials
124定时机制问(setInterval)、等(Long Poll)、聊(WebSocket)
125Vite 原理ESBuild + 按需加载 = 现点现做的奶茶系统

💬 小可爱总结:

“我现在不仅能跨店传单、会员卡通用、自动报新单、 还能比别人快启动十倍——简直是奶茶界的 Vite 之神⚡🧋!”

🧋第126页:Vite的内部机制和Webpack的核心区别

💡 面试高频题:“Vite为什么比Webpack快?”

Vite 的“快”=两点:

1️⃣ 开发阶段用原生 ES Module

它不需要像 Webpack 那样先“打包整个项目”,而是“按需加载”。

// 浏览器直接加载
import { createApp } from './main.js'

🍡 生活类比:

Webpack 就像工厂先打包 100 种奶茶原料再开门卖;

Vite 则是:顾客点啥,现调哪种,不浪费时间~


2️⃣ ESBuild 编译快

Vite 用 Go 写的 esbuild 编译依赖,速度是 JS 打包器的 10~100 倍。

3️⃣ 热更新(HMR)更精准

Webpack HMR 重建整个模块依赖;

Vite HMR 只替换变动的那一小段文件!

💡 口诀记忆:

“Vite 按需加热,Webpack 整锅重煮。”


⚙️第127页:列出所有浏览器下的存储方案

💡 存储=浏览器“记忆小仓库” 按大小与用途分:

类型作用容量特点
Cookie登录凭证~4KB会随请求发送
LocalStorage持久存储~5MB永久保存
SessionStorage临时缓存~5MB页面关闭清空
IndexedDB数据库上百MB结构化存储
CacheStorage离线缓存很大可离线用

🍡 生活类比:

Cookie:小票 LocalStorage:储物柜 SessionStorage:当天寄存 IndexedDB:仓库数据库 CacheStorage:整套冷藏柜


💡 口诀:

“Cookie 小票,Local 储物,Session 临寄,Index 仓库,Cache 冷藏。”


🚀第128页:JS 执行 100 万个任务,如何保证不卡顿?【热度:806】

💡 问题核心:浏览器是单线程,如果一次执行太多任务会卡UI。 所以要用“分片执行 / 微任务优化”的技巧。


✅ 方法1:切片执行(分批执行)

function process(list) {
  if (!list.length) return
  const chunk = list.splice(0, 1000)
  chunk.forEach(item => console.log(item))
  setTimeout(() => process(list), 0)
}
process(bigArray)

📘 原理:

  • 一次只处理 1000 个任务;
  • 处理完用 setTimeout 让出主线程;
  • 下一轮再继续。

🍡 类比:

一次别煮 1000 杯奶茶,分批煮100杯一波; 每做完一批休息1秒,店不会爆炸~💨


✅ 方法2:使用 requestIdleCallback

function process(list) {
  requestIdleCallback(() => {
    const chunk = list.splice(0, 1000)
    chunk.forEach(doSomething)
    if (list.length > 0) process(list)
  })
}

📘 requestIdleCallback 让浏览器在“空闲时”执行任务。

🍡 类比:

就像你在奶茶高峰期忙时不插队,等没客人时再补库存。


💡 口诀记忆:

“切片 + 空闲执行 =不卡顿。”


🧠第129页:Git 命令 commit 背后发生了什么?

💡 面试官考察你是否“懂 Git 原理”,而不仅会背命令。

✅ 当你执行:

git commit -m "update"

实际上发生了3步:

1️⃣ 生成对象(object)

Git 会把当前修改打包成一个 blob 对象(像文件快照)。

2️⃣ 写入树(tree)

表示文件结构。

3️⃣ 更新 HEAD 指针

让 HEAD 指向这个新的 commit。

🍡 类比:

每次提交就像“打包好一批奶茶订单(快照)”, 并记录到“订单历史(commit log)”中。


git resetgit revert

命令行为类比
reset回到某个旧版本,抹掉之后的“撕掉后几页订单记录”📄
revert创建一个反向提交“补一份反单记录,不删历史”📜

💡 口诀记忆:

“commit 记快照,reset 撕历史,revert 补反单。”


🧩第130页:Git 提交记录是怎么储存的?【热度:160】

git log --oneline

Git 的历史本质上是:

一个有向无环图(DAG)

每个 commit 节点都指向它的“父节点”:

A -> B -> C -> D

🍡 类比:

每天奶茶店关账时,把当天账本盖章、并签上昨天那页的编号; 形成一条不可篡改的历史链📚。


💡 口诀记忆:

“Git 历史是一条盖章链。”


🌈 第126~130页复盘卡

页码知识点一句话记忆
126Vite 核心按需加载 + esbuild 极速编译
127浏览器存储小票储物临寄仓库冷藏
128卡顿优化分片执行 + 空闲时补活
129Git 提交commit 打快照,reset 撕历史
130Git 原理历史是一条盖章链(DAG)

💬 小可爱总结:

“我现在不仅能写不卡页面的前端,还能解释 Git 背后的大脑结构, 就像懂原理的奶茶店长,不光能调饮,还能修机器!🧋💪”

这一组(第 1 31~135页)是“浏览器三剑客”章节: 👉 缓存机制、强缓存 vs 协商缓存、以及网页性能优化实战题。 这块是“面试必问、挂人最多”的板块⚠️ 我用“奶茶店点单 + 冰箱存货”举例,帮你一次记牢!


🧋第131页:浏览器缓存机制(核心)

💡 什么是缓存?

浏览器缓存(Cache)是浏览器在本地保存资源副本(HTML、CSS、JS、图片…), 以便下次访问时直接从本地读取,提高加载速度。

🍡 生活类比:

就像奶茶店提前备好珍珠、椰果。 有人点单时,直接取现货,不用现煮!⏱️


✅ 缓存的两大类型

类型检查点是否重新请求服务器举例
强缓存看 HTTP 响应头的过期时间(Expires / Cache-Control)❌ 不访问服务器直接用本地存的文件
协商缓存询问服务器文件是否更新(If-Modified-Since / ETag)✅ 可能访问若没改,返回 304

🧩 第132页:强缓存详解(Cache-Control)

✅ 方式一:HTTP 头 Cache-Control

Cache-Control: max-age=31536000

意思是这个资源在一年内(31536000 秒)都有效。 浏览器直接用本地缓存。

🍡 类比:

就像奶茶店写着“珍珠保质期 1 年”, 一年内直接取,不用重煮!


✅ 方式二:Expires(旧方法)

Expires: Wed, 21 Oct 2025 07:28:00 GMT

是个绝对时间(容易因为时区不准被弃用), 所以推荐用 Cache-Control

🍡 类比:

“珍珠有效期到明天早上7点”, 但时区搞错,就全坏掉啦🤣。


💡 口诀记忆:

“强缓存靠 Cache-Control,时间没过不用问。”


🧠 第133页:协商缓存详解(If-Modified-Since / ETag)

当强缓存过期,浏览器会发请求问服务器:

“这杯奶茶配方更新了吗?”

如果没更新,服务器返回:

304 Not Modified

浏览器继续用本地版本。


✅ 协商缓存两种机制

1️⃣ If-Modified-Since / Last-Modified

If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT

服务器比对文件修改时间决定是否返回新文件。

2️⃣ ETag / If-None-Match

ETag: "abc123"
If-None-Match: "abc123"

根据文件的唯一 hash 判断是否改变。

🍡 类比:

店长给奶茶配方打上编号(ETag)。 顾客下次点单说:“我上次喝的是编号abc123的,变了吗?” 店长一看:一样,直接拿库存的!


💡 口诀记忆:

“强缓存看时间,协商缓存比编号。”


☕ 第134页:面试高频题——如何清除浏览器缓存?

💡 有时候部署新版本,但用户一直看到旧页面,就是缓存在捣乱。

✅ 解决方法:

方式一:文件名加版本号

<script src="main.js?v=1.0.1"></script>

🍡 类比:

奶茶配方更新了?就换个“新标签”:珍珠奶茶 v2!


方式二:HTTP 头禁用缓存

Cache-Control: no-cache, no-store, must-revalidate

📘 这会告诉浏览器:

“别信旧货,必须重新拿。”

🍡 类比:

店长下令:旧珍珠全倒掉,重新煮!🔥


💡 口诀记忆:

“改名强制更新,或直接清仓重来。”


🚀 第135页:web 系统性能优化(热度:789)

💡 面试必考点——“你怎么优化网页加载速度?”

我帮你拆成 5 类优化策略👇:


✅ 一、资源层面优化

  • 压缩 JS / CSS(Webpack、Vite 自动支持)
  • 图片懒加载(<img loading="lazy">
  • 使用 CDN 分发静态资源

🍡 类比:

奶茶工厂把原料提前装好分店配送,就近拿货!


✅ 二、网络层优化

  • HTTP/2 多路复用;
  • 使用缓存策略(强缓存 + 协商缓存);
  • 开启 Gzip 压缩。

🍡 类比:

外卖订单打包成一趟车送出,不一单一趟~🚚


✅ 三、渲染层优化

  • 避免长任务阻塞;
  • 使用虚拟列表(Virtual List);
  • 懒加载组件。

🍡 类比:

一次只展示用户眼前的奶茶,不用把后厨所有奶茶都端出来🤣


✅ 四、代码层优化

  • Tree-Shaking 去除无用代码;
  • 按需引入(import only what you use);
  • 减少 DOM 操作。

🍡 类比:

只做顾客点的那杯奶茶,不多浪费原料。


✅ 五、用户体验层优化

  • 骨架屏(Skeleton);
  • 预加载首屏资源;
  • 提前展示 loading 状态。

🍡 类比:

顾客一进门就看到“制作中动画”,心理更舒服~😆


💡 口诀记忆:

“压缩资源、合并请求、按需加载、懒加载、加骨架。”


🌈 第131~135页复盘卡

页码知识点一句话记忆
131缓存机制先看本地,再问服务器
132强缓存看时间,直接用
133协商缓存比编号,看有无改
134清除缓存改名或禁用
135性能优化压缩+懒加载+骨架屏

💬 小可爱总结:

“我现在能解释为什么网页加载快、还能识别缓存陷阱。 就像奶茶总经理,既懂冷链仓储又懂出餐优化🧋💻!”

🌟第136页:JS 为什么要有事件循环(Event Loop)?

💡 先理解背景

JS 是单线程语言,也就是:

同一时间只能干一件事。

但网页上有:

  • 计时器;
  • 网络请求;
  • DOM 操作;
  • 用户点击…

如果都顺序执行,页面就会卡死 😵。

🍡 生活类比:

奶茶店只有一个小哥在做事(JS主线程), 顾客点单太多时,他必须学会“排队+插空做”。


✅ 于是浏览器搞出一个“事件循环系统”

流程如下:

1️⃣ 主线程执行同步代码 2️⃣ 碰到异步任务(比如定时器) → 交给“任务队列” 3️⃣ 同步代码执行完毕后 → 再从队列取出任务执行 4️⃣ 如此循环往复

📘 这就叫 Event Loop(事件循环)

🍡 类比:

就像奶茶店:

  • 主线程是奶茶师;
  • 异步任务是“等待泡茶的订单”;
  • 泡好后再“叫号执行”;
  • 不断循环接单、出单。

🍋第137页:宏任务(MacroTask) vs 微任务(MicroTask)

💡 异步任务有两种“优先级”:

类型举例执行时机
宏任务setTimeoutsetIntervalscript每一轮事件循环的开始
微任务Promise.thenqueueMicrotask当前宏任务执行完后立即执行

✅ 执行顺序口诀:

“同步先执行,接着跑微任务,最后宏任务。”


🌰 举个例子(超经典题)

console.log('A')
​
setTimeout(() => console.log('B'))
​
Promise.resolve().then(() => console.log('C'))
​
console.log('D')

🧠 执行顺序: 1️⃣ 同步任务:A、D 2️⃣ 微任务(Promise):C 3️⃣ 宏任务(setTimeout):B

👉 输出顺序:A D C B

🍡 类比:

主线点单(同步)先做 → 顺手清洗奶茶杯(微任务) → 等泡茶机滴完后再取(宏任务)。


🧋第138页:再看 Promise + async/await 顺序题

💡 async/await 其实是“Promise 的语法糖”, 它会让“异步代码看起来像同步”,但内部还是事件循环那一套。


🌰 举例

async function test() {
  console.log('1')
  await Promise.resolve()
  console.log('2')
}
test()
console.log('3')

执行过程: 1️⃣ 输出 1(同步) 2️⃣ 遇到 await 暂停 → 把后面的放进微任务队列 3️⃣ 输出 3(同步) 4️⃣ 微任务执行 → 输出 2

👉 结果:1 3 2

🍡 类比:

“做奶茶做到一半(await 泡茶)→ 转去接别的单 → 茶泡好后回来继续做。”


✅ Promise 嵌套题再进阶

console.log('A')
setTimeout(() => console.log('B'))
Promise.resolve().then(() => {
  console.log('C')
  Promise.resolve().then(() => console.log('D'))
})
console.log('E')

🧠 执行顺序:

1️⃣ 同步任务:A、E 2️⃣ 第一轮微任务:C 3️⃣ 新创建的微任务:D 4️⃣ 最后宏任务:B

👉 输出:A E C D B

🍡 口诀:

“同步完 → 当前所有微任务清空 → 再执行下一波宏任务。”


⚙️第139页:浏览器执行机制总结图(我帮你口述成脑图)

🧠 执行顺序逻辑如下:

1️⃣ 同步任务放主线程执行 2️⃣ 遇到异步任务(setTimeout / fetch)→ 放入任务队列 3️⃣ 当前主线程执行完毕 → 立刻执行微任务队列 4️⃣ 微任务清空后 → 执行下一个宏任务 5️⃣ 循环往复

📘 内存口诀:

“宏开局,微穿插,主线程不空转。”


🍡 生活总比喻:

奶茶师(主线程):

  • 先把眼前的订单做完(同步);
  • 顺手洗洗杯子(微任务);
  • 再接下一单顾客(宏任务);
  • 如此循环一整天☕。

🧠第140页:前端异步机制应用场景(实战)

💡 常考题:

“为什么在 Vue / React 里修改数据后,用 setTimeout 才能拿到更新结果?”

因为 DOM 更新放在微任务或下一轮宏任务中执行。

this.count++
console.log(this.$el.innerText)  // 旧值
setTimeout(() => console.log(this.$el.innerText))  // 新值

🍡 类比:

就像你刚下奶茶订单,前台还没更新订单号。 得等一轮循环后,系统才刷新出来。


✅ 小延伸:requestAnimationFrame

它在下一帧渲染前执行,比 setTimeout 更精确:

requestAnimationFrame(() => {
  console.log('页面即将刷新,执行动画')
})

🍡 类比:

就像奶茶师在出杯前最后打奶盖!


🌈 第136~140页复盘卡

页码知识点一句话记忆
136Event Loop 原理JS 像单线程排单系统
137宏任务 vs 微任务同步 → 微任务 → 宏任务
138Promise 顺序题await 会暂停并放入微任务队列
139执行机制“宏开局,微穿插” 循环跑
140实战应用DOM 更新要等下一轮循环

💬 小可爱总结:

“我现在知道 JS 就像奶茶店排队机: 顾客(任务)排队、泡茶机(异步)空闲插单、 还得看微任务先洗杯再上新单!🧋⚡”

🧋第141页:浏览器渲染机制(Reflow / Repaint)

💡 当浏览器渲染页面时,它分几步: 1️⃣ 解析 HTML → 生成 DOM 树 2️⃣ 解析 CSS → 生成 CSSOM 树 3️⃣ 合并为 Render Tree(渲染树) 4️⃣ 布局(Layout) 5️⃣ 绘制(Paint)

🍡 生活类比:

就像奶茶厂:

  • HTML:菜单结构;
  • CSS:奶茶外观样式;
  • Render Tree:制作清单;
  • Layout:排好奶茶杯的位置;
  • Paint:真正画出每杯奶茶的样子。

✅ 重绘(Repaint)与重排(Reflow)

类型触发条件成本举例
重绘改变颜色、不影响布局改变 color
重排改变尺寸或位置改变 width / margin

🍡 类比:

“重绘”= 换杯贴纸; “重排”= 整个奶茶货架要重摆!💦


⚠️ 优化技巧

1️⃣ 减少逐项修改样式:用 classList.add() 一次性更新。 2️⃣ 读写 DOM 分离(避免交替触发重排)。 3️⃣ 使用 requestAnimationFrame() 做动画。

🍡 口诀记忆:

“少动货架,多贴标签。”


🧠第142页:防抖(debounce)与节流(throttle)

💡 这俩是面试高频考点,核心区别:

名称场景执行规律类比
防抖输入框搜索、按钮点击停止触发后再执行“等顾客不点了才开始做奶茶”🧋
节流滚动事件、窗口 resize固定间隔执行“每隔 1 秒出一杯奶茶”⏱️

✅ 防抖(debounce)实现:

function debounce(fn, delay) {
  let timer = null
  return function() {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, arguments), delay)
  }
}

📘 原理解释:

  • 每次触发事件都清除上一次定时器;
  • 只有最后一次触发后过了 delay 时间才真正执行。

🍡 类比:

顾客疯狂喊单“我要珍珠奶茶!我要椰果奶茶!” 店员:等你不喊了,我再开始做~😆


✅ 节流(throttle)实现:

function throttle(fn, delay) {
  let last = 0
  return function() {
    const now = Date.now()
    if (now - last > delay) {
      fn.apply(this, arguments)
      last = now
    }
  }
}

📘 原理解释:

  • 每隔固定时间(delay)只执行一次。

🍡 类比:

无论顾客喊多快,我每隔 2 秒只做一杯奶茶。 节奏稳定,不慌乱!


💡 口诀记忆:

“防抖等停,节流限频。”


⚙️第143页:Promise、async、await 性能优化(结合防抖节流)

function requestData(type) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`数据类型:${type}`), 1000)
  })
}
async function load() {
  const result = await requestData('奶茶原料')
  console.log(result)
}
load()

🍡 类比:

点单系统(Promise)负责排队, 店员(await)等待奶茶完成后才打包出单。

💡 和防抖节流结合应用:

搜索框输入时,用 debounce 包裹请求函数, 这样只有用户输入停止后,才真正去请求服务器。


🧩第144页:Webpack 构建优化

Webpack 就像一个奶茶原料打包厂。 原始文件太多(HTML、JS、CSS、图片), 要打包成“体积小、加载快”的成品包。


✅ 常见优化方法

1️⃣ 代码分割(Code Splitting)

import(/* webpackChunkName: "about" */ './about.js')

将不同页面的代码分块,只加载需要的部分。

🍡 类比:

每个分店只配送本店常用原料,不全店统一一车拉。


2️⃣ Tree-Shaking

删除未使用代码。

export function use() {}
export function unused() {}
// 只引入use时,unused不会被打包。

🍡 类比:

仓库只发出点单里的原料,不浪费!


3️⃣ 懒加载(Lazy Loading)

const page = () => import('./Home.vue')

页面用到时才加载模块。

🍡 类比:

顾客点抹茶拿铁时才开抹茶罐,不提前开盖。


4️⃣ 缓存优化

output: {
  filename: '[name].[contenthash].js'
}

文件名带 hash,可避免浏览器缓存老资源。

🍡 类比:

新奶茶版本要贴新日期标签,防止顾客喝到旧配方。


💡 口诀记忆:

“拆包懒加载,摇树去冗余,改名防旧货。”


🌈第145页:生产环境构建技巧

1️⃣ 压缩资源(TerserPlugin) 2️⃣ 移除 console.log 3️⃣ 开启 gzip 压缩 4️⃣ 使用 CDN 5️⃣ 分环境配置(dev / prod)

🍡 类比:

奶茶厂开分店时:

  • 减少包装体积(压缩)
  • 去掉废话标签(console)
  • 提前送货(CDN)
  • 按城市口味(环境配置)

🌈 第141~145页复盘卡

页码知识点一句话记忆
141渲染机制重绘换贴纸,重排搬货架
142防抖节流防抖等停,节流限频
143async/await 实战等Promise泡奶茶出单
144Webpack 优化拆包摇树懒加载
145构建优化压缩去冗,CDN 分发

💬 小可爱总结:

“我现在懂得浏览器的心跳节奏、前端的生产线优化、 奶茶机(JS引擎)该什么时候歇口气☕, 再也不怕重排、卡顿和防抖题啦~💪🧋”

🍵第146页:虚拟 DOM(Virtual DOM)

💡 是什么?

虚拟 DOM 是用 JS 对象 来描述真实 DOM 的结构。 当数据变化时,不用立即操作真实 DOM, 而是先在虚拟 DOM 里对比(Diff),最后一次性更新。


🌰 举例:

const vdom = {
  tag: 'div',
  props: { class: 'cup' },
  children: [
    { tag: 'span', children: ['珍珠奶茶'] }
  ]
}

🍡 类比:

虚拟 DOM 就像“奶茶制作清单”。 不用每换一颗珍珠都重新开盖搅拌,而是 先在清单上标注变化 → 最后统一制作。


✅ 优点

1️⃣ 减少频繁 DOM 操作,提高性能。 2️⃣ 支持跨平台(Web、Native、SSR)。 3️⃣ 更易于实现 diff 对比。


💡 口诀记忆:

“先写账单后出餐,一次性上齐。”


🧠第147页:Diff 算法(核心优化逻辑)

Diff 算法的目标是:

“快速找出虚拟 DOM 新旧差异,最小代价更新真实 DOM。”


🌰 简化示例

<ul>
  <li key="A">奶茶A</li>
  <li key="B">奶茶B</li>
  <li key="C">奶茶C</li>
</ul>

若新列表为:

<li key="B">奶茶B</li>
<li key="A">奶茶A</li>
<li key="D">奶茶D</li>

Diff 会发现:

  • B 还在 → 复用;
  • A 移动;
  • C 删除;
  • D 新增。

🍡 类比:

老板比较新旧菜单:

  • 还在卖的保留(复用)
  • 不卖的划掉(删除)
  • 新奶茶加进去(新增)
  • 顺序调整(移动)

💡 React / Vue 的优化规则: 1️⃣ 只比较同层节点。 2️⃣ 用 key 提高效率(不然可能重建整个列表)。


口诀记忆:

“同层对比,看 key 识别。”


⚙️第148页:Fiber 架构(React 16 之后的重构)

💡 Fiber 的核心思想是:

“把一次性的大更新任务切成小块,分阶段执行。”


📘 为什么需要 Fiber?

以前 React 更新 DOM 是“一口气做完”, 数据量大时就会造成卡顿 ❌。 Fiber 让它可以中断 / 恢复,保持页面流畅 ✅。


🌰 类比:

奶茶师以前做单都是从头到尾不停(一次性任务)。 现在有了 Fiber:

  • 可以“分段制作”;
  • 中途接待顾客(优先级高的任务);
  • 再回来继续做剩下的奶茶。

✅ Fiber 的特征

1️⃣ 可中断 / 恢复。 2️⃣ 任务优先级(高优先先执行)。 3️⃣ 时间切片(分帧渲染)。


💡 口诀记忆:

“大单切小段,忙中可插单。”


🧋第149页:OAuth 2.0 授权机制(热度 210)

💡 OAuth 2.0 是一种授权协议(不是认证), 它允许“第三方应用”安全地访问用户资源。


🌰 举例:

你用「微信登录小程序」, 小程序并不会拿到你的密码, 而是通过“授权令牌 token”访问微信数据。


✅ 核心流程

1️⃣ 用户登录授权页; 2️⃣ 第三方拿到授权码(code); 3️⃣ 服务器用 code 向认证服务器换取 token; 4️⃣ 用 token 请求资源(如用户信息); 5️⃣ token 过期需刷新。


fetch('https://auth-server.com/token', {
  method: 'POST',
  body: {
    code: 'abc123',
    client_id: 'app123',
    client_secret: 'xxx',
    redirect_uri: 'http://myapp.com/callback'
  }
})

🍡 类比:

用户扫码授权,就像给奶茶店一个「限时会员卡」。 店员拿卡刷一次 → 获取顾客偏好信息。 卡过期要重新领~


💡 口诀记忆:

“凭卡授权,不交密码。”


🧩第150页:单点登录(SSO)与常见鉴权方式

💡 单点登录 SSO 是什么?

用户在一个系统登录后,其他系统自动登录。


🌰 场景:

公司内部:

  • 登录「企业门户」→ 自动登录「考勤系统」「OA」「CRM」。

🍡 类比:

员工进公司刷一次工卡, 办公室门、茶水间、打印室全都能进!


✅ 实现方式

方案一:Cookie + Session

浏览器携带 cookie 校验身份。 适合同域系统。

方案二:JWT(JSON Web Token)

const token = jwt.sign({ user: 'xiaokeai' }, 'secret', { expiresIn: '2h' })

Token 存在前端(localStorage / header), 服务端只校验签名,不存状态。

🍡 类比:

发一个加密的“工牌”,任何门禁都能识别。


✅ 常见登录鉴权方式

鉴权方式特点适用场景
Session简单、安全高同域
Token(JWT)无状态、易扩展跨域 / 微服务
OAuth2第三方授权微信、GitHub 登录
SSO统一登录企业级内部系统

💡 口诀记忆:

“SSO 一次通行,JWT 无状态,OAuth 授权卡。”


🌈 第146~150页复盘卡

页码知识点一句话记忆
146虚拟 DOM清单先记,最后出餐
147Diff 算法同层对比,看 key 识别
148Fiber 架构大单切小段,插单不卡顿
149OAuth2凭授权卡,不交密码
150SSO & 鉴权一卡通行,全站互信

💬 小可爱总结:

“我现在懂了!原来 React 的更新机制像流水线分段生产, 登录授权又像企业发工卡~ 以后面试官问 OAuth、Diff、Fiber、SSO,我能像倒奶茶一样顺滑讲出来🧋💪。”

🥤第151页:跨域 Cookie 与请求行为

💡 题目:

“假如在域名 A 请求域名 B 时,B 想设置 Cookie,浏览器会不会保存?”


✅ 背景知识

Cookie 有两个关键属性:

  • domain:控制作用域;
  • sameSite:防止跨站攻击。

默认规则下,

A 网站发请求到 B 网站,B 网站返回的 Cookie 不会被浏览器保存

因为这涉及安全问题(防止 CSRF 攻击)。


🌰 举例:

fetch('https://b.com/api', {
  method: 'POST',
  credentials: 'include'
})

服务端设置:

Set-Cookie: token=123; SameSite=None; Secure

🍡 要点:

  • SameSite=None 允许跨域携带;
  • Secure 必须 HTTPS 才能生效;
  • credentials: 'include' 告诉浏览器:请求要带 Cookie。

🍵 类比说明:

就像奶茶加盟店(b.com)要在总部(a.com)的电脑里留会员卡(Cookie)。 总部要明确同意(include),总部网络要安全(HTTPS), 加盟店还得开口说“我允许跨店存会员卡”(SameSite=None)。


口诀记忆:

“跨域留卡要双同意,HTTPS 保安全。”


⚙️第152页:Vite 和 Webpack 区别(热度 530)

💡 核心区别总结

对比项ViteWebpack
打包原理基于 ES Module(原生 import)基于 bundle 打包
启动速度秒级(不打包直接运行)慢(先构建)
构建工具esbuild(Go 语言)webpack 自身(JS)
热更新局部更新全量刷新
适合场景开发环境快生产环境稳定

🍡 类比:

Webpack 像传统奶茶厂: 先一次性混好所有原料 → 再开业。

Vite 像智能点单机: 点哪杯现做哪杯,效率高但需要新设备支持(现代浏览器)。


口诀记忆:

“Vite 快如闪电,Webpack 稳如老厂。”


🧋第153页:封装请求函数(含错误处理)

这部分是必考的“工程题”~ 面试官常说:“封装一个请求函数,并处理错误。”


✅ 代码拆解

export async function request(url, options = {}) {
  const res = await fetch(url, { ...options })
  if (!res.ok) throw new Error('网络异常')
  return await res.json()
}

📘 加强版(含超时 + 拦截器):

export function request(url, options, timeout = 5000) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) =>
      setTimeout(() => reject('请求超时'), timeout)
    )
  ])
}

🍡 类比:

顾客点奶茶时,如果 5 秒内没做出来(超时), 系统直接取消订单,防止顾客生气!💢


✅ 异常捕获

try {
  const data = await request('/api/order')
  console.log(data)
} catch (e) {
  console.error('请求失败:', e)
}

🍡 类比:

就像奶茶店老板设置“5分钟内没做完就退款”的自动规则。


💡 口诀记忆:

“超时退单,错误拦截。”


🧠第154页:前端如何处理请求超时(timeout)

💡 背景: fetch 默认没有超时机制! 必须自己实现。


✅ 方法 1:Promise.race()

Promise.race([
  fetch('/api'),
  new Promise((_, reject) => setTimeout(() => reject('timeout'), 3000))
])

谁先完成谁赢,fetch 太慢就判超时。


✅ 方法 2:AbortController(推荐)

const controller = new AbortController()
​
setTimeout(() => controller.abort(), 3000)
​
fetch('/api', { signal: controller.signal })
  .catch(err => console.error('请求被中止:', err))

🍡 类比:

老板说:“这杯奶茶三秒没出杯就取消订单。” 店员听到信号立刻丢弃制作过程。


💡 口诀记忆:

“race 抢先判,abort 手动断。”


☕第155页:axios 请求封装最佳实践

✅ 封装思想

import axios from 'axios'

const instance = axios.create({
  baseURL: '/api',
  timeout: 5000
})

// 请求拦截
instance.interceptors.request.use(config => {
  config.headers.Authorization = localStorage.getItem('token')
  return config
})

// 响应拦截
instance.interceptors.response.use(
  res => res.data,
  err => {
    if (err.response?.status === 401) alert('请重新登录')
    return Promise.reject(err)
  }
)

🍡 类比:

axios 就像奶茶总部系统:

  • 请求拦截器 = 点单前自动检查会员卡;
  • 响应拦截器 = 出单后检查奶茶是否过期。

优点

  • 统一错误处理;
  • 自动附带 token;
  • 可配置重试逻辑。

💡 口诀记忆:

“axios 总部控单,自动验卡报错。”


🌈 第151~155页复盘卡

页码知识点一句话记忆
151跨域 Cookie跨域留卡要双同意
152Vite vs Webpack新厂快,老厂稳
153请求封装超时退单,错误拦截
154超时处理race 抢先判,abort 手动断
155axios 拦截器总部控单,统一验卡

💬 小可爱总结:

“我现在懂啦!跨域像奶茶跨店发会员卡, fetch 超时像顾客取消订单,axios 拦截像总部验卡。 原来每个机制都能和奶茶店流程对应上~甜又专业!🧋✨”

🧠第156页:Node.js 如何充分利用多核 CPU?(热度:725)

💡 背景

Node.js 是单线程的(只有一个主线程跑 JS)。 那问题来了:我的服务器有 8 核 CPU,为啥 Node 只吃一核?🤔


✅ 解决方法:Cluster 模块

Node 提供 cluster 模块,可以创建多个子进程(worker) 共享同一个端口。

import cluster from 'cluster'
import os from 'os'
import http from 'http'

if (cluster.isPrimary) {
  const cpus = os.cpus().length
  for (let i = 0; i < cpus; i++) cluster.fork() // 创建多个子进程
} else {
  http.createServer((req, res) => {
    res.end('多核并发OK')
  }).listen(3000)
}

🍡 生活类比:

以前只有一个奶茶师(Node主线程)在干活。 cluster 模块让你雇了 8 个师傅(子进程)同时做奶茶! 顾客再多也不怕堵单!💪


口诀记忆:

“单线程调度,多师傅干活。”


🍵第157页:后端一次性返回海量数据,前端怎么优化?

💡 场景: 后端返回 10 万条订单数据,直接渲染会导致页面卡死!🫠


✅ 解决方案:

1️⃣ 分页加载(分页器)

👉 只请求当前页数据。 🍡 类比:奶茶工厂每次只做一批,不会一次全做。


2️⃣ 懒加载(Lazy Load)

👉 用户滚动到底部时再加载下一批。 🍡 类比:顾客一边喝一边再点下一杯。


3️⃣ 虚拟列表(Virtual List)

👉 只渲染屏幕范围内的内容。 代码核心逻辑是计算出“可视区域”的数据 slice。

const visibleData = data.slice(start, end)

🍡 类比:

货架上其实只有几杯“假展示奶茶”, 顾客往下看时,后面的奶茶才偷偷替换出来~


4️⃣ Web Worker 多线程

👉 把数据处理放到后台线程,避免主线程阻塞。 🍡 类比:让后厨去切珍珠,前台奶茶师继续接单不耽误!


口诀记忆:

“分页取数、懒加载、只渲染看得见、重活甩后台。”


⚙️第158页:为什么前端性能越来越慢?(热度:625)

💡 原因主要有四类问题👇


🧩 一、资源体积太大

  • JS 包太多、图片太大、依赖臃肿。

🧋像点奶茶时,每杯都塞满布丁珍珠椰果芋圆——太重了当然慢!


🧩 二、渲染层问题

  • 重排重绘频繁;
  • DOM 节点太多;
  • CSS 选择器复杂。

💡 比喻:奶茶店货架太挤,每次换个招牌都要全店重摆。


🧩 三、网络瓶颈

  • 没做缓存;
  • 未启用 gzip;
  • CDN 配置不当。

🚚 像奶茶原料都要从总仓调货,路远当然慢。


🧩 四、逻辑阻塞

  • 同步循环太久;
  • 大计算任务阻塞主线程;
  • 未使用 Web Worker。

🍡 类比:

前台收银员自己去仓库拿料,柜台全堵死。


优化方向总结: 1️⃣ 减包体积(Tree Shaking、按需加载); 2️⃣ 优化渲染(虚拟列表、防抖节流); 3️⃣ 加缓存(CDN、localStorage); 4️⃣ 多线程(Web Worker);


💡 口诀记忆:

“轻装上阵、快交互、加缓存、多线程。”


🧋第159页:页面加载速度优化(性能优化思维)

💡 面试官最爱问这题:

“你如何系统性地优化一个网站的性能?”

我们可以分三层思考:网络层、渲染层、代码层。


🌍 网络层优化

方向技巧
减少请求数合并资源、雪碧图、懒加载
提升传输速率开启 gzip / brotli、HTTP/2
减少传输体积压缩代码、删除 console
使用缓存CDN + 本地缓存策略

🧠 渲染层优化

方向技巧
DOM 操作批量处理、虚拟 DOM
动画优化使用 requestAnimationFrame
图片优化懒加载、WebP 格式
预加载<link rel="preload"> 提前拉资源

💻 代码层优化

方向技巧
减少计算量防抖节流
分片执行requestIdleCallback
代码分割动态 import()
异步加载import + await

🍡 生活类比:

像经营奶茶店提速:

  • 网络层 = 原料运输快;
  • 渲染层 = 摆奶茶架高效;
  • 代码层 = 店员分工明确。

口诀记忆:

“网速快、渲染省、代码轻。”


☕第160页:前端日志监控与 SDK 设计思路(热度:755)

💡 日志监控 = 网站“黑匣子”。 能帮你发现:用户卡顿、接口报错、崩溃原因。


✅ 核心思路

1️⃣ 日志采集

window.onerror = (msg, url, line, col, err) => {
  send({ msg, url, line, col, stack: err.stack })
}

🍡 类比:奶茶机一出错就自动上报总部。


2️⃣ 性能数据监控

使用 PerformanceObserver 监听:

  • 页面加载时间;
  • 首屏渲染;
  • 请求耗时。

3️⃣ 用户行为埋点

document.addEventListener('click', e => {
  report({ type: 'click', target: e.target.tagName })
})

🍡 类比:总部统计顾客最常点哪杯奶茶。


4️⃣ 上报机制

  • 批量上报:节省带宽;
  • 离线缓存:断网后补发;
  • 采样策略:减少噪声。

💡 口诀记忆:

“出错即报、性能监听、点啥上报、断网补发。”


🌈 第156~160页复盘卡

页码知识点一句话记忆
156Node 多核多师傅干活并发爽
157大数据渲染只渲染可视,后台切珍珠
158性能变慢原因轻装快交互
159性能优化体系三层提速法
160日志监控出错即报、点击埋点

💬 小可爱总结:

“我现在完全懂了~Node多线程就像雇多师傅; 性能优化像奶茶店升级流水线; 日志监控就像总部装摄像头📹。 一整套性能体系串起来,好喝又稳~🧋✨”

🍵第161页:前端日志埋点 SDK 设计思路

💡 SDK 是什么? 就是“工具包”或“监控插件”——比如你的网页想监控用户行为(点击、停留、报错等), 就得封装一个 SDK 自动帮你收集上报。


✅ SDK 核心流程

1️⃣ 数据采集(点了什么、在哪个页面) 2️⃣ 数据上报(通过接口发给服务器) 3️⃣ 上报策略(节流、防抖、批量、断网补发) 4️⃣ SDK 打包发布(npm、cdn)


🍡 生活类比:

奶茶总部要监控全国分店的点单情况。 SDK 就是装在每家店里的“智能收银机插件”, 自动记录顾客点单、报错、等待时间,一键上报总部。


💡 口诀记忆:

“采数据,上报送,总部洞察靠打通。”


✨ SDK 初始化示例

import { Tracker } from './tracker.js'
const tracker = new Tracker({ url: 'https://server.com/report' })
tracker.send({ event: 'click', user: 'xiaokeai' })

这里的 Tracker 就像奶茶店的“上报机器人”, url 是汇报总部的接口地址。


🧠第162页:SDK 核心逻辑代码讲解

class Tracker {
  constructor(options) {
    this.url = options.url
  }
  
  send(data) {
    navigator.sendBeacon(this.url, JSON.stringify(data))
  }
}

🔹 navigator.sendBeacon: 浏览器内置的“异步上报通道”, 即使页面关闭也能把数据发出去,不会堵主线程。

🍡 类比:

奶茶店关门时还能自动上传营业数据,不会丢失。


改进版:加缓存 + 批量上报

this.cache.push(data)
if (this.cache.length > 5) this.flush()

每次攒 5 条数据再上传,就像集中发货省成本。


💡 口诀记忆:

“批量上报更省流,Beacon 关门不掉包。”


⚙️第163页:节流防抖 + 用户行为埋点上报

💡 用户操作频繁,比如疯狂点击按钮,就可能造成上报风暴。 所以要加「节流」和「防抖」。


✅ 防抖(Debounce)

👉 等操作停止一段时间后再执行。

function debounce(fn, delay) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), delay)
  }
}

🍡 类比:

顾客连续说“我要奶茶!我要奶茶!我要奶茶!” 店员等他安静 3 秒后才开始做。


✅ 节流(Throttle)

👉 固定间隔执行一次。

function throttle(fn, interval) {
  let last = 0
  return (...args) => {
    const now = Date.now()
    if (now - last >= interval) {
      fn(...args)
      last = now
    }
  }
}

🍡 类比:

奶茶店 10 秒才接一次订单,哪怕顾客催得飞起,也得排队。


💡 口诀记忆:

“防抖等安静,节流限频率。”


☕第164页:在 React / Vue 中埋点上报实现

🧩 React 实现

useEffect(() => {
  const handleClick = () => tracker.send({ event: 'click' })
  window.addEventListener('click', handleClick)
  return () => window.removeEventListener('click', handleClick)
}, [])

📘 解释:

useEffect 相当于页面“加载完安装监听器”, return 清理监听器,防止内存泄漏。

🍡 类比:

店员上班时打开摄像头监听顾客行为,下班关掉。


🧩 Vue 实现

onMounted(() => {
  window.addEventListener('click', () => tracker.send({ event: 'click' }))
})
onUnmounted(() => {
  window.removeEventListener('click', ...)
})

📘 Vue 与 React 类似: 生命周期钩子里注册和注销事件。

🍡 类比:

Vue 店长上班安装摄像头,关门自动拆除。


💡 口诀记忆:

“React 用 useEffect,Vue 用 onMounted。”


🔐第165页:Token 鉴权机制(热度:942)

💡 Token 就像网站发给用户的“通行证”。 比传统 Cookie 更轻便、安全。


✅ Token 的三种类型

类型特点适用场景
Access Token短期访问令牌普通登录
Refresh Token用于续签新 Token长登录
ID Token认证用户身份OAuth2 / OpenID

🌰 举例说明

登录成功后,后端返回:

{
  "access_token": "abc123",
  "refresh_token": "xyz456"
}

前端保存 access_token 到 header:

Authorization: Bearer abc123

当 access_token 过期,自动用 refresh_token 换新。

🍡 类比:

你办了奶茶店会员卡(access_token), 到期后拿身份证(refresh_token)续卡。


✅ Token 的验证方式

1️⃣ 签名验证(JWT) 2️⃣ Redis 校验(集中式) 3️⃣ 混合模式(登录态 + JWT)


💡 口诀记忆:

“短卡进店,长卡续期,签名验真。”


🌈 第161~165页复盘卡

页码知识点一句话记忆
161SDK 架构智能插件帮总部监控
162数据上报Beacon 异步 + 批量节省
163防抖节流防抖等停,节流限频
164前端埋点React useEffect,Vue onMounted
165Token 机制短卡进店,长卡续期

💬 小可爱总结:

“我懂啦~SDK 就像奶茶总部的监控小助手, Beacon 帮忙送消息,节流防抖防过劳; React 和 Vue 负责不同店的监听, Token 就是会员卡~续期、验真都有逻辑🧋✨!”

🧋第166页:Token 与 Cookie 的区别与结合使用

💡 这是超级经典面试题:

“Token 和 Cookie 有什么区别?什么时候用?能一起用吗?”


✅ 一、两者核心区别

对比项CookieToken
存储位置浏览器内部自动管理localStorage / sessionStorage
携带方式自动随请求发送需手动放在 header 里
跨域支持不友好(受同源策略限制)可跨域
安全性易被 CSRF 攻击相对更安全
使用场景老式登录状态保持前后端分离、移动端、API 接口认证

🍡 生活类比:

Cookie 像“自动会员卡”——每次点奶茶自动出示; Token 像“电子二维码通行证”——你得手动出示,但更灵活,跨店也能用!


口诀记忆:

“Cookie 自动随身带,Token 手动通行跨店快。”


✅ 二、能不能一起用?

👉 可以!最佳实践是两者结合: 1️⃣ 服务端签发 Token; 2️⃣ Token 存进 Cookie; 3️⃣ 每次请求浏览器自动带上; 4️⃣ 后端验证 Token 签名合法性。

🍡 类比:

奶茶总部(后端)给顾客一个带签名的“电子会员卡”, 存进顾客手机的钱包(Cookie),每次扫码(请求)就能验证真伪。


✅ 三、安全建议

  • Cookie 要加 HttpOnly(防止被 JS 读取);
  • Token 要加签名验证(防伪造);
  • 使用 HTTPS(防窃听);
  • 设置过期时间 + 刷新机制。

🍡 类比:

会员卡:要有签名、防盗刷、设有效期,还得走加密通道~


💡 口诀记忆:

“Cookie 防偷读,Token 防伪造。”


🧠第167页:前端权限控制与系统架构设计(热度:329)

💡 面试常问:

“你在项目中是怎么设计权限系统的?”


✅ 一、常见权限模型

模型说明举例
RBAC(角色控制)用户分角色,角色授予权限管理员可删帖,普通用户只能看
ABAC(属性控制)根据属性动态判断部门=技术 && 时间<18:00 才能访问
PBAC(策略控制)按规则脚本化执行拥有 tag=VIP 时开放某模块

🍡 类比:

奶茶总部:

  • 店长(Admin)能改菜单;
  • 员工(Staff)只能打奶盖;
  • 顾客(User)只能下单;
  • VIP 客户有“隐藏菜单权限”。

口诀记忆:

“角色定权限,属性加条件,策略最灵活。”


✅ 二、前端权限体系设计

1️⃣ 登录时获取权限数据:

const userInfo = await getUserInfo()
store.roles = userInfo.roles

2️⃣ 路由动态加载:

router.addRoute({
  path: '/admin',
  meta: { roles: ['admin'] }
})

3️⃣ 按钮级别权限控制:

<button v-if="roles.includes('admin')">删除</button>

🍡 类比:

前端页面就是奶茶菜单, 后台根据“职位”决定显示哪些按钮。 店长能改价,员工只能点单。


安全提示:

权限控制要后端兜底。 前端只是“展示层过滤”, 真正权限校验必须在服务器做。

🍡 类比:

你前台菜单隐藏了“改价按钮”,但只要后厨不验身份,别人照样能改价!🚨


💡 口诀记忆:

“前端限展示,后端真守门。”


⚙️第168页:系统防御策略总结(配合前端权限)

💡 这页是“系统安全合辑”:前后端都该配合的守护法。


✅ 常见防御手段

1️⃣ 登录态校验(Token + Refresh Token) 2️⃣ 接口签名(加密验证) 3️⃣ 接口限流(防暴力请求) 4️⃣ 数据脱敏(掩码显示手机号) 5️⃣ 权限兜底(后端最终审核)


🍡 生活类比:

奶茶店:

  • 限制每人点单次数(限流);
  • 奶茶配方做模糊展示(脱敏);
  • 点单时核对会员卡签名(签名验证)。

口诀记忆:

“签名防伪造,限流防滥刷,脱敏护隐私。”


🧋第169页:低代码(Low-Code)平台架构设计(热度:399)

💡 题目:

“请你说说低代码平台的底层原理。”


✅ 一、核心思想

低代码平台 = 拖拽组件 + 动态渲染 + 元数据驱动


✅ 二、底层原理

1️⃣ 每个页面、组件都转成 JSON 描述:

{
  "type": "Button",
  "props": { "text": "提交", "color": "blue" }
}

2️⃣ 前端渲染引擎解析 JSON → 生成真实界面; 3️⃣ 后端保存这份 JSON,随时可还原页面。


🍡 类比:

奶茶店总部提供“DIY 奶茶机”: 你只要拖拽杯子、添加珍珠、命名口味,系统就能自动生成奶茶配方。


✅ 三、关键模块

  • 可视化拖拽编辑器;
  • 组件库管理;
  • 渲染引擎;
  • 数据绑定;
  • 权限与发布系统。

🍡 类比:

像搭乐高积木一样搭页面,每块积木都有定义、参数和权限。


口诀记忆:

“拖拽搭积木,数据控页面。”


🧠第170页:低代码平台的运行机制(继续)

💡 低代码平台怎么运行生成的页面?


✅ 一、Schema 渲染流程

1️⃣ 用户操作界面保存 JSON; 2️⃣ 渲染引擎读取 JSON; 3️⃣ 按组件库规则动态 createElement(); 4️⃣ 最终生成可交互页面。

function render(schema) {
  const el = document.createElement(schema.type)
  Object.assign(el, schema.props)
  document.body.append(el)
}

🍡 类比:

就像总部拿到奶茶配方表(JSON),根据配方一步步还原那杯奶茶。


✅ 二、运行时绑定

  • 双向绑定数据;
  • 动态事件注入;
  • 异步请求绑定;
  • 权限动态控制。

🍡 类比:

你修改奶茶甜度 → 系统自动更新配方 → 同步所有门店配料。


💡 口诀记忆:

“Schema 定蓝图,引擎造真机。”


🌈 第166~170页复盘卡

页码知识点一句话记忆
166Cookie vs Token自动卡 vs 通行证,结合最稳
167权限控制设计前端限展示,后端真守门
168系统防御策略签名防伪造,限流防滥刷
169低代码原理拖拽搭积木,数据控页面
170Schema 渲染蓝图生成真机,动态绑定活起来

💬 小可爱总结:

“我现在完全能想象~Cookie 是自动会员卡,Token 是跨店通行证; 权限系统像奶茶总部审批菜单; 低代码平台就是 DIY 奶茶机——配方是 JSON,渲染就是泡奶茶!🧋✨”

🧋第171页:[低代码平台] 架构设计思路(热度:263)

💡 题目问的是:

“低代码平台的整体分层架构是怎么设计的?”


✅ 一、三层架构思路(超经典!)

层级说明比喻
1️⃣ 设计层(Editor)拖拽页面、可视化配置奶茶师在调配台上拖拖拽拽选配料
2️⃣ 运行层(Runtime)执行渲染逻辑、事件绑定把配方转成真正的奶茶
3️⃣ 存储层(Schema/Server)保存 JSON、生成模板把奶茶配方存在总部系统里

🍡 生活类比:

想象你在“智能奶茶机”上设计饮品(设计层), 点“生成”后机器实际调奶(运行层), 系统自动保存配方(存储层), 以后想喝同款,只要再调出来即可!


口诀记忆:

“设计层搞编辑,运行层管展示,存储层留配方。”


✨ 代码例子:渲染器简化版

function render(schema) {
  const el = document.createElement(schema.type)
  Object.assign(el, schema.props)
  document.body.append(el)
}

拿到一份配方(schema)→ 根据描述(props)→ 动态生成对应元素。

🍡 就像总部拿到饮品 JSON 配方后,一键让机器调好奶茶。


🧠第172页:[Webpack] 构建性能优化思路(热度:1,163)

💡 面试常问:

“Webpack 项目太慢了,怎么提速?”


✅ 一、性能优化的八大思路:

分类优化手段类比
构建速度缓存、并行、多线程多人同时煮奶茶
体积优化压缩、Tree Shaking去掉多余糖浆
资源加载代码分割、懒加载顾客来了再现煮
缓存策略hash 文件名每杯奶茶贴上时间标签
打包范围exclude node_modules不重复煮原料
loader 提速thread-loader、cache-loader并行打奶盖
分环境构建开发 / 生产打样奶茶 vs 批量出货
分包策略splitChunks不同口味独立打包

🍡 生活类比总结:

Webpack 就像奶茶生产流水线,

  • Tree Shaking:去掉没人点的口味
  • 多线程 Loader:几台奶盖机一起打
  • hash 缓存:每次出货都贴上批次号

口诀记忆:

“去重、分包、多线、贴签、懒加载。”


⚙️第173页:Webpack Loader 与 Plugin 优化

Webpack 的核心是两个引擎:

Loader(加工原料) + Plugin(打包增强器)


✅ Loader 优化技巧

1️⃣ 减少匹配范围:

{
  test: /.js$/,
  use: 'babel-loader',
  exclude: /node_modules/
}

🍡 不加工“外厂原料”(node_modules)。

2️⃣ 使用缓存:

use: ['cache-loader', 'babel-loader']

🍡 奶茶机缓存上次调好的比例,下次不用重配。


✅ Plugin 优化技巧

const TerserPlugin = require('terser-webpack-plugin')
optimization: {
  minimize: true,
  minimizer: [new TerserPlugin({ parallel: true })]
}

🍡 让“打包小哥”同时开多台打包机,并行压缩代码~💪


口诀记忆:

“Loader 减范围,Plugin 多线程。”


🍵第174页:Webpack 缓存与分包策略

✅ 一、缓存策略(hash)

output: {
  filename: '[name].[contenthash].js'
}

当内容没变,hash 不变,浏览器可用老缓存。

🍡 类比:

奶茶贴标签 [红豆奶茶.批号.2025-10-20],没变就别重煮。


✅ 二、分包策略(SplitChunks)

optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: { test: /node_modules/, name: 'vendors', chunks: 'all' }
    }
  }
}

将第三方库(node_modules)单独打包成 vendors.js。 避免业务代码改动导致第三方库缓存失效。

🍡 类比:

“珍珠、奶盖、红茶”分开煮,方便复用~


口诀记忆:

“缓存靠标签,分包防浪费。”


☕第175页:Webpack 构建提速实战技巧

💡 最终面试 killer 问法:

“Webpack 打包很慢,你具体会做哪些优化?”


✅ 一键提速清单

方向手段
🧩 缓存优化cache-loader、babel 缓存
⚙️ 并行编译thread-loader、Terser parallel
🚀 缩小范围exclude node_modules
📦 分包提速splitChunks 动态拆分
🧠 按需加载import() 懒加载组件
🧹 精简代码Tree Shaking、去掉 console
🧴 CDN 加速第三方库外链化
🪄 模式区分dev = 快,prod = 优化后上线

🍡 生活类比:

你要开奶茶连锁工厂:

  • 把常用原料单独冷藏(缓存);
  • 多个奶茶师一起煮(并行);
  • 不重复煮“老配方”(exclude);
  • 不用时不提前泡(懒加载)。

口诀记忆:

“多线快打包,缓存防复煮。”


🌈 第171~175页复盘卡

页码知识点一句话记忆
171低代码三层架构设计拖拽 → 渲染执行 → 保存配方
172Webpack 优化总览八路提速法,像奶茶厂流水线
173Loader & Plugin 优化Loader 减范围,Plugin 并行干
174缓存 + 分包hash 贴标签,拆包防浪费
175实战提速清单多线程 + 懒加载 + Tree Shaking

💬 小可爱总结:

“我懂了~Webpack 优化其实就像开奶茶工厂! Tree Shaking 是‘去掉没人点的口味’, cache-loader 是‘保留老配方’, splitChunks 是‘分锅煮珍珠奶盖’, 一条流水线多线程一开,速度嗖嗖的~🧋💨”

🍵第176页:Webpack 打包优化代码实战(上)

💡 我们先看 Webpack 的优化配置结构。 这页主要教你如何配置 plugins、loader、缓存、分包 等。


✅ 一、基础配置

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.[contenthash].js', // 加 hash,方便浏览器缓存
    clean: true, // 每次构建前清空 dist
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
  ],
};

📘 解释

  • entry: 入口文件;
  • output: 输出文件;
  • filename[contenthash] 就像“奶茶批次号”,内容不变就不重做;
  • clean: true 表示“出新奶茶前先清洗机器”;
  • HtmlWebpackPlugin 自动帮你把 JS 注入 HTML 文件。

🍡 类比记忆:

每次出货前清洗奶茶机,打包好的奶茶(JS 文件)贴上批次号,顾客拿到最新口味不串味!


🧠第177页:CSS 提取与压缩

💡 打包 CSS 也能提速!我们用 MiniCssExtractPlugin + css-minimizer-webpack-plugin


✅ CSS 提取配置:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' })],
};

📘 解释

  • MiniCssExtractPlugin.loader: 把 CSS 单独“拉出来”;
  • 这样 JS 和 CSS 各走各的,互不干扰。

🍡 类比记忆:

JS 是“奶茶液体”,CSS 是“包装膜”,现在分开生产,互不拖慢。


✅ CSS 压缩

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

optimization: {
  minimizer: [new CssMinimizerPlugin()],
}

压缩体积,让奶茶杯盖更紧致 💅。


⚙️第178页:JS 压缩 + 多线程并行打包

💡 JS 文件大,编译慢?那就开“多台机器”打包!


✅ TerserPlugin 并行压缩

const TerserPlugin = require('terser-webpack-plugin');

optimization: {
  minimize: true,
  minimizer: [
    new TerserPlugin({
      parallel: true, // 多线程压缩!
    }),
  ],
};

🍡 类比:

以前一台奶茶机挤珍珠,现在十台一起挤,效率翻十倍 🧋💨。


✅ 二次导出

export { default as Sugar } from './Sugar.js'
export { default as Milk } from './Milk.js'

Tree Shaking 时能更精准删除没用模块。 就像只保留“奶茶机常用口味”,丢掉没人点的“香芋奶茶 2008 限定版”。


☕第179页:Preload / Prefetch 原理与应用

💡 浏览器优化加载速度的两把“神器”:

preloadprefetch


✅ preload:立刻加载,优先级高

<link rel="preload" href="/main.js" as="script">

🍡 类比:

看到顾客走近奶茶店,立刻开始提前煮茶。


✅ prefetch:空闲时加载,优先级低

<link rel="prefetch" href="/about.js" as="script">

🍡 类比:

现在客人少,先偷偷备一批珍珠。下次有顾客来就不慌。


💡 区别口诀:

“Preload 马上做,Prefetch 有空做。”


✅ Webpack 配置中用法:

output: {
  filename: '[name].[contenthash].js'
},
plugins: [
  new HtmlWebpackPlugin({
    template: './index.html',
    preload: ['main.js'],
    prefetch: ['about.js']
  })
]

自动帮你生成 preload/prefetch 标签。


🧋第180页:Tree Shaking 原理与配置

💡 Tree Shaking = 摇掉没用的代码, 就像“把多余的奶茶配料摇走,只留核心原料”。


✅ 基础配置

optimization: {
  usedExports: true,
}

📘 意思是:标记出哪些导出被使用。


✅ 结合 ESModule 实现

export function drink() {}
export function waste() {}
import { drink } from './milk.js'

编译时发现 waste() 没被引用 → 自动删除它!

🍡 类比记忆:

你菜单上 10 款奶茶,只要顾客点了 3 款,其它都不煮,省时又省料。


✅ 更高级的压缩版

const TerserPlugin = require('terser-webpack-plugin')

optimization: {
  minimize: true,
  minimizer: [new TerserPlugin()]
}

Tree Shaking + 压缩双管齐下,打包体积直接减半!

🍡 类比:

奶茶配方精简完再浓缩一遍,完美~🧋✨


🌈 第176~180页复盘卡

页码知识点一句话记忆
176基础打包配置批次号 hash,打包清洁机
177CSS 优化分离包装膜,压缩更轻盈
178JS 优化多机器并行挤珍珠,导出更干净
179预加载优化preload 马上做,prefetch 空时做
180Tree Shaking摇掉没点的奶茶,精简高效

💬 小可爱总结:

“我懂啦~Webpack 优化就像开奶茶工厂: 多机器挤珍珠(多线程)、分锅煮奶茶(分包)、 预热备料(preload/prefetch)、 还要摇掉没人点的口味(Tree Shaking)~ 打包就能像做奶茶一样快又香甜💨🧋!”

🧋第181页:Webpack 模块热更新(HMR)原理

💡 题目问:

“Webpack 的热更新是怎么实现的?为什么它能不刷新页面就更新代码?”


✅ 一、什么是 HMR?

HMR(Hot Module Replacement)= 模块热替换。 就是在你改了前端文件后,浏览器不用整个刷新页面, 而是“只更新变动的部分模块”🔥。


🍡 生活类比:

想象你的奶茶机在运行时:

  • 以前:每次换口味都要关机 + 清洗 + 重启(全刷新)。
  • 现在:智能奶茶机支持 “热切换口味” ,只替换糖浆,不停机(HMR)。

✅ 二、原理机制

1️⃣ Webpack-dev-server 开启一个 WebSocket 通信; 2️⃣ 一旦文件变化,Webpack 通知浏览器:

“嘿,奶茶糖浆配方变了!” 3️⃣ 浏览器下载新模块(新的 JS chunk); 4️⃣ 替换掉旧模块,不刷新整页。


✅ 三、关键配置

devServer: {
  hot: true, // 启用热更新
}

🍡 类比记忆:

hot: true = “奶茶机支持边调味边出杯!”


💬 口诀记忆:

“WebSocket 通信换模块,热更新不重启。”


☕第182页:Webpack 构建流程精讲

💡 题目问:

“Webpack 的打包流程是什么?从入口到输出经历了哪些阶段?”


✅ 一、流程概览

阶段作用类比
1️⃣ 初始化读取配置文件开机读取奶茶配方表
2️⃣ 编译模块分析依赖(import、require)看哪些奶茶原料要准备
3️⃣ 模块转换用 Loader 处理各种文件加热、搅拌、打奶盖
4️⃣ 输出资源生成 bundle 文件装杯、封膜、打标签

✅ 二、示例代码

module.exports = {
  entry: './src/index.js',
  output: { filename: 'bundle.js' },
  module: {
    rules: [{ test: /.css$/, use: ['style-loader', 'css-loader'] }],
  },
};

🍡 类比:

JS 是主料,CSS 是辅料, Loader 是奶茶机的“加工器”。


💬 口诀记忆:

“读配置 → 分依赖 → 转文件 → 出成果。”


🧠第183页:IndexedDB 原理与使用

💡 题目问:

“IndexedDB 和 LocalStorage 有什么区别?它的底层原理是什么?”


✅ 一、区别总结

对比项LocalStorageIndexedDB
存储大小约 5MB可存几十 MB 或更多
数据类型只能字符串可存对象、数组、二进制
操作方式同步异步(基于事件)
使用场景简单配置离线应用、缓存大数据

🍡 生活类比:

LocalStorage 就像奶茶店前台的“小抽屉”,放几张优惠券还行; IndexedDB 就像后厨仓库,能存珍珠、红豆、芋圆(大量数据)。


✅ 二、代码示例

let request = indexedDB.open('MilkTeaDB', 1)

request.onsuccess = e => console.log('连接成功')
request.onupgradeneeded = e => {
  let db = e.target.result
  db.createObjectStore('orders', { keyPath: 'id' })
}

📘 解释: 1️⃣ 打开数据库; 2️⃣ 如果不存在就创建; 3️⃣ 设置主键存结构化数据。

🍡 类比记忆:

开仓库(open),建货架(createObjectStore),放订单数据(orders)。


💬 口诀记忆:

“Local 小抽屉,Indexed 大仓库。”


⚙️第184页:浏览器的存储机制总结

💡 这页是复盘大合集,考点多、但逻辑清晰。


✅ 一、存储类型对比

类型特点场景
Cookie小、自动携带、用于登录会话保持
LocalStorage简单 key-value,5MB配置缓存
SessionStorage临时存储,关页消失临时状态
IndexedDB结构化数据离线存储
Cache Storage网络请求缓存PWA 离线应用

🍡 类比:

  • Cookie = 自动会员卡
  • LocalStorage = 前台抽屉
  • IndexedDB = 后厨仓库
  • CacheStorage = 物流仓储系统

口诀记忆:

“Cookie 自动带,Local 存配置,Indexed 放大货,Cache 缓接口。”


🧋第185页:Webpack Chunk 原理与拆分策略

💡 题目问:

“Webpack 如何划分 chunk?项目中如何控制它?”


✅ 一、什么是 Chunk?

Chunk = 一块打包产物(JS 文件)。

🍡 类比:

奶茶工厂的“装奶茶杯”:每杯就是一个 chunk。


✅ 二、如何拆分 chunk?

optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: { test: /node_modules/, name: 'vendors', chunks: 'all' }
    }
  }
}

📘 解释:

  • 第三方库(node_modules)单独拆出去;
  • 避免业务逻辑改动时 vendor 缓存失效;
  • 提高浏览器复用率。

🍡 类比记忆:

把“珍珠”、“奶盖”、“红茶”分开包装。顾客下次点单时,直接复用珍珠~


✅ 三、异步加载 chunk

function loadAbout() {
  import('./about.js').then(module => module.default())
}

这就是“懒加载”:只有当顾客点“关于我们奶茶”时,才现煮那杯。


💬 口诀记忆:

“同步打主包,异步拆分煮。”


🎨第185页下:Canvas vs SVG

💡 面试问:

“为什么大多数图表库(ECharts 等)用 Canvas 而不是 SVG?”


✅ 一、根本区别

对比项CanvasSVG
绘制方式位图(像素级)矢量图(节点级)
性能快,适合大量元素慢,适合少量元素
交互性需手动绑定自带节点事件
放大缩小模糊清晰

🍡 类比:

  • Canvas = 奶茶照片(像素图),一次画完,改动要重画;
  • SVG = 奶茶线稿(矢量图),每个部分都是独立可控的。

总结口诀:

“Canvas 性能强,SVG 精细美。”


🌈 第181~185页复盘卡

页码知识点一句话记忆
181HMR 热更新热切换口味不停机
182构建流程读配置→分依赖→加工→出成果
183IndexedDB小抽屉 vs 大仓库
184存储体系Cookie 登录,Indexed 缓大货
185Chunk 拆分 + Canvas分锅煮奶茶,Canvas 快速渲染

💬 小可爱总结:

“我现在懂了~Webpack 热更新就像奶茶机能换口味不重启; 浏览器存储就像奶茶店的前台抽屉 + 后厨仓库; 而 ECharts 用 Canvas,是因为它出奶茶更快不掉帧!🧋💨”

🌸 第186页:Canvas vs SVG 的性能对比

💡 面试问:

“Canvas 和 SVG 有什么区别?为什么 ECharts 选择用 Canvas?”


✅ 一、性能对比

项目CanvasSVG
绘制方式像素画布,一次性画好每个图形是节点,可单独操作
性能适合大量图形适合少量、精细操作
重绘改一点要重画全部改谁就改谁
清晰度放大会糊放大仍清晰(矢量)

🍡 类比:

  • Canvas = 一张奶茶海报(像素图),改配料要重画整张。
  • SVG = 奶茶插画稿(每个元素独立),换个珍珠颜色不影响其他部分。

✅ 二、为什么 ECharts 选 Canvas?

1️⃣ Canvas 性能更高,能快速绘制上千个点; 2️⃣ SVG 节点太多时会卡爆浏览器; 3️⃣ 对交互不敏感的图表(柱状图、折线图)Canvas 性价比高。


💬 口诀记忆:

“SVG 精细慢,Canvas 粗犷快。”


🧠 第187页:Vite 为什么比 Webpack 快?

💡 面试问:

“Vite 为什么构建速度比 Webpack 快这么多?”


✅ 一、核心区别:打包方式不同!

项目WebpackVite
处理方式先打包再运行先运行再按需加载
启动速度
原理把所有代码打成 bundle利用 ESModule 动态导入
适合场景大型项目,兼容性好开发环境快如闪电 ⚡

🍡 生活类比:

  • Webpack:开奶茶店前,要先煮好全部口味
  • Vite:有顾客点哪款,再现煮那一杯

✅ 二、Vite 的技术原理

1️⃣ 开发时用原生 ESM(import/export) ; 2️⃣ 用 esbuild(Go 写的编译器) 处理依赖,比 JS 写的 webpack 快几十倍; 3️⃣ 生产构建时,仍用 Rollup 打包,保证体积小。


💬 口诀记忆:

“Webpack 先打包再跑,Vite 现点现煮超快。”


⚙️ 第188页:常用的 Webpack Plugin

💡 面试问:

“说几个你常用的 Webpack 插件(Plugin),并解释它们的作用。”


插件作用类比
HtmlWebpackPlugin自动生成 HTML 并注入 JS自动帮奶茶贴标签
DefinePlugin定义全局常量定义店名、LOGO
MiniCssExtractPlugin分离 CSS 文件分开做奶茶液体和杯膜
TerserWebpackPlugin压缩 JS把奶茶打包成迷你杯
CleanWebpackPlugin构建前清空输出目录清洗奶茶机再出新货

🍡 类比记忆:

Plugin 就像奶茶工厂的“自动化工具组”: 有的清洗机器、有的贴标签、有的装杯压缩。


💬 口诀记忆:

“Html 贴标签,Define 定品牌,Terser 压缩装小杯。”


🧋 第189页:常用的 Webpack Loader

💡 Loader 是用来“加工原料”的,Plugin 是“辅助打包”的。

Loader作用类比
babel-loader把 ES6 转旧版 JS翻译新配方让旧奶茶机也能懂
css-loader解析 CSS 文件把样式融入奶茶液体
style-loader把 CSS 注入页面把奶茶倒进杯子里
file-loader处理图片、字体打包奶茶海报图
url-loader小文件转 base64小图直接融进奶茶里

💬 口诀记忆:

“Babel 翻译配方,Style 注入奶茶。”


🧠 第190页(上):React 如何解决状态共享?

💡 面试问:

“多个组件要共享状态怎么办?”


✅ 一、三种方式

方法特点类比
1️⃣ 父组件传 props简单但层层传递奶茶店老板一层层传指令
2️⃣ Context跨组件共享店长广播通知所有员工
3️⃣ Redux / Zustand / Jotai独立全局状态管理中控电脑直接分配任务

✅ 二、Context 用法:

const UserContext = createContext();

function App() {
  return (
    <UserContext.Provider value="小可爱">
      <Header />
    </UserContext.Provider>
  )
}

function Header() {
  const name = useContext(UserContext)
  return <h1>欢迎回来,{name}~</h1>
}

🍡 类比记忆:

Provider = 店长发通知 useContext = 员工接收广播


💬 口诀记忆:

“Props 一层层传,Context 全店广播。”


💻 第190页(下):React 性能优化

💡 面试问:

“React 性能优化有哪些手段?”


✅ 一、常见优化手段

优化手段含义类比
React.memo组件 props 不变就不重渲染一杯奶茶口味没变不重做
useMemo缓存计算结果缓存珍珠煮好时间
useCallback缓存函数引用记住上次调奶茶动作
虚拟列表渲染可视区域一次只展示 10 杯奶茶,不全摆出来
懒加载(lazy)延迟加载组件客人点哪杯再现做那杯

🍡 类比记忆:

React 优化就是“懒得重煮”:

  • 不变的不做
  • 重复的缓存
  • 看得见的先出

💬 口诀记忆:

“记住上次煮的,别重复出杯。”


🌈 第186~190页复盘卡

页码知识点一句话记忆
186Canvas vs SVGSVG 精致慢,Canvas 粗犷快
187Vite vs WebpackWebpack 先打包,Vite 现点现煮
188常用 PluginHtml 贴标签,Terser 压缩装小杯
189常用 LoaderBabel 翻译配方,Style 注入奶茶
190React 状态共享与优化Props 传递累,Context 广播快

💬 小可爱总结:

“Webpack 是‘老式奶茶厂’,Vite 是‘自动奶茶机’, Plugin 是‘工厂配件’,Loader 是‘原料加工机’, React.memo 就是‘不变不重煮’, 用这一套比喻我都能秒懂啦~🧋✨!”

🍵 第191页:Vue 中 v-if 和 v-for 的优先级问题

💡 面试问:

“为什么不推荐在同一个标签上同时使用 v-ifv-for?”


✅ 解释

Vue 解析模板时: 1️⃣ v-for 优先级比 v-if 高。 2️⃣ 所以会先循环再判断

👉 比如:

<li v-for="item in list" v-if="item.show">
  {{ item.name }}
</li>

相当于:

list.map(item => {
  if (item.show) return item
})

🍡 问题: 如果你的 list 很大(比如 1000 条),那每次都得遍历 + 判断,白白浪费性能。


正确写法

<li v-for="item in showList" :key="item.id">
  {{ item.name }}
</li>
computed: {
  showList() {
    return this.list.filter(item => item.show)
  }
}

🍡 类比记忆:

就像奶茶店有 1000 张订单,先过滤出“已付款”的再做,不要边做边查是不是付款。

💬 口诀记忆:

“先过滤后循环,不边走边判断。”


🧋 第192页:静态资源缓存的方式

💡 面试问:

“静态资源缓存有哪些方式?浏览器是怎么判断文件要不要重新加载的?”


✅ 一、缓存类型

类型特点类比
强缓存直接用本地缓存,不发请求“昨天泡好的珍珠,今天直接用”
协商缓存要问一下服务器能不能用旧的“打电话问仓库:昨天的珍珠还能用吗?”

✅ 二、强缓存

靠两个响应头控制:

Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2025 07:28:00 GMT

🍡 浏览器逻辑:

在 3600 秒内,不去请求服务器,直接用缓存。


✅ 三、协商缓存

靠 ETag / Last-Modified 机制:

ETag: "abc123"
If-None-Match: "abc123"

📘 过程: 1️⃣ 浏览器请求文件; 2️⃣ 服务器比对 ETag; 3️⃣ 一样则返回 304(不用传内容)。


🍡 类比记忆:

ETag 就是“珍珠批次号”。 如果批次没变,就不再做新奶茶,直接用旧的。

💬 口诀记忆:

“强缓存不问,协商缓存问一下。”


⚙️ 第193页:SPA 单页面路由实现原理

💡 面试问:

“SPA(单页应用)的路由跳转不刷新页面,原理是什么?”


✅ 一、核心思想:

SPA 其实是“前端自己控制页面切换”,浏览器并没有重新请求页面。


✅ 二、两种路由模式:

模式原理URL 表现类比
hash 模式监听 window.onhashchange/home#about奶茶订单后面贴标签
history 模式通过 history.pushState/about修改账单但不通知服务器

✅ 三、hash 模式实现:

window.addEventListener('hashchange', () => {
  console.log(location.hash)
})

✅ 四、history 模式实现:

history.pushState({}, '', '/about')
window.onpopstate = () => {
  console.log(location.pathname)
}

🍡 类比记忆:

hash 模式像是在奶茶杯上贴标签; history 模式是换菜单但不告诉老板。

💬 口诀记忆:

“Hash 改标签,History 改菜单。”


🧠 第194页:Axios 的原理与封装机制

💡 面试问:

“Axios 的实现原理是什么?为什么浏览器和 Node 都能用?”


✅ 一、Axios 的架构

Axios 其实是对原生请求(XMLHttpRequest / http 模块)的封装。 内部结构像“中间层工厂”,有拦截器、适配器、配置合并等模块。


✅ 二、核心执行流程

axios({
  url: '/api/milk',
  method: 'get'
})

执行步骤如下: 1️⃣ 合并配置; 2️⃣ 请求拦截器; 3️⃣ 适配器发请求(XHR / Node HTTP); 4️⃣ 响应拦截器; 5️⃣ 返回结果。


✅ 三、拦截器示例

axios.interceptors.request.use(config => {
  console.log('准备发请求', config)
  return config
})

🍡 类比记忆:

  • 请求拦截器 = 点单员检查奶茶口味是否合规
  • 响应拦截器 = 出杯员检查奶茶质量是否正常

💬 口诀记忆:

“发前查一遍,收后看一眼。”


🧋 第195页:前端如何预警 Web 应用的请求异常

💡 面试问:

“如果 Web 页面请求失败,你怎么监控并报警?”


✅ 一、try-catch + 上报

try {
  await axios.get('/api/milk')
} catch (err) {
  reportError(err)
}

用 try-catch 捕获错误,调用 reportError() 上报。


✅ 二、全局捕获

window.onerror = function(message, source, line, col, error) {
  reportError(error)
}

✅ 三、Promise 异常捕获

window.addEventListener('unhandledrejection', e => {
  reportError(e.reason)
})

🍡 类比记忆:

奶茶机坏了?立刻报警灯闪烁,记录错误日志发给维修站。

💬 口诀记忆:

“异常不慌,全局上报。”


🌈 第191~195页复盘卡

页码知识点一句话记忆
191v-if vs v-for先过滤后循环,别边做边查
192缓存机制强缓存不问,协商缓存问一下
193SPA 路由原理Hash 改标签,History 改菜单
194Axios 原理发前查一遍,收后看一眼
195错误上报机制奶茶机坏立刻报警灯闪

💬 小可爱总结:

“我现在懂啦~Vue 的 v-if 要先筛单再循环, 浏览器缓存就像奶茶批次号判断要不要重煮, SPA 的路由换菜单不刷新店面, Axios 发单前后都查一遍, 奶茶机坏了还得立刻报警灯闪~🧋💨”

🌸 第196页:前端如何监控网页性能?

💡 面试问:

“前端如何检测网页加载速度、白屏时间、首屏时间、接口耗时?”


✅ 一、核心思路:性能监控要测“三个时间”

名称含义类比
白屏时间页面开始渲染前的空白时间顾客进店到看到第一个奶茶广告的时间
首屏时间用户看到首屏主要内容的时间顾客看到菜单的时间
总加载时间页面全部资源加载完奶茶原料全上架完的时间

✅ 二、如何获取这些时间?

👉 通过浏览器提供的 Performance API

window.addEventListener('load', () => {
  const t = performance.timing
  console.log('白屏时间:', t.responseStart - t.navigationStart)
  console.log('首屏时间:', t.domContentLoadedEventEnd - t.navigationStart)
  console.log('总加载时间:', t.loadEventEnd - t.navigationStart)
})

🍡 类比记忆:

  • responseStart = 第一批奶茶广告上架时间
  • domContentLoaded = 菜单可看时间
  • loadEventEnd = 所有原料齐备

💬 口诀记忆:

“白屏测首图,首屏测菜单,全载测完仓。”


🧋 第197页:如何监控前端接口耗时?

💡 面试问:

“怎么统计前端请求的响应时间和错误率?”


✅ 一、最直接的办法:封装 Axios 拦截器

axios.interceptors.request.use(config => {
  config.meta = { startTime: Date.now() }
  return config
})

axios.interceptors.response.use(
  res => {
    const duration = Date.now() - res.config.meta.startTime
    console.log(`接口 ${res.config.url} 耗时: ${duration}ms`)
    return res
  },
  err => {
    console.error(`接口 ${err.config.url} 请求失败`)
    return Promise.reject(err)
  }
)

🍡 类比记忆:

就像奶茶点单系统:

  • 记录下“开始做”的时间(startTime)
  • 客人拿到奶茶后算耗时(duration)
  • 失败单自动报警。

💬 口诀记忆:

“下单记时,出杯算秒。”


☕ 第198页:前端实现网页截图的方式

💡 面试问:

“如何在前端网页上实现截图功能?”


✅ 一、核心方案:html2canvas

import html2canvas from 'html2canvas'

html2canvas(document.querySelector('#app')).then(canvas => {
  const img = canvas.toDataURL('image/png')
  document.body.appendChild(canvas)
})

✅ 二、原理

html2canvas 会遍历 DOM,把每个元素绘制成 Canvas,再转成图片。


🍡 类比记忆:

就像给奶茶店大厅拍一张照片📸,不是“拷贝”,而是“画出来的复制品”。

💬 口诀记忆:

“DOM 画成 Canvas,Canvas 变成照片。”


应用场景:

  • 导出网页快照(报表);
  • 用户反馈时截图;
  • 在线签名、电子表单。

📸 第199页:H5 拍照上传功能实现

💡 面试问:

“H5 页面怎么实现‘点击按钮拍照并上传’?”


✅ 一、实现思路:

HTML + JS + 设备调用 API。


✅ 二、HTML 结构

<input type="file" accept="image/*" capture="camera">

这行代码的关键在 capture="camera", 📱 它告诉浏览器:“唤起手机相机进行拍照上传”。


✅ 三、JS 处理逻辑

const input = document.querySelector('input')
input.onchange = e => {
  const file = e.target.files[0]
  const formData = new FormData()
  formData.append('photo', file)

  fetch('/upload', {
    method: 'POST',
    body: formData
  }).then(res => res.json())
}

🍡 类比记忆:

用户点按钮 = “打开前置摄像头” FormData = 奶茶打包机 fetch('/upload') = 把奶茶上传到云厨房 ☁️

💬 口诀记忆:

“打开相机,FormData 打包,fetch 上传。”


☁️ 第200页:H5 拍照 + React 封装示例(进阶版)

这页给了 React 封装的写法,让你在项目中能直接复用👇

import React, { useRef, useState } from 'react'

export default function App() {
  const inputRef = useRef(null)
  const [photo, setPhoto] = useState(null)

  const handleTakePhoto = () => inputRef.current.click()

  const handleChange = e => {
    const file = e.target.files[0]
    const url = URL.createObjectURL(file)
    setPhoto(url)
  }

  return (
    <div>
      <input
        ref={inputRef}
        type="file"
        accept="image/*"
        capture="camera"
        onChange={handleChange}
        style={{ display: 'none' }}
      />
      <button onClick={handleTakePhoto}>📷 拍照上传</button>
      {photo && <img src={photo} alt="预览" style={{ width: 200 }} />}
    </div>
  )
}

🍡 类比记忆:

  • useRef = 备用相机按钮;
  • useState = 存放拍好的奶茶图;
  • URL.createObjectURL = 快速预览本地奶茶;
  • fetch 上传后才真正“出厂”。

💬 口诀记忆:

“Ref 触发,State 存图,URL 预览。”


🌈 第196~200页复盘卡

页码知识点一句话记忆
196性能监控白屏测首图,首屏测菜单,全载测完仓
197接口耗时统计下单记时,出杯算秒
198网页截图DOM 画成 Canvas,Canvas 变成照片
199拍照上传(H5)打开相机,FormData 打包,fetch 上传
200React 拍照封装Ref 触发相机,State 存图预览

💬 小可爱总结:

“现在我懂了~ 性能监控像记录奶茶制作时长, 截图是给奶茶拍照存档📸, 拍照上传是让顾客直接上传自拍照, 一整套监控+上传闭环搞定了!⚙️🧋”

🧋第201页:React 下拉刷新功能实现

💡 面试问:

“怎么在 React 页面里实现‘下拉刷新’功能?(比如移动端网页向下拉,自动重新加载内容)”


✅ 一、用的库

npm install react-pull-to-refresh

这行命令安装了一个第三方库,名字叫 react-pull-to-refresh。 它帮我们处理复杂的“手势下拉 + 松手回弹 + 触发刷新”逻辑。


✅ 二、代码实现

import React from 'react'
import PullToRefresh from 'react-pull-to-refresh'

function App() {
  const handleRefresh = () => {
    return new Promise(resolve => {
      setTimeout(() => {
        console.log('页面刷新完成!')
        resolve()
      }, 2000)
    })
  }

  return (
    <PullToRefresh onRefresh={handleRefresh}>
      <div>🍹 下拉刷新一下吧~</div>
    </PullToRefresh>
  )
}

export default App

🧠 解释(小白友好版)

🍡 当用户往下拉网页时,PullToRefresh 组件检测到这个“手势动作”, 👉 就会自动调用 handleRefresh()

handleRefresh() 返回一个 Promise

  • 只有当 Promise 执行完(也就是 resolve() 被调用),
  • 才代表“刷新结束,界面可以恢复啦~”。

💬 类比:

把网页想象成奶茶店的菜单墙🍧。 “下拉刷新”就像店长用力扯下旧菜单、贴上新菜单。 贴新菜单前要等后台准备好数据(那段 setTimeout 模拟的等待时间)。


💡 口诀记忆:

“下拉→触发→等 Promise 结束 → 页面更新。”


🧠第202~204页:如何修改第三方 npm 包?

💡 面试问:

“npm 安装的第三方包不符合需求,我要改源码怎么办?” 这个问题其实考察你会不会用 monorepo + 本地包链接机制(npm link / workspace)


✅ 一、思路总结

我们不能直接去 node_modules 里改代码(因为一重新安装包就被覆盖)。 正确姿势是👇

💡 “拷贝出来 → 改源码 → 建本地包 → 项目用自己改的版本。”


✅ 二、详细步骤(一步步讲)

1️⃣ 安装第三方包

npm install example-package

假设你要改的包叫 example-package


2️⃣ 新建一个项目

mkdir my-project && cd my-project

3️⃣ 拷贝原包源码

cp -r node_modules/example-package packages/example-package

把它复制到自己的 packages 文件夹中。 就像你把别人奶茶的配方抄下来,放到自己仓库。


4️⃣ 修改 package.json

{
  "dependencies": {
    "example-package": "file:packages/example-package"
  }
}

这里的 "file:" 表示: 👉 “别去 npm 下载包,用我本地这个版本!”


5️⃣ 进入包目录开始修改

code ./packages/example-package

你可以像改自己项目一样调试这个库。


6️⃣ 重新安装依赖并测试

cd my-project
npm install

确保现在项目用的就是你本地修改后的版本。


7️⃣ 如果改好了要发布:

cd packages/example-package
npm publish

或者发布到你的私有 npm 仓库:

npm publish --registry http://your-private-registry.com

🍡 类比记忆:

  • 从别人那拿到“奶茶秘方(第三方包)”
  • 抄一份到自己后厨(packages
  • 改口味(修改源码)
  • 不再买外面的原料(file: 指向自己)
  • 好喝就开分店(npm publish

💬 口诀记忆:

“抄到本地改口味,file 路径连自家。”


🧠 补充:pnpm / yarn 的更优做法(现代方案)

现在很多项目是 monorepo 结构,比如:

my-project/
 ├── packages/
 │    └── example-package/
 ├── web/
 └── package.json

可以用 workspace 管理:

{
  "workspaces": ["packages/*", "web"]
}

这样修改完包,运行一次 npm install,项目自动更新依赖。

🍡 类比:

就像“总店与分店共用一个仓库”,配方更新立刻同步所有奶茶分店。


🌈 第201~204页复盘卡

页码知识点一句话记忆
201React 下拉刷新下拉→Promise 结束→刷新完
202改 npm 包原因官方包不合口味
203改法拷贝源码→file 路径→npm install
204进阶方案用 workspace 管理更方便

💬 小可爱总结:

“原来下拉刷新就是网页版的‘奶茶菜单更新系统’🍵, 而改 npm 包就像我不想用别人家的配方, 要在自己厨房改口味再卖出去~ file 指向自己,workspace 一改全同步, 学会这套我也是前端奶茶厂 CTO 啦~💼🧋!”