2025年面试题总结

138 阅读16分钟

这是2025年10月份我离职,重新找工作时将每次面试中的八股文记录收集起来并让AI梳理成文档。方便自己以后查看,也希望能帮到正在找工作的jym,并祝你们早日找到工作~~~

1. CSS 定位有哪些

概念与行为

  • position: static:默认,不脱离文档流,top/left 无效。
  • position: relative:仍在文档流中,但可以相对自身偏移(视为“视觉偏移”),不影响兄弟元素布局占位。
  • position: absolute:脱离文档流,相对于最近的已定位祖先(非 static)进行定位;若无定位祖先则相对于根(<html>/viewport)。
  • position: fixed:脱离文档流,相对于视口固定(即滚动不影响),常用于回到顶部/固定头部。
  • position: sticky:粘性定位,在其父容器内依赖滚动位置:在指定阈值前表现为 normal(跟随文档流),超过阈值后变为固定(类似 fixed),需要设置阈值(如 top:0)并且父容器不能 overflow:hidden(可能会影响)。

面试精炼回答

“CSS 有 static、relative、absolute、fixed、sticky 五种常用定位。relative 不脱流只是偏移自身,absolute/fixed 脱离文档流;absolute 相对最近的定位祖先,fixed 相对视口,sticky 在滚动临界点切换成固定。”

注意点

  • absolute 元素定位参考链重要(定位上下文是最近的非 static 祖先)。
  • fixed 在移动端与 transform/contain 的上下文结合时可能不是相对于视口(含有 transform 的父级会创建新的层),小心测试。

2. CSS 中的选择器有哪些,并说明它们的优先级(权重)

常见选择器(举例)

  • 基础:元素(div)、通配符(*
  • 类:.class
  • ID:#id
  • 属性:input[type="text"]
  • 伪类::hover, :nth-child()
  • 伪元素:::after, ::before
  • 组合选择器:后代(a b)、子(a > b)、相邻兄弟(a + b)、通用兄弟(a ~ b
  • 复杂:链式类、伪类组合等

优先级规则(权重计算)

通常用四元组表示:(a, b, c, d)

  • a — 是否为 !important(特殊处理,优先级最高)
  • b — 行内样式(style="...")计 1(相当于 1000)
  • c — ID 选择器个数(每个计 100)
  • d — 类、伪类、属性选择器个数(每个计 10)
  • e — 元素、伪元素选择器个数(每个计 1)

示例:#app .card div => 100 + 10 + 1 权重

!important

  • !important 会覆盖正常权重规则(但多个 !important 比较时仍按优先级比较)。尽量避免使用。

面试精炼回答

“选择器按 specificity 排序:内联 > ID > 类/伪类/属性 > 标签/伪元素;!important 覆盖常规规则。权重通常表示为 1000/100/10/1 的组合。”


3. CSS 中水平垂直居中方式;说说 BFC(块级格式化上下文)

水平 & 垂直居中常用方法

1) Flex(推荐)

.parent {
  display: flex;
  justify-content: center; /* 水平 */
  align-items: center;     /* 垂直 */
}

优点:简洁、支持动态尺寸、常用于单个子项或行列布局。

2) Grid(简洁)

.parent {
  display: grid;
  place-items: center; /* 同时水平垂直 */
}

3) 绝对定位 + transform(脱流情形)

.child {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

适用于固定定位元素或 modal。

4) 行高(单行文本)

.line {
  height: 40px;
  line-height: 40px;
  text-align: center;
}

仅限单行文本居中。

5) table/table-cell 技术(古老)

.parent { display: table; }
.child { display: table-cell; vertical-align: middle; text-align: center; }

4. BFC(块级格式化上下文,Block Formatting Context)

