前言
大家好,我是木斯佳。
相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的"增删改查"岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。
这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。专栏快速地址

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:京东
🕐面试时间:近期
💻面试岗位:前端实习一面
📝面试体验:凉经
❓面试问题:
- 我看你简历上写的基于echars的定制化看板是什么
- js的基本数据类型
- vue3与vue2的区别
- vue3与vue2的响应式原理,以及各自的优缺点(没答好)
- 什么时候用vue2,什么时候用vue3
- 你写的统一封装网络层是怎么做的
- 登录流程是怎么样的
- token是随机生成的吗(答错了)
- 你写的高内聚低耦合是什么意思
- 接口封装是高内聚还是低耦合(都有,我只答了高内聚)
- 有用过ai辅助编码吗,带来的好处和坏处是什么
- 如何更好地利用ai生成自己想要的结果
- ts的元组有什么特点(没答出来)
- react的useState为什么要用数组来接收(其实就是数组解构和对象解构区别,没答上)
- es6的数据存储方式有哪些
- 有了解过session storage吗(只是听过)
- 什么时候用localstorage,什么时候用indexedDB
- 为什么选择pinia进行状态管理
- js类如何隔离封装数据
- js的class是怎么实现的
- private protected public的区别
- 箭头函数和普通函数的区别
- 项目里遇到的一个难点是什么
- 用户刷新页面更新数据,和在代码里清理缓存相比,各有什么优势
- 频繁点击的话,页面就要频繁更新怎么办
来源:牛客网不想做实验
💡 木木有话说(刷前先看)
最近高质量面经比较多,4月份算是一个招聘窗口期了,再往后的岗位应该会少一些,所以最近加更一下。
📝 京东前端实习一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | 基础密集型 + 框架原理型 + 工程概念型 |
| 难度评级 | ⭐⭐⭐⭐(四星,框架原理深,概念细) |
| 考察重心 | Vue响应式原理、TypeScript元组、React useState设计、存储方案选型、class私有属性 |
| 特殊之处 | 问题密度高(25问),多个细节追问,考察基础扎实程度 |
🔍 逐题深度解析
三、Vue3与Vue2的区别
| 维度 | Vue2 | Vue3 |
|---|---|---|
| 响应式 | Object.defineProperty | Proxy |
| API风格 | Options API | Composition API(推荐) |
| TypeScript | 支持较弱 | 原生支持 |
| 体积 | 较大 | 更小(tree-shaking) |
| 性能 | 初始化递归遍历 | 懒递归,性能更好 |
| 新特性 | - | Teleport、Suspense、Fragment |
四、Vue3与Vue2响应式原理及优缺点
Vue2(Object.defineProperty):
- 优点:兼容性好(IE9+)
- 缺点:无法监听新增/删除属性、数组索引/length变更、需要递归遍历(初始化慢)
Vue3(Proxy):
- 优点:可监听13种操作、新增属性自动响应、数组原生支持、懒递归(初始化快)
- 缺点:无法代理基本类型(需ref)、不兼容IE
八、token是随机生成的吗
正确答案 :不是简单的随机字符串。JWT(JSON Web Token)由三部分组成:Header(算法)、Payload(用户信息、过期时间等)、Signature(签名)。签名是对Header和Payload用密钥加密生成的,用于防篡改。
javascript
// JWT结构
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywiZXhwIjoxNzEyMzQ1Njc4fQ.sflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
// header payload signature
九、高内聚低耦合的含义
- 高内聚:一个模块内部的功能高度相关,职责单一明确
- 低耦合:模块之间依赖关系弱,一个模块变化对其他模块影响小
示例:
- 高内聚:一个用户管理模块只处理用户相关功能(注册、登录、信息修改)
- 低耦合:用户模块通过事件/消息与其他模块通信,而不是直接调用
十、接口封装是高内聚还是低耦合
正确答案 :两者都有。
- 高内聚:封装了请求发送、拦截器、错误处理等网络相关逻辑
- 低耦合:对外暴露简洁的API,调用方不关心内部实现
javascript
// 高内聚:网络层内部职责单一
class HttpClient {
interceptors = { request: [], response: [] }
request(config) { /* 统一处理 */ }
get(url) { /* GET请求 */ }
post(url, data) { /* POST请求 */ }
}
// 低耦合:业务代码只需调用,不依赖内部实现
const data = await http.get('/api/user')
十三、TS元组的特点
元组(Tuple) :允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
typescript
// 普通数组:所有元素类型相同
let arr: number[] = [1, 2, 3]
// 元组:元素类型固定
let tuple: [string, number] = ['Tom', 18]
tuple[0] = 'Jerry' // ✅
tuple[1] = 20 // ✅
tuple[2] = 100 // ❌ 越界(TS默认会报错)
特点:
- 长度固定
- 每个位置类型固定
- 支持可选元素(
[string, number?]) - 支持剩余元素(
[string, ...number[]])
十四、React的useState为什么用数组接收
核心原因 :数组解构可以任意命名,而对象解构必须属性名匹配。
javascript
// 数组解构(useState的设计)
const [count, setCount] = useState(0)
const [name, setName] = useState('')
// 如果是对象解构(假设的设计)
const { state: count, setState: setCount } = useState(0)
// 必须写 state 和 setState,不能自定义命名
好处:
- 调用方可以自由命名
- 简洁(一行代码)
- 适合多个state的调用(无需起不同属性名)
十五、ES6的数据存储方式
| 方式 | 特点 |
|---|---|
| Map | 键可以是任意类型,有序,可迭代 |
| Set | 值唯一,有序,可迭代 |
| WeakMap | 键是对象,弱引用,不阻止GC,不可迭代 |
| WeakSet | 值是对象,弱引用,不阻止GC,不可迭代 |
使用场景:
- Map:需要非字符串键、需要有序遍历
- Set:数组去重、存储唯一值
- WeakMap:存储DOM元素关联数据(避免内存泄漏)
十六~十七:sessionStorage、localStorage、IndexedDB
| 维度 | sessionStorage | localStorage | IndexedDB |
|---|---|---|---|
| 生命周期 | 标签页关闭 | 永久 | 永久 |
| 容量 | 5-10MB | 5-10MB | 250MB+ |
| 数据格式 | 字符串 | 字符串 | 结构化数据(对象/Blob) |
| 同步/异步 | 同步 | 同步 | 异步 |
| 查询能力 | 无 | 无 | 索引、事务、游标 |
选型建议:
- localStorage:简单配置、用户偏好
- IndexedDB:大量结构化数据(聊天记录、离线数据)、需要复杂查询
十九、JS类如何隔离封装数据
方式:
- 闭包:通过IIFE返回私有变量
- ES6 class + 私有字段 :使用
#前缀(ES2022) - Symbol/WeakMap:模拟私有属性
- TypeScript private:编译后消失,仅为类型约束
javascript
// 闭包
function createPerson(name) {
let _name = name // 私有
return { getName: () => _name }
}
// ES6私有字段
class Person {
#name // 私有字段
constructor(name) { this.#name = name }
getName() { return this.#name }
}
二十、JS的class是怎么实现的
本质 :class是语法糖,底层仍是构造函数+原型链。
javascript
// class写法
class Person {
constructor(name) { this.name = name }
sayHi() { console.log(this.name) }
}
// 等价的构造函数写法
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function() {
console.log(this.name)
}
二十一、private、protected、public的区别
| 修饰符 | 类内部 | 子类 | 实例 |
|---|---|---|---|
| public | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ❌ |
| private | ✅ | ❌ | ❌ |
注意 :TypeScript的private/protected在编译后消失,仅编译时检查。真正的运行时私有需用#字段。
二十二、箭头函数和普通函数的区别
| 维度 | 箭头函数 | 普通函数 |
|---|---|---|
| this绑定 | 静态,定义时继承外层 | 动态,调用时决定 |
| arguments | 无(用rest参数) | 有 |
| 构造函数 | 不能(new报错) | 可以 |
| prototype | 无 | 有 |
二十四、用户刷新页面更新数据 vs 代码里清理缓存
| 方式 | 优点 | 缺点 |
|---|---|---|
| 刷新页面 | 简单粗暴,数据一定最新 | 用户体验差,页面闪烁,状态丢失 |
| 代码清理缓存 | 无感更新,用户体验好 | 需要设计缓存策略(过期时间、主动失效) |
最佳实践:SWR(stale-while-revalidate)策略------先展示缓存数据,后台请求最新数据,更新后自动刷新UI。
二十五、频繁点击导致页面频繁更新
解决方案 :防抖(Debounce)
javascript
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
button.addEventListener('click', debounce(handleClick, 300))
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| Vue响应式 | Vue2(defineProperty) vs Vue3(Proxy),优缺点对比 |
| JWT | 三部分:Header+Payload+Signature,不是随机字符串 |
| 高内聚低耦合 | 模块职责单一,依赖关系弱 |
| TS元组 | 长度固定、类型固定、支持可选/剩余元素 |
| useState设计 | 数组解构可任意命名,对象解构需属性名匹配 |
| ES6存储 | Map/Set/WeakMap/WeakSet,适用场景不同 |
| 存储选型 | localStorage简单配置,IndexedDB大量结构化数据 |
| class私有 | 闭包、#私有字段、Symbol/WeakMap |
| class本质 | 构造函数+原型链语法糖 |
| 访问修饰符 | public/protected/private,TS编译后消失 |
| 缓存刷新 | 刷新页面vs代码清理,SWR策略最佳 |
| 频繁点击 | 防抖(debounce)合并多次请求 |
📌 最后一句:
京东这场一面,暴露了用户在多个基础点上的薄弱:响应式原理、token生成机制、TS元组、useState设计思想、存储方案选型......面试官的问题并不偏门,全是前端核心。这场面经的教训是:框架原理要懂到实现层面,JS特性要理解设计原因,工程概念要能说清应用场景。扎实的基础,才是通过面试的最硬通货。