字节跳动前端代码题汇总(2025-2026)

25 分钟

目录


一、高频手写代码题

1. 带并发限制的 Promise 调度器(必考 ⭐⭐⭐⭐⭐)

class Scheduler {
  constructor(limit) {
    this.limit = limit;
    this.running = 0;
    this.queue = [];
  }

  add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.run();
    });
  }

  run() {
    while (this.running < this.limit && this.queue.length) {
      const { task, resolve, reject } = this.queue.shift();
      this.running++;
      task()
        .then(resolve, reject)
        .finally(() => {
          this.running--;
          this.run();
        });
    }
  }
}

// 测试
const scheduler = new Scheduler(2);
const task = (id, delay) => () =>
  new Promise(resolve => {
    console.log(`任务${id}开始`);
    setTimeout(() => {
      console.log(`任务${id}完成`);
      resolve(id);
    }, delay);
  });

scheduler.add(task(1, 1000));
scheduler.add(task(2, 500));
scheduler.add(task(3, 300));
scheduler.add(task(4, 800));
// 输出: 1开始 2开始 → 2完成 3开始 → 3完成 4开始 → 1完成 4完成

2. 带重试和超时的 fetch

/**
 *
 * @param {string} url
 * @param {{ retrytimes: number; timeout: number; }} options
 * @returns
 */
async function myFetch(url, options) {
  const { retrytimes = 1, timeout = 5000, ...args } = options;
  let retryCount = 0;

  return new Promise((resolve, reject) => {
    const run = () => {
      const controller = new AbortController();
      const timer = setTimeout(() => controller.abort(), timeout);
      fetch(url, { signal: controller.signal, ...args })
        .then((res) => {
          clearTimeout(timer);
          resolve(res);
        })
        .catch((err) => {
          clearTimeout(timer);
          if (retryCount < retrytimes) {
            retryCount++;
            console.log(
              `第 ${retryCount} 次重试,${retryCount * 1000}ms 后执行`,
            );
            setTimeout(() => run(), retryCount * 1000);
          } else {
            reject(err);
          }
        });
    };

    run();
  });
}

3. 深拷贝(含循环引用)

function deepClone(obj, map = new WeakMap()) {
  if (typeof obj !== 'object' || obj === null) return obj;
  if (map.has(obj)) return map.get(obj);

  const target = Array.isArray(obj) ? [] : {};
  map.set(obj, target);

  for (const key of Object.keys(obj)) {
    target[key] = deepClone(obj[key], map);
  }

  return target;
}

关键点:为什么用 WeakMap?弱引用不会阻止 GC 回收,防止内存泄漏。

4. 防抖(debounce)

两种防抖对应两种触发时机:

Trailing(尾部触发,immediate = false)

  • 事件停止触发后,等待 delay 才执行一次。
  • 只要还在连续触发,就一直往后推,永远不执行,直到安静下来。
  • 特点:执行的是"最后一次"的结果,有延迟感。
  • 场景:搜索框输入联想(等用户打完字再发请求)、窗口 resize 结束后再重新布局。

Leading(头部触发,immediate = true)

  • 第一次触发立即执行,然后进入 delay 冷却期。
  • 冷却期内的触发只会不断刷新计时,不执行;直到安静 delay 后才恢复,下次触发又能立即执行。
  • 特点:响应快(第一下就有反应),之后拦截高频重复。
  • 场景:按钮防连点(第一下就提交,后面的点击忽略)、防止表单重复提交。