触发 BFC 的常见方式

  • overflow 不为 visible(如 hidden/auto/scroll
  • display: flow-root
  • position: absolute/fixed(绝对或固定定位的块)
  • display: inline-block/flex/grid(某些情况下触发)
  • float(浮动元素创建 BFC 的子集效果)

BFC 的特性 / 作用

  • BFC 内的盒子不会与外部盒子发生 margin 折叠(避免 margin 重叠)。
  • BFC 会包含其内部的浮动元素(父容器高度包住浮动子元素),常用于“清除浮动”替代 clearfix。
  • BFC 可限制内部元素不影响外部布局(例如不对外部产生浮动影响、避免外部元素重叠)。

使用场景

  • 清除浮动:父元素设置 overflow: hiddendisplay: flow-root
  • 避免 margin 重叠:设置 BFC 可阻止相邻块级元素的 margin 折叠。
  • 处理包含浮动子元素导致父高度塌陷问题。

面试精炼回答

“常用居中方式有 flex、grid、绝对定位+transform、行高等。BFC 是块级格式化上下文,一旦触发,内部布局与外部隔离,能解决清除浮动、margin 重叠等问题,常用 overflow:autodisplay:flow-root 来触发。”


5. CSS 中的盒子模型

两种盒模型

content-box(默认)

元素的 width 表示内容宽度,paddingborder 在外侧会增加元素总宽度。

border-box

width 包含 paddingborder(更易于布局)。
通常在项目中统一使用:

* { box-sizing: border-box; }

组成(从内到外)

  • content(内容区)
  • padding(内边距)
  • border(边框)
  • margin(外边距,不影响元素的背景/边框)

回流 & 重绘

  • 改变 box 大小、位置会触发回流(reflow/layout),开销大。
  • 改变颜色等样式触发重绘(repaint),比回流小。

面试精炼回答

“盒模型有 content-box 和 border-box 两种。border-box 把 padding 和 border 包含在 width/height 内,更便于控制元素总尺寸。改动尺寸会触发回流,需尽量避免频繁引起回流的操作。”


6. JavaScript 数据类型

基本类型(Primitive,不可再分)

  • stringnumberbooleanundefinednullsymbolbigint

注意:typeof null === 'object' 是历史遗留问题。

引用类型(Object)

  • ObjectArrayFunctionDateRegExpMapSetWeakMapWeakSet 等。

判断类型常用方法

  • typeof:判断基本类型(返回字符串),但对数组/对象区分不足。
  • Array.isArray():判断数组。
  • instanceof:判断构造函数的实例(受原型链影响)。
  • Object.prototype.toString.call(x)[object Type],通用且靠谱。

面试精炼回答

“JS 类型分为基本类型(七种)和引用类型。常见判断用 typeof、Array.isArray、instanceof、Object.toString 等。注意 null 判定与 NaN 等特殊值。”


7. JS / 浏览器 / 前端性能优化(要点与实践)

将优化分为:网络、资源、渲染与代码层面

网络层

  • 使用 CDN 分发静态资源。
  • 启用压缩:Gzip / Brotli。
  • 合理设置缓存策略:Cache-Control(强缓存)与 ETag/Last-Modified(协商缓存)。
  • 减少请求数:合并、雪碧图(图标尽量用 iconfont/SVG)。
  • 使用 HTTP/2 或 HTTP/3 多路复用。

资源层

  • 图片优化:WebP/AVIF,按需尺寸、懒加载(loading="lazy")。
  • 静态资源离线缓存(Service Worker)用于 PWA。
  • 代码分割(动态 import、route-level split),减少首屏体积。

渲染层(避免卡顿)

  • 减少回流(layout)与重绘(repaint):合并读写 DOM、使用 transform & opacity 做动画。
  • 使用 requestAnimationFrame 做动画帧控制。
  • 使用虚拟滚动(large lists)或分片渲染(windowing)。
  • 将昂贵计算移到 Web Worker。

JS 代码层

  • Tree shaking(移除未用代码)。
  • 使用现代构建(esbuild/rollup/webpack5)和压缩(Terser)。
  • 减少 polyfills,仅按需引入。
  • 减少依赖体积(按需引入 lodash 模块而非整个库)。

加载策略

  • deferasync 的区别:defer 保证顺序且在 DOMContentLoaded 前执行;async 无序,谁先加载先执行。
  • 关键资源预加载:<link rel="preload">prefetch

性能监控

  • 使用 Lighthouse、WebPageTest、Chrome DevTools Coverage、RUM(Real User Monitoring)收集真实用户数据。

面试精炼回答

“性能优化从网络到渲染多方面入手:压缩与缓存、CDN、代码分割、按需加载、图片优化;渲染层避免回流重绘、使用 rAF、Web Worker,结合监控工具持续优化。”


8. v-ifv-show

区别

  • v-if:条件渲染,满足条件时创建 DOM,不满足则销毁 DOM —— 首次渲染成本高但切换成本高(因为每次都 mount/unmount)。
  • v-show:不移除 DOM,仅控制 display 样式显示/隐藏 —— 首次渲染就存在元素(开销),切换显示隐藏成本低。

何时用

  • 频繁切换显示:用 v-show(比如切换 tab)。
  • 低频或条件少:用 v-if(如权限控制、表单切换)。

面试精炼回答

“v-if 是销毁/创建元素,v-show 用 display 控制。频繁切换用 v-show、不常切换用 v-if。”


9. Vue 中 key 的作用

作用

  • 提供虚拟 DOM diff 的稳定标识,帮助框架在列表更新时正确复用或移动节点。
  • 防止错误复用组件实例(例如带内部状态的子组件在重排时会复用错误状态)。

典型问题

列表重排(如 1,2,33,2,1)若没有 key,渲染器可能做最小 DOM 操作但造成组件实例与数据错位;加上唯一 key 后能正确重新挂载/移动。

面试精炼回答

“key 是给虚拟 DOM 一个稳定唯一标识,优化 diff 并保证组件实例正确映射到数据上,避免状态混淆。”


10. 浏览器跨域,如何处理跨域(本地 & 服务器端)

原因

浏览器同源策略(协议、域名、端口三者任一不同就被视为跨域)为安全限制。

常见解决方案

服务器端(推荐)

  • CORS(跨域资源共享) :在服务器端设置响应头,例如:

    Access-Control-Allow-Origin: https://yourdomain.com
    Access-Control-Allow-Methods: GET,POST,OPTIONS
    Access-Control-Allow-Headers: Content-Type, Authorization
    

    若需要携带 cookie:Access-Control-Allow-Credentials: true,且 Access-Control-Allow-Origin 不能为 *

  • 代理 (Reverse Proxy) :通过 Nginx 将 /api 代理到后端域,使浏览器认为同源。适合线上与本地统一处理。

  • JSONP(仅 GET) :利用 <script> 标签不受同源限制,适用于老旧场景,不推荐新项目。

本地开发

  • 开发服务器代理(如 webpack-dev-server、Vite 的 proxy):在本地 devServer 上配置代理把请求转发到后端,解决跨域开发问题。
  • Mock 后端 / 使用 CORS:在后端临时允许本地 origin 或开启 CORS。

其他技术

  • postMessage(跨 window/frame 安全通信)
  • WebSocket(不是受同源限制的常规 HTTP 请求)
  • 服务器端中转:后端自身向第三方请求数据,再返回给前端(后端同源)。

面试精炼回答

“跨域源于浏览器同源策略。最佳做法是在服务器端通过 CORS 或反向代理解决;开发时使用 devServer 代理。JSONP 仅限 GET,安全性与功能有限。”


11. JS 和 TS 的区别

核心差异

  • 类型系统:JS 是动态弱类型,TS 是静态强类型(编译期类型检查)。
  • 开发体验:TS 提供更强的 IDE 智能提示、提前捕获错误、接口与泛型等。
  • 编译/运行:TS 在编译阶段会把代码转成 JS,再执行(不会在运行时强制类型检查)。
  • 学习成本:TS 增加类型学习曲线,但在大型项目能显著减少错误、提高可维护性。

面试精炼回答

“TS 是 JS 的超集,添加了静态类型、接口、泛型,能在编译期捕获错误、提高协作效率和代码可维护性。运行时仍是 JS。”


12. React 中常用的 Hooks(汇总)

  • useState:本地状态
  • useEffect:副作用/生命周期
  • useRef:持久化引用或 DOM 节点
  • useMemo:缓存计算结果,避免重复计算
  • useCallback:缓存函数引用,优化子组件重渲染
  • useReducer:复杂状态管理(类似 Redux 的 reducer 模式)
  • useContext:跨组件传递状态(结合 Context)
  • useLayoutEffect:同步副作用,比 useEffect 早(在 DOM 绘制前执行)
  • 并发/性能相关:useTransitionuseDeferredValueuseIduseSyncExternalStore(React 18+)

面试精炼回答

“常用 hooks 包括 useState/useEffect/useRef/useMemo/useCallback/useReducer/useContext 等,分别用于状态、副作用、引用、性能与复杂状态管理。”


13. useStateuseReducer

核心区别

  • useState:适合局部、简单状态(数值、布尔、少量字段)。setState 更新直接传新值或函数式更新(prev => new)。
  • useReducer:适合复杂状态与多字段更新,将更新逻辑抽离到 reducer(state, action) 中,通过 dispatch(action) 调用,便于维护与测试;当多个子状态更新耦合或需要基于 action 做复杂业务时更易管理。

示例

useState(简单)

const [count, setCount] = useState(0);
setCount(prev => prev + 1);

useReducer(复杂)

function reducer(state, action) {
  switch(action.type) {
    case 'increment': return { ...state, count: state.count + 1 };
    case 'setName': return { ...state, name: action.payload };
    default: return state;
  }
}
const [state, dispatch] = useReducer(reducer, { count: 0, name: ''});
dispatch({ type: 'increment' });

何时用

  • 复杂表单、多个交互状态、多人协作代码库:useReducer
  • 简单局部状态:useState

面试精炼回答

“useState 更适合简单场景,useReducer 把更新逻辑集中到 reducer,便于管理复杂状态和测试。组件复杂或多人协作时,我倾向用 useReducer。”


14. React 中的虚拟 DOM(Virtual DOM)理解

概念

虚拟 DOM 是框架维护的一棵 JS 对象树(VNode),用于描述 UI 的结构与属性。每次状态改变时,React 生成新的虚拟 DOM,和旧的进行 diff,找出最小修改集(patch),然后把变更批量应用到真实 DOM,减少直接 DOM 操作开销。

优点

  • 抽象真实 DOM,减少直接操作开销
  • 能做跨平台渲染(Web、Native、canvas)
  • 更容易实现批量更新与优化(如合并更新、优先级调度)

限制与误解

  • 虚拟 DOM 并非“万能加速器”,错误使用(频繁创建大量复杂对象、无必要的重新渲染)仍会导致性能问题;合理优化(shouldComponentUpdate / memo / useMemo / useCallback)仍很重要。

面试精炼回答

“虚拟 DOM 用 JS 对象描述 UI,diff 找差异并最小化对真实 DOM 的操作,从而提升性能并支持跨平台。但仍需合理避免频繁重渲染。”


15. React 16 推出的 Fiber,谈谈你的理解

为什么要引入 Fiber

React 15 的协调(reconciliation)是递归同步执行的:更新大树会占用主线程较长时间,造成页面卡顿、掉帧、输入延迟等问题。Fiber 重写了解构与调度机制,让渲染过程可中断、可恢复、可分片。

核心能力

  • 可中断/分片(time-slicing) :把大任务拆为小片段,在空闲时间执行,避免阻塞主线程。
  • 优先级调度:不同更新可设不同优先级(交互优先、动画优先),高优先级任务可中断低优先级任务。
  • 增量渲染:能够在多帧内完成一次更新。
  • Fiber 节点结构:每个 fiber 表示组件/元素,包含必要的调度元数据(优先级/副作用等)。

影响

  • 提升 UI 响应性(输入、动画流畅)。
  • 为并发特性(Concurrent Mode / Suspense)奠定基础(React 18 的并发处于其演进方向)。

面试精炼回答

“Fiber 是 React 16 重写的协调引擎,使渲染可中断、可分片并支持优先级调度,从而提高主线程响应性和用户体验。”


16. TypeScript 中断言是什么

定义

类型断言(Type Assertion)是告诉 TypeScript 编译器“相信我,这个值的类型是 X”,不会在运行时做任何转换,仅影响编译期类型检查。

语法

// 推荐(在 TSX 文件中也安全)
const el = document.getElementById('id') as HTMLInputElement;

// 旧语法(在 TSX 中可能冲突)
const el2 = <HTMLInputElement>document.getElementById('id');

常见用途

  • DOM 操作(getElementById 返回 HTMLElement | null
  • JSON.parse 后指定类型(需谨慎)
  • 联合类型收窄(当你比编译器更确定类型)

风险

  • 乱用断言会掩盖真实错误(破坏类型安全)——尽量用类型保护或显式校验代替盲目断言。

面试精炼回答

“断言是编译期的类型告诉编译器使用者知道类型,但不做运行时检查。用时需谨慎,避免滥用掩盖错误。”


17. Vue2 与 Vue3 响应式原理、Vue2 的缺点、Vue3 的解决方式

Vue2(基于 Object.defineProperty

  • 对对象属性通过 Object.defineProperty 的 getter/setter 做依赖收集与派发。

  • 对数组通过劫持数组方法(push/pop/shift/unshift/splice/sort/reverse)来触发更新。

  • 缺点:

    • 无法检测新增 / 删除属性(需 Vue.set)。
    • 无法检测数组索引(arr[1] = x 不触发)。
    • 响应式需要遍历嵌套对象导致初始化开销(深度递归)。
    • 一些边界场景复杂性高(例如 proxy 化后的 API 更难与 defineProperty 混合)。

Vue3(基于 Proxy

  • 使用 Proxy 拦截 get/set/has/deleteProperty 等所有操作,实现对属性访问与变更的侦测(包括新增/删除)。

  • 优点:

    • 自动支持新增/删除属性与数组索引变化。
    • 更少的初始化开销(按需拦截)。
    • 更丰富的操作拦截(如 Reflect 结合使用)。
    • 性能与内存更优,API 更统一(ref/reactive)。

源码层面(总结)

  • Vue3 的响应式核心仍是 effect -> track -> trigger,但利用 Proxy 让实现更完整、更高效。
  • refreactive 在使用层表现不同:ref 包装基本类型(需要 .value 访问),reactive 直接代理对象;模板自动 unwrap ref

面试精炼回答

“Vue2 用 Object.defineProperty 做响应式,无法监听新增属性和数组索引,且初始化时需递归处理。Vue3 用 Proxy,能完整拦截对象/数组操作,性能与功能上都有显著改善。”


18. 说说 v-model

本质

v-model 是语法糖:双向绑定的简写,底层用 prop + 事件通信实现(Vue2 用 value + input,Vue3 用 modelValue + update:modelValue 支持多 v-model)。

Vue2(默认行为)

  • 组件上使用 v-model 等价于:props: ['value'] + 触发 this.$emit('input', newValue)

Vue3(更灵活)

  • 默认 prop 名称 modelValue,事件 update:modelValue
  • 支持自定义 v-model:foo 来映射到 foo prop 与 update:foo 事件(从而允许多 v-model)。

使用注意

  • 在组件内部不要直接修改 prop,应通过派发事件告诉父组件更新。
  • 在 Composition API 中结合 toRef/toRefs/emits 使用更安全。

面试精炼回答

“v-model 是双向绑定语法糖,Vue2 用 value+input,Vue3 用 modelValue+update:modelValue 并支持多 v-model,组件内部应通过 emit 更新父值。”


19. React 常见的 Hooks(补充)

重复提醒与扩展:

  • useLayoutEffect:在 DOM 更改但在浏览器绘制之前运行,适合测量 DOM 或同步更新。
  • useDeferredValue / useTransition:用于并发模式下延迟低优先级更新,提升交互流畅性。
  • useId:生成稳定的 id,用于服务端渲染/无冲突 id。
  • useSyncExternalStore:用于外部可订阅存储(React 18+)实现可预测的订阅更新。

20. 说说 useEffect

用途

  • 执行副作用:数据请求、订阅、事件监听、DOM 操作、计时器等。

执行时机

  • 初次渲染后执行(在浏览器绘制后),依赖变化时再次执行。具体是将回调放到微任务/宏任务队列中于渲染后执行(实现细节与 React 版本有关)。

清理函数

  • useEffect 可返回一个清理函数(用于移除订阅、清除定时器等),该函数在下一次 effect 执行前或组件卸载时调用。
useEffect(() => {
  const id = setInterval(() => { ... }, 1000);
  return () => clearInterval(id); // 清理
}, [dep]);

常见陷阱

  • 遗漏依赖:导致闭包引用旧值或内存泄露。应把 effect 中用到的外部变量(函数/值)列入依赖,或用 eslint-plugin-react-hooks 辅助。
  • 函数式更新 vs 直接使用:当需要依赖旧 state 更新时,使用 setState(prev => prev + 1) 避免闭包过期问题。
  • useEffect 与 useLayoutEffect 的选择:若需要在浏览器绘制前读写布局(测量 DOM),用 useLayoutEffect;一般副作用用 useEffect

面试精炼回答

“useEffect 用于处理副作用,依赖数组控制何时触发,返回清理函数用于卸载或 effect 重新执行前的清理。注意依赖完整性与闭包问题。”


21. React 中你使用的状态管理工具 & 实际用途(示例模板)

面试时把自己实际使用经验替换模板内容即可。

示例回答:

“在项目中我用过 Redux(含 redux-toolkit)、Zustand 与 Recoil。

  • Redux/RTK:在大型 app 中负责用户信息、权限、路由状态、持久化(localStorage)、复杂异步流程(thunks/sagas)。使用 RTK 减少样板代码并改善类型推导。
  • Zustand:在中小型项目或局部状态共享场景(侧边栏、模态、主题切换)快速搭建,API 简洁易测。
  • Recoil:尝试过用作复杂依赖的状态派生(selector),便于组件级原子化状态管理。
    使用这些状态管理工具后,主要作用是:减少 props drilling、统一权限控制、实现跨页面共享筛选条件和请求缓存、便于中间件插入(日志/持久化/恢复)。”

22. ES6 新特性(重点)

列举并示例(面试常问)

  • let/const(块级作用域)
  • 箭头函数(=>,无自身 this
  • 模板字符串(`Hello ${name}`
  • 解构赋值(对象/数组)
  • 扩展运算符(...
  • Promise / Promise.all / Promise.race
  • Class 语法(classextends
  • 模块化 import / export
  • Map / Set
  • Symbol(唯一标识)
  • async/await(基于 Promise 的语法糖)
  • Proxy(拦截对象操作)
  • Reflect(反射 API)

面试精炼回答

“ES6 引入块级作用域、箭头函数、类、模块、Promise、解构、扩展运算符、Map/Set、Symbol、Proxy 等,极大改善了可读性和模块化能力。”


23. 说说 Promise,以及它有哪些状态

三种状态

  • pending(进行中)
  • fulfilled(已成功/已完成)
  • rejected(已拒绝/出错)

状态一旦变更为 fulfilled/rejected 就不可逆。

相关 API

  • then(onFulfilled, onRejected)
  • catch(onRejected)then(null, onRejected) 的语法糖)
  • finally(() => {})(无论成功失败都会调用)
  • 静态方法:Promise.resolvePromise.rejectPromise.allPromise.racePromise.allSettledPromise.any

与微任务(microtask)关系

  • Promise 的 .then 回调是微任务,会在当前 macrotask 同步代码执行完毕后、下一个 macrotask 执行前执行(优先于 setTimeout 的回调)。

面试精炼回答

“Promise 有 pending、fulfilled、rejected 三种状态,.then/.catch/.finally 用于处理链式调用。Promise 回调运行在微任务队列,优先于宏任务(如 setTimeout)。”


24. Vue 中的插槽(slots)

类型

  • 默认插槽:不带 name 的 <slot />,用于父组件直接插入内容。
  • 具名插槽<slot name="header" />,父组件使用 <template v-slot:header>#header 填充。
  • 作用域插槽 / 具名作用域插槽(scoped slot) :子组件通过 slot 传回一组数据(slot props),父组件通过接收这些数据渲染内容(类似反向数据流)。

使用示例(Vue 3 语法)

子组件:

<template>
  <div>
    <slot name="header"></slot>
    <slot :row="rowData"></slot> <!-- 作用域插槽 -->
  </div>
</template>
<script> export default { setup(){ const rowData = { id:1 }; return { rowData } } }</script>

父组件:

<MyComp>
  <template #header>
    <h1>标题</h1>
  </template>
  <template #default="{ row }">
    <div>{{ row.id }}</div>
  </template>
</MyComp>

实现原理(简述)

  • 编译阶段:插槽被转换为函数(slot functions),运行时在渲染时调用这些函数生成 vnode。
  • 作用域插槽是惰性执行,只有渲染需要时才会执行,能结合 patchFlags 优化性能。

面试精炼回答

“插槽分默认、具名和作用域插槽。作用域插槽允许子组件向父组件暴露数据(slot props),父组件用这些数据渲染内容,适合可插拔且需要子组件数据的场景。”