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

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:字节跳动TikTok
🕐面试时间:近期
💻面试岗位:前端一面
📝面试体验:面完一身冷汗
❓面试问题:
- 自我介绍并说一下最近的这个项目,觉得哪里有一些复杂度比较高的,你可以详细聊聊
- 这个项目是你实习做的吗?还是你自己练习的项目?(我说是老师项目组的)
- 我看你了解的知识面还挺广的,你是通过哪些方式进行前端的学习的?(GitHub、B站、掘金、极客时间)
- 有没有做过系统一点的学习?比如书籍之类的(我说你不知道的JavaScript)
- 这本书有什么理念,或者说你觉得印象比较深刻的?(说了this,把 this 全部过一遍)
- 那你在实际项目中有哪些场景有用this去做一些方案,或者说做一些小的工具函数之类的?(我说有,比如单例模式,然后全部过一遍)
- 那单例模式,你是在什么样的一个场景里面去用的?为什么会选择单例模式?有什么优缺点?
- 你刚刚说扩展性比较差,弹簧组件是全局的,那假设另外一个场景也需要使用这个组件,但是它又要有些不一样,那这个时候你会用到什么设计模式呢?
- 我看你项目写了响应式布局,你是之前做过移动端吗?(我说有)
- 那你是怎么做的适配的?因为有高倍屏、低倍屏,还有些手机尺寸不一样。
- 你所有的布局都是用的 px 去进行布局的吗?(我说还有 rem)
- 那你觉得 rem 的值应该设置为多少呢?(我说是除以10,追问,我说在GitHub上面阿里源码扒下来的)
- 对于样式布局,你了解哪些方式呢?(我说有 flex弹性布局、grid网格布局、table表格布局、定位布局、浮动布局) 哪种用的比较多呢?(我说flex弹性布局)
- 那你可以说一些 flex 的样式属性吗?(我说有flex-direction、flex-wrap、flex-justify、flex-align、flex: 1)
- 听到我说 flex 布局,面试官扔了道题目过来:用 flex 布局完成从左边的到右边的效果
- 手撕树形结构遍历:要求最后的结果为 28
- 继续回归项目:对于 jwt 认证保障的话,你可以说一下它大致怎么实现的呢?
- token 你是存在哪里的?(我说存在 localStorage 中) 为什么存在 localStorage 中?(我说一开始是想放在 cookie 中,但是刚好接触了 axios,然后进行封装,更方便)
- 你这7天过期的逻辑放在 localStorage 中是怎么做的呢?(我说我设计了长 token 和短 token) 面试官说这个方式没见过,比较有意思😄
- 短 token 过期,前端拦截到错误然后用长token做请求,那你这个逻辑岂不是接口会做一个重试的逻辑?那如果长 token 也过期了,那这个时候你会怎么做?(我说会跳转到登录界面,重新登录)
- 跳转界面是在前端还是后端做的重定向?(我说是在前端,axios 封装,错误码)
- 你前端做重定向的时候,你是通过什么样的一个 API 去做的?(我说是 window.location.href('/login')) 那你这种方式跟 location.replace('/login')、location.push('/login') 有什么区别吗?(我说 window.location.href('/login') 是会刷新页面的,而 location.replace('/login') 是不会刷新页面的)
- 你点的浏览器回退,你还是可以回到上一个地址吗?(我说直接退出到登录界面)
- 我看你另外一个项目设计了一个拖拽式布局,你可以说一下会用到哪些API吗?还是说你这个功能是通过第三方库实现的?(我说原来是自己写的,后面效果不是特别好,就用了第三方库,在 npm 上的 Allotment) 那如果让你用原生 JS,你知道要用到哪些 API 呢?(我说 mouseDown、mouseMove、mouseUp、mouseOut,监听鼠标的点击事件,获取鼠标的坐标,计算容器的宽度)
- 面试官说前端基础就面到这,我心想这还只是基础吗😭,然后反问
来源:牛客网 风止何安啊
💡 木木有话说(刷前先看)
字节TikTok这场一面,是一份"压力感拉满"的面经。从this理念、单例模式的应用场景与扩展性问题,到rem适配值从阿里源码扒下来、双token刷新机制、location API区别,再到原生JS拖拽实现......
📝 字节TikTok前端一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | 压力追问型 + 实战细节型 + 设计思维型 |
| 难度评级 | ⭐⭐⭐⭐⭐(五星,追问极深,考察系统设计) |
| 考察重心 | JS核心(this/闭包)、设计模式、响应式适配、双token鉴权、原生JS拖拽、树形遍历 |
| 特殊之处 | 围绕"单例模式扩展性差"追问替代方案,考察设计模式应用能力 |
🔍 逐题深度解析
五、《你不知道的JavaScript》中this的理念
回答思路:这本书的核心是"this不是在编写时绑定,而是在运行时绑定",它取决于函数调用的位置。
this绑定规则(优先级从低到高):
- 默认绑定:独立函数调用,非严格模式指向window,严格模式为undefined
- 隐式绑定:对象方法调用,指向调用对象
- 显式绑定:call/apply/bind指定this
- new绑定:构造函数调用,指向新创建的实例
箭头函数:没有自己的this,继承外层作用域,不能被call/apply/bind覆盖。
六~七:单例模式及优缺点
回答思路:用户回答了单例模式(全局弹窗组件),面试官追问扩展性差的问题。
单例模式:保证一个类只有一个实例,并提供全局访问点。
javascript
const Singleton = (function() {
let instance = null
function createInstance() {
return { name: 'Singleton' }
}
return {
getInstance: function() {
if (!instance) instance = createInstance()
return instance
}
}
})()
优点:
- 节省内存(只有一个实例)
- 全局访问点,便于管理
缺点:
- 扩展性差:无法派生子类或替换实现
- 隐藏依赖关系,不利于单元测试
- 全局状态可能引发命名冲突
八:场景需要不同扩展时的设计模式
问题:同一个组件需要不同变体(如弹窗在A场景需要footer,在B场景不需要),如何设计?
解决方案:
- 工厂模式:创建不同配置的弹窗实例
- 建造者模式:分步构建复杂弹窗
- 策略模式:将变化部分(footer行为)封装成策略
- 装饰器模式:用装饰器动态添加功能
javascript
// 工厂模式示例
class DialogFactory {
createDialog(type) {
switch(type) {
case 'alert': return new AlertDialog()
case 'confirm': return new ConfirmDialog()
default: return new BaseDialog()
}
}
}
十~十二:移动端适配、rem值设置
回答思路:用户提到rem除以10(即根字体为视口宽度的1/10),源自阿里的flexible方案。
常用方案:
- rem适配 :设置根字体为
width / 10,设计稿750px下根字体75px,写样式时100px = 1.333rem - vw/vh适配 :
1vw = 1%视口宽度,配合PostCSS自动转换 - viewport缩放:不推荐
阿里flexible核心:
javascript
function setRem() {
const width = document.documentElement.clientWidth
const rem = width / 10 // 除以10是经验值
document.documentElement.style.fontSize = rem + 'px'
}
面试官追问 :不同倍屏适配,通过<meta name="viewport" content="width=device-width, initial-scale=1">控制缩放比例。
十五:flex布局实现左右排列
题目:用flex完成从左到右的排列效果。
css
.container {
display: flex;
flex-direction: row; /* 默认就是行 */
justify-content: space-between; /* 左右两端对齐 */
align-items: center; /* 垂直居中 */
}
十六:手撕树形结构遍历(结果为28)
推测题目:计算树形结构中所有节点值的和,结果为28。
javascript
function sumTree(node) {
if (!node) return 0
let sum = node.val || 0
if (node.children) {
for (const child of node.children) {
sum += sumTree(child)
}
}
return sum
}
十七~二十三:JWT双token鉴权
双token方案:
- access_token:短期(如2小时),存在内存或localStorage
- refresh_token:长期(如7天),存在httpOnly cookie
刷新流程:
- 请求返回401,判断是access_token过期
- 调用刷新接口,携带refresh_token
- 后端返回新access_token
- 重试原请求队列
- 若refresh_token也过期,跳转登录
用户被追问的点:
- 重试逻辑:需要将过期期间的请求加入队列,刷新成功后批量重试
- 长token过期:清除本地token,跳转登录页
- location API区别 :
window.location.href:跳转并记录历史(可回退)location.replace():跳转不记录历史(不可回退)location.assign():类似href,记录历史- 没有
location.push()(那是前端路由的API)
二十四:原生JS拖拽实现API
核心API:
mousedown:记录初始位置,绑定move和up事件mousemove:计算位移,更新元素位置mouseup:解绑事件clientX/clientY:获取鼠标坐标
javascript
element.addEventListener('mousedown', (e) => {
const startX = e.clientX, startY = e.clientY
const startLeft = element.offsetLeft, startTop = element.offsetTop
function onMouseMove(e) {
const dx = e.clientX - startX
const dy = e.clientY - startY
element.style.left = startLeft + dx + 'px'
element.style.top = startTop + dy + 'px'
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
})
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| this绑定 | 默认/隐式/显式/new绑定,箭头函数无this |
| 单例模式 | 全局唯一实例,优点省内存,缺点扩展性差 |
| 扩展方案 | 工厂/建造者/策略/装饰器模式 |
| 移动端适配 | rem(阿里flexible)、vw/vh,viewport控制 |
| flex布局 | flex-direction、justify-content、align-items、flex:1 |
| 树形遍历 | DFS递归求和 |
| 双token | access_token短期+refresh_token长期,401刷新队列 |
| location API | href记录历史,replace不记录历史 |
| 原生拖拽 | mousedown→mousemove→mouseup,计算偏移量 |
📌 最后一句:
字节TikTok这场一面,是一场对"真实能力"的极限拷问。从《你不知道的JavaScript》里的this理念,到单例模式的扩展性缺陷,从阿里flexible源码的rem值,到双token刷新队列的重试逻辑,再到location API的细微区别......面试官的问题看似"基础",但每一个都问到了原理层面和实战细节。用户虽然面完"一身冷汗",但能坚持回答下来,已经证明了自己的基础厚度。字节的基础,不是背八股,而是你真的理解、真的用过、真的踩过坑。