前端八股文面经大全:字节前端一面(2026-2-1)·面经深度解析

前言

大家好,我是木斯佳。

在这个春节假期,当大家都在谈论返乡、团圆与休息时,作为一名技术人,我的思考却不由自主地转向了行业的「冬」与「春」。

相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的"增删改查"岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。

正值春节,也是复盘与规划的好时机。结合CSDN这次「春节代码贺新年」活动所提倡的"用技术视角记录春节、复盘成长",我决定在这个假期持续更新专栏,帮助年后参加春招的同学。

这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。

我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。

在这个假期,让我们一起充电,为下一个技术春天做好准备。

面经原文内容

📍面试公司:字节

🕐面试时间: 2 月 1 日

💻面试岗位:前端

❓面试问题:

  • 1.自我介绍
  • 2.平时如何学习前端
  • 3.实验室的指导
  • 4.用户在浏览器地址栏输入一万到页面展示发生了什么(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)列表到数的数据变形

来源: 牛客网 温柔的海螺ssp到手了

📝 字节前端一面·面经深度解析

🎯 面试整体画像

维度 特征
部门定位 字节跳动(未明确部门)
面试风格 广度覆盖型
难度评级 ⭐⭐(两星,覆盖面广都是常见八股文)
考察重心 网络原理、事件循环、Vue原理、设计模式、Git协作、手写实现

🌐 从输入URL到页面展示·完整流程

问题:用户在浏览器地址栏输入URL到页面展示发生了什么

✅ 参考复习知识:

第一阶段:网络请求
javascript 复制代码
// 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──> 服务端
第二阶段:浏览器渲染
javascript 复制代码
// 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)
- 避免重绘重排
第三阶段:特殊处理
javascript 复制代码
// 优化点
1. 预加载扫描器(Preload Scanner)
   - 在构建DOM树的同时,提前加载资源

2. 渲染阻塞
   - CSS阻塞渲染(CSSOM未构建完成)
   - JS阻塞解析(async/defer优化)

3. 关键渲染路径
   - 首屏渲染优化
   - 避免长时间JS执行

💡 面试官考察点:

这个问题不是让你背流程,而是看你对Web技术栈的整体理解

能讲出细节(如TLS握手、渲染树构建) → 加分

能讲出优化点 → 加分


🔄 异步代码输出·事件循环

问题:异步代码输出 + JS事件循环

✅ 完整解析:

1. 典型考题
javascript 复制代码
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. 关键点解析
javascript 复制代码
// 为什么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. 事件循环机制
javascript 复制代码
// 完整流程
1. 执行当前宏任务(同步代码)
2. 执行所有微任务(清空微任务队列)
3. 尝试渲染(requestAnimationFrame)
4. 取下一个宏任务

// 宏任务与微任务
宏任务队列: [setTimeout, setInterval, I/O, UI渲染]
微任务队列: [Promise.then, MutationObserver, queueMicrotask]

// 特殊说明
- 微任务中产生的微任务,会在当前宏任务结束前执行(不是下一轮)
- requestAnimationFrame 在渲染前执行
- requestIdleCallback 在空闲时执行

🎨 CSS垂直居中·全方案总结

问题:CSS垂直居中 + position垂直居中具体做法

✅ 完整方案:

1. Flexbox方案(推荐)
css 复制代码
.container {
  display: flex;
  align-items: center;     /* 垂直居中 */
  justify-content: center; /* 水平居中(可选) */
  height: 300px;
}
2. Grid方案
css 复制代码
.container {
  display: grid;
  place-items: center; /* 同时垂直水平居中 */
  height: 300px;
}
3. Position + Transform(最常考)
css 复制代码
.container {
  position: relative;
  height: 300px;
}

.child {
  position: absolute;
  top: 50%;
  transform: translateY(-50%); /* 向上移动自身高度的一半 */
  /* 水平居中 */
  left: 50%;
  transform: translate(-50%, -50%);
}
4. Position + Margin(需知宽高)
css 复制代码
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 200px;
  height: 100px;
  margin-top: -50px;  /* 高度的一半 */
  margin-left: -100px; /* 宽度的一半 */
}
5. Line-height(单行文本)
css 复制代码
.container {
  height: 300px;
  line-height: 300px; /* 与高度相等 */
  text-align: center;
}
6. Table-cell
css 复制代码
.container {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
  height: 300px;
}
7. 伪元素法
css 复制代码
.container::before {
  content: '';
  display: inline-block;
  height: 100%;
  vertical-align: middle;
}

