前端真题 130 题 · 架构 / OS / 手写 / 综合
浏览器与网络补遗、设计模式与架构、操作系统、JS 手写题(含 Vue keep-alive)、HTTP/JS 继承/浏览器工作原理综合收尾,109-130 题。
七、浏览器与网络(补遗)
说明:第 106、107 题在原题表中位于 Node.js 与设计模式之间,主题分属浏览器内存与网络,故单独成章。复习时建议与「一、HTML & 浏览器基础」的渲染/进程章节、「六、Node.js」的内存管理对照阅读。
106. 内存泄漏
定义:本应被 GC 回收的对象因仍被可达引用而无法回收 → 内存持续增长。(GC 用的是可达性分析,不是引用计数——所以"循环引用"本身不会泄漏,只有"从根可达但业务上已无用"才会。)
常见来源(按面试被问频率排序):
- 未清理的定时器 / 事件监听器(组件销毁时要 clearInterval、removeEventListener;React 里
useEffect返回 cleanup,Vue 里onUnmounted); - 订阅未退订(EventBus、store.subscribe、WebSocket、MutationObserver);
- 闭包长期持有大对象(如把整个 response 挂进闭包,只用到其中一个字段);
- 意外的全局变量(未声明赋值、
this指向 window); - 脱离文档的 DOM 引用(已 remove 但 JS 变量仍指向它);
- 无上限的缓存 / Map(要配 LRU 或 WeakMap——WeakMap 的 key 是弱引用,不阻止回收)。
排查步骤(能说出步骤才是真做过):
- Chrome DevTools → Memory 面板;
- 操作前打一次 Heap Snapshot;
- 反复执行"进入/退出该页面"多次;
- 再打快照,选 Comparison 视图对比 Delta,按增量倒序找可疑对象;
- 点开对象看 Retainers(保留树) ——它回答"谁在引用我",是定位泄漏源的关键;
- 配合 Performance Monitor 看 JS heap size 曲线是否持续上升不回落(这是最直观的判据)。
考点:归因到"可达性分析仍可达";定时器/监听器最常考;能说出"快照对比 + Retainers"就比只会背条目高一个层级。
107. HTTPS
HTTPS = HTTP + TLS/SSL:提供加密 + 身份认证 + 完整性。
TLS 1.2 握手:
- ClientHello(版本、套件、随机数);
- ServerHello + 证书(含公钥);
- 客户端验证证书(信任链到 CA、域名、有效期),生成预主密钥加密发回(或 ECDHE);
- 双方派生会话密钥(对称);
- 之后数据用对称加密(AES,快);握手用非对称/ECDHE 协商。
要点:混合加密(非对称传密钥/认证 + 对称传数据);摘要保完整性;证书 = "公钥 + CA 签名";TLS 1.3 握手 1-RTT、0-RTT 恢复。
考点:混合加密原因;证书链与中间人防御;与 HTTP/2/3 关系。
TLS 1.2 握手图解:
sequenceDiagram
autonumber
participant C as 客户端
participant S as 服务器
participant CA as CA(证书颁发机构)
C->>S: ① ClientHello(支持的 TLS 版本 / 加密套件 / 随机数 Random1)
S->>C: ② ServerHello(选定的套件 / 随机数 Random2)+ 证书
Note over C: ③ 校验证书链<br/>CA 签名是否有效 → 域名是否匹配 → 是否过期
C->>CA: (本地或离线)验证证书信任链
CA-->>C: 证书可信 ✅
Note over C,S: ④ 密钥交换(ECDHE 或 RSA 加密预主密钥)
C->>S: 发送用服务器公钥加密的预主密钥 / ECDHE 参数
Note over C,S: ⑤ 双方用 Random1 + Random2 + 预主密钥 派生出会话密钥
C->>S: ⑥ Finished(用会话密钥加密,验证握手完整性)
S->>C: ⑦ Finished
Note over C,S: 后续数据全部用<b>对称加密</b>(AES)传输
读图要点:混合加密是本图的核心——握手用非对称/ECDHE(慢但安全地协商出密钥),传输用对称加密(快)。
加分点:TLS 1.3 把握手压缩到 1-RTT(甚至 0-RTT 恢复);同时废弃了 RSA 密钥交换,只保留前向安全的 (EC)DHE;对称部分默认用 AES-GCM 或 ChaCha20-Poly1305。
八、设计模式与架构
108. 常用设计模式
前端高频设计模式(讲清"意图 + 场景 + 例子"):
- 单例模式:全局唯一——Vuex/Pinia store、Redux store、全局弹窗、缓存。ESM 模块级变量天然单例;
- 观察者 / 发布订阅:事件总线 EventBus、Vue 响应式、addEventListener。观察者(目标直接通知观察者)vs 发布订阅(事件中心解耦);
- 工厂模式:批量创建对象——React.createElement、消息类型分发;
- 策略模式:算法族可替换——表单校验策略集、支付方式,消除大 if/switch;
- 装饰器模式:动态增强——HOC、mixin、中间件;
- 代理模式:控制访问——事件委托、图片懒加载代理、防抖节流、Vue3 Proxy;
- 适配器模式:兼容接口——axios 适配浏览器/node、浏览器差异抹平;
- 迭代器模式:for...of、Generator;
- 职责链模式:请求沿链传递——express/koa 中间件、事件冒泡;
- 命令模式:操作对象化——撤销/重做。
考点:说 3-5 个能举真实例子的;对比易混项;本质是"应对变化的复用方案",结合函数式/hooks 讲更显深度。
109. 重构(Refactoring)
重构:不改变外部行为前提下改进内部结构。核心纪律:小步 + 行为不变 + 测试护航。
手法:提炼函数/变量、消重复、拆过长函数、改命名;拆分 God Object、模块边界整理;拆分大组件、提取 hooks;接口隔离、依赖注入。
正确姿势:
- 先有测试做安全网,每小步跑测试;
- 一次只做一种重构,频繁提交(可回滚);
- 不混业务改动;用 Lint/TS 辅助。
考点:强调"行为不变 + 小步 + 测试";"重构 vs 重写"取舍。
110. MVVM
MVVM = Model-View-ViewModel:
- Model:数据与业务(不碰 DOM);
- View:视图/模板;
- ViewModel:胶水层,数据绑定让 View 与 VM 自动同步,免手动 DOM 操作。
前端实现:Vue(data + 模板 + methods)、React(受控组件 + 单向数据流,常归"类 MVVM")。核心是数据驱动视图(声明式 UI)。
单向 vs 双向:Vue v-model 是语法糖(:value + @input)实现双向;React 单向受控,状态来源明确易调试。
考点:MVVM 结构图与绑定方向;与 MVC/MVP 的"谁驱动谁"区别。
111. MVC
MVC = Model-View-Controller:
- Model:数据与业务规则;
- View:展示;
- Controller:接收输入,协调 Model 与 View。
传统 Web MVC(后端):路由 → Controller → Model → 渲染 View 返回 HTML。
前端 MVC(Backbone):View 监听 DOM 事件 → Controller → 更新 Model → View 重渲染。
缺点:前端场景 View 与 Model 同步需大量手动代码 → 演进出 MVVM 数据绑定。
考点:前端 MVC 中 View 也监听事件职责重叠——演进动力。
112. MVP
MVP = Model-View-Presenter:View 与 Model 完全解耦,Presenter 是唯一中间层:
- View:只渲染 + 把事件交给 Presenter(被动视图,接口化,不含业务);
- Presenter:持 View 接口与 Model,处理交互、更新数据与视图;
- Model:纯数据/业务。
对比:
| 模式 | View 与 Model | 驱动者 | 绑定 |
|---|---|---|---|
| MVC | 观察者关联 | Controller | 无 |
| MVP | 完全解耦 | Presenter(可测) | 手动同步 |
| MVVM | 解耦 | ViewModel | 自动绑定 |
优点:Presenter 不依赖真实 DOM,mock View 即可单测(Android 常用)。缺点:胶水代码多。
考点:MVP 的 View 是被动的(passive view),与 MVC 中 View 主动观察 Model 的本质差异。
九、操作系统
113. 进程通信(操作系统层)
进程间通信方式:
- 管道:半双工字节流(亲缘进程);命名管道 FIFO(无亲缘);
- 消息队列:内核维护消息链表,传结构化消息;
- 共享内存:最快,直接读写同一物理内存;需信号量同步;
- 信号量:计数器 + P/V 操作,解决互斥同步;
- 信号:异步通知(SIGINT/SIGTERM),携带信息少;
- 套接字 Socket:跨主机/本地(TCP/UDP/Unix socket),最通用;
- 文件。
选择依据:速度、跨机器、数据量、同步需求。前端联系:浏览器多进程、Node IPC、Worker postMessage(消息 vs 共享内存)。
114. 进程调度策略
- FCFS:先来先服务,护航效应;
- SJF/SRTF:平均等待最短,长任务可能饥饿;
- 时间片轮转 RR:公平、交互好,时间片需权衡;
- 优先级调度:可能饥饿(用老化解决);
- 多级反馈队列 MLFQ:多队列 + 动态升降,现代 OS 主流。
考点:抢占/非抢占;MLFQ 为何自适应 IO 密集进程。
115. 死锁
死锁四必要条件:
- 互斥;2. 占有且等待;3. 不可剥夺;4. 循环等待。
处理:
- 预防:破坏四条件之一(一次性分配、按序申请);
- 避免:银行家算法(判断安全状态);
- 检测恢复:检测等待图有环 → 撤销/剥夺;
- 鸵鸟策略(概率低就重启)。
考点:背四条件 + 各举一例;预防 vs 避免 vs 检测;死锁 vs 活锁。
116. IO 多路复用
一个线程同时监听多个 fd,内核通知就绪再处理——高并发网络的基础。
演进:
- select:轮询 O(n),fd 上限 1024,拷贝集合;
- poll:链表存 fd 无上限,仍 O(n);
- epoll(Linux):事件驱动,红黑树 + 就绪链表,只返回就绪 fd(O(1));支持 LT/ET;无需反复拷贝。
macOS 用 kqueue;Windows 用 IOCP。
与前端联系:Node 的 libuv 在 Linux 用 epoll → 单线程 + 非阻塞 + epoll = 高并发模型;Nginx 同理。
考点:三模型对比表;"Node 单线程凭什么高并发";ET/LT 区别。
十、JS 手写题(含 Vue keep-alive)
说明:本章除第 117 题(Vue
keep-alive)外,118–127 均为通用 JS 手写题,与 Vue 无关——按原题表顺序收录,特此标注以免误判归属。
117. Vue keep-alive
<keep-alive> 缓存动态组件/路由组件实例,避免重复销毁重建(保留状态与 DOM)。
<keep-alive include="A,B" max="10"><router-view/></keep-alive>;- 新增钩子:
activated(重新激活)与deactivated(被缓存停用); - 原理:组件不销毁,移入隐藏容器缓存,命中直接复用;
- 缓存过多用
max(LRU 淘汰)。
<keep-alive :include="['List']" :max="10">
<router-view />
</keep-alive>
118. 实现防抖函数(debounce)
触发后延迟执行,期间再触发重置计时——搜索联想、resize:
function debounce(fn, wait = 300, immediate = false) {
let timer = null;
return function (...args) {
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) fn.apply(this, args);
}, wait);
if (callNow) fn.apply(this, args);
};
}
要点:保留 this;可加 cancel 方法。
119. 实现节流函数(throttle)
固定频率执行——滚动加载、拖拽、连点:
// 时间戳版
function throttle(fn, wait = 300) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= wait) { last = now; fn.apply(this, args); }
};
}
// 定时器版(尾部补一次)
function throttleTimer(fn, wait) {
let timer = null;
return function (...args) {
if (!timer) timer = setTimeout(() => { timer = null; fn.apply(this, args); }, wait);
};
}
防抖 vs 节流:防抖="停下才算完",节流="按时执行限频"。
120. 实现字符串翻转
const rev = s => [...s].reverse().join(''); // 正确处理 Unicode 码点
// 双指针
function reverseStr(s) {
const arr = [...s];
let i = 0, j = arr.length - 1;
while (i < j) { [arr[i], arr[j]] = [arr[j], arr[i]]; i++; j--; }
return arr.join('');
}
考察点:split('') 会拆坏 emoji/代理对 → 用 [...s];进阶"按单词翻转"。
121. 数组去重
const uniq = arr => [...new Set(arr)]; // 基本类型
const uniqBy = (arr, key) => [...new Map(arr.map(o => [key(o), o])).values()]; // 对象
考察点:对象数组去重需 Map + 唯一 key;NaN(Set 能去重,indexOf 方案不行)。
122. 实现千位分隔符
function format(n) {
const [int, dec = ''] = String(n).split('.');
const withComma = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return dec ? `${withComma}.${dec}` : withComma;
}
format(1234567.891); // '1,234,567.891'
// 原生: (1234567.891).toLocaleString('en-US')
考察点:正则 \B(?=(\d{3})+(?!\d)) 原理;兼容小数。
123. 判断是否是回文数
const isPalindrome = x => {
const s = String(x);
return s === [...s].reverse().join('');
};
// 数值版(反转一半防溢出)
function isPalindromeNum(x) {
if (x < 0 || (x % 10 === 0 && x !== 0)) return false;
let rev = 0;
while (x > rev) { rev = rev * 10 + (x % 10); x = Math.floor(x / 10); }
return x === rev || x === Math.floor(rev / 10);
}
124. 实现一个模板引擎
function render(tpl, data) {
return tpl.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, key) =>
key.split('.').reduce((o, k) => (o == null ? o : o[k]), data) ?? ''
);
}
render('你好 {{ user.name }}', { user: { name: '张三' } }); // '你好 张三'
考点:正则 + reduce 深层读取;进阶 with + new Function 的原理与 XSS 风险;联想 Vue 模板编译生成 render 函数。
125. 判断一个数是否是素数
function isPrime(n) {
if (n < 2) return false;
if (n <= 3) return true;
if (n % 2 === 0 || n % 3 === 0) return false;
for (let i = 5; i * i <= n; i += 6)
if (n % i === 0 || n % (i + 2) === 0) return false;
return true;
}
考察点:边界(0/1/2);只需试到 √n;6k±1 步进。
126. 翻转二叉树
function invertTree(root) {
if (!root) return null;
[root.left, root.right] = [root.right, root.left];
invertTree(root.left);
invertTree(root.right);
return root;
}
// 迭代 BFS 同样逐个交换
考点:递归三要素;警惕交换后用原引用递归的坑。
127. 获取 n 以内所有素数(埃氏筛)
function primesBelow(n) {
const isPrime = new Array(n + 1).fill(true);
isPrime[0] = isPrime[1] = false;
for (let i = 2; i * i <= n; i++)
if (isPrime[i])
for (let j = i * i; j <= n; j += i) isPrime[j] = false; // 从 i² 开始标记
return isPrime.map((v, i) => (v ? i : -1)).filter(i => i >= 0);
}
要点:从 i*i 开始(更小倍数已被标);O(n log log n);超大 n 用分段筛。
十一、综合收尾(HTTP / JS 继承 / 浏览器工作原理)
说明:第 129 题(JS 继承)属语言基础,第 128 题(HTTP)属网络,第 130 题(浏览器工作原理)是全局总览题——三题主题不同,按原表顺序收在本章。复习建议:第 130 题请与第三章 C 组的"第 45 题 URL 到页面"对照看,两题是同一渲染流程的"细讲版 vs 总览版"。
128. HTTP
HTTP:应用层协议,请求-响应、无状态。
结构:请求行(method + URL + 版本)+ headers + body;响应 = 状态行 + headers + body。
方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS;GET/PUT/DELETE 幂等。
状态码:2xx(200/201/204);3xx(301 永久、302 临时、304 缓存命中);4xx(400/401/403/404/405/429);5xx(500/502/503/504)。
特性:无状态(Cookie/Token 维持);演进(队头阻塞 → HTTP/2/3)。
考点:301/302/304 区分;GET vs POST(语义/幂等/缓存);常见请求头(Cache-Control、CORS 的 Origin 等)。
129. 继承(JS)
继承实现方式演进:
- 原型链继承:子原型指向父实例——引用类型共享问题;
- 构造函数继承:父.call(this)——方法无法复用;
- 组合继承:两者结合,父构造调用两次;
- 寄生组合继承(标准):
function inherit(Child, Parent) {
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
}
function Parent(name) { this.name = name; }
Parent.prototype.say = function () { return this.name; };
function Child(name, age) { Parent.call(this, name); this.age = age; }
inherit(Child, Parent);
const c = new Child('tom', 8);
c instanceof Parent; // true
- ES6 class extends:语法糖(寄生组合 + super 顺序要求)。
考点:每种方案解决什么/引入什么;Object.create 作用;extends 的 proto 两条线。
130. 浏览器工作原理(总览)
从 HTML 到像素的完整链路:
- 导航:URL → DNS → TCP/TLS → 请求 HTML;
- 解析 HTML → DOM 树;解析 CSS → CSSOM;
- 合并 Render Tree(剔除 display:none 等不可见);
- Layout:计算几何;Paint:生成绘制指令;
- 合成 Composite:分层 → 光栅化 → GPU 合成显示;
- 脚本时机:JS 阻塞解析(defer 延后、async 即下即执);CSS 阻塞渲染。
现代渲染优化:
- 关键渲染路径(CRP)优化首屏;
- 流式解析边下边渲染;
- 渲染进程多线程:主线程(JS/layout/paint)、合成线程(滚动合成)。
考点:按"解析→渲染树→布局→绘制→合成"完整复述,说明哪些阶段可被 JS 影响、如何优化。
以上 130 题答案整理自《前端面试真题,会80%直接进大厂》多维表格中的面试知识点,按原表顺序逐题给出详解。
原表「知识点(点击链接学习)」列保留了每题的参考学习链接,建议配合原文档对照使用。