前言
大家好,我是木斯佳。
相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的“增删改查”岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。
这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。专栏快速地址
温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:来未来
🕐面试时间:近期
💻面试岗位:前端实习一面
❓面试问题:
- JavaScript 中的闭包是什么,应用场景及缺点、处理方法?
- JavaScript 常用的基础类型有哪些?
- 前端的垃圾回收机制是什么?
- 请介绍 JavaScript 的事件循环(同步任务、微任务、宏任务的执行顺序及示例)?
- Promise 和 setup 哪个先执行?
- Vue 2 和 Vue 3 的区别(响应式实现、生命周期、API、根节点)?
- Vue 2 和 Vue 3 中 v-if 和 v-for 的优先级区别及调整原因?
- Vue 中 watch 和 computed 的区别?
- Vue 3 setup 语法糖中 ref 和 reactive 的区别?
- Promise 的工作原理(状态、不可逆性、结果获取方式)?
- Promise 与 Async/Await 的关系及使用规则?
- 跨域的机制及解决方案?
- POST 和 GET 请求方式的区别?
- HTTP 的缓存机制有哪些(强制缓存、协商缓存的逻辑)?
- 项目与封装组件的依据、需求场景及难点?
来源:牛客网 寻觅美妙实习中🔥
💡木木有话说(刷前先看)
来未来这场实习一面,是一份非常标准的前端基础面经。适合校招/实习同学用来检验基础是否扎实。难度不大,作为补充。
📝 来未来前端实习一面·深度解析
🎯面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | 基础全面型 + 框架对比型 + 网络基础型 |
| 难度评级 | ⭐⭐⭐(三星,基础为主,偶有细节考点) |
| 考察重心 | JS核心(闭包/事件循环/Promise)、Vue框架(2/3差异)、网络基础(跨域/缓存) |
| 特殊之处 | 第5题“Promise和setup先执行”是Vue3细节考点 |
🔍逐题深度解析
一、JavaScript闭包:定义、应用、缺点及处理
定义:函数可以访问其外部作用域的变量,即使外部函数已执行完毕。
应用场景:
- 防抖/节流(保存timer)
- 模块化封装(私有变量)
- 循环中保存正确索引
- 函数工厂
缺点:
- 内存泄漏:闭包引用的外部变量不会被垃圾回收,尤其当引用大对象或DOM元素时
处理方法:
- 及时解除引用:
fn = null - 避免在闭包中引用不必要的大对象
- 使用
WeakMap/WeakSet存储弱引用
// 内存泄漏示例及修复functionleak(){constlargeData=newArray(1000000)constbtn=document.getElementById('btn')btn.onclick=()=>console.log(largeData.length)// 修复:btn.onclick = null 或 避免引用largeData}二、JavaScript常用的基础类型
7种基础数据类型:
numberstringbooleannullundefinedsymbol(ES6)bigint(ES2020)
1种引用类型:object(包含数组、函数、日期、正则等)
区别:基础类型存栈(值),引用类型存堆(地址)。
三、前端的垃圾回收机制
核心机制:可达性(Reachability)
引用计数(已不常用):
- 记录每个对象被引用的次数,为0时回收
- 问题:循环引用导致无法回收
标记-清除(Mark-Sweep,主流):
- 标记阶段:从根对象(window、global)出发,标记所有可达对象
- 清除阶段:回收未被标记的对象
优化算法:
- 分代回收:新生代(存活短)和老生代(存活长)分开处理
- 增量标记:标记过程分批执行,减少停顿
- 空闲时间回收:
requestIdleCallback时机执行
// 避免内存泄漏的做法letobj={name:'Tom'}letarr=[obj]obj=null// 解除引用,但arr仍引用,对象不会被回收arr=null// 全部解除,对象可被回收四、事件循环(同步、微任务、宏任务)
执行顺序:
- 执行所有同步代码
- 清空微任务队列(Promise.then、MutationObserver、queueMicrotask)
- 执行一个宏任务(setTimeout、setInterval、I/O、UI渲染)
- 重复2-3
console.log('1')// 同步setTimeout(()=>console.log('2'),0)// 宏任务Promise.resolve().then(()=>console.log('3'))// 微任务console.log('4')// 同步// 输出:1,4,3,2五、Promise和setup哪个先执行
答案:setup先执行。
原因:
setup是Vue3组件初始化的同步函数,在组件实例创建时立即执行Promise的回调(.then)是微任务,在当前同步代码执行完后才执行
执行顺序示例:
<script setup> console.log('1: setup开始') Promise.resolve().then(() => console.log('3: Promise.then')) console.log('2: setup结束') </script> // 输出:1,2,3注意:如果setup中有await,被await后面的代码会变成微任务,但setup函数本身是同步执行到第一个await。
六、Vue 2 和 Vue 3 的区别
| 维度 | Vue 2 | Vue 3 |
|---|---|---|
| 响应式 | Object.defineProperty | Proxy(支持新增/删除属性、数组索引) |
| 生命周期 | beforeCreate、created、beforeMount、mounted等 | 类似,但beforeDestroy→beforeUnmount,destroyed→unmounted |
| API风格 | Options API(data、methods、computed等) | Composition API(setup),Options API仍支持 |
| 根节点 | 单根节点(template下只能一个元素) | 多根节点(Fragment支持) |
| TypeScript | 支持较弱 | 原生支持 |
| 体积 | 较大 | 更小(tree-shaking) |
| 性能 | 初始化递归遍历 | 懒递归,性能更好 |
七、v-if 和 v-for 的优先级区别及调整原因
| 版本 | 优先级 | 问题 |
|---|---|---|
| Vue 2 | v-for > v-if | 同一元素上同时使用时,v-for每次循环都会执行v-if,浪费性能 |
| Vue 3 | v-if > v-for | 先判断条件再循环,避免不必要的循环 |
调整原因:
- Vue 2中
v-for优先级高,即使大部分元素需要被过滤,也会先遍历整个列表,性能差 - Vue 3调整为
v-if优先,先过滤再循环,更符合开发者直觉,性能更好
最佳实践:避免在同一元素上同时使用,应使用计算属性先过滤列表。
<!-- ❌ 不推荐 --> <div v-for="item in list" v-if="item.visible" :key="item.id"> <!-- ✅ 推荐 --> <div v-for="item in visibleList" :key="item.id">八、watch 和 computed 的区别
| 维度 | computed | watch |
|---|---|---|
| 缓存 | 有(依赖不变不重新计算) | 无(每次触发都执行) |
| 返回值 | 必须返回(作为属性使用) | 无返回值(执行副作用) |
| 异步 | 不支持 | 支持 |
| 使用场景 | 依赖多个数据、派生状态 | 数据变化时执行异步/开销较大的操作 |
| 默认执行时机 | 创建时立即计算 | 创建时不执行,需配置immediate: true |
// computed:缓存派生值constfullName=computed(()=>`${firstName.value}${lastName.value}`)// watch:监听变化执行异步操作watch(searchKeyword,async(newVal)=>{constresults=awaitsearchAPI(newVal)searchResults.value=results})九、ref 和 reactive 的区别
| 维度 | ref | reactive |
|---|---|---|
| 数据类型 | 任意类型(基本类型+对象) | 仅对象(Object、Array、Map、Set) |
| 访问方式 | .value | 直接访问 |
| 解构 | 解构会丢失响应式 | 解构会丢失响应式(需toRefs) |
| 本质 | RefImpl类,getter/setter | Proxy代理 |
| 传递 | 可传递,响应式不会丢失 | 传递属性会丢失响应式 |
constcount=ref(0)// 基本类型用refconststate=reactive({count:0})// 对象用reactive// 访问count.value++state.count++// ref的自动解包(在模板中)<template>{{count}}</template>// 无需.value十、Promise的工作原理
三种状态:
pending:初始状态fulfilled:成功(调用resolve)rejected:失败(调用reject)
特点:
- 状态不可逆:一旦从pending变为fulfilled或rejected,不能再改变
- 结果获取:通过
.then(onFulfilled, onRejected)或.catch()获取 - 链式调用:
.then返回新Promise,支持链式 - 微任务:
.then回调放入微任务队列
newPromise((resolve,reject)=>{setTimeout(()=>resolve('success'),1000)}).then(res=>console.log(res))// 微任务.catch(err=>console.error(err))十一、Promise与Async/Await的关系及使用规则
关系:
async/await是Promise的语法糖,让异步代码写得更像同步async函数返回Promiseawait后面跟Promise,会暂停函数执行,等待Promise resolve
使用规则:
await只能在async函数内部使用(顶层await需ES2022)async函数返回的Promise会自动包裹返回值- 错误处理用
try/catch或.catch()
// 等价的两种写法// PromisefunctiongetData(){returnfetch('/api/data').then(res=>res.json()).catch(err=>console.error(err))}// async/awaitasyncfunctiongetData(){try{constres=awaitfetch('/api/data')returnawaitres.json()}catch(err){console.error(err)}}十二、跨域的机制及解决方案
跨域原因:浏览器同源策略(协议、域名、端口任一不同即跨域),限制脚本间交互。
解决方案:
| 方案 | 原理 | 适用场景 |
|---|---|---|
| CORS | 服务端设置Access-Control-Allow-Origin | 最常用,需服务端配合 |
| JSONP | <script>不受同源限制 | 仅GET,已较少使用 |
| 代理转发 | 同源请求→代理服务器→目标API | 开发环境(webpack proxy),生产环境(Nginx) |
| postMessage | 跨窗口通信 | iframe与父页面 |
| WebSocket | 不受同源限制 | 实时双向通信 |
// CORS服务端响应头Access-Control-Allow-Origin:https://example.com Access-Control-Allow-Methods:GET,POST,PUTAccess-Control-Allow-Headers:Content-Type十三、POST 和 GET 的区别
| 维度 | GET | POST |
|---|---|---|
| 语义 | 获取资源 | 提交/创建资源 |
| 参数位置 | URL查询字符串 | 请求体(Body) |
| 长度限制 | 有(浏览器限制URL长度) | 无 |
| 安全性 | 参数暴露在URL,不安全 | 相对安全 |
| 缓存 | 可缓存 | 不可缓存 |
| 幂等性 | 是 | 否 |
| 书签 | 可保存为书签 | 不可 |
注意:GET请求也可以带Body,但约定上不应这样做。
十四、HTTP缓存机制
分类:
| 缓存类型 | 判断依据 | 行为 | 相关头 |
|---|---|---|---|
| 强制缓存 | 缓存未过期 | 直接使用缓存,不发请求 | Cache-Control: max-age=3600、Expires |
| 协商缓存 | 缓存已过期 | 向服务端验证资源是否变化 | ETag/If-None-Match、Last-Modified/If-Modified-Since |
流程图:
请求 → 检查强缓存 → 未过期 → 使用缓存 ↓ 过期 → 发送协商缓存请求 → 304 → 使用缓存 ↓ 200 → 更新缓存Cache-Control常用指令:
max-age=3600:缓存1小时no-cache:每次都协商缓存(验证后再用)no-store:完全不缓存public:允许被任何中间节点缓存private:只允许浏览器缓存
十五、项目与封装组件的依据、需求场景及难点
回答思路:结合项目实际,说明封装组件的决策过程。
封装依据:
- 复用性:同一UI/逻辑在多个地方使用(≥2次)
- 复杂度:单个组件代码超过200行,考虑拆分
- 单一职责:组件只做一件事,便于维护
- 业务边界:独立业务模块(如商品卡片、评论列表)
需求场景:
- 表单输入框(带校验、错误提示)
- 弹窗/对话框(确认、取消、自定义内容)
- 表格(分页、排序、筛选)
- 列表项(统一样式、交互)
难点:
- Props设计:既要灵活(支持多种场景),又不能太复杂
- 插槽设计:哪些部分可自定义(标题、内容、底部)
- 状态管理:组件内部状态与外部传入状态的协调
- 性能:避免不必要的重渲染(使用
v-memo/React.memo) - 文档与示例:让团队成员知道如何使用
// 一个好的组件设计示例<FormInput v-model="username"label="用户名":rules="[required, minLength(3)]"placeholder="请输入用户名"><template #prefix>👤</template><template #suffix><Tooltip content="用户名只能包含字母数字"/></template></FormInput>📚知识点速查表
| 知识点 | 核心要点 |
|---|---|
| 闭包 | 函数+外部作用域,防抖/模块化,内存泄漏需及时解除引用 |
| 基础类型 | 7种(number/string/boolean/null/undefined/symbol/bigint) |
| 垃圾回收 | 标记-清除,分代回收,可达性判断 |
| 事件循环 | 同步→清空微任务→一个宏任务→循环 |
| Promise vs setup | setup先执行(同步函数),Promise.then是微任务 |
| Vue 2/3区别 | defineProperty vs Proxy、Options vs Composition、Fragment |
| v-if/v-for | Vue3中v-if优先,避免同元素使用,用计算属性替代 |
| watch vs computed | computed有缓存,watch无;computed返回派生值,watch执行副作用 |
| ref vs reactive | ref支持基本类型(需.value),reactive仅对象(直接访问) |
| Promise原理 | pending→fulfilled/rejected,状态不可逆,.then是微任务 |
| async/await | Promise语法糖,需try/catch处理错误 |
| 跨域 | CORS(服务端头)、代理、JSONP、postMessage |
| GET vs POST | GET参数URL,可缓存;POST参数Body,不可缓存 |
| HTTP缓存 | 强缓存(Cache-Control)、协商缓存(ETag/Last-Modified) |
| 组件封装 | 复用性、单一职责、Props/插槽设计、文档 |
📌 最后一句:
没啥好说的,比较基础,典型八股