面经原文内容
📍面试公司:字节
🕐面试时间:2 月 1 日
💻面试岗位:前端
❓面试问题:
- 1.自我介绍
- 2.平时如何学习前端
- 3.实验室的指导
- 4.用户在浏览器地址栏输入 URL 到页面展示发生了什么(http,浏览器渲染逻辑)
- 5.异步代码输出(set time out,async)
- 6.js 事件循环
- 7.css 垂直居中
- 8.position 垂直居中具体做法
- 9.vue 响应式原理(vue2.3)
- 10.工作中遇到的设计模式
- 11.选择器优先级
- 12.git 代码提交冲突怎么解决
- 13.项目:拖拽功能实现,图片懒加载(什么情况下加载什么情况下不加载)
- 14.项目中最有技术挑战的是什么
-
- (1)实现 new 操作符 (2)列表到数的数据变形
📝 字节前端一面·面经深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 部门定位 | 字节跳动(未明确部门) |
| 面试风格 | 广度覆盖型 |
| 难度评级 | ⭐⭐(两星,覆盖面广都是常见八股文) |
| 考察重心 | 网络原理、事件循环、Vue 原理、设计模式、Git 协作、手写实现 |
🌐 从输入 URL 到页面展示·完整流程
问题:用户在浏览器地址栏输入 URL 到页面展示发生了什么
✅ 参考复习知识:
第一阶段:网络请求
// 1. URL 解析
// - 协议(http/https)
// - 域名(www.example.com)
// - 端口(默认 80/443)
// - 路径(/path/to/resource)
// - 参数(?key=value)
// - 哈希(#section)
// 2. DNS 解析
// - 浏览器缓存 → 操作系统缓存 → 本地 hosts 文件 → 路由器缓存 → ISP DNS → 根域名服务器
// - 递归查询 vs 迭代查询
// - DNS 预解析:<link rel="dns-prefetch" href="//example.com">
// 3. TCP 连接(三次握手)
// 客户端 ──SYN──> 服务端
// 客户端 <─SYN+ACK─ 服务端
// 客户端 ──ACK──> 服务端
// 4. TLS 握手(HTTPS)
// - Client Hello(支持的加密套件)
// - Server Hello(选择加密套件 + 证书)
// - 客户端验证证书
// - 密钥交换(RSA/ECDHE)
// - 生成会话密钥
// - 加密通信开始
// 5. HTTP 请求
// - 请求行(GET/path HTTP/1.1)
// - 请求头(Host、User-Agent、Accept 等)
// - 请求体(POST 数据)
// 6. 服务器处理
// - 负载均衡
// - 反向代理(Nginx)
// - 应用服务器处理
// - 数据库查询
// - 生成响应
// 7. HTTP 响应
// - 状态码(200、304、404 等)
// - 响应头(Content-Type、Cache-Control 等)
// - 响应体(HTML/JSON 等)
// 8. TCP 四次挥手
// 客户端 ──FIN──> 服务端
// 客户端 <─ACK─── 服务端
// 客户端 <─FIN─── 服务端
// 客户端 ──ACK──> 服务端
第二阶段:浏览器渲染
// 1. 解析 HTML 生成 DOM 树
// HTML 字符串 → 词法分析 → 语法分析 → DOM 树
// 2. 解析 CSS 生成 CSSOM 树
// CSS 字符串 → 词法分析 → 语法分析 → CSSOM 树
// 3. 合并生成渲染树(Render Tree)
// DOM 树 +CSSOM 树 → 去除不可见元素(head、display:none)→ 渲染树
// 4. 布局(Layout/Reflow)
// 计算每个元素在视口中的确切位置和大小
// - 盒模型计算
// - 相对单位转换(rem、vw 等)
// - 浮动、定位处理
// 5. 绘制(Paint)
// 将元素绘制到屏幕上
// - 绘制背景、边框、文字、阴影等
// - 分层绘制(图层独立)
// 6. 合成(Composite)
// 将不同图层合成最终图像
// -GPU 加速(transform、opacity)
// - 避免重绘重排
第三阶段:特殊处理
// 优化点
// 1. 预加载扫描器(Preload Scanner)
// - 在构建 DOM 树的同时,提前加载资源
// 2. 渲染阻塞
// - CSS 阻塞渲染(CSSOM 未构建完成)
// - JS 阻塞解析(async/defer 优化)
// 3. 关键渲染路径
// - 首屏渲染优化
// - 避免长时间 JS 执行
💡 面试官考察点:
这个问题不是让你背流程,而是看你对 Web 技术栈的整体理解 能讲出细节(如 TLS 握手、渲染树构建) → 加分 能讲出优化点 → 加分
🔄 异步代码输出·事件循环
问题:异步代码输出 + JS 事件循环
✅ 完整解析:
1. 典型考题
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
2. 关键点解析
// 为什么 await 后面的代码在 promise2 之前?
// await async2() 相当于 Promise.resolve(async2()).then(()=>{
// console.log('async1 end')
// })
// 但这里有一个微任务队列的微妙顺序
// 更精确的理解
async function test() {
console.log('1');
await 1; // 相当于 Promise.resolve(1).then()
console.log('2');
}
// 等价于
function test() {
console.log('1');
return Promise.resolve(1).then(() => {
console.log('2');
});
}
3. 事件循环机制
// 完整流程
// 1. 执行当前宏任务(同步代码)
// 2. 执行所有微任务(清空微任务队列)
// 3. 尝试渲染(requestAnimationFrame)
// 4. 取下一个宏任务
// 宏任务与微任务
// 宏任务队列:[setTimeout, setInterval,I/O,UI 渲染]
// 微任务队列:[Promise.then, MutationObserver, queueMicrotask]
// 特殊说明
// - 微任务中产生的微任务,会在当前宏任务结束前执行(不是下一轮)
// - requestAnimationFrame 在渲染前执行
// - requestIdleCallback 在空闲时执行
🎨 CSS 垂直居中·全方案总结
问题:CSS 垂直居中 + position 垂直居中具体做法
✅ 完整方案:
1. Flexbox 方案(推荐)
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中(可选) */
height: 300px;
}
2. Grid 方案
.container {
display: grid;
place-items: center; /* 同时垂直水平居中 */
height: 300px;
}
3. Position + Transform(最常考)
.container {
position: relative;
height: 300px;
}
.child {
position: absolute;
top: 50%;
transform: translateY(-50%); /* 向上移动自身高度的一半 */
/* 水平居中 */
left: 50%;
transform: translate(-50%, -50%);
}
4. Position + Margin(需知宽高)
.child {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 100px;
margin-top: -50px; /* 高度的一半 */
margin-left: -100px; /* 宽度的一半 */
}
5. Line-height(单行文本)
.container {
height: 300px;
line-height: 300px; /* 与高度相等 */
text-align: center;
}
6. Table-cell
.container {
display: table-cell;
vertical-align: middle;
text-align: center;
height: 300px;
}
7. 伪元素法
.container::before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
}
.child {
display: inline-block;
vertical-align: middle;
}
💡 面试官追问:
position 垂直居中为什么用 translateY(-50%) 而不是 margin-top?
/*
- translateY(-50%) 相对于元素自身高度,不需要知道具体数值
- margin-top 相对于父容器宽度(怪异),且需要知道高度
- translate 触发 GPU 加速,性能更好
*/
⚛️ Vue 响应式原理·Vue2 vs Vue3
问题:vue 响应式原理(vue2.3)
✅ 完整对比:
Vue2 响应式(Object.defineProperty)
// 核心实现
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
observe(val);
const dep = new Dep();
// 依赖收集器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
if (Dep.target) { dep.depend(); }
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 对新值进行响应式处理
observe(newVal);
// 触发更新
dep.notify();
}
});
}
// 数组处理(需要 hack)
const arrayMethods = Object.create(Array.prototype);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
const original = arrayMethods[method];
arrayMethods[method] = function (...args) {
const result = original.apply(this, args);
const ob = this.__ob__; // 触发更新
ob.dep.notify();
return result;
};
});
Vue2 的局限性
// 1. 无法检测属性新增/删除
// Vue.set(obj,'newProp', value); // 必须用 Vue.set
// Vue.delete(obj,'prop'); // 必须用 Vue.delete
// 2. 无法直接监听数组索引变化
// this.arr[0]=1; // 不会触发更新
// this.arr.splice(0,1,1); // 必须用 splice
// 3. 初始化时递归遍历所有属性,性能开销
Vue3 响应式(Proxy)
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
const value = Reflect.get(target, key, receiver);
// 懒响应式:只有访问时才处理嵌套对象
if (typeof value === 'object' && value !== null) {
return reactive(value);
}
return value;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
// 触发更新
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = Reflect.has(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
trigger(target, key);
}
return result;
}
});
}
Vue3 的优势
// 1. 可以监听新增/删除属性
const obj = reactive({});
obj.newProp = 'value'; // ✅ 响应式
// 2. 可以监听数组索引变化
const arr = reactive([1,2,3]);
arr[0] = 10; // ✅ 触发更新
// 3. 懒响应式,性能更好
// 只有访问嵌套对象时才进行响应式处理
// 4. 支持 Map、Set 等
const map = reactive(new Map());
map.set('key', 'value'); // ✅ 响应式
🏗️ 设计模式·前端常见模式
问题:工作中遇到的设计模式
✅ 前端常用设计模式:
1. 单例模式(Singleton)
// 场景:全局状态管理、全局弹窗
class Store {
constructor() {
if (Store.instance) return Store.instance;
this.state = {};
Store.instance = this;
}
}
// Vuex/Pinia 本质就是单例
const store = createPinia();
2. 观察者模式(Observer)
// 场景:事件总线、Vue 响应式
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(data));
}
}
}
// Vue 的$on/$emit 就是观察者模式
3. 工厂模式(Factory)
// 场景:创建不同类型的组件
class ButtonFactory {
createButton(type) {
switch (type) {
case 'primary': return new PrimaryButton();
case 'danger': return new DangerButton();
default: return new DefaultButton();
}
}
}
4. 策略模式(Strategy)
// 场景:表单验证、动画算法
const validators = {
required: (value) => !!value,
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
minLength: (value, length) => value.length >= length
};
function validate(value, rules) {
for (const rule of rules) {
const [name, ...args] = rule.split(':');
if (!validators[name](value, ...args)) return false;
}
return true;
}
5. 装饰器模式(Decorator)
// 场景:高阶组件、AOP
function withLogging(Component) {
return (props) => {
useEffect(() => {
console.log('组件渲染', Component.name);
});
return <Component {...props} />;
};
}
// 使用
const LoggedButton = withLogging(Button);
6. 代理模式(Proxy)
// 场景:图片懒加载、缓存代理
const imageProxy = (src) => {
const img = new Image();
const proxy = new Proxy(img, {
set(target, key, value) {
if (key === 'src') {
// 显示占位图
target.src = 'placeholder.jpg';
// 真正加载
setTimeout(() => {
target.src = value;
}, 100);
}
return true;
}
});
proxy.src = src;
return proxy;
};
🎯 CSS 选择器优先级
问题:选择器优先级
✅ 完整规则:
1. 优先级计算(权重)
/* 权重计算规则 */
!important > 内联样式 > ID 选择器 > 类选择器/属性/伪类 > 元素/伪元素 > 通配符
/* 具体数值(256 进制) */
- 内联样式:1000
- ID 选择器:0100
- 类/属性/伪类:0010
- 元素/伪元素:0001
- 通配符 (*): 0000
- 继承:无权重
/* 示例 */
#nav .list li a:hover
/* 权重:0100 + 0010 + 0001 + 0001 + 0010 = 0122 */
2. 特殊情况
/* !important 最高优先级 */
.color {
color: blue !important; /* 最高 */
}
/* 相同权重,后面的覆盖前面的 */
.title {
color: red;
}
.title {
color: blue;
}
/* 生效 */
/* 继承的样式优先级最低 */
.parent {
color: red;
}
.child {
/* 继承的 color 优先级为 0,会被任何直接样式覆盖 */
}
3. 实用技巧
/* 增加优先级的方法 */
- 重复选择器:.title.title { color: red; } /* 权重加倍 */
- 添加父选择器:.container .title
- 使用 id:不要滥用
- !important:最后手段
🔧 Git 代码冲突解决
问题:git 代码提交冲突怎么解决
✅ 完整流程:
1. 冲突产生的原因
# 两人修改同一文件的同一区域
# A 修改了 main.js 的第 10 行并 push
# B 也修改了 main.js 的第 10 行,然后 pull 时产生冲突
2. 解决步骤
# 1. 拉取最新代码
git pull origin main
# 2. 看到冲突提示
# Automatic merge failed; fix conflicts and then commit the result.
# 3. 查看冲突文件
git status
# both modified: src/main.js
# 4. 打开冲突文件
<<<<<<< HEAD
# 当前分支的修改
=======
# 合并进来的修改
>>>>>>> feature-branch
# 5. 手动解决冲突
# 选择保留哪个,或者合并两者
# 删除冲突标记 <<< === >>>
# 6. 标记为已解决
git add src/main.js
# 7. 完成合并
git commit -m "解决冲突"
# 8. 推送
git push
3. 更优雅的解决方式
# 使用 merge 工具
git mergetool
# 使用 rebase 避免多余合并记录
git pull --rebase
# 然后逐个解决冲突
git add .
git rebase --continue
# 如果解决过程中想放弃
git merge --abort
# 或
git rebase --abort
4. 预防冲突的策略
// 1. 频繁 pull git pull --rebase 每天上班第一件事
// 2. 小粒度提交 不要攒一周的代码才提交
// 3. 明确分工 避免多人同时修改同一文件
// 4. 代码审查 PR/MR 时及早发现潜在冲突
🖱️ 拖拽功能实现
问题:项目中的拖拽功能实现
✅ 完整实现:
1. 原生拖拽 API
<!-- HTML5 Drag and Drop -->
<div draggable="true" ondragstart="dragStart(event)">可拖拽元素</div>
function dragStart(e) {
e.dataTransfer.setData('text/plain', e.target.id);
e.dataTransfer.effectAllowed = 'move';
}
function drop(e) {
e.preventDefault();
const data = e.dataTransfer.getData('text/plain');
const element = document.getElementById(data);
e.target.appendChild(element);
}
2. 鼠标事件模拟(更常用)
class Draggable {
constructor(element) {
this.element = element;
this.isDragging = false;
this.startX = 0;
this.startY = 0;
this.initialLeft = 0;
this.initialTop = 0;
this.bindEvents();
}
bindEvents() {
this.element.addEventListener('mousedown', this.onMouseDown.bind(this));
document.addEventListener('mousemove', this.onMouseMove.bind(this));
document.addEventListener('mouseup', this.onMouseUp.bind(this));
}
onMouseDown(e) {
this.isDragging = true;
// 记录初始位置
this.startX = e.clientX;
this.startY = e.clientY;
// 获取元素当前位置
const rect = this.element.getBoundingClientRect();
this.initialLeft = rect.left;
this.initialTop = rect.top;
this.element.style.position = 'absolute';
this.element.style.zIndex = '1000';
this.element.style.cursor = 'grabbing';
// 阻止默认事件(避免选中文本)
e.preventDefault();
}
onMouseMove(e) {
if (!this.isDragging) return;
// 计算移动距离
const dx = e.clientX - this.startX;
const dy = e.clientY - this.startY;
// 设置新位置
this.element.style.left = this.initialLeft + dx + 'px';
this.element.style.top = this.initialTop + dy + 'px';
// 可选:添加边界限制
this.applyBoundary();
}
onMouseUp() {
this.isDragging = false;
this.element.style.cursor = 'grab';
}
applyBoundary() {
const left = parseInt(this.element.style.left);
const top = parseInt(this.element.style.top);
const parent = this.element.parentElement;
if (parent) {
const maxLeft = parent.clientWidth - this.element.offsetWidth;
const maxTop = parent.clientHeight - this.element.offsetHeight;
this.element.style.left = Math.max(0, Math.min(left, maxLeft)) + 'px';
this.element.style.top = Math.max(0, Math.min(top, maxTop)) + 'px';
}
}
}
🖼️ 图片懒加载
问题:图片懒加载(什么情况下加载,什么情况下不加载)
✅ 参考实现:
1. Intersection Observer(推荐)
class LazyLoader {
constructor() {
this.images = document.querySelectorAll('img[data-src]');
this.observer = null;
this.init();
}
init() {
// 创建观察者
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
// 当图片进入视口时加载
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
// 加载后停止观察
this.observer.unobserve(img);
}
});
}, {
root: null, // 视口
rootMargin: '50px', // 提前 50px 加载
threshold: 0.01 // 出现 1% 时触发
});
// 开始观察
this.images.forEach(img => {
this.observer.observe(img);
});
}
loadImage(img) {
const src = img.dataset.src;
if (src) {
img.src = src;
img.removeAttribute('data-src');
// 添加加载完成类
img.addEventListener('load', () => {
img.classList.add('loaded');
});
}
}
}
// 使用
const lazyLoader = new LazyLoader();
2. 什么情况下加载?
// 加载条件:
// 1. 图片进入视口(或即将进入)
// 2. 浏览器空闲时(预加载)
// 3. 网络状态良好(4G/WiFi)
// 4. 用户即将滚动到该位置(预测)
// 不加载条件:
// 1. 图片远离视口
// 2. 网络状态差(2G/弱网)
// 3. 节省流量模式开启
// 4. 图片被 display:none 隐藏
3. 优化版本(考虑网络状态)
class SmartLazyLoader extends LazyLoader {
constructor() {
super();
this.connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
this.checkNetwork();
}
checkNetwork() {
if (this.connection) {
// 监听网络变化
this.connection.addEventListener('change', this.onNetworkChange.bind(this));
// 判断是否应该加载
if (this.shouldLoad()) {
this.startObserving();
} else {
this.pauseObserving();
}
}
}
shouldLoad() {
if (!this.connection) return true;
// 2G 网络不预加载
if (this.connection.effectiveType.includes('2g')) return false;
// 流量敏感
if (this.connection.saveData) return false;
return true;
}
onNetworkChange() {
if (this.shouldLoad()) {
this.startObserving();
} else {
this.pauseObserving();
}
}
}
🏗️ 实现 new 操作符
问题:实现 new 操作符
✅ 参考实现:
function myNew(constructor, ...args) {
// 1. 创建一个空对象,原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 将构造函数的 this 指向这个对象,并执行构造函数
const result = constructor.apply(obj, args);
// 3. 判断返回值
// 如果构造函数返回对象,则返回该对象;否则返回创建的对象
return (typeof result === 'object' && result !== null) || typeof result === 'function' ? result : obj;
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
// 如果没有 return,默认返回 this
}
const p1 = myNew(Person, 'Tom', 18);
console.log(p1); // Person { name: 'Tom', age: 18 }
console.log(p1 instanceof Person); // true
// 测试构造函数返回对象的情况
function ReturnsObject(name) {
this.name = name;
return { custom: 'object' };
}
const p2 = myNew(ReturnsObject, 'Jerry');
console.log(p2); // { custom: 'object' },不是 ReturnsObject 的实例
关键点解析:
// 1. Object.create 相当于
// const obj = {};
// obj.__proto__ = constructor.prototype;
// 2. 处理返回值
// - 如果构造函数返回原始类型(string/number/boolean 等),忽略,返回 obj
// - 如果构造函数返回对象/函数,返回该对象
// - null 是特殊情况(typeof null==='object'),返回 obj
🌳 列表转树·数据变形
问题:列表到树的数据变形
✅ 参考实现:
1. 题目理解
// 输入
const list = [
{ id: 1, name: '节点 1', parentId: null },
{ id: 2, name: '节点 2', parentId: 1 },
{ id: 3, name: '节点 3', parentId: 1 },
{ id: 4, name: '节点 4', parentId: 2 },
{ id: 5, name: '节点 5', parentId: 3 }
];
// 输出
const tree = [
{ id: 1, name: '节点 1', children: [
{ id: 2, name: '节点 2', children: [
{ id: 4, name: '节点 4', children: [] }
]},
{ id: 3, name: '节点 3', children: [
{ id: 5, name: '节点 5', children: [] }
]}
]}
];
2. 一次遍历 + Map
function listToTree(list) {
const map = new Map();
const roots = [];
// 第一次遍历:建立 id -> node 映射
list.forEach(item => {
map.set(item.id, { ...item, children: [] });
});
// 第二次遍历:建立父子关系
list.forEach(item => {
const node = map.get(item.id);
if (item.parentId === null || item.parentId === undefined) {
// 根节点
roots.push(node);
} else {
// 子节点,找到父节点并添加
const parent = map.get(item.parentId);
if (parent) {
parent.children.push(node);
} else {
// 处理父节点不存在的情况(数据错误)
roots.push(node);
}
}
});
return roots;
}
3. 递归版本(适用于小数据)
function listToTreeRecursive(list, parentId = null) {
return list
.filter(item => item.parentId === parentId)
.map(item => ({
...item,
children: listToTreeRecursive(list, item.id)
}));
}
4. 进阶要求:排序、过滤
function listToTreeAdvanced(list, options = {}) {
const {
idKey = 'id',
parentKey = 'parentId',
childrenKey = 'children',
sortKey = null,
sortOrder = 'asc',
filter = null
} = options;
const map = new Map();
const roots = [];
// 过滤
const filteredList = filter ? list.filter(filter) : list;
// 建立映射
filteredList.forEach(item => {
map.set(item[idKey], { ...item, [childrenKey]: [] });
});
// 建立关系
filteredList.forEach(item => {
const node = map.get(item[idKey]);
const parentId = item[parentKey];
if (parentId === null || parentId === undefined) {
roots.push(node);
} else {
const parent = map.get(parentId);
if (parent) {
parent[childrenKey].push(node);
}
}
});
// 排序
if (sortKey) {
const sortFn = (a, b) => {
if (sortOrder === 'asc') {
return a[sortKey] > b[sortKey] ? 1 : -1;
} else {
return a[sortKey] < b[sortKey] ? 1 : -1;
}
};
const sortTree = (nodes) => {
nodes.sort(sortFn);
nodes.forEach(node => {
if (node[childrenKey].length) {
sortTree(node[childrenKey]);
}
});
};
sortTree(roots);
}
return roots;
}
5. 应用场景
// 1. 组织架构图
// 2. 权限菜单树
// 3. 评论楼中楼
// 4. 商品分类
// 5. 地区选择器
🎁 附:字节一面复习清单
| 知识点 | 掌握程度 | 重点方向 |
|---|---|---|
| 输入 URL 全过程 | ⭐⭐⭐⭐⭐ | DNS、TCP、TLS、HTTP、渲染 |
| 事件循环 | ⭐⭐⭐⭐⭐ | 宏任务/微任务、async/await |
| CSS 垂直居中 | ⭐⭐⭐⭐ | 多种方案、优缺点 |
| Vue 响应式 | ⭐⭐⭐⭐ | Vue2/3 对比、原理实现 |
| 设计模式 | ⭐⭐⭐ | 常见模式、应用场景 |
| 选择器优先级 | ⭐⭐⭐ | 权重计算、特殊情况 |
| Git 冲突解决 | ⭐⭐⭐ | 解决流程、预防策略 |
| 拖拽实现 | ⭐⭐⭐ | 原生 API、鼠标模拟 |
| 图片懒加载 | ⭐⭐⭐ | Intersection Observer、网络判断 |
| new 实现 | ⭐⭐⭐⭐ | 原型链、返回值处理 |
| 列表转树 | ⭐⭐⭐⭐ | 一次遍历、递归、排序过滤 |
本文整理了字节前端一面面试真题,涵盖网络请求流程、事件循环机制、CSS 布局方案、Vue 响应式原理、设计模式应用、Git 冲突解决及常见手写算法题。通过深度解析面试官考察点与参考实现,帮助求职者掌握前端核心知识点与实战技巧。