function debounce(fn, delay = 300, immediate = false) {
  let timer = null;

  const debounced = function (...args) {
    if (timer) clearTimeout(timer);

    if (immediate) {
      if(timer === null) fn.apply(this, args);
      timer = setTimeout(() => { timer = null; }, delay);
    } else {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };

  debounced.cancel = () => { clearTimeout(timer); timer = null; };
  return debounced;
}

5. 节流(throttle)

节流(throttle)的定义:在一段连续、高频的调用中,限制目标函数在固定时间间隔内最多执行一次。

核心是「稀释频率」。不管你在窗口期内触发多少次,函数只按 delay 这个节奏执行,把密集的调用压缩成均匀的、有上限频率的执行。

和防抖(debounce)的区别

这两个经常被搞混,对比着看最清楚:

| | 节流 throttle | 防抖 debounce | | ---------- | --------------------------------- | | 规则 | 每隔 delay 至多执行一次 | 停止触发后等 delay 再执行 | | 高频调用时 | 会按节奏执行(如每 200ms 执行一次) | 一直不执行,直到你停手 | | 类比 | 技能冷却 CD,期间狂点也没用 | 电梯门,有人进就重新等,没人了才关 |

触发时机的三种变体

「执行一次」具体在窗口的哪个时刻,分三种,这也是你上一个问题的关键:

  • leading(首次执行):窗口一开始就立即执行。响应快,但停手后最后那次可能丢。
  • trailing(末次执行):窗口结束时执行。能拿到最后一次的最新值,但首次有延迟。
  • leading + trailing:两者都要——首次立即响应,连续调用结束后再补一次末次。你 2.js 里实现的就是这种,也是最常用、体验最好的一种。

典型场景

  • 滚动监听 scroll、窗口 resize——避免每像素都触发回调
  • 鼠标 mousemove、拖拽
  • 按钮防连点、搜索联想的频率控制

一句话概括:防抖是「等你安静下来再干」,节流是「按固定节奏干,多催也没用」。

// 定时器版:最后一次也会执行
function throttle(fn, delay = 300) {
  let timer = null;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

// 时间戳版:立即执行,停止后不再执行
function throttle2(fn, delay = 300) {
  let lastTime = 0;
  return function (...args) {
    const nowTime = Date.now();
    if (nowTime - lastTime > delay) {
      fn.apply(this, args);
      lastTime = nowTime;
    }
  };
}

// 立即执行,最后一次也执行
function throttle(fn, delay) {
  let pre = 0;
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);

    const now = +new Date();

    if (now - pre >= delay) {
      fn.apply(this, args);
      pre = now;
      timer = null;
    } else {
      timer = setTimeout(
        () => {
          fn.apply(this, args);
          pre = +new Date();
          timer = null;
        },
        delay - (now - pre),
      );
    }
  };
}

6. 数组转树形结构

function listToTree(data) {
  const map = {};
  const tree = [];
  data.forEach(item => { map[item.id] = { ...item, children: [] }; });
  data.forEach(item => {
    if (item.parentId === 0) {
      tree.push(map[item.id]);
    } else if (map[item.parentId]) {
      map[item.parentId].children.push(map[item.id]);
    }
  });
  return tree;
}

7. 函数柯里化

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return (...nextArgs) => curried.apply(this, [...args, ...nextArgs]);
  };
}

// 使用
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6

8. 手写 Promise.all

Promise.myAll = function (promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) return reject(new TypeError('参数必须是数组'));
    const result = [];
    let count = 0;

    if (promises.length === 0) return resolve(result);

    promises.forEach((p, index) => {
      Promise.resolve(p).then(
        res => {
          result[index] = res;
          count++;
          if (count === promises.length) resolve(result);
        },
        err => reject(err)
      );
    });
  });
};

9. 手写 Promise.race

Promise.myRace = function (promises) {
  return new Promise((resolve, reject) => {
    promises.forEach(p => {
      Promise.resolve(p).then(resolve, reject);
    });
  });
};

10. 版本号比较

function compareVersion(v1, v2) {
  const arr1 = v1.split('.').map(Number);
  const arr2 = v2.split('.').map(Number);
  const len = Math.max(arr1.length, arr2.length);

  for (let i = 0; i < len; i++) {
    const a = arr1[i] || 0;
    const b = arr2[i] || 0;
    if (a > b) return 1;
    if (a < b) return -1;
  }
  return 0;
}