.child {
  display: inline-block;
  vertical-align: middle;
}

💡 面试官追问:

position垂直居中为什么用translateY(-50%)而不是margin-top?

css 复制代码
- translateY(-50%) 相对于元素自身高度,不需要知道具体数值
- margin-top 相对于父容器宽度(怪异),且需要知道高度
- translate 触发GPU加速,性能更好

⚛️ Vue响应式原理·Vue2 vs Vue3

问题:vue响应式原理(vue2.3)

✅ 完整对比:

Vue2响应式(Object.defineProperty)
javascript 复制代码
// 核心实现
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的局限性
javascript 复制代码
// 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)
javascript 复制代码
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的优势
javascript 复制代码
// 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)
javascript 复制代码
// 场景:全局状态管理、全局弹窗
class Store {
  constructor() {
    if (Store.instance) {
      return Store.instance;
    }
    this.state = {};
    Store.instance = this;
  }
}

// Vuex/Pinia 本质就是单例
const store = createPinia();
2. 观察者模式(Observer)
javascript 复制代码
// 场景:事件总线、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)
javascript 复制代码
// 场景:创建不同类型的组件
class ButtonFactory {
  createButton(type) {
    switch(type) {
      case 'primary':
        return new PrimaryButton();
      case 'danger':
        return new DangerButton();
      default:
        return new DefaultButton();
    }
  }
}
4. 策略模式(Strategy)
javascript 复制代码
// 场景:表单验证、动画算法
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)
javascript 复制代码
// 场景:高阶组件、AOP
function withLogging(Component) {
  return (props) => {
    useEffect(() => {
      console.log('组件渲染', Component.name);
    });
    return <Component {...props} />;
  };
}

// 使用
const LoggedButton = withLogging(Button);
6. 代理模式(Proxy)
javascript 复制代码
// 场景:图片懒加载、缓存代理
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. 优先级计算(权重)
css 复制代码
/* 权重计算规则 */
!important > 内联样式 > ID选择器 > 类选择器/属性/伪类 > 元素/伪元素 > 通配符

/* 具体数值(256进制) */
- 内联样式:1000
- ID选择器:0100
- 类/属性/伪类:0010
- 元素/伪元素:0001
- 通配符(*):0000
- 继承:无权重

/* 示例 */
#nav .list li a:hover  /* 权重:0100 + 0010 + 0001 + 0001 + 0010 = 0122 */
2. 特殊情况
css 复制代码
/* !important 最高优先级 */
.color {
  color: blue !important; /* 最高 */
}

/* 相同权重,后面的覆盖前面的 */
.title { color: red; }
.title { color: blue; } /* 生效 */

/* 继承的样式优先级最低 */
.parent {
  color: red;
}
.child {
  /* 继承的color优先级为0,会被任何直接样式覆盖 */
}
3. 实用技巧
css 复制代码
/* 增加优先级的方法 */
- 重复选择器:.title.title { color: red; } /* 权重加倍 */
- 添加父选择器:.container .title
- 使用id:不要滥用
- !important:最后手段

🔧 Git代码冲突解决

问题:git代码提交冲突怎么解决

✅ 完整流程:

1. 冲突产生的原因
bash 复制代码
# 两人修改同一文件的同一区域
A修改了main.js的第10行并push
B也修改了main.js的第10行,然后pull时产生冲突
2. 解决步骤
bash 复制代码
# 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. 更优雅的解决方式
bash 复制代码
# 使用merge工具
git mergetool

# 使用rebase避免多余合并记录
git pull --rebase
# 然后逐个解决冲突
git add .
git rebase --continue

# 如果解决过程中想放弃
git merge --abort
# 或
git rebase --abort
4. 预防冲突的策略
javascript 复制代码
// 1. 频繁pull
git pull --rebase 每天上班第一件事

// 2. 小粒度提交
不要攒一周的代码才提交

// 3. 明确分工
避免多人同时修改同一文件

// 4. 代码审查
PR/MR时及早发现潜在冲突

