前端工程前端总纲HTMLCSSJavaScriptReact工程化网络
前端面试总纲
通用前端考点详答:HTML/CSS → JavaScript → 框架 → 工程化 → 网络 → 计算机基础 → 进阶专题,覆盖第一轮系统复习。
本文定位:这是通用前端面试总纲,按「HTML/CSS → JavaScript → 框架 → 工程化 → 网络 → 计算机基础 → 进阶专题」逐项给出详细答案。它不是目录索引(文件名
index.md是历史遗留,容易误解)。与同目录其他文档的分工(避免重复背诵):
- 本文 = 总纲 / 覆盖面最广,适合第一轮系统复习;
前端面试真题130题完整答案.md= 真题演练(按原题表顺序,含 12 张图解),适合第二轮;Vue3.md/Threejs.md/WebAssembly.md/浏览器渲染优化指南= 专项深挖,本文对应章节只给结论,细节请看专项文档。
第一阶段:核心基础知识答案
1. HTML & CSS 答案
1.1 语义化
- 答案: 语义化是指用恰当含义的HTML标签(如
<header>、<nav>、<article>、<section>、<aside>、<footer>)来构建页面结构,而不是通篇使用<div>和<span>。 - 为什么重要?
- 对开发者友好: 使代码结构清晰,易于阅读、维护和团队协作。
- 对SEO友好: 搜索引擎爬虫依赖标签来判断网页内容和权重,有助于提升搜索排名。
- 对无障碍友好: 屏幕阅读器等辅助技术可以根据语义标签更好地理解并朗读页面,帮助视障用户访问。
1.2 盒模型
- 答案: CSS盒模型描述了元素在布局中占据的空间,由内到外包括:
content(内容)、padding(内边距)、border(边框) 和margin(外边距)。 - 标准盒模型 vs IE盒模型:
- 标准盒模型:
width=content的宽度。元素实际占宽 =width+padding+border+margin。 - IE盒模型 (怪异盒模型):
width=content+padding+border的宽度。元素实际占宽 =width+margin。
- 标准盒模型:
box-sizing的应用:box-sizing: content-box;(默认) 使用标准盒模型。box-sizing: border-box;使用IE盒模型。在实际开发中更常用,因为它让设置的宽度直接就是元素可见宽度的总和,方便布局,尤其在处理百分比宽度和内边距时。
1.3 BFC (块级格式化上下文)
- 答案: BFC 是 Web 页面的一块独立渲染区域,内部元素的布局不会影响外部元素。可以把它想象成一个隔离的容器。
- 如何触发? 以下任一条件均可触发:
- 根元素 (
<html>) - 浮动元素 (
float不为none) - 绝对定位元素 (
position为absolute或fixed) display为inline-block、table-cell、table-caption、flex、inline-flex、grid、inline-gridoverflow不为visible的元素 (如overflow: hidden/auto/scroll)
- 根元素 (
- 解决了什么问题?
- 清除浮动: 计算BFC高度时,浮动元素也参与计算,可以解决父元素高度塌陷问题。
- 防止外边距重叠: 同一个BFC内的两个相邻块级元素,上下
margin会重叠。但分属不同BFC的元素,margin不会重叠。 - 阻止元素被浮动元素覆盖: 一个开启了BFC的元素不会与浮动元素重叠,可用于实现自适应两栏布局。
1.4 重绘与回流 (Repaint & Reflow)
- 答案:
- 回流 (Reflow): 当页面布局和几何信息改变(如窗口大小改变、增删DOM、修改元素宽高/位置)时,浏览器需要重新计算元素的位置和大小,这个过程叫回流。
- 重绘 (Repaint): 当元素外观发生改变(如改变颜色、背景色、可见性
visibility),但不影响布局时,浏览器会重新绘制该元素,这个过程叫重绘。
- 如何避免?
- 避免逐条修改样式,最好一次性修改
class或使用cssText。 - 将需要多次重排的元素的
display设为none,操作完再显示。 - 使用文档片段
documentFragment批量操作DOM。 - 对于复杂动画,使用
position: absolute/fixed使其脱离文档流,减少对后续元素的影响。 - 避免使用
table布局,一个小改动可能引起整个表格回流。
- 避免逐条修改样式,最好一次性修改
1.5 移动端适配
- 答案: 主要方案有 rem 和 vw/vh。
- rem 方案: 相对
html根元素的字体大小。通过JS动态计算视口宽度并设置根元素font-size,所有使用rem单位的元素都会等比缩放。常用方案:手淘的flexible.js库(原理是设置data-dpr和font-size)。 - vw/vh 方案:
vw(viewport width) 视口宽度的 1/100,vh同理。直接使用vw作为单位,无需JS。例如:设计稿宽度750px,一个元素宽150px,则可写为width: (150/750)*100vw = 20vw。 - 1px 问题: 在高分辨率(DPR=2/3)屏幕上,CSS 的 1px 会被渲染成 2~3 个物理像素,看起来偏粗。注意:不能直接对整个元素
transform: scale(0.5)——那会把内容和文字一起缩小。正确做法是给伪元素画边框再缩放它:
其他方案:.hairline { position: relative; } .hairline::after { content: ''; position: absolute; left: 0; top: 0; width: 200%; height: 200%; border: 1px solid #ddd; transform: scale(0.5); /* 只缩这条伪元素边框 */ transform-origin: 0 0; pointer-events: none; } @media (-webkit-min-device-pixel-ratio: 3) { /* DPR=3 时 scale(0.333) */ }box-shadow: 0 0 0 0.5px #ddd模拟、border-image、或用viewport缩放(initial-scale配合rem)。
- rem 方案: 相对
2. JavaScript 答案
2.1 闭包
- 答案: 闭包是指有权访问另一个函数作用域中变量的函数。它通常是一个函数内部嵌套了另一个函数,并将该内部函数返回。
- 原理: 利用JavaScript的词法作用域(静态作用域)机制,内部函数引用了外部函数的变量,即使外部函数执行完毕,其变量对象也不会被销毁,因为被内部函数引用着。
- 应用场景:
- 创建私有变量: 模拟私有方法。
- 函数柯里化: 将多参数函数转化为单参数函数链。
- 在循环中保存变量状态(如
var导致的经典问题,常用立即执行函数或let解决)。
- 缺点: 使用不当可能导致内存泄漏(因为变量无法被垃圾回收)。解决: 在退出函数前,将不再使用的局部变量置为
null。
2.2 原型与原型链
- 答案: 每个函数都有一个
prototype属性,它指向一个对象,这个对象就是通过该构造函数创建的实例的原型。每个对象都有一个__proto__属性,它指向创建该对象的构造函数的prototype。当访问一个对象的属性时,如果对象本身没有,就会沿着__proto__链向上查找,直到找到或链的末端(Object.prototype.__proto__为null),这就是原型链。 - 关系:
实例.__proto__ === 构造函数.prototype,构造函数.prototype.constructor === 构造函数本身。 - 继承实现:
- ES5 (组合继承): 使用
Parent.call(this)继承属性,使用Child.prototype = new Parent()继承方法。缺点: 调用了两次父构造函数。 - ES5 (寄生组合继承): 使用
Parent.call(this)继承属性,使用Child.prototype = Object.create(Parent.prototype)和Child.prototype.constructor = Child继承方法。这是最理想的ES5继承方式。 - ES6:
class Child extends Parent,本质上是寄生组合继承的语法糖。
- ES5 (组合继承): 使用
2.3 异步编程与事件循环 (Event Loop)
- 答案: JS是单线程语言,为了协调事件、用户交互、脚本、渲染、网络请求等任务,有了事件循环机制。
- 宏任务与微任务:
- 宏任务 (MacroTask):
script(整体代码)、setTimeout、setInterval、setImmediate(Node.js)、I/O、MessageChannel、UI 事件回调。- ⚠️ 「UI 渲染」不是宏任务——渲染是浏览器在"微任务清空后、下一个宏任务前"择机执行的独立步骤,不在任务队列里。同理
requestAnimationFrame也不属于宏/微任务,它挂在渲染步骤之前。这两个是最常见的错答点。
- ⚠️ 「UI 渲染」不是宏任务——渲染是浏览器在"微任务清空后、下一个宏任务前"择机执行的独立步骤,不在任务队列里。同理
- 微任务 (MicroTask):
Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick(Node.js)。
- 宏任务 (MacroTask):
- 执行流程:
- 执行一个宏任务(最开始执行
script整体代码)。 - 执行过程中遇到微任务,将其放入微任务队列。
- 当前宏任务执行完毕后,清空整个微任务队列(按入队顺序执行)。
- 微任务队列清空后,浏览器可能会进行UI渲染(如果有需要)。
- 从宏任务队列中取出下一个宏任务执行,循环往复。
- 执行一个宏任务(最开始执行
- 浏览器与Node环境区别: Node.js 之前版本有
process.nextTick(优先级高于微任务)和微任务队列,并且有setImmediate。新版本Node与浏览器趋于一致,但仍有微小差异(如setTimeout和setImmediate执行顺序不确定)。
2.4 手写代码 (核心)
- 防抖 (Debounce)
function debounce(func, wait, immediate) { let timeout; return function(...args) { const context = this; const later = function() { timeout = null; if (!immediate) func.apply(context, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } - 节流 (Throttle)
function throttle(func, wait, options = { leading: true, trailing: true }) { let timeout, context, args, result; let previous = 0; const later = function() { previous = options.leading === false ? 0 : Date.now(); timeout = null; func.apply(context, args); if (!timeout) context = args = null; }; const throttled = function(..._args) { const now = Date.now(); if (!previous && options.leading === false) previous = now; const remaining = wait - (now - previous); context = this; args = _args; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } }; return throttled; } - 深拷贝
function deepClone(obj, hash = new WeakMap()) { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (hash.has(obj)) return hash.get(obj); // 解决循环引用 const cloneObj = new obj.constructor(); hash.set(obj, cloneObj); for (let key in obj) { if (obj.hasOwnProperty(key)) { cloneObj[key] = deepClone(obj[key], hash); } } return cloneObj; } new操作符原理function myNew(constructor, ...args) { // 1. 创建一个空对象 const obj = {}; // 2. 将空对象的原型指向构造函数的 prototype Object.setPrototypeOf(obj, constructor.prototype); // 3. 将构造函数的 this 指向这个新对象,并执行构造函数 const result = constructor.apply(obj, args); // 4. 如果构造函数返回了一个对象,则返回那个对象;否则返回新创建的对象 return result instanceof Object ? result : obj; }Promise.all实现function promiseAll(promises) { return new Promise((resolve, reject) => { if (!Array.isArray(promises)) return reject(new TypeError('Arguments must be an array')); const results = []; let count = 0; if (promises.length === 0) return resolve(results); promises.forEach((promise, index) => { Promise.resolve(promise).then(value => { results[index] = value; count++; if (count === promises.length) resolve(results); }).catch(reject); }); }); }
3. 主流框架 (React) 答案
3.1 Virtual DOM 与 Diff 算法
- Virtual DOM: 是一个JS对象,是对真实DOM的轻量级抽象表示。
- 为什么快? 直接操作真实DOM代价高(引起回流重绘)。VDOM 在JS层面操作,通过Diff算法找出最小变更,然后一次性批量更新真实DOM,减少了浏览器回流重绘次数。
- Diff 算法原理 (React):
- 同级比较: 只会对同一层级的节点进行比较,不会跨层级比较,复杂度为 O(n)。
- 不同类型节点: 如果节点类型不同(如
div变p),直接删除旧子树,重建新子树。 - 相同类型节点: 递归比较属性,然后继续比较子节点。
- 列表比较 (key的作用): 通过
key来识别节点是否可以复用、移动或删除。key应该是稳定的、唯一的,最好不要用数组索引,因为索引会随元素删除/插入而变化,导致性能问题或状态错乱。
3.2 Hooks (核心)
- 为什么要有 Hooks?
- 逻辑复用难: 之前使用 HOC 和 Render Props 会导致组件嵌套地狱,代码难以理解。
- 复杂组件难拆分: 生命周期函数里常常混入不相关的逻辑(如数据获取和事件监听)。
- Class 的困扰:
this指向问题,bind麻烦,不利于编译优化(如压缩、tree-shaking)。
useState和setState区别:setState在类组件中会合并更新(浅合并),而useState的set函数是替换更新。setState有回调函数获取最新状态,useState需要使用useEffect。- 在异步事件中,React 18 之前,
setState在合成事件和生命周期中是异步批处理的,useState也是;React 18 之后,自动批处理(Automatic Batching)让两者在更多场景下(如 Promise、setTimeout)也能批量更新。
useEffect与useLayoutEffect区别:useEffect: 在浏览器绘制完成后异步执行,不会阻塞页面渲染。useLayoutEffect: 在 DOM 变更后、浏览器绘制之前同步执行。会阻塞视觉更新,用于读取/修改DOM并立即生效的情况(如测量DOM元素宽高),应尽量少用,避免性能问题。
- Hooks 原理: React 内部为每个组件维护一个链表来存储 Hooks。
useState、useEffect等 Hook 的调用顺序决定了它们在链表中的位置,因此Hooks 不能在条件语句、循环或嵌套函数中调用,必须保证每次渲染时顺序一致。
3.3 性能优化
React.memo(用于函数组件): 对props进行浅比较,如果props没变,则跳过该组件及其子组件的重新渲染。useMemo: 缓存计算结果,只有依赖项变化才重新计算。useCallback: 缓存函数引用,只有依赖项变化才返回新函数。- 代码分割: 使用
React.lazy和Suspense配合动态import()按需加载组件,减少首屏包体积。 shouldComponentUpdate/PureComponent: 类组件中避免不必要的更新。
4. 工程化与进阶答案
4.1 Webpack
- Loader 和 Plugin 的区别:
- Loader: 本质是一个转换器,用于对模块的源代码进行转换。因为 Webpack 原生只认识 JS 和 JSON,Loader 负责处理其他类型文件(如
.css、.vue、图片)并将其转换为有效模块。 - Plugin: 本质是一个扩展器,监听 Webpack 在整个生命周期中广播的事件,通过钩子函数来执行更广泛的任务(如打包优化、资源管理、环境变量注入)。
- Loader: 本质是一个转换器,用于对模块的源代码进行转换。因为 Webpack 原生只认识 JS 和 JSON,Loader 负责处理其他类型文件(如
- 热更新 (HMR) 原理:
- 文件监听: Webpack 监听文件变化,重新编译打包,并将代码保存到内存中。
- 通知:
webpack-dev-server通过 WebSocket 与浏览器建立长连接,通知浏览器新模块的hash。 - 请求更新: 浏览器端的 HMR runtime 根据
hash通过fetch/XHR向服务器拉取 **hot-update.json(更新清单)**与对应的hot-update.js(增量模块代码),再执行回调完成替换。- ⚠️ 纠偏:早期
webpack-hot-middleware用的是 JSONP,现代的webpack-dev-server已改为fetch/XHR 拉取 chunk(带webpackHotUpdate回调)。答 JSONP 会被认为停留在老版本。
- ⚠️ 纠偏:早期
- 模块替换: HMR runtime 执行模块替换,并通知应用代码执行相应的回调(如
module.hot.accept),实现页面无刷新更新。
- 构建流程:
- 初始化参数: 从配置文件和 Shell 语句中读取并合并参数,得到最终参数。
- 开始编译: 用参数初始化
Compiler对象,加载所有插件,执行run方法。 - 确定入口: 根据配置中的
entry找出所有入口文件。 - 编译模块: 从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,递归处理直到所有入口依赖的文件都经过了处理。
- 完成模块编译: 得到每个模块被翻译后的最终内容以及它们之间的依赖关系图。
- 输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
chunk,再把每个chunk转换成一个单独的文件加入到输出列表。 - 输出完成: 根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
4.2 TypeScript
interface和type的区别:- 相同点: 都可以用来描述对象或函数类型。
- 不同点:
interface可以重复声明(声明合并),type不能重复声明。interface可以通过extends继承,type可以通过交叉类型&扩展。type可以定义联合类型 (|)、元组、基本类型别名等,更灵活。- 官方推荐优先使用
interface,无法满足需求时用type。
- 泛型: 是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。例如:
function identity<T>(arg: T): T { return arg; }。 - 工具类型实现原理:
Partial<T>: 将T中所有属性变为可选。{ [P in keyof T]?: T[P] }。Pick<T, K>: 从T中挑选一组属性K构建新类型。{ [P in K]: T[P] }。Readonly<T>: 将T中所有属性变为只读。{ readonly [P in keyof T]: T[P] }。Record<K, T>: 构造一个类型,其键名为K,键值为T。{ [P in K]: T }。
5. 浏览器与网络答案
5.1 从输入URL到页面显示完整过程
- 答案: 这是一个经典的高频面试题,考察知识广度。
- URL 解析: 检查输入合法性,判断是搜索关键字还是网址。
- DNS 解析: 将域名解析为 IP 地址。查找顺序:浏览器缓存 -> 操作系统 Hosts 文件 -> 本地 DNS 服务器 -> 根/顶级/权威 DNS 服务器。
- 建立 TCP 连接: 与目标服务器进行三次握手,建立可靠的连接。
- 发送 HTTP/HTTPS 请求: 浏览器向服务器发送请求报文(请求行、请求头、请求体)。如果是 HTTPS,还有 TLS 握手过程。
- 服务器处理请求并返回响应: 服务器处理请求并返回响应报文(状态码、响应头、响应体)。
- 浏览器接收响应并解析渲染:
- 判断状态码,处理重定向等。
- 如果响应内容是 HTML,开始解析渲染过程。
- 断开 TCP 连接: 通过四次挥手断开连接(如果是短连接)。现代浏览器通常使用长连接
Connection: keep-alive。 - 浏览器渲染页面: 解析 HTML 构建 DOM 树,解析 CSS 构建 CSSOM 树,合并成 Render 树,计算布局(Layout/Reflow),绘制(Paint),合成(Composite)。
5.2 浏览器缓存机制
- 答案: 缓存分为强缓存和协商缓存。
- 强缓存: 直接从本地缓存读取,不与服务器通信。相关字段:
Expires(HTTP/1.0,绝对时间,受本地时间影响) 和Cache-Control(HTTP/1.1,相对时间,优先级更高,常用max-age=3600)。 - 协商缓存: 向服务器发送请求,由服务器判断缓存是否可用。如果可用,返回 304 状态码,让浏览器使用本地缓存。相关字段:
Last-Modified(最后修改时间) 和If-Modified-Since;ETag(文件唯一标识,基于内容) 和If-None-Match。ETag比Last-Modified更精确。
- 强缓存: 直接从本地缓存读取,不与服务器通信。相关字段:
5.3 XSS 和 CSRF
- XSS (跨站脚本攻击): 攻击者将恶意脚本注入到网页中,当其他用户浏览该网页时执行恶意脚本。
- 防御: 输入过滤,对输出进行转义(HTML 实体编码),使用 CSP (内容安全策略),对 Cookie 设置
HttpOnly。
- 防御: 输入过滤,对输出进行转义(HTML 实体编码),使用 CSP (内容安全策略),对 Cookie 设置
- CSRF (跨站请求伪造): 攻击者诱导用户访问恶意网站,利用用户在其他网站的登录状态,以用户身份发起恶意请求。
- 防御: 使用 Token 验证(请求中携带服务器生成的随机 token),验证 Referer,对敏感操作使用二次验证(如验证码)。
6. 计算机基础答案
6.1 数据结构与算法
- 常见数据结构:
- 数组: 连续内存空间,随机访问快 O(1),插入删除慢 O(n)。
- 链表: 非连续内存,插入删除快 O(1),查找慢 O(n)。常见题:反转链表、判断是否有环。
- 栈: 后进先出 (LIFO)。应用:函数调用栈、括号匹配。
- 队列: 先进先出 (FIFO)。应用:任务队列。
- 树: 二叉树、二叉搜索树(左小右大)、平衡二叉树。
- 哈希表: 键值对存储,通过哈希函数计算存储位置,查找、插入、删除平均 O(1)。应用:对象、Map。
- 常见算法思想:
- 递归: 函数调用自身。必须有终止条件。
- 分治: 将大问题分解为若干个小问题,分别解决后合并。如:归并排序、快速排序。
- 动态规划: 将复杂问题分解为简单子问题,并保存子问题结果避免重复计算。如:斐波那契数列、背包问题。
- 回溯: 一种试错思想,尝试分步解决,当发现当前选择不满足条件时,回退到上一步重新选择。如:八皇后、全排列。
当然,面试准备是一个持续迭代的过程,除了上述的核心技术点,还有一些重要的补充内容,可以帮助你在面试中脱颖而出。以下是对之前答案的补充,涵盖了进阶技术、软技能、以及一些更深入的知识点。
补充内容:进阶与扩展
7. 性能优化(进阶篇)
除了常规的代码分割、懒加载,还可以深入理解以下内容:
7.1 性能指标
- 核心 Web Vitals:
- LCP (Largest Contentful Paint): 最大内容绘制,衡量加载性能(应≤2.5s)。
- INP (Interaction to Next Paint): 交互到下一次绘制,衡量页面整个生命周期中的交互响应性(良好阈值通常为 ≤200ms,按第 75 百分位评估)。INP 已于 2024-03-12 取代 FID 成为 Core Web Vital。
- CLS (Cumulative Layout Shift): 累积布局偏移,衡量视觉稳定性(应≤0.1)。
- 其他指标: FCP (First Contentful Paint)、TTI (Time to Interactive)、TBT (Total Blocking Time)。
7.2 性能监控
- RUM (Real User Monitoring): 使用 Performance API 收集真实用户数据,上报到监控平台(如 Sentry、自研)。
- 性能面板分析: 熟练使用 Chrome DevTools 的 Performance、Lighthouse、Network 面板进行瓶颈分析。
7.3 关键渲染路径优化
- 减少关键资源数量: 内联关键 CSS,延迟加载非关键 CSS/JS。
- 减少关键资源大小: 压缩、Tree Shaking、代码分割。
- 减少关键往返次数: 使用 HTTP/2 多路复用,合并文件。
7.4 渲染层优化
- 避免强制同步布局: 在修改 DOM 后立即读取布局属性会导致强制回流。
- 使用
will-change: 提前告知浏览器哪些元素会变化,以便优化(但不要过度使用)。 - CSS 动画优于 JS 动画: 使用 transform 和 opacity 可以触发合成,避免布局和绘制。
8. 微前端
如果你在简历中提到了微前端,或者面试的是中高级岗位,可能会被问到。
- 定义: 将前端应用拆分成多个独立开发、独立部署的子应用,组合成一个整体。
- 核心价值:
- 技术栈无关: 不同团队可以使用不同框架。
- 独立开发部署: 降低耦合,提升迭代速度。
- 增量升级: 老项目可以逐步重构,而不是重写。
- 主流方案:
- 基座模式 (single-spa, qiankun): 通过路由加载子应用,JS 沙箱、样式隔离。
- 自组织模式 (Webpack Module Federation): 模块联邦,实现运行时共享模块。
- 微前端组合层 (iframe): 简单但体验差,不推荐。
- 核心问题:
- 样式隔离: CSS Module、CSS-in-JS、Shadow DOM、约定式命名(如 BEM)。
- JS 沙箱: 快照沙箱、Proxy 沙箱(qiankun 实现原理)。
- 应用通信: 全局事件总线、URL 传参、共享状态。
9. Node.js 相关知识
前端工程师往往也需要掌握一定的 Node.js 知识,尤其是在工程化、BFF(Backend For Frontend)层。
- 核心模块:
fs、path、http、events。 - 事件循环: Node.js 的事件循环与浏览器的区别(阶段划分:timers、pending callbacks、idle prepare、poll、check、close)。
- Stream: 可读流、可写流、双工流、转换流。背压处理,流式数据处理优势。
- 进程与集群:
child_process、cluster模块,利用多核 CPU。 - 常用框架: Express、Koa、Egg.js、NestJS。了解中间件机制(洋葱模型)。
- BFF 层职责: 接口聚合、数据裁剪、SSR(服务端渲染)、鉴权。
10. 测试
了解测试策略,至少会写单元测试。
- 测试金字塔: 单元测试 > 集成测试 > E2E 测试。
- 单元测试工具: Jest、Mocha、Jasmine + 断言库(Chai)。
- React 测试工具: React Testing Library、Enzyme。
- E2E 测试: Cypress、Playwright、Puppeteer。
- 测试要点:
- 测试前准备(Arrange)、执行(Act)、断言(Assert)。
- 测试覆盖率(行覆盖率、分支覆盖率)。
- Mock 外部依赖(网络请求、模块)。
11. 前端架构设计
对于高级岗位,可能会考察架构设计能力。
- 设计原则: 高内聚低耦合、开闭原则、单一职责。
- 分层架构: 展示层、业务逻辑层、数据访问层(如 Model-View-ViewModel)。
- 组件设计: 原子设计(原子、分子、组织、模板、页面),组件分类(基础组件、业务组件、页面组件)。
- 状态管理选型: Redux、MobX、Zustand、Recoil 的优劣对比和适用场景。
- 权限设计: 路由权限、按钮权限、数据权限的实现方式。
- 错误处理: 全局错误边界(Error Boundaries)、请求重试、降级方案。
12. 软技能与行为面试
很多技术不错的候选人会因为软技能不足而败北。
12.1 常见行为问题(STAR 法则回答)
- “请分享一个你遇到过的技术难题,以及你是如何解决的?”
- 情境: 项目背景,时间压力等。
- 任务: 你需要解决的具体问题。
- 行动: 你采取的具体步骤,如调研方案、设计、代码实现、测试、复盘。
- 结果: 最终效果(用数据说话,如性能提升、Bug减少、团队采纳)。
- “请分享一个你与团队成员意见不合的例子。”
- 展示你的沟通技巧、同理心、如何达成共识。
- “你如何看待加班?”
- 表明你对工作的责任心,同时强调效率。
- “你为什么想来我们公司?”
- 表达对公司业务、技术、文化的认同,结合自身发展规划。
12.2 其他软技能
- 沟通能力: 能够清晰表达技术方案,与产品、后端、UI 有效协作。
- 学习能力: 展示你如何追踪新技术,如何快速学习新框架/工具。
- 领导力/影响力: 指导新人、推动技术改进、知识分享。
13. 前沿技术趋势
虽然不必精通,但了解趋势能展现你的技术视野。
- Serverless: 无需管理服务器,按需执行函数。前端可以结合 Faas 实现后端逻辑。
- WebAssembly (WASM): 将 C/C++/Rust 等编译到浏览器运行,适合高性能计算(如图像处理、游戏)。
- PWA (Progressive Web App): 可离线、可安装、可推送,提升 Web 体验。
- GraphQL: 一种查询语言,由客户端指定所需字段,避免 over-fetching 和 under-fetching。
- Rust 在前端工具链的应用: SWC(替代 Babel)、Turbopack(Webpack 继任者)等,追求极致的编译速度。
14. 一些经典的手写代码补充
除了之前提到的,面试中还可能遇到:
- 实现一个简单的 Event Bus (发布-订阅模式)
class EventBus { constructor() { this.events = {}; } on(event, listener) { (this.events[event] || (this.events[event] = [])).push(listener); } emit(event, ...args) { (this.events[event] || []).forEach(listener => listener(...args)); } off(event, listener) { if (!this.events[event]) return; this.events[event] = this.events[event].filter(l => l !== listener); } once(event, listener) { const wrapper = (...args) => { listener(...args); this.off(event, wrapper); }; this.on(event, wrapper); } } - 实现一个 compose 函数(函数组合)
function compose(...funcs) { if (funcs.length === 0) return arg => arg; if (funcs.length === 1) return funcs[0]; return funcs.reduce((a, b) => (...args) => a(b(...args))); } - 实现一个简单的 Promise(符合 Promises/A+ 规范的部分核心)(这是一个大挑战,但了解 then 链式调用即可)
- 实现一个简单的 Redux(createStore)
function createStore(reducer, preloadedState, enhancer) { if (enhancer) return enhancer(createStore)(reducer, preloadedState); let state = preloadedState; let listeners = []; function getState() { return state; } function dispatch(action) { state = reducer(state, action); listeners.forEach(listener => listener()); return action; } function subscribe(listener) { listeners.push(listener); return () => { listeners = listeners.filter(l => l !== listener); }; } dispatch({ type: '@@redux/INIT' }); return { getState, dispatch, subscribe }; }
15. 项目经验展示技巧
- 挑选 1-2 个最能体现你能力的项目,在面试中主动引导话题。
- 从业务价值出发: 项目解决了什么业务痛点?带来了什么商业价值?
- 技术难点: 详细描述一个技术难点,展示你的深度思考。
- 架构设计: 如果你参与了架构设计,画出架构图(口头描述或简单草图),说明选型理由。
- 数据支撑: 用数据量化成果(性能提升、接入率、错误率下降)。