compareVersion('1.0.1', '1.0.0'); // 1
compareVersion('1.0.0', '1.0.1'); // -1
compareVersion('1.0', '1.0.0');   // 0

11. 洗牌算法(Fisher-Yates)

function shuffle(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}

12. URL 解析 query 参数

function parseQuery(url) {
  const params = {};
  const queryString = url.split('?')[1];
  if (!queryString) return params;

  queryString.split('&').forEach(pair => {
    const [key, val] = pair.split('=');
    params[decodeURIComponent(key)] = val ? decodeURIComponent(val) : '';
  });
  return params;
}

13. 数组扁平化

function flatten(arr, depth = 1) {
  if (depth === 0) return arr;
  return arr.reduce(
    (acc, val) =>
      acc.concat(Array.isArray(val) ? flatten(val, depth - 1) : val),
    []
  );
}

// 完全扁平
function flattenDeep(arr) {
  return arr.reduce(
    (acc, val) => acc.concat(Array.isArray(val) ? flattenDeep(val) : val),
    []
  );
}

14. 大数相加

function addBigNumber(a, b) {
  let i = a.length - 1, j = b.length - 1, carry = 0, result = '';

  while (i >= 0 || j >= 0 || carry) {
    const sum = (+a[i] || 0) + (+b[j] || 0) + carry;
    result = (sum % 10) + result;
    carry = Math.floor(sum / 10);
    i--; j--;
  }
  return result;
}

15. 模拟 setTimeout 实现 setInterval

function mySetInterval(fn, delay) {
  let timer = null;
  let cancelled = false;

  function execute() {
    if (cancelled) return;
    fn();
    timer = setTimeout(execute, delay);
  }

  timer = setTimeout(execute, delay);

  return {
    clear: () => {
      cancelled = true;
      clearTimeout(timer);
    }
  };
}

// 为什么不用 setInterval?
// setInterval 不会等回调执行完就继续触发下一次,可能造成回调堆积。
// 用 setTimeout 递归可以保证每次执行完才启动下一次。

16. 实现 compose 中间件(Koa 洋葱模型)

function compose(middlewares) {
  return function (context) {
    let index = -1;

    function dispatch(i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'));
      index = i;

      const fn = middlewares[i];
      if (!fn) return Promise.resolve();

      try {
        return Promise.resolve(fn(context, () => dispatch(i + 1)));
      } catch (err) {
        return Promise.reject(err);
      }
    }

    return dispatch(0);
  };
}

// 测试
const m1 = async (ctx, next) => { console.log(1); await next(); console.log(1.1); };
const m2 = async (ctx, next) => { console.log(2); await next(); console.log(2.2); };
compose([m1, m2])({});
// 输出: 1 → 2 → 2.2 → 1.1

17. 实现 instanceof

function myInstanceof(obj, constructor) {
  let proto = Object.getPrototypeOf(obj);
  while (proto) {
    if (proto === constructor.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

二、算法与数据结构

1. LRU 缓存机制(必考 ⭐⭐⭐⭐⭐)

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) return -1;
    const val = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, val); // 移到最"新"位置
    return val;
  }

  put(key, value) {
    if (this.cache.has(key)) this.cache.delete(key);
    this.cache.set(key, value);
    if (this.cache.size > this.capacity) {
      // Map 的 keys() 返回插入顺序,第一个就是最久未使用的
      const oldest = this.cache.keys().next().value;
      this.cache.delete(oldest);
    }
  }
}

2. 超时自动删除的 LRU(2026 新变种)

