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

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:bilibili
🕐面试时间:近期,用户上传于2026-03-26
💻面试岗位:前端一面
⏱️面试时长:45分钟
📝面试体验:未提及
❓面试问题:
- 为什么选择学习前端
- 你有说到前端技术更新迭代较快,那么平时你都是在哪些平台知道新技术以及学习这些新技术?
- html5语义化标签
- 是否了解meta,作用是什么?
- 对于搜索引擎,meta对他有什么帮助?
- 如何实现一个不知高宽的按钮的水平居中,你知道几种方法?
- html5中如何实现0.5的边框?
- 什么是js闭包以及使用场景?
- eventLoop流程?核心解决什么问题?
- 有哪些常见的宏任务以及微任务?
- vue2你了解么?了解多少?
- SSE是什么?和websocket的区别?
- SSE的数据格式。
- 跨域问题的解决方式和原理?
- localStorage,sessionstorage,cookie的区别
- sessionStorage存贮了一些数据,如果刷新了页面还存在吗?(存在)
- localstorage存储上线只有4mb,如果存满了,还需要存储,应该怎么办(LRU/indexDb)
- 强缓存和协商缓存?
两道编程题
一、用promise实现图片的懒加载
二、用promise实现一个红绿灯交替实现,红灯3s,黄灯1s,绿灯3s。
来源:牛客网 胡L1104
💡 木木有话说(刷前先看)
bilibili这场感觉难度不大,题目中规中矩,相对基础,编程题手撕的话需要准备,其他的还好。
📝 bilibili前端一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | 基础扎实型 + 细节追问型 + 实战编程型 |
| 难度评级 | ⭐⭐⭐(三星半,基础全面,编程题有区分度) |
| 考察重心 | HTML/CSS基础、JS核心(闭包/事件循环)、网络与存储、Promise异步编程 |
| 特殊之处 | 两道Promise编程题,既考基础又考实际编码能力 |
🔍 逐题深度解析
一、为什么选择学习前端
回答思路 :参考上一篇腾讯面经的第一题解析。重点是展现兴趣起源 + 能力匹配 + 价值认同 + 持续学习。
加分点:结合bilibili的业务特点,可以说"bilibili是一个内容社区,前端在其中承担着内容呈现和用户交互的核心角色,我希望能用技术让好内容被更多人看见"。
二、学习新技术的平台
回答思路 :面试官在考察你的学习能力和信息获取渠道。
推荐回答:
- 技术资讯:掘金、前端之巅、Medium、Twitter(关注尤雨溪、Dan Abramov等)
- 系统学习:MDN、现代JavaScript教程、React/Vue官方文档
- 视频课程:bilibili(正好是面试公司)、YouTube(Fireship、The Net Ninja)
- 实践验证:写demo、参与开源、做side project
加分点:提到"看完文档或教程后,我会自己动手实现一个最小demo,这样理解更深"。
三、HTML5语义化标签
回答思路:说出常见语义化标签及其作用。
核心标签:
<header>:页眉<nav>:导航<main>:主要内容<article>:独立内容<section>:章节<aside>:侧边栏<footer>:页脚
作用:
- SEO:搜索引擎更准确识别内容结构
- 可访问性:屏幕阅读器更好理解页面
- 可维护性:代码结构清晰,便于团队协作
四、meta标签及作用
回答思路 :meta提供HTML文档的元数据,位于<head>中。
常见meta:
html
<!-- 字符编码 -->
<meta charset="UTF-8">
<!-- 视口设置(移动端适配) -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 页面描述(影响SEO) -->
<meta name="description" content="页面描述">
<!-- 关键词(SEO权重已降低) -->
<meta name="keywords" content="关键词1,关键词2">
<!-- 作者 -->
<meta name="author" content="作者名">
<!-- 机器人爬虫规则 -->
<meta name="robots" content="index,follow">
对搜索引擎的帮助 :description会作为搜索结果摘要;keywords(已弱化)曾用于关键词匹配;robots控制爬虫行为。
五、不知高宽的按钮水平居中(多种方法)
回答思路:面试官想考察CSS居中的多种实现方案。
方法1:flex(推荐)
css
.container {
display: flex;
justify-content: center;
}
方法2:text-align(内联元素)
css
.container {
text-align: center;
}
.button {
display: inline-block;
}
方法3:绝对定位 + transform
css
.container {
position: relative;
}
.button {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
方法4:margin: 0 auto(块级元素需定宽)
对于不知宽高的按钮不适用,但可以结合display: table或display: inline-block。
六、HTML5中实现0.5px边框
回答思路:高清屏上1px物理边框在CSS中通常显示为2px,需要特殊处理。
方案1:transform: scale
css
.half-border {
position: relative;
}
.half-border::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
background: #000;
transform: scaleY(0.5);
transform-origin: 0 0;
}
方案2:viewport + rem
html
<meta name="viewport" content="width=device-width, initial-scale=0.5, user-scalable=no">
但会影响全局缩放,不推荐。
方案3:box-shadow
css
.half-border {
box-shadow: 0 0.5px 0 0 #000;
}
但兼容性有限。
七、JS闭包及使用场景
回答思路:先解释闭包定义,再说明常见场景。
定义 :函数可以访问其外部作用域的变量,即使外部函数已执行完毕。形成条件是函数嵌套 + 内部函数被引用。
javascript
function outer() {
let count = 0
return function inner() {
count++
return count
}
}
const counter = outer()
console.log(counter()) // 1
使用场景:
- 封装私有变量(如上面的计数器)
- 函数工厂:根据不同参数生成特定功能的函数
- 回调函数/事件处理:保存状态
- 防抖/节流:保存timer引用
- 模块化模式:IIFE创建私有作用域
注意 :闭包会导致内存泄漏,如果不再使用的闭包应手动置null释放。
八、事件循环流程及核心解决的问题
回答思路:参考上一篇腾讯面经的第二题。
核心问题:JavaScript是单线程,需要处理异步操作(定时器、网络请求等),避免阻塞主线程。
流程:
- 执行同步代码
- 遇到异步任务,交给相应API处理
- 异步任务完成后,回调进入任务队列(宏任务或微任务队列)
- 同步代码执行完,清空微任务队列
- 执行一个宏任务,再清空微任务队列,循环
关键:微任务优先级高于宏任务。
九、常见宏任务和微任务
宏任务(MacroTask):
setTimeout、setInterval- I/O操作(文件读写、网络请求)
- UI渲染
setImmediate(Node.js)
微任务(MicroTask):
Promise.then、Promise.catch、Promise.finallyMutationObserverqueueMicrotaskprocess.nextTick(Node.js)
十、Vue2了解程度
回答思路:诚实回答自己掌握的程度,可以分层次说明。
基础层:响应式原理(Object.defineProperty)、虚拟DOM、模板编译、组件生命周期
API层面:指令(v-if/v-for/v-model)、组件通信(props/emit、事件总线、Vuex)、计算属性/侦听器、插槽
进阶:nextTick原理、keep-alive、mixin混入、自定义指令、render函数
实践经验:可以结合自己做过的项目,说明用Vue2解决了什么具体问题。
十一、SSE与WebSocket的区别及SSE数据格式
回答思路:参考之前AI应用开发面经中关于SSE的详细解析。
SSE数据格式:
data: 这是一条消息\n\n
data: 第一条消息\n
data: 第二条消息\n\n
event: customEvent\n
data: 自定义事件消息\n\n
id: 12345\n
data: 带ID的消息\n\n
data::消息内容,多个data行表示多行消息event::自定义事件类型,前端监听对应事件id::消息ID,断线重连时会发送Last-Event-ID头- 每条消息以
\n\n结尾
客户端示例:
javascript
const source = new EventSource('/events')
source.onmessage = (e) => console.log(e.data)
source.addEventListener('customEvent', (e) => console.log(e.data))
十二、跨域问题的解决方式和原理
回答思路:先说跨域是什么(同源策略),再说解决方案。
跨域:浏览器安全策略,限制非同源(协议、域名、端口任一不同)的脚本间交互。
解决方案:
| 方案 | 原理 | 适用场景 |
|---|---|---|
| CORS | 服务端设置Access-Control-Allow-Origin |
最常用,现代Web标准 |
| JSONP | 利用<script>不受同源策略限制 |
仅支持GET,已较少使用 |
| 代理转发 | 同源请求→代理服务器→目标API | 开发环境(webpack proxy),生产环境(Nginx) |
| postMessage | 跨窗口通信 | iframe与父页面 |
| WebSocket | 不受同源限制 | 实时双向通信 |
CORS细节:简单请求直接发;非简单请求(PUT/DELETE/自定义头)先发OPTIONS预检。
十三、localStorage、sessionStorage、cookie区别
| 维度 | cookie | localStorage | sessionStorage |
|---|---|---|---|
| 容量 | 4KB | 5-10MB | 5-10MB |
| 生命周期 | 可设置过期时间 | 永久(手动清除) | 标签页关闭即失效 |
| 作用域 | 同源 + 可设置path | 同源 | 同源 + 同标签页 |
| 自动携带 | 是(每次请求) | 否 | 否 |
| API | 复杂(document.cookie) | 简单(setItem/getItem) | 简单 |
十四、sessionStorage刷新后还在吗?
答案 :存在 。sessionStorage的生命周期是标签页/会话,刷新页面不会清除,只有关闭标签页或浏览器才清除。
追问验证 :可以打开控制台执行sessionStorage.setItem('test', '1'),刷新后执行sessionStorage.getItem('test')验证。
十五、localStorage存满后的处理
回答思路:用户回答"LRU/indexDb",这是正确的方向。
解决方案:
- LRU(最近最少使用)策略:在应用层实现,存数据时检查大小,超出时删除最久未使用的数据
- 使用IndexedDB:容量更大(通常>250MB),支持索引、事务,适合存储大量结构化数据
- 分片存储:将大数据拆分成多个key存储
- 服务端存储:重要数据不应依赖本地存储
javascript
// 简单LRU实现思路
class LRUStorage {
constructor(limit = 5) {
this.limit = limit
this.cache = new Map()
}
set(key, value) {
if (this.cache.size >= this.limit) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(key, value)
}
get(key) {
const value = this.cache.get(key)
if (value !== undefined) {
this.cache.delete(key)
this.cache.set(key, value) // 移到最新
}
return value
}
}
十六、强缓存和协商缓存
回答思路:参考上一篇腾讯面经补充的第六题,此处简要概括。
强缓存 :缓存未过期直接使用,不请求服务器。相关头:Cache-Control: max-age=3600、Expires
协商缓存 :缓存过期后向服务器询问,返回304则使用缓存,返回200则更新。相关头:ETag / If-None-Match、Last-Modified / If-Modified-Since
流程图:
请求 → 检查强缓存 → 未过期 → 使用缓存
↓
过期 → 发送协商缓存请求 → 304 → 使用缓存
↓
200 → 更新缓存
编程题一:用Promise实现图片懒加载
题目要求:用Promise实现图片懒加载,即图片进入可视区时才加载。
javascript
function lazyLoadImage(imgElement) {
return new Promise((resolve, reject) => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
const src = img.dataset.src
if (src) {
img.src = src
img.onload = () => {
observer.unobserve(img)
resolve(img)
}
img.onerror = () => reject(new Error(`图片加载失败: ${src}`))
}
}
})
})
observer.observe(imgElement)
})
}
// 使用示例
const img = document.querySelector('img[data-src]')
lazyLoadImage(img).then(img => {
console.log('图片加载完成', img)
}).catch(err => console.error(err))
核心点:
- 使用
IntersectionObserver检测可见性 - 图片真实地址存在
data-src属性 - 加载完成后resolve Promise
- 支持错误处理
编程题二:用Promise实现红绿灯交替
题目要求:红灯3秒,黄灯1秒,绿灯3秒,循环交替。
javascript
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
function red() {
console.log('红灯亮')
return sleep(3000)
}
function yellow() {
console.log('黄灯亮')
return sleep(1000)
}
function green() {
console.log('绿灯亮')
return sleep(3000)
}
async function trafficLight() {
while (true) {
await red()
await green()
await yellow()
}
}
// 或者用Promise链式调用
function trafficLight2() {
red()
.then(() => green())
.then(() => yellow())
.then(() => trafficLight2())
}
// 启动
trafficLight()
核心点:
- 封装
sleep函数返回Promise - 每个灯返回Promise,用
await或.then()串联 - 循环执行
面试官可能的追问 :如何停止循环?可以用一个标志位控制while循环。
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| 学习平台 | 掘金、MDN、bilibili、GitHub、官方文档 |
| 语义化标签 | header/nav/main/article/section/aside/footer,SEO+可访问性 |
| meta标签 | charset/viewport/description/robots |
| 水平居中 | flex、text-align、绝对定位+transform |
| 0.5px边框 | transform:scaleY(0.5)、box-shadow |
| 闭包 | 函数+外部作用域引用,封装私有变量、函数工厂 |
| 事件循环 | 同步→清空微任务→一个宏任务→循环 |
| SSE | 单向、HTTP协议、data格式、自动重连 |
| 跨域 | CORS、JSONP、代理、postMessage |
| 存储 | cookie(4KB/自动携带)、localStorage(永久)、sessionStorage(标签页) |
| 缓存 | 强缓存(Cache-Control)、协商缓存(ETag/Last-Modified) |
| LRU | 最近最少使用淘汰策略,可用于localStorage满时 |
| Promise懒加载 | IntersectionObserver + Promise |
| Promise红绿灯 | sleep + async/await或Promise链 |
📌 最后一句:
bilibili这场一面,考察的是前端工程师最核心的基础硬实力 。从HTML语义化到CSS布局细节,从JS闭包到事件循环,从网络存储到缓存策略,最后两道Promise编程题,每一题都在检验你是否具备"扎实的前端基本功"。前端技术日新月异,但底层原理和核心能力,永远是安身立命的根本。