面完这轮前端实习面试,我在地铁上就把问题一条条记下来了。作为大专大二的学生,这个暑假能不能拿到一份前端实习,基本决定了秋招简历上有没有东西可写。整场面试聊了一个多小时,面试官一半时间在追问项目里脚手架的搭建过程,另一半在问 HTTP 和 XSS 相关的八股,中间还穿插了两道手写题和几个开发场景排查。
回来后我做了完整复盘,把问到的每个点重新推导了一遍,包括脚手架从零怎么搭、强缓存和协商缓存的完整字段、DOM 型 XSS 的攻击链路和防御方案。这篇内容就是这次面经的实录,不仅记录面试官问了什么,更会讲清楚每个问题背后的原理、我的回答思路以及当时没答好的地方。希望给同样在准备前端暑期实习的同学一些参考,尤其是学历不占优势、靠自学走到现在的朋友。
1. 面试前的准备:大专生怎么规划暑期实习
1.1 为什么大二就得出来面试
坦白讲,投这份实习之前我纠结了很久。大二上学期结束、下学期刚开始,身边很多同学都觉得实习还早,但我自己算了一笔时间账:大三一开学就要卷秋招,而秋招简历上最值钱的板块就是实习经历。如果等到大三再去投暑期实习,面试大概率会撞上秋招提前批,压力会大很多。
所以我的规划是:大二下学期就开始投,目标锁定暑期实习,公司大小无所谓,外包、远程、创业团队都行,先上车积累一段真实业务经验再说。事实证明这个决定是对的,面试官对我的学历没有太多偏见,反而更关注我有没有实际做过东西、基础概念能不能讲清楚。
这次面试能约上,我觉得和我的准备方向也有关。我没有海投,而是挑了十几家规模不大但技术栈匹配的团队,根据岗位要求微调简历,把我做过的项目和脚手架搭建经历写在最显眼的位置。比起一份“精通 Vue 全家桶”的空话简历,面试官明显更愿意看一个能讲清楚自己写了什么的人。
1.2 我把复习重心放在了哪三块
确定目标后,我列了一份复习清单,主要分三块:基础八股、项目细节、手写题。八股里优先级最高的是 HTTP、XSS、浏览器原理、Vue 响应式原理这几个方向,因为这些几乎每家都会问。项目细节我会把脚手架的搭建过程、请求封装、权限拦截一条条写成文档,确保被追问时能说出具体实现而不是含糊带过。
手写题我每天刷 3 到 5 个,重点不是背答案,而是理解函数内部发生了什么。实际面试时,面试官看到你写对了还不一定满意,会追问几个边界情况,比如“防抖的 this 指向怎么保存”“reduce 不传初始值会怎样”,这种题没有真理解是答不出来的。
另外我还准备了一个“自我介绍模板”,控制在两分钟内,核心逻辑是:我是谁、我做过什么、我擅长什么、我为什么想加入这里。因为是大专学历,我会重点强调自己的动手能力和复盘习惯,比如写过博客、参与过开源项目。这些内容在这次面试里发挥了很大作用,后面会详细说。
1.3 简历不花哨,但每一条都能被追问
我的简历只有一页,项目经历写了两个:一个是后台管理系统,一个是这个面经里重点聊的脚手架工具。管理系统重点是权限控制和数据可视化,脚手架工具则体现了我的工程化能力。每一段描述我都会提前想好至少三个会被追问的点,比如“你这个脚手架怎么实现的”“下载模板失败了怎么办”“Vite 比 webpack 快在哪里”。
这里有个经验:简历上写的任何东西,面试官都有可能在现场让你展开讲,写上去之前先问自己一句“如果被问到最深的细节,我能答上来吗”。答不上来的东西宁可不写,写了就要做好准备。我身边很多同学简历写得花团锦簇,一问项目细节就支支吾吾,反而扣分更严重。
这次面试开场,面试官就顺着简历问了一句“你简历上说你搭过脚手架,这个脚手架解决了什么问题”,我一下就明白,项目细节才是这轮面试的主线。
2. 脚手架搭建的高频题:从使用到实现
2.1 面试官为什么揪着脚手架不放
面试官问脚手架,表面上是问工具,实际上是在摸底三段能力:第一,你有没有工程化意识,知不知道一个项目从零到一需要哪些步骤;第二,你有没有研究过常用工具背后的原理,还是只会机械地敲命令;第三,你有没有动手扩展过工具链,比如改过打包配置、写过脚本。这三段能力对应的是“熟练工”“工程师”“能独当一面”三个层次。
很多同学回答脚手架只会说“我用 Vue CLI 和 Vite 创建过项目”,这个答案太浅了。面试官想听的是,脚手架到底替你做了哪些事:初始化目录结构、统一依赖版本、生成公共配置、启动开发服务器、提供构建命令、集成代码规范和测试工具。如果你能把这些环节拆开讲,就已经超过了大多数候选人。
我这次准备的思路是:先讲清楚“脚手架的本质是自动化工程初始化”,再讲我实际怎么从零写了一个迷你脚手架,把 bin 配置、交互式问答、模板下载、动态渲染这几个核心步骤用自己的话说一遍。面试官听完明显有兴趣继续追问,后面好几个问题都是顺着这条路展开的。
2.2 Vue CLI 和 Vite 的区别要这样答
面试官问我的第一道技术题是“你平时用哪个脚手架,Vite 和 Vue CLI 有什么区别”。我先说了结论:Vue CLI 基于 webpack,Vite 基于原生 ESModule。然后我展开解释了两者的本质差异——webpack 在启动开发服务器时,需要把所有模块打包成一个 bundle,项目越大,启动越慢;Vite 则利用了浏览器原生支持 import 的特性,开发时不需要整体打包,而是按需把请求到的模块返回给浏览器,所以冷启动速度非常快。
依赖处理也是一个关键点。Vite 会用 esbuild 做依赖预构建,把 node_modules 里的依赖提前转成 ESModule 并统一版本,减少浏览器请求次数;Vue CLI 则走 webpack 的完整构建流程,配置多、编译步骤多,但生态更成熟,遇到问题时社区方案也更多。生产构建上两者也有区别:Vue CLI 用 webpack 打包,Vite 使用 Rollup。
我特意补了一个细节:自己在写脚手架时,会同时支持“拉取 Vite 模板”和“拉取 Vue CLI 模板”两种选择,因为不同团队维护的老项目可能还在用 Vue CLI,新项目则更推荐 Vite。面试官听完就说了一句“你能从使用场景出发考虑方案,这个思路是对的”。我当时心里松了一口气,感觉这一题拿到了加分。
2.3 手写一个迷你脚手架的核心流程
这道题是我准备最充分的,因为简历上写了。面试官问我具体怎么实现,我直接把设计思路和核心代码讲了一遍,代码不用完整背,但流程必须清楚。
第一步,在 package.json 里配置 bin 字段,指定命令入口文件:
{ "name": "my-cli", "version": "1.0.0", "bin": { "my-cli": "./bin/index.js" } }第二步,在入口文件头部加上#!/usr/bin/env node,告诉系统用 Node.js 执行这个文件。
第三步,用 commander 或者单纯解析 process.argv 处理命令。我写了一个create命令,用法是my-cli create my-project:
#!/usr/bin/env node import { program } from 'commander' program .command('create <projectName>') .description('create a new project') .action((projectName) => { console.log(`creating project: ${projectName}`) }) program.parse(process.argv)第四步,用 inquirer 做交互式问答,让用户选择框架模板,比如 React 还是 Vue,TypeScript 还是 JavaScript,是否需要 ESLint。这个环节对应的是 create-vite 和 vue-cli 初始化时那一堆交互选项。
第五步,根据用户选择,从 GitHub 或 Gitee 拉取对应的模板仓库,这一步用 download-git-repo 库实现,支持direct:前缀直接下载 zip 包。下载完成后,需要用 handlebars 或 ejs 对模板里的变量做替换,把 package.json 的 name、version、author 替换成用户输入的内容。最后提示用户进入目录并执行 npm install、npm run dev。
这个流程听起来不长,但每一步都有坑。比如 download-git-repo 对 GitHub 的域名解析偶尔会超时,需要切换到国内镜像;模板渲染时会遇到{{ }}冲突,需要在模板里转义;bin 文件名和 package.json 里的命令名不一致会导致安装后命令找不到。这些坑我都实际踩过,所以能说得比较具体,面试官也能感觉到我真的实现过,而不是背了一段网上的教程。
2.4 脚手架题的加分回答模板
如果只讲步骤,这道题也就是及格分。我后续补充了两个加分项:一是讲清楚自己为什么做这个脚手架,而不是直接用 create-vite。我的理由是团队里老项目技术栈不统一,有人用 JS,有人用 TS,有人用 Vue2,有人用 Vue3,创建一个新项目时每次都要手动改一堆配置。我写脚手架的目标就是让新人一条命令拉起符合规范的项目,省去重复劳动。
二是讲了“脚手架如何做模板版本管理”。我一开始是从固定分支拉模板,后来发现模板更新后老项目想升级很麻烦,于是改成打 tag,脚手架支持指定版本号,比如my-cli create app --template vue3-ts@1.2.0。这个设计灵感来自 create-vite 的模板命名方式,也让面试官觉得我考虑了工程化中的版本演进问题。
最后我补了一句:如果只是做一个给个人用的脚手架,直接复制 create-vite 改一改就好了,没必要重复造轮子;自己做是因为想完全掌控模板内容和初始化流程,也为了学习原理。这句话表达了一种务实的技术判断,面试官一般比较认可。
3. HTTP 相关八股:真实面试题与答题思路
3.1 从「输入 URL 到页面展示」开头
HTTP 这一块,面试官是从一道综合性问题开始的:“你在地址栏输入一个网址到页面展示出来,中间发生了什么”。这道题范围很广,实际上是给考生一块自由发挥的画布,面试官会在你停顿的地方继续深挖。
我按照这个顺序回答:先做 DNS 解析,把域名解析成 IP;然后通过 TCP 三次握手建立连接;如果是 HTTPS,还要多做一次 TLS 握手,协商加密套件和证书验证;接着浏览器构造 HTTP 请求报文,发送到服务器;服务器处理请求后返回 HTTP 响应,状态码、响应头、响应体;浏览器拿到 HTML 后解析,遇到 CSS、JS 等静态资源会再次发请求;最后构建 DOM 树和 CSSOM 树,执行 JavaScript,完成渲染。
我说到“TCP 三次握手”时,面试官打断了,问“为什么是三次而不是两次”。这个问题很多人会背“防止重复连接”之类的结论,但我换了个角度解释:三次握手实际上是双方互相确认彼此的收发能力。第一次客户端告诉服务端“我要连你”,第二次服务端告诉客户端“我收到你的请求,我也能发”,第三次客户端告诉服务端“我收到你的应答,准备开始传输数据”。只有完成这三步,双方才能确认自己的发送和接收都正常。这样答,面试官能看出你不是死记硬背。
3.2 HTTPS 比 HTTP 安全在哪
接下来面试官直接问“HTTP 和 HTTPS 的区别”。我先说基本点:HTTP 明文传输,HTTPS 在 HTTP 和 TCP 之间加了一层 TLS/SSL 加密协议。然后我展开讲了加密原理——HTTP 本身没有加密能力,HTTPS 通过混合加密方案解决安全问题。
具体是这么个逻辑:直接用对称加密,密钥一旦被中间人截获,所有通信内容都能被解密;直接用非对称加密,性能开销太大。所以 TLS 握手阶段使用非对称加密协商一个对称密钥,之后用这个对称密钥加密实际传输的数据。非对称加密解决了密钥分发问题,对称加密解决了性能问题。证书的作用是验证服务器身份,防止中间人冒充服务器,浏览器会校验证书是否由可信 CA 签发、域名是否匹配、是否过期。
面试官追问:“如果证书过期了,浏览器会怎么做?”我回答浏览器会给出不安全的警告,用户可以选择继续访问,但正常情况下应该停止访问,因为服务器身份已经无法验证。他没有继续深挖证书链,我已经准备好要讲根证书、中间证书和证书链校验了,这题就算过了。
3.3 HTTP 状态码高频考点速查表
状态码是 HTTP 八股里的送分题,但也是细节最多的一类。面试官这次没有让我背全部状态码,而是问了几个实际场景:“你平时开发中见过哪些状态码,分别代表什么问题”。
我把常见状态码按照几个大类和实际排查经验过了一遍,这里也整理成表格,方便大家直接背:
| 状态码 | 含义 | 常见触发场景 |
|---|---|---|
| 200 | 请求成功 | 正常返回页面或接口数据 |
| 201 | 创建成功 | POST 创建资源成功 |
| 204 | 无内容 | 删除操作成功,无返回体 |
| 301 | 永久重定向 | 网站换域名,旧地址跳转新地址 |
| 302 | 临时重定向 | 未登录跳转到登录页 |
| 304 | 未修改 | 协商缓存命中,服务器判定资源没变 |
| 400 | 请求参数错误 | 必填字段缺失、JSON 格式不对 |
| 401 | 未认证 | 未携带 token 或 token 失效 |
| 403 | 无权限 | 已登录但无权访问该资源,或跨域被拦截 |
| 404 | 资源不存在 | 接口路径写错、路由未匹配 |
| 500 | 服务器内部错误 | 后端代码异常、数据库未启动 |
| 502 | 网关错误 | 网关拿不到上游服务响应,服务挂了或没启动 |
| 503 | 服务不可用 | 服务器过载、停机维护 |
| 504 | 网关超时 | 上游服务处理太久,网关等不下去了 |
我特意强调了一个容易混淆的点:401 和 403 的区别。401 表示“你是谁我不知道”,需要先认证;403 表示“我知道你是谁,但你没有权限做这件事”。如果登录后发现请求返回 403,比较多的情况是跨域配置没放开,或者角色权限不够。这种细节如果不理解,排查接口问题时很容易走弯路。
3.4 强缓存与协商缓存:别只背概念
缓存机制是 HTTP 面试题里的重头戏,每家几乎都会问。面试官这次问的是“强缓存和协商缓存的区别”,我按照字段、优先级、触发流程三个方面回答。
强缓存,核心字段是Cache-Control: max-age=3600和Expires。浏览器在有效期内不会发请求,直接使用本地副本,状态码显示 200,但来源是 memory cache 或 disk cache。Cache-Control中的max-age是相对时间,Expires是绝对时间,两者同时存在时Cache-Control优先级更高,因为Expires依赖客户端本地时间,本地时间不准就会出问题。
协商缓存,核心是Last-Modified和ETag。浏览器带着If-Modified-Since或If-None-Match去请求,服务器判断资源没变,返回 304,浏览器继续用本地副本。ETag比Last-Modified更精确,因为Last-Modified只能精确到秒,同一秒内修改多次就判断不出来,而且有些服务器返回的修改时间不准确。
我的回答里还加了一个实战场景:在脚手架的模板下载里,我也用到了缓存思路,对下载过的模板做本地缓存,二次创建时直接复用,减少网络请求。这样面试官会觉得你是真的把知识用起来了,而不只是会背字段。
3.5 HTTP 版本对比:1.1、2.0、3.0
这个问题是我主动提的,因为前面聊到 TCP,我觉得可以顺带把 HTTP 版本演进讲掉。幸好我提前准备过,不然很容易翻车。
HTTP/1.1 的问题主要有两个:一是队头阻塞,同一个连接上多个请求必须按顺序排队,前一个响应没返回,后面的只能等着;二是头部冗长,每次请求都重复携带大量头信息。虽然 1.1 有了 keep-alive 连接复用,但还是必须严格遵守“先进先出”的响应顺序。
HTTP/2 为了解决这些问题,引入了二进制分帧层,多个请求可以同时在同一个连接上交错传输,这叫多路复用。它还用 HPACK 算法对头部做压缩,大大减少了重复头部开销。另外 2.0 支持服务端推送,服务器可以主动推送 CSS、JS 等资源。但 2.0 并没有彻底解决队头阻塞,因为 TCP 层的丢包重传机制还是会阻塞整个连接。
HTTP/3 换了个思路,直接放弃 TCP,改用基于 UDP 的 QUIC 协议。QUIC 实现了可靠传输和快速握手,还支持 0-RTT 连接建立,避免了 TCP 慢启动带来的延迟,也解决了 TCP 层队头阻塞。面试官听完点了点头,这个知识点虽然不一定每家都问,但答出来绝对是加分项。
4. XSS 漏洞详解:三种类型、payload 与防御
4.1 反射型、存储型、DOM 型的区别
XSS 是前端安全里最高频的面试题,也是这次面试的压轴环节。面试官先让我讲三种 XSS 的区别,我把分类标准和攻击链路都讲了一遍。
反射型 XSS,也叫非持久性 XSS,恶意脚本藏在 URL 参数里,服务端把参数内容原样拼到 HTML 返回,浏览器解析时就执行了。特点是攻击链路短,需要诱导用户点击恶意链接。存储型 XSS,也叫持久性 XSS,恶意脚本被保存在服务器数据库里,比如一个评论功能没做过滤,用户发了一条包含脚本的评论,所有访问这个页面的用户都会中招,危害最大。DOM 型 XSS 不走服务端,恶意脚本通过 URL、localStorage 等输入源进入页面,被前端 JavaScript 用 innerHTML、document.write 等方式插入 DOM 后执行,纯前端侧就完成了攻击。
三种类型的对比,我整理了一个表格:
| 类型 | 脚本是否入库 | 攻击入口 | 执行位置 | 典型场景 |
|---|---|---|---|---|
| 反射型 | 否 | URL 参数 | 服务端渲染后 | 搜索页报错信息回显 |
| 存储型 | 是 | 表单、评论 | 服务端渲染后 | 评论区、用户昵称 |
| DOM 型 | 否 | URL hash、query | 前端 DOM 操作 | 前端路由参数渲染 |
我强调了一个点:DOM 型 XSS 不依赖服务端拼接,所以很多后端开发者觉得“我做了转义就安全了”其实并不够,前端如果用了 innerHTML 和v-html这类危险 API,依然会中招。
4.2 DOM 型 XSS 的完整攻击流程
面试官听到我说 DOM 型后,追问了一个很细的问题:“DOM 型 XSS 的攻击流程是怎样的,你手写给我看”。我现场写了这样一个链路:
第一步,攻击者构造恶意链接,把 payload 放进 URL 参数:
https://example.com/item?id=<img src=x onerror=alert(document.cookie)>第二步,受害者点击链接,页面 JavaScript 读取 URL 参数并直接插入 DOM:
const params = new URLSearchParams(location.search) const id = params.get('id') document.getElementById('item').innerHTML = '商品编号:' + id第三步,<img>标签被插入后,浏览器尝试加载src=x这个不存在的资源,触发onerror事件,执行其中的 JavaScript,恶意代码就这么跑起来了。
我补了一个细节:现代很多单页应用会把参数放在 hash 路由里,比如https://example.com/#/item?id=xxx,这种情况下参数不会随着 HTTP 请求发送给服务端,但会留在浏览器端被 JavaScript 读取,所以更容易被开发者忽略。面试官听到这个点,又追问了一句“那怎么确认漏洞是否存在”,我说通常会用一个无害的弹窗测试 payload 是否执行,然后进一步验证能否窃取 cookie、能否读取页面数据。但这些测试只应该在自有环境或授权范围内进行,这一点我在回答时也明确表达了。
4.3 一个 XSS payload 的逐段含义
面试官问了一道让我印象深刻的题:“如果面试项目里出现了 XSS 攻击,你能不能把 payload 拆开讲清楚每一段在干什么”。我现场写了一个经典 payload,逐段解释:
<img src=x onerror=alert(document.cookie)><img>是 HTML 图像标签,浏览器会自动尝试加载 src 指向的资源。这里的关键是,浏览器加载图片失败后不会停止解析,而是会触发 onerror 事件。src=x是一个不存在的资源地址,作用就是故意触发错误。onerror=alert(document.cookie)是事件处理属性,一旦错误事件触发,浏览器就会执行这段 JavaScript。document.cookie读取的是当前站点的 cookie,攻击者拿到后可以冒充用户身份发起请求。
我继续补了一个更隐蔽的变体:
<a href="javascript:alert(document.cookie)">点击领取福利</a>这种 payload 不需要任何事件触发,只要用户点击链接就会执行。还有基于 SVG 的:
<svg/onload=alert(1)>SVG 标签加载完成时自动触发 onload,不需要用户交互。拆解 payload 的每个部分,其实就是理解 HTML 解析器、事件循环、JavaScript 执行上下文三者如何配合。这段回答让面试官比较满意,因为我不只停留在“会背 payload”,而是真正理解了执行链路。
4.4 防御手段:转义、CSP、HttpOnly
讲完攻击,面试官自然要问怎么防御。我把防御体系从“前端、后端、浏览器”三个层面展开。
前端第一层,永远不要用 innerHTML 插入不可信数据,改用 textContent。textContent 会把内容当作文本处理,<script>标签就不会被执行。第二层,Vue 默认的插值语法{{ }}和 React 的{ }都会自动转义,进入 DOM 前变成了纯文本,所以平时写代码只要不碰v-html和dangerouslySetInnerHTML,框架本身能挡住大部分 XSS。第三层,如果业务确实需要渲染富文本,就引入 DOMPurify 这类白名单过滤库,把script、iframe这些危险标签和onerror、javascript:这类危险属性统统过滤掉。
后端层面,所有用户输入进入数据库前必须做校验和过滤,输出时也要转义。不能依赖前端过滤,因为攻击者完全可以绕过前端直接用 postman 发包。
浏览器层面,这里有两个关键机制。第一个是 CSP,Content-Security-Policy。通过响应头限制脚本资源来源,比如:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com这样即使页面被植入了恶意脚本,浏览器也会拒绝执行非白名单来源的脚本。第二个是 HttpOnly,Cookie 设置了 HttpOnly 属性后,JavaScript 的document.cookie读不到这个 Cookie,即使 XSS 执行了,也无法直接窃取登录凭证。但同时要认识到,HttpOnly 只能防 cookie 被读取,不能防恶意脚本发请求、改页面。
4.5 面试官追问:怎么修复线上 XSS
面试官最后出了一个场景题:“你的项目上线后收到安全报告,说搜索页存在反射型 XSS,你会怎么排查和修复”。
我按自己的实操经验回答:先定位搜索关键词是怎么渲染到页面上的,找到对应的服务端模板或者前端渲染代码。如果搜索关键词被直接拼进了 HTML,就在输出时做 HTML 实体编码,把<转成<,把>转成>,&转成&,这样浏览器会把它显示成普通文本而不是解析成标签。
如果页面是 Vue 或 React 写的,就看是不是用了v-html或dangerouslySetInnerHTML,如果是,改成普通插值。业务必须保留富文本时,加上 DOMPurify 清洗。同时通知后端增加输出转义,部署 CSP 响应头,复查其他类似接口有没有同样问题。修复完成后,我会跑一遍自动扫描和手工测试,确认 payload 不再执行,再发布上线。
这一套流程涵盖了“复现、定位、修复、部署、验证”五个环节,面试官听完后问了一句“这些都是你自己做过的吗”,我说是的,之前自己写功能时真的遇到过类似问题,所以印象很深。这种真实经历带来的细节,比背一百道面试题都管用。
5. 面试现场实录:意外问题与翻车复盘
5.1 npm run dev 背后发生了什么
面试官在聊到脚手架时突然换了个角度:“你执行 npm run dev 的时候,你的开发服务器究竟做了哪些事情”。这个问题表面问命令,实际上是在考对前端工具链底层流程的掌握程度。
我按顺序回答:npm run dev 会在 node_modules/.bin 里查找对应的可执行文件,这个可执行文件是通过 package.json 的 bin 字段注册的。Vite 项目执行的就是 vite 命令,Vue CLI 项目执行的是 vue-cli-service serve。接下来,开发服务器会读取配置文件,Vite 会解析 vite.config.js,启动一个基于 Node 的 HTTP 服务器,然后对浏览器请求做中间件处理。Vite 把当前项目依赖用 esbuild 预构建,把 node_modules 里的第三方库转成 ESModule,缓存到 node_modules/.vite 目录。
浏览器访问页面时,Vite 拦截请求,找到对应的 .vue 文件或 .ts 文件,实时编译后返回给浏览器。开发服务器和浏览器之间还会建立一条 WebSocket 连接,用来做热更新,当代码变化时,Vite 通过这条连接通知浏览器替换对应模块,而不是整页刷新。
我特意说了一下 Vue 单文件组件在浏览器里是什么形式:一个 .vue 文件会被拆分成 template、script、style 三个部分,分别编译成 JS 函数、渲染函数和 CSS,浏览器拿到的实际上是一个包含了 render 函数的 JS 模块。这些细节如果不了解,只能在面试时说“它帮我开了个服务器,然后页面就能热更新了”,一下就露馅了。
5.2 开发中的 403、404、502 怎么排查
这个问题是面试官临时加的,估计是想看我的实际排错能力,而不是只背书。他说:“你平时开发中遇到接口返回 403、404、502,分别会怎么处理”。
我先回答了 404。遇到 404 第一步先确认接口地址拼得对不对,重点检查 baseURL 和实际请求 URL 是否拼接正确;第二步看代理配置,Vite 的 proxy 有没有把 /api 前缀转发到正确的 target;第三步问后端排查路由是否注册、服务是否部署到对应环境。很多时候前端的 404 其实是代理没配好,后端根本没收到请求。
403 我强调先分清楚是认证问题还是权限问题。如果请求带了 token 还是返回 403,优先考虑 CORS 跨域配置,因为浏览器拦截跨域请求时,控制台经常报 403 或 CORS 相关错误;其次确认后端是否校验了角色权限。我自己遇到过的一种情况是,cookie 没设置 SameSite 属性,跨域携带 cookie 被浏览器拦截,接口直接返回 403。
502 我讲了两种典型场景。本地的 502 一般是后端服务没启动,或者代理 target 配错了 IP;线上的 502 大多是网关后面的服务挂了、超时或者正在发布重启。排查思路是先确认是偶发还是持续,然后看后端日志、网关日志,确认上游服务状态。我不建议前端一遇到 502 就疯狂刷新,更稳妥的做法是先判断服务是否还在运行。这个回答思路清晰,面试官没有再追问。
5.3 手写题复盘:防抖、节流、reduce
最后面试官出了两道手写题,第一道是防抖,第二道是 reduce。防抖我写得比较顺利,重点是把 this 和参数正确传递回去:
function debounce(fn, delay) { let timer = null return function (...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args) timer = null }, delay) } }面试官问了一个细节:“这里的 fn.apply(this, args) 为什么要传 this”。我说因为事件处理函数里的 this 指向原 DOM 元素,如果直接调用 fn(),this 就会丢失,导致内部依赖 this 的逻辑出错。这题没问题。
reduce 那题我翻车了。面试官让我实现一个 Array.prototype.reduce,我一开始写的时候没有处理“不传初始值时,从第二项开始遍历”的边界情况。面试官及时提示了一句“如果不传 initialValue 会发生什么”,我才补上了对空数组的判断和首项作为累加器的逻辑。这个坑很典型,建议大家在复习时把 reduce、map、filter、call、apply、bind 全部手写一遍,只背用法是不够的。
这次翻车给我的教训是:手写题面试的时候,即使第一遍写错了,只要能在提示下修正并讲清楚原因,面试官依然会给分。最怕的是写错了还硬着头皮不承认,那样反而暴露了基础不牢。
6. 大专生暑期实习的投递策略与心态
6.1 投递时间线:什么时候投命中率更高
不少同学问我“暑期实习到底什么时候开始投”,我的经验是提前半年到三个月。大二上学期九十月份就可以开始刷招聘信息,提前了解目标公司的招聘时间线;大二下学期二月到四月是投递高峰;五六月才投,很多坑位已经被占了。
我这次赶的是二月底投递、三月中旬面试,节奏刚刚好。投递渠道方面,我用得最多的是招聘软件直投,其次是公司官网的校招入口,然后是牛客、V2EX 这类社区里的内推贴。内推的最大好处是简历可以直接到用人部门,减少被 HR 条件筛选卡掉的风险。
投递量上,我建议不要只投三五家觉得“够得上”的,也不要盲目海投几百家。我的策略是分三批:第一批投最想去的,第二批投比较合适的中型团队,第三批是保底的本地公司或外包岗。每批投完后根据反馈调整简历,比一次性全投出去有效得多。
6.2 学历短板怎么补:作品、博客、开源
大专学历在简历筛选阶段确实会有劣势,但屏幕对面真正干活的人更在意你能不能上手。所以我的思路是,用可以被直接验证的东西补齐学历差距。
作品集中在两个方面:一个是线上可访问的项目,另一个是开源代码仓库。面试官看到你的简历时,如果附带了可以直接访问的线上地址,他会优先打开看看,而不是只看学校栏。我在简历上放了两个线上链接,一个是后台管理系统的演示地址,一个是脚手架的 GitHub 仓库,都在面试前一整周做了完整打磨。
博客的作用不是用来炫耀流量,而是展示你的学习方式和复盘习惯。我写了大概十几篇技术笔记,其中有几篇是踩坑记录,比如“npm run dev 启动失败,端口被占用怎么排查”“脚手架下载模板超时怎么解决”。面试官其实会看这些文章,因为内容质量直接反映了一个人的表达能力和总结能力。这些都是大专生弥补学历背景比较靠谱的路径。
6.3 如果没拿到 offer 怎么办
这个问题我在面试前想过很多次,也会在面试最后主动问面试官“您觉得我目前还需要提升哪些方面”。这轮面试的结果还没出,但我做好了万一没过的准备。
如果暑期实习失败了,我的备选路线是:先找小公司的日常实习或者远程外包项目,把真实业务经验攒起来,同时继续维护开源项目和博客。秋招投递时,就算没有大厂暑期实习,我也能拿出“一个自己从零搭脚手架的工程化项目 + 半年线上业务经验”来谈,比一张空白简历强太多。
这里给同样是大专的同学一个建议:不要把暑期实习当成“一锤子买卖”,它只是秋招前的一次热身。面试官看的是长期成长曲线,你只要每次面试后都认真复盘、补齐短板,两三个月后水平会有明显差距。我自己就是在这次面试复盘时把 HTTP 缓存和 DOM 型 XSS 重新过了一遍,很多细节在第一次学习时根本没注意到。
复盘完整场面试,我最大的感受是:大专生面试前端实习岗位,面试官真的不会要求你把框架源码讲得多深,但非常在意两件事——基础扎不扎实,项目里的事情是不是真做过。脚手架、HTTP、XSS 这些题能成为高频八股,恰恰是因为它们就是日常开发每天都在碰的东西,背答案没有用,只有自己搭过脚手架、修过 502 报错、排查过 XSS 漏洞,才能在临场答出让人信服的细节。
最后再分享一个小技巧:每次面试结束后,趁记忆还热着,把面试官问过的所有问题全部记下来,标注哪些答得好、哪些翻车了,再用几天时间把翻车的题目重新整理成文档。这份复盘文档会越攒越厚,到秋招前它就是你自己最靠谱的面试题库。祝大家暑期都能拿到满意的实习,哪怕第一封感谢信是拒信,也别停下手里的代码。