class LRUCacheWithTTL {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
    this.timers = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) return -1;
    const { value, ttl, timestamp } = this.cache.get(key);
    if (ttl && Date.now() - timestamp > ttl) {
      this.cache.delete(key);
      this._clearTimer(key);
      return -1;
    }
    this.cache.delete(key);
    this.cache.set(key, { value, ttl, timestamp: Date.now() });
    return value;
  }

  put(key, value, ttl) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
      this._clearTimer(key);
    }
    this.cache.set(key, { value, ttl, timestamp: Date.now() });
    if (ttl) {
      this.timers.set(key, setTimeout(() => {
        this.cache.delete(key);
        this.timers.delete(key);
      }, ttl));
    }
    if (this.cache.size > this.capacity) {
      const oldest = this.cache.keys().next().value;
      this.cache.delete(oldest);
      this._clearTimer(oldest);
    }
  }

  _clearTimer(key) {
    if (this.timers.has(key)) {
      clearTimeout(this.timers.get(key));
      this.timers.delete(key);
    }
  }
}

3. 岛屿数量(必考 ⭐⭐⭐⭐⭐)

function numIslands(grid) {
  let count = 0;
  const m = grid.length, n = grid[0].length;

  function dfs(i, j) {
    if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] === '0') return;
    grid[i][j] = '0'; // "沉岛"
    dfs(i + 1, j); dfs(i - 1, j);
    dfs(i, j + 1); dfs(i, j - 1);
  }

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (grid[i][j] === '1') {
        count++;
        dfs(i, j);
      }
    }
  }
  return count;
}

4. 二叉树非递归遍历(高频)

// 前序:根 → 左 → 右
function preorder(root) {
  if (!root) return [];
  const stack = [root], res = [];
  while (stack.length) {
    const node = stack.pop();
    res.push(node.val);
    if (node.right) stack.push(node.right);
    if (node.left) stack.push(node.left);
  }
  return res;
}

// 中序:左 → 根 → 右
function inorder(root) {
  const stack = [], res = [];
  let cur = root;
  while (cur || stack.length) {
    while (cur) {
      stack.push(cur);
      cur = cur.left;
    }
    cur = stack.pop();
    res.push(cur.val);
    cur = cur.right;
  }
  return res;
}

// 后序:左 → 右 → 根 (前序反过来)
function postorder(root) {
  if (!root) return [];
  const stack = [root], res = [];
  while (stack.length) {
    const node = stack.pop();
    res.unshift(node.val);
    if (node.left) stack.push(node.left);
    if (node.right) stack.push(node.right);
  }
  return res;
}