🖱️ 拖拽功能实现

问题:项目中的拖拽功能实现

✅ 完整实现:

1. 原生拖拽API
javascript 复制代码
// 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. 鼠标事件模拟(更常用)
javascript 复制代码
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(推荐)
javascript 复制代码
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. 什么情况下加载?
javascript 复制代码
// 加载条件:
1. 图片进入视口(或即将进入)
2. 浏览器空闲时(预加载)
3. 网络状态良好(4G/WiFi)
4. 用户即将滚动到该位置(预测)

// 不加载条件:
1. 图片远离视口
2. 网络状态差(2G/弱网)
3. 节省流量模式开启
4. 图片被display:none隐藏
3. 优化版本(考虑网络状态)
javascript 复制代码
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操作符

✅ 参考实现:

javascript 复制代码
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的实例
关键点解析:
javascript 复制代码
// 1. Object.create 相当于
const obj = {};
obj.__proto__ = constructor.prototype;

// 2. 处理返回值
- 如果构造函数返回原始类型(string/number/boolean等),忽略,返回obj
- 如果构造函数返回对象/函数,返回该对象
- null是特殊情况(typeof null === 'object'),返回obj

🌳 列表转树·数据变形

问题:列表到树的数据变形

✅ 参考实现:

1. 题目理解
javascript 复制代码
// 输入
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
javascript 复制代码
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. 递归版本(适用于小数据)
javascript 复制代码
function listToTreeRecursive(list, parentId = null) {
  return list
    .filter(item => item.parentId === parentId)
    .map(item => ({
      ...item,
      children: listToTreeRecursive(list, item.id)
    }));
}
4. 进阶要求:排序、过滤
javascript 复制代码
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. 应用场景
javascript 复制代码
// 1. 组织架构图
// 2. 权限菜单树
// 3. 评论楼中楼
// 4. 商品分类
// 5. 地区选择器


🎁 附:字节一面复习清单

知识点 掌握程度 重点方向
输入URL全过程 ⭐⭐⭐⭐⭐ DNS、TCP、TLS、HTTP、渲染
事件循环 ⭐⭐⭐⭐⭐ 宏任务/微任务、async/await
CSS垂直居中 ⭐⭐⭐⭐ 多种方案、优缺点
Vue响应式 ⭐⭐⭐⭐ Vue2/3对比、原理实现
设计模式 ⭐⭐⭐ 常见模式、应用场景
选择器优先级 ⭐⭐⭐ 权重计算、特殊情况
Git冲突解决 ⭐⭐⭐ 解决流程、预防策略
拖拽实现 ⭐⭐⭐ 原生API、鼠标模拟
图片懒加载 ⭐⭐⭐ Intersection Observer、网络判断
new实现 ⭐⭐⭐⭐ 原型链、返回值处理
列表转树 ⭐⭐⭐⭐ 一次遍历、递归、排序过滤

📌 最后一句:

木木复盘:虽然分享者没有明确指出是实习还是校招面试,个人感觉这里的大部分八股已经在其他的面经中有遇到了,整体准备充分的话,这场面试应该会比较从容。这也是我提倡大家去复盘面经的原因。

相关推荐
宇木灵2 小时前
C语言基础-四、函数
c语言·开发语言·前端·学习
We་ct2 小时前
LeetCode 114. 二叉树展开为链表:详细解题思路与 TS 实现
前端·数据结构·算法·leetcode·链表·typescript
L-李俊漩2 小时前
手机端的google chrome 浏览器 怎么看响应的日志和请求报文
前端·chrome·智能手机
明月_清风2 小时前
HTML 早已不是标签了,它现在是系统级接口:这 9 个 API 直接干翻常用 JS 库
前端·html
岱宗夫up2 小时前
【前端基础】HTML + CSS + JavaScript 快速入门
前端·css·html
明月_清风2 小时前
告别后端转换:前端实现 Word & PDF 高性能预览实战
前端
skywalk81632 小时前
electrobun 使用TypeScript构建超快速、小巧且跨平台的桌面应用程序(待续)
前端·javascript·typescript
吴声子夜歌3 小时前
小程序——生命周期函数和事件处理函数
服务器·前端·小程序
薛一半4 小时前
React的数据绑定
前端·javascript·react.js