跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端算法

字节前端一面面经解析:网络原理、Vue 及手写题详解

字节前端一面面试真题,涵盖网络请求流程、事件循环机制、CSS 布局方案、Vue 响应式原理、设计模式应用、Git 冲突解决及常见手写算法题。通过深度解析面试官考察点与参考实现,帮助求职者掌握前端核心知识点与实战技巧。

ByteFlow发布于 2026/4/6更新于 2026/9/581 浏览
字节前端一面面经解析:网络原理、Vue 及手写题详解

面经原文内容

📍面试公司:字节

🕐面试时间: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. (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 冲突解决及常见手写算法题。通过深度解析面试官考察点与参考实现,帮助求职者掌握前端核心知识点与实战技巧。

目录

  1. 面经原文内容
  2. 📝 字节前端一面·面经深度解析
  3. 🎯 面试整体画像
  4. 🌐 从输入 URL 到页面展示·完整流程
  5. 问题:用户在浏览器地址栏输入 URL 到页面展示发生了什么
  6. ✅ 参考复习知识:
  7. 第一阶段:网络请求
  8. 第二阶段:浏览器渲染
  9. 第三阶段:特殊处理
  10. 🔄 异步代码输出·事件循环
  11. 问题:异步代码输出 + JS 事件循环
  12. ✅ 完整解析:
  13. 1\. 典型考题
  14. 2\. 关键点解析
  15. 3\. 事件循环机制
  16. 🎨 CSS 垂直居中·全方案总结
  17. 问题:CSS 垂直居中 + position 垂直居中具体做法
  18. ✅ 完整方案:
  19. 1\. Flexbox 方案(推荐)
  20. 2\. Grid 方案
  21. 3\. Position + Transform(最常考)
  22. 4\. Position + Margin(需知宽高)
  23. 5\. Line-height(单行文本)
  24. 6\. Table-cell
  25. 7\. 伪元素法
  26. ⚛️ Vue 响应式原理·Vue2 vs Vue3
  27. 问题:vue 响应式原理(vue2.3)
  28. ✅ 完整对比:
  29. Vue2 响应式(Object.defineProperty)
  30. Vue2 的局限性
  31. Vue3 响应式(Proxy)
  32. Vue3 的优势
  33. 🏗️ 设计模式·前端常见模式
  34. 问题:工作中遇到的设计模式
  35. ✅ 前端常用设计模式:
  36. 1\. 单例模式(Singleton)
  37. 2\. 观察者模式(Observer)
  38. 3\. 工厂模式(Factory)
  39. 4\. 策略模式(Strategy)
  40. 5\. 装饰器模式(Decorator)
  41. 6\. 代理模式(Proxy)
  42. 🎯 CSS 选择器优先级
  43. 问题:选择器优先级
  44. ✅ 完整规则:
  45. 1\. 优先级计算(权重)
  46. 2\. 特殊情况
  47. 3\. 实用技巧
  48. 🔧 Git 代码冲突解决
  49. 问题:git 代码提交冲突怎么解决
  50. ✅ 完整流程:
  51. 1\. 冲突产生的原因
  52. 两人修改同一文件的同一区域
  53. A 修改了 main.js 的第 10 行并 push
  54. B 也修改了 main.js 的第 10 行,然后 pull 时产生冲突
  55. 2\. 解决步骤
  56. 1. 拉取最新代码
  57. 2. 看到冲突提示
  58. Automatic merge failed; fix conflicts and then commit the result.
  59. 3. 查看冲突文件
  60. both modified: src/main.js
  61. 4. 打开冲突文件
  62. 当前分支的修改
  63. 合并进来的修改
  64. 5. 手动解决冲突
  65. 选择保留哪个,或者合并两者
  66. 删除冲突标记 <<< === >>>
  67. 6. 标记为已解决
  68. 7. 完成合并
  69. 8. 推送
  70. 3\. 更优雅的解决方式
  71. 使用 merge 工具
  72. 使用 rebase 避免多余合并记录
  73. 然后逐个解决冲突
  74. 如果解决过程中想放弃
  75. 或
  76. 4\. 预防冲突的策略
  77. 🖱️ 拖拽功能实现
  78. 问题:项目中的拖拽功能实现
  79. ✅ 完整实现:
  80. 1\. 原生拖拽 API
  81. 2\. 鼠标事件模拟(更常用)
  82. 🖼️ 图片懒加载
  83. 问题:图片懒加载(什么情况下加载,什么情况下不加载)
  84. ✅ 参考实现:
  85. 1\. Intersection Observer(推荐)
  86. 2\. 什么情况下加载?
  87. 3\. 优化版本(考虑网络状态)
  88. 🏗️ 实现 new 操作符
  89. 问题:实现 new 操作符
  90. ✅ 参考实现:
  91. 关键点解析:
  92. 🌳 列表转树·数据变形
  93. 问题:列表到树的数据变形
  94. ✅ 参考实现:
  95. 1\. 题目理解
  96. 2\. 一次遍历 + Map
  97. 3\. 递归版本(适用于小数据)
  98. 4\. 进阶要求:排序、过滤
  99. 5\. 应用场景
  100. 🎁 附:字节一面复习清单

更多推荐文章

查看全部
  • 前端 SSG 静态站点生成技术解析
  • 无人机路径规划算法详解:原理、对比与优化
  • 主流 AI 大模型集合与功能介绍
  • Z-Image:无需登录、无需注册的免费AI图像生成工具深度评测
  • Java 二维数组双色球随机数生成与频率统计
  • GitHub Copilot Agent 模式使用经验总结
  • Python+Copilot 全流程开发效率提升指南:从语法纠错到项目重构
  • 基于 Llama-3.1-70B 的聊天机器人部署与功能实测分析
  • Java 对象比较详解:基本类型与自定义类实现
  • 密码安全指南:从弱口令风险到多因素认证实践
  • Ghostty + Yazi + Lazygit 构建现代化终端开发工作流
  • C++ 多线程任务系统实现:mutex、condition_variable 与 atomic 组合实战
  • Socket 标准参数详解:BACKLOG、KEEPALIVE 与 TCP_NODELAY
  • Stable Diffusion v2-1-base 模型安装与基础使用指南
  • Flutter 应用架构从入门到可扩展的演进实践
  • 基于 AI 快速开发 MCP 服务插件并实现本地与线上部署
  • C++ STL queue 容器适配器源码逻辑深度解析
  • C++ 虚函数深度解析:多态、语法与底层原理
  • 大模型微调的技术含量与实施策略深度解析
  • C++ 继承中的同名成员隐藏规则详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online