// 层序遍历
function levelOrder(root) {
  if (!root) return [];
  const queue = [root], res = [];
  while (queue.length) {
    const len = queue.length;
    const level = [];
    for (let i = 0; i < len; i++) {
      const node = queue.shift();
      level.push(node.val);
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
    res.push(level);
  }
  return res;
}

5. 其他高频算法题速查

题号题目类型难度频率
LeetCode 1两数之和哈希表Easy⭐⭐⭐⭐⭐
LeetCode 3无重复字符的最长子串滑动窗口Medium⭐⭐⭐⭐
LeetCode 5最长回文子串动态规划Medium⭐⭐⭐
LeetCode 15三数之和双指针Medium⭐⭐⭐⭐
LeetCode 20有效括号Easy⭐⭐⭐⭐⭐
LeetCode 53最大子序和DPEasy⭐⭐⭐⭐
LeetCode 70爬楼梯DPEasy⭐⭐⭐⭐⭐
LeetCode 88合并有序数组双指针Easy⭐⭐⭐⭐
LeetCode 102二叉树层序遍历BFSMedium⭐⭐⭐⭐⭐
LeetCode 121买卖股票最佳时机DPEasy⭐⭐⭐⭐
LeetCode 146LRU 缓存设计Medium⭐⭐⭐⭐⭐
LeetCode 198打家劫舍DPMedium⭐⭐⭐⭐
LeetCode 200岛屿数量DFS/BFSMedium⭐⭐⭐⭐⭐
LeetCode 206反转链表链表Easy⭐⭐⭐⭐
LeetCode 300最长递增子序列DP/二分Medium⭐⭐⭐⭐
LeetCode 322零钱兑换DPMedium⭐⭐⭐⭐
LeetCode 415字符串相加(大数加法)字符串Easy⭐⭐⭐⭐
LeetCode 695岛屿最大面积DFSMedium⭐⭐⭐⭐
LeetCode 994腐烂的橘子BFSMedium⭐⭐⭐

三、场景设计题

1. 大文件上传(分片 + 断点续传 + 秒传)

核心思路

  • 前端用 Blob.slice() 分片
  • 用 SparkMD5 计算文件 hash 实现秒传
  • 上传前先请求 /check 接口,返回已上传的切片索引
  • 并发上传未完成的切片(配合并发调度器)
  • 全部上传完成后调 /merge 合并
class BigFileUploader {
  constructor(file, options = {}) {
    this.file = file;
    this.chunkSize = options.chunkSize || 1024 * 1024 * 5; // 5MB
    this.concurrent = options.concurrent || 3;
    this.scheduler = new Scheduler(this.concurrent);
  }

  async upload() {
    const chunks = this.createChunks();
    const hash = await this.computeHash(chunks);
    // 1. 检查已上传切片
    const uploaded = await this.checkUpload(hash);
    // 2. 上传未完成的切片
    const tasks = chunks
      .filter((_, i) => !uploaded.includes(i))
      .map(chunk => this.scheduler.add(() => this.uploadChunk(chunk)));
    await Promise.all(tasks);
    // 3. 合并
    await this.merge(hash, chunks.length);
  }

  createChunks() {
    const chunks = [];
    let start = 0;
    while (start < this.file.size) {
      chunks.push({
        file: this.file.slice(start, start + this.chunkSize),
        index: chunks.length,
        hash: `${this.file.name}-${chunks.length}`,
      });
      start += this.chunkSize;
    }
    return chunks;
  }

  async computeHash(chunks) {
    // 简化版:真实场景用 SparkMD5 + Web Worker 计算 hash
    return `${this.file.name}-${this.file.size}`;
  }

  async checkUpload(hash) {
    const res = await fetch(`/check?hash=${hash}`).then(r => r.json());
    return res.uploaded || [];
  }

  async uploadChunk(chunk) {
    const formData = new FormData();
    formData.append('chunk', chunk.file);
    formData.append('index', chunk.index);
    formData.append('hash', chunk.hash);
    return fetch('/upload', { method: 'POST', body: formData });
  }

  async merge(hash, total) {
    return fetch('/merge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ hash, total }),
    });
  }
}

2. 虚拟滚动 / 虚拟列表

核心思路:只渲染可视区域内的 DOM 节点,通过 transform: translateY 占位。

class VirtualList {
  constructor(container, items, itemHeight) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2; // +2 缓冲
    this.startIndex = 0;
    this.scrollEl = container;
    this.render();
    this.scrollEl.addEventListener('scroll', () => this.onScroll());
  }

  onScroll() {
    const scrollTop = this.scrollEl.scrollTop;
    this.startIndex = Math.floor(scrollTop / this.itemHeight);
    this.render();
  }

  render() {
    this.container.innerHTML = '';
    const list = document.createElement('div');
    list.style.height = `${this.items.length * this.itemHeight}px`;
    list.style.position = 'relative';

    const endIndex = Math.min(this.startIndex + this.visibleCount, this.items.length);
    for (let i = this.startIndex; i < endIndex; i++) {
      const item = document.createElement('div');
      item.style.position = 'absolute';
      item.style.top = `${i * this.itemHeight}px`;
      item.style.height = `${this.itemHeight}px`;
      item.textContent = this.items[i];
      list.appendChild(item);
    }
    this.container.appendChild(list);
  }
}

3. JS 执行 100 万个任务不卡顿(时间分片)

function performChunk(tasks, budget = 5) {                                                                                        
  let i = 0;                                                                                                                      
                                                                                                                                  
  function _run() {                                                                                                               
    const start = performance.now();                                                                                              
    // 只要还有任务,且本帧预算没用完,就继续执行               
    while (i < tasks.length && performance.now() - start < budget) {                                                              
      tasks[i]();                                                                                                                 
      i++;                                                                                                                        
    }                                                                                                                             
    if (i < tasks.length) {                                     
      requestAnimationFrame(_run);
    }
  }

  requestAnimationFrame(_run);                                                                                                    
}

4. 批量请求失败只弹一个 toast

class ToastManager {
  constructor() {
    this.toasting = false;
    this.pendingCount = 0;
  }

  async request(fn) {
    try {
      return await fn();
    } catch (err) {
      this.pendingCount++;
      if (!this.toasting) {
        this.toasting = true;
        // 延迟收集,合并同一批错误
        setTimeout(() => {
          this.showToast(`共 ${this.pendingCount} 个请求失败`);
          this.toasting = false;
          this.pendingCount = 0;
        }, 100);
      }
      throw err;
    }
  }

  showToast(msg) {
    console.log('Toast:', msg);
  }
}

5. 列表分页快速翻页下的竞态问题

class PageManager {
  constructor() {
    this.requestId = 0;
  }

  async fetchPage(page) {
    const id = ++this.requestId;
    const data = await fetch(`/api/list?page=${page}`).then(r => r.json());
    // 只处理最新请求,丢弃过时的
    if (id === this.requestId) {
      this.render(data);
    }
  }
}

6. 前端截图实现

思路

  • 常规方案:html2canvas(遍历 DOM → 绘制到 Canvas → toDataURL / toBlob
  • 跨域图片需要服务端代理或设置 CORS + crossorigin="anonymous"
  • 部分特殊元素(iframe、视频)需要降级处理

7. 微前端 JS 隔离原理

方案隔离方式特点
qiankun快照沙箱 / Proxy 沙箱快照沙箱兼容性好但有性能开销;Proxy 沙箱现代浏览器可用
MicroAppProxy + CustomEvent更轻量,基于 Web Components
wujieiframe 隔离 + Web Components天然隔离,但通信成本高
Module Federation构建时隔离Webpack 5 原生,共享依赖

四、框架与原理

1. Vue3 响应式原理简写

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key); // 依赖收集
      const result = Reflect.get(target, key, receiver);
      return typeof result === 'object' ? reactive(result) : result; // 懒代理
    },
    set(target, key, value, receiver) {
      const oldVal = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldVal !== value) {
        trigger(target, key); // 触发更新
      }
      return result;
    }
  });
}

function ref(value) {
  return {
    get value() { track(this, 'value'); return value; },
    set value(newVal) {
      if (newVal !== value) {
        value = newVal;
        trigger(this, 'value');
      }
    }
  };
}

// computed
function computed(getter) {
  let dirty = true;
  let cached;
  const result = {
    get value() {
      track(result, 'value');
      if (dirty) {
        cached = getter();
        dirty = false;
      }
      return cached;
    }
  };
  effect(() => { dirty = true; trigger(result, 'value'); }, getter);
  return result;
}

2. React Hooks 闭包陷阱及解决

// 问题:setInterval 内拿到的 count 始终是初始值
function BadCounter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // 始终输出 0
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);
}

// 解决方案1:用函数式 setState
function GoodCounter1() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1); // 不依赖外部 count
    }, 1000);
    return () => clearInterval(timer);
  }, []);
}

// 解决方案2:useRef 保存最新值
function GoodCounter2() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);
  countRef.current = count;
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(countRef.current + 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);
}

3. React 简易 Virtual DOM 转 HTML

function vdomToHtml(node) {
  if (typeof node === 'string') return node;

  const { type, props = {} } = node;
  const attrs = Object.entries(props)
    .filter(([k]) => k !== 'children')
    .map(([k, v]) => ` ${k}="${v}"`)
    .join('');

  const children = (props.children || [])
    .map(child => vdomToHtml(child))
    .join('');

  if (type === 'br' || type === 'hr' || type === 'img' || type === 'input') {
    return `<${type}${attrs} />`;
  }
  return `<${type}${attrs}>${children}</${type}>`;
}

const vdom = {
  type: 'div',
  props: {
    class: 'container',
    children: [
      { type: 'h1', props: { children: ['Hello'] } },
      { type: 'p', props: { children: ['World'] } },
    ]
  }
};
// vdomToHtml(vdom) => <div class="container"><h1>Hello</h1><p>World</p></div>

五、2025-2026 新增趋势题

1. SSE 流式输出处理

class SSEClient {
  constructor(url) {
    this.url = url;
    this.abortController = null;
  }

  async connect(onMessage, onError) {
    this.abortController = new AbortController();
    try {
      const response = await fetch(this.url, {
        signal: this.abortController.signal,
        headers: { 'Accept': 'text/event-stream' },
      });

      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop(); // 保留不完整的行

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') return;
            try {
              onMessage(JSON.parse(data));
            } catch {
              onMessage(data);
            }
          }
        }
      }
    } catch (err) {
      if (err.name !== 'AbortError') onError(err);
    }
  }

  disconnect() {
    this.abortController?.abort();
  }
}

2. TypeScript 类型体操

// DeepReadonly
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function ? T[K] : DeepReadonly<T[K]>
    : T[K];
};

// DeepPartial
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

// Promise 返回值类型提取
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;

// 元组转联合类型
type TupleToUnion<T extends any[]> = T[number];

3. QQ 在线峰值问题

题目:给定一组登录登出时间 [start, end],求同时在线的最大用户数。

function maxOnline(users) {
  // users = [[loginTime, logoutTime], ...]
  const events = [];
  for (const [login, logout] of users) {
    events.push([login, 1]);    // 上线 +1
    events.push([logout, -1]);  // 下线 -1
  }
  events.sort((a, b) => a[0] - b[0] || a[1] - b[1]); // 先下线再上线

  let max = 0, current = 0;
  for (const [, delta] of events) {
    current += delta;
    max = Math.max(max, current);
  }
  return max;
}

4. MCP 协议基础概念(2026 新增考察点)

  • MCP (Model Context Protocol):AI 模型与外部工具/数据源之间的标准协议
  • 核心概念:Tool、Resource、Prompt
  • 前端关注点:如何在应用中集成 MCP 客户端、流式响应处理、工具调用 UI

5. AI 相关高频问答题

  • 如何设计 AI 辅助编码平台:代码补全服务(SSE 流式)、上下文收集(RAG)、生成代码质量审核
  • Token 消耗优化:前端侧如何控制 context 长度、压缩历史消息、缓存公共 prompt
  • D2C (Design to Code) 的质量保障策略
  • AI 生成代码的 Code Review 流程

面试流程与备考策略

字节前端面试轮次

轮次时长考察重点
一面~60minJS 基础 + 2~3 道手写代码题 + 项目浅挖
二面~60min框架原理 + 场景设计 + 架构能力 + 算法
三面~60min系统设计 + 技术视野 + 业务理解 + 跨团队协作
HR 面~50min职业规划 + 薪资期望 + 软素质

备考优先级

  1. 手写题(一面敲门砖):Promise 调度器、深拷贝、防抖节流,必须能 15 分钟无 bug 默写
  2. 算法:LRU、岛屿数量、二叉树遍历,字节高频 TOP 3
  3. 场景设计(二面/三面):大文件上传、虚拟滚动、并发控制、SSE 流式
  4. 框架原理:Vue3 响应式 / React Fiber + Hooks 闭包陷阱
  5. AI 相关(2026 新增):SSE 流式处理、AI 编码工具的使用和原理

数据来源:掘金、牛客网、CSDN、语雀等平台 2025-2026 字节前端面经

声明:以上题目均为面试者分享的回忆版,实际面试题目可能有所不同,仅供参考学习。

南一

前端工程师,在这里整理面试知识,也记录从零做一个网站的过程。

关于本站 →