跳转至

前端面试准备

前端面试准备

📚 章节目标

本章节将全面介绍前端面试的准备策略,包括算法题、系统设计、项目经验、大厂面试题等,帮助学习者准备前端面试。

学习目标

  1. 理解前端面试的核心要点
  2. 掌握常见算法题和解法
  3. 掌握系统设计题和思路
  4. 掌握项目经验准备方法
  5. 熟悉大厂面试题和解答技巧
  6. 掌握面试技巧和注意事项

🎯 面试准备概述

1. 面试类型

JavaScript
// 前端面试类型
// 1. 技术面试 - 考察技术能力
// 2. 算法面试 - 考察算法和数据结构
// 3. 系统设计面试 - 考察架构设计能力
// 4. 行为面试 - 考察软技能和文化匹配
// 5. 项目面试 - 考察项目经验和解决问题的能力

// 面试准备策略
// 1. 技术基础 - 巩固基础知识
// 2. 算法练习 - 刷LeetCode等平台
// 3. 系统设计 - 准备常见系统设计题
// 4. 项目经验 - 梳理项目经验,准备STAR法则
// 5. 模拟面试 - 进行模拟面试练习

2. 面试准备清单

JavaScript
// 面试准备清单
const interviewChecklist = {
  // 技术基础
  technicalBasics: [
    'HTML/CSS基础',
    'JavaScript基础',
    'React/Vue/Angular基础',
    'TypeScript基础',
    '前端工程化',
    '性能优化',
    '前端安全',
  ],

  // 算法数据结构
  algorithms: [
    '数组操作',
    '字符串操作',
    '链表操作',
    '树操作',
    '图操作',
    '动态规划',
    '贪心算法',
    '排序算法',
    '搜索算法',
  ],

  // 系统设计
  systemDesign: [
    '设计URL短链服务',
    '设计聊天系统',
    '设计新闻Feed系统',
    '设计文件存储系统',
    '设计实时通知系统',
  ],

  // 项目经验
  projectExperience: [
    '梳理项目列表',
    '准备项目难点',
    '准备项目优化',
    '准备项目成果',
  ],

  // 软技能
  softSkills: [
    '沟通能力',
    '团队协作',
    '问题解决',
    '学习能力',
    '抗压能力',
  ],
};

🔢 算法题

1. 数组操作

1.1 两数之和

JavaScript
// 两数之和
// 给定一个整数数组nums和一个目标值target
// 请你在该数组中找出和为目标值的那两个整数
// 并返回它们的数组下标

// 解法1:暴力法
function twoSum(nums, target) {
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] === target) {
        return [i, j];
      }
    }
  }
  return [];
}

// 解法2:哈希表
function twoSum(nums, target) {
  const map = new Map();

  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];

    if (map.has(complement)) {
      return [map.get(complement), i];
    }

    map.set(nums[i], i);
  }

  return [];
}

// 测试
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

1.2 合并两个有序数组

JavaScript
// 合并两个有序数组
// 给定两个有序整数数组nums1和nums2
// 请你将nums2合并到nums1中
// 使nums1成为一个有序数组

function merge(nums1, m, nums2, n) {
  let i = m - 1;
  let j = n - 1;
  let k = m + n - 1;

  while (i >= 0 && j >= 0) {
    if (nums1[i] > nums2[j]) {
      nums1[k--] = nums1[i--];
    } else {
      nums1[k--] = nums2[j--];
    }
  }

  while (j >= 0) {
    nums1[k--] = nums2[j--];
  }

  return nums1;
}

// 测试
console.log(merge([1, 2, 3, 0, 0, 0], 3, [2, 5, 6], 3));
// [1, 2, 2, 3, 5, 6]

2. 字符串操作

2.1 最长无重复字符子串

JavaScript
// 最长无重复字符子串
// 给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度

function lengthOfLongestSubstring(s) {
  const map = new Map();
  let maxLen = 0;
  let left = 0;

  for (let right = 0; right < s.length; right++) {
    const char = s[right];

    if (map.has(char) && map.get(char) >= left) {
      left = map.get(char) + 1;
    }

    map.set(char, right);
    maxLen = Math.max(maxLen, right - left + 1);
  }

  return maxLen;
}

// 测试
console.log(lengthOfLongestSubstring('abcabcbb')); // 3
console.log(lengthOfLongestSubstring('bbbbb')); // 1
console.log(lengthOfLongestSubstring('pwwkew')); // 3

2.2 字符串反转

JavaScript
// 字符串反转
// 给定一个字符串s,请你反转字符串

function reverseString(s) {
  return s.split('').reverse().join('');
}

function reverseString2(s) {
  let reversed = '';
  for (let i = s.length - 1; i >= 0; i--) {
    reversed += s[i];
  }
  return reversed;
}

function reverseString3(s) {
  let left = 0;
  let right = s.length - 1;
  const arr = s.split('');

  while (left < right) {
    [arr[left], arr[right]] = [arr[right], arr[left]];
    left++;
    right--;
  }

  return arr.join('');
}

// 测试
console.log(reverseString('hello')); // 'olleh'
console.log(reverseString2('hello')); // 'olleh'
console.log(reverseString3('hello')); // 'olleh'

3. 树操作

3.1 二叉树遍历

JavaScript
// 二叉树遍历
// 给定一个二叉树,返回其前序、中序、后序遍历

class TreeNode {
  constructor(val, left, right) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

// 前序遍历
function preorderTraversal(root) {
  const result = [];

  function traverse(node) {
    if (!node) return;

    result.push(node.val);
    traverse(node.left);
    traverse(node.right);
  }

  traverse(root);
  return result;
}

// 中序遍历
function inorderTraversal(root) {
  const result = [];

  function traverse(node) {
    if (!node) return;

    traverse(node.left);
    result.push(node.val);
    traverse(node.right);
  }

  traverse(root);
  return result;
}

// 后序遍历
function postorderTraversal(root) {
  const result = [];

  function traverse(node) {
    if (!node) return;

    traverse(node.left);
    traverse(node.right);
    result.push(node.val);
  }

  traverse(root);
  return result;
}

// 测试
const root = new TreeNode(
  1,
  new TreeNode(2, new TreeNode(4), new TreeNode(5)),
  new TreeNode(3, null, new TreeNode(6))
);

console.log(preorderTraversal(root)); // [1, 2, 4, 5, 3, 6]
console.log(inorderTraversal(root)); // [4, 2, 5, 1, 3, 6]
console.log(postorderTraversal(root)); // [4, 5, 2, 6, 3, 1]

3.2 二叉树的最大深度

JavaScript
// 二叉树的最大深度
// 给定一个二叉树,找出其最大深度

function maxDepth(root) {
  if (!root) return 0;

  const leftDepth = maxDepth(root.left);
  const rightDepth = maxDepth(root.right);

  return Math.max(leftDepth, rightDepth) + 1;
}

// 测试
const root = new TreeNode(
  1,
  new TreeNode(2, new TreeNode(4), new TreeNode(5)),
  new TreeNode(3, null, new TreeNode(6))
);

console.log(maxDepth(root)); // 3

🏗️ 系统设计

1. 设计URL短链服务

JavaScript
// 设计URL短链服务
// 功能需求:
// 1. 用户输入长URL,系统生成短URL
// 2. 用户访问短URL,系统重定向到长URL
// 3. 支持自定义短URL
// 4. 支持访问统计

// 技术选型:
// 1. 数据库:Redis(缓存)+ MySQL(持久化)
// 2. 缓存:Redis
// 3. 负载均衡:Nginx
// 4. CDN:加速访问

// 数据库设计:
// 1. 短URL表
//    - id: 主键
//    - short_url: 短URL
//    - long_url: 长URL
//    - created_at: 创建时间
//    - expires_at: 过期时间
//    - user_id: 用户ID

// 2. 访问统计表
//    - id: 主键
//    - short_url_id: 短URL ID
//    - ip: 访问IP
//    - user_agent: 用户代理
//    - referer: 来源
//    - created_at: 访问时间

// API设计:
// 1. POST /api/shorten - 创建短URL
//    - 参数:long_url, custom_url, expires_at
//    - 返回:short_url

// 2. GET /:short_url - 访问短URL
//    - 参数:short_url
//    - 返回:重定向到长URL

// 3. GET /api/stats/:short_url - 获取统计
//    - 参数:short_url
//    - 返回:访问统计

// 实现示例:
class URLShortener {
  constructor(redis, mysql) {
    this.redis = redis;
    this.mysql = mysql;
  }

  async shorten(longUrl, customUrl, expiresAt) {
    const shortUrl = customUrl || this.generateShortUrl();

    // 存储到Redis
    await this.redis.setex(shortUrl, 86400, longUrl);

    // 存储到MySQL
    await this.mysql.query(
      'INSERT INTO short_urls (short_url, long_url, expires_at) VALUES (?, ?, ?)',
      [shortUrl, longUrl, expiresAt]
    );

    return shortUrl;
  }

  async getLongUrl(shortUrl) {
    // 先从Redis获取
    let longUrl = await this.redis.get(shortUrl);

    if (!longUrl) {
      // 从MySQL获取
      const result = await this.mysql.query(
        'SELECT long_url FROM short_urls WHERE short_url = ?',
        [shortUrl]
      );

      if (result.length > 0) {
        longUrl = result[0].long_url;

        // 存储到Redis
        await this.redis.setex(shortUrl, 86400, longUrl);
      }
    }

    return longUrl;
  }

  // ⚠️ Math.random() 不是密码学安全的,高并发下易产生碰撞。
  // 生产环境建议使用 crypto.randomUUID()、nanoid 或数据库自增 ID + Base62 编码。
  generateShortUrl() {
    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let shortUrl = '';

    for (let i = 0; i < 6; i++) {
      shortUrl += chars.charAt(Math.floor(Math.random() * chars.length));
    }

    return shortUrl;
  }
}

2. 设计聊天系统

JavaScript
// 设计聊天系统
// 功能需求:
// 1. 用户可以发送和接收消息
// 2. 支持一对一聊天和群聊
// 3. 支持消息已读状态
// 4. 支持离线消息

// 技术选型:
// 1. 实时通信:WebSocket
// 2. 消息队列:Kafka
// 3. 数据库:MongoDB
// 4. 缓存:Redis

// 数据库设计:
// 1. 用户表
//    - id: 主键
//    - username: 用户名
//    - email: 邮箱
//    - created_at: 创建时间

// 2. 聊天室表
//    - id: 主键
//    - name: 聊天室名称
//    - type: 类型(一对一/群聊)
//    - created_at: 创建时间

// 3. 消息表
//    - id: 主键
//    - chat_room_id: 聊天室ID
//    - sender_id: 发送者ID
//    - content: 消息内容
//    - created_at: 创建时间

// 4. 已读状态表
//    - id: 主键
//    - message_id: 消息ID
//    - user_id: 用户ID
//    - read_at: 已读时间

// API设计:
// 1. POST /api/messages - 发送消息
//    - 参数:chat_room_id, content
//    - 返回:message

// 2. GET /api/messages/:chat_room_id - 获取消息
//    - 参数:chat_room_id, limit, offset
//    - 返回:messages

// 3. POST /api/messages/:message_id/read - 标记已读
//    - 参数:message_id
//    - 返回:success

// 实现示例:
class ChatSystem {
  constructor(websocket, kafka, mongodb, redis) {
    this.websocket = websocket;
    this.kafka = kafka;
    this.mongodb = mongodb;
    this.redis = redis;
  }

  async sendMessage(chatRoomId, senderId, content) {  // async定义异步函数;await等待Promise完成
    const message = {
      chatRoomId,
      senderId,
      content,
      createdAt: new Date(),
    };

    // 存储到MongoDB
    await this.mongodb.collection('messages').insertOne(message);  // await等待异步操作完成

    // 发送到Kafka
    await this.kafka.send('messages', message);

    // 通过WebSocket发送
    this.websocket.emit(`chat:${chatRoomId}`, message);

    return message;
  }

  async getMessages(chatRoomId, limit, offset) {
    const messages = await this.mongodb
      .collection('messages')
      .find({ chatRoomId })
      .sort({ createdAt: -1 })
      .limit(limit)
      .skip(offset)
      .toArray();

    return messages.reverse();
  }

  async markAsRead(messageId, userId) {
    await this.mongodb.collection('read_status').insertOne({
      messageId,
      userId,
      readAt: new Date(),
    });

    return { success: true };
  }
}

📝 项目经验准备

1. STAR法则

JavaScript
// STAR法则
// Situation - 情境
// Task - 任务
// Action - 行动
// Result - 结果

// 项目经验示例
const projectExperience = {
  situation: '在电商项目中,用户反馈商品详情页加载速度慢',
  task: '优化商品详情页的加载速度,提升用户体验',
  action: [
    '分析性能瓶颈,发现主要问题是图片加载慢',
    '实现图片懒加载和预加载',
    '优化API请求,减少不必要的请求',
    '使用CDN加速静态资源',
    '实现服务端渲染,提升首屏加载速度',
  ],
  result: [
    '页面加载时间从3秒降低到1.5秒',
    '用户满意度提升20%',
    '转化率提升15%',
    '获得团队技术分享奖',
  ],
};

2. 项目难点和解决方案

JavaScript
// 项目难点和解决方案
const projectChallenges = {
  challenge1: {
    difficulty: '处理大量数据的渲染性能问题',
    solution: [
      '使用虚拟滚动技术,只渲染可见区域的数据',
      '使用分页加载,减少一次性加载的数据量',
      '使用Web Worker处理数据计算',
      '使用React.memo和useMemo避免不必要的渲染',
    ],
    result: '渲染性能提升80%,用户流畅度显著提升',
  },

  challenge2: {
    difficulty: '实现复杂的表单验证逻辑',
    solution: [
      '使用Yup库进行表单验证',
      '实现自定义验证规则',
      '使用React Hook Form管理表单状态',
      '实现实时验证和错误提示',
    ],
    result: '表单验证准确率达到99%,用户体验显著提升',
  },

  challenge3: {
    difficulty: '实现实时数据同步',
    solution: [
      '使用WebSocket实现实时通信',
      '使用Redis缓存实时数据',
      '实现断线重连机制',
      '实现数据冲突解决策略',
    ],
    result: '实时同步延迟低于100ms,用户体验流畅',
  },
};

🏢 大厂面试题

1. 字节跳动面试题

JavaScript
// 面试题1:实现防抖和节流
// 防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时
// 节流:规定在一个单位时间内,只能触发一次函数

function debounce(func, wait) {
  let timeout;

  return function executedFunction(...args) {  // ...展开运算符:展开数组/对象
    const later = () => {
      clearTimeout(timeout);
      // 使用 apply 绑定正确的 this 上下文
      func.apply(this, args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// ⚠️ 注意:此实现只保留 leading(首次)调用,throttle 期间内的最后一次调用会丢失。
// 生产环境建议增加 trailing 执行逻辑,或直接使用 lodash.throttle。
function throttle(func, limit) {
  let inThrottle;
  let lastArgs; // 记录最后一次被节流的调用参数

  return function executedFunction(...args) {
    if (!inThrottle) {
      func(...args);
      inThrottle = true;
      setTimeout(() => {
        inThrottle = false;
        if (lastArgs) {          // trailing:执行最后一次被丢弃的调用
          func(...lastArgs);
          lastArgs = null;
        }
      }, limit);
    } else {
      lastArgs = args;          // 暂存,等 timer 到期后执行
    }
  };
}

// 测试
const debouncedFn = debounce(() => console.log('Debounced'), 1000);
const throttledFn = throttle(() => console.log('Throttled'), 1000);

// 面试题2:实现Promise.all
function promiseAll(promises) {
  return new Promise((resolve, reject) => {  // Promise异步操作容器:pending→fulfilled/rejected
    const results = [];
    let completed = 0;

    if (promises.length === 0) {
      resolve(results);
      return;
    }

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          results[index] = value;
          completed++;

          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(reject);
    });
  });
}

// 测试
promiseAll([
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3),
]).then((results) => console.log(results)); // [1, 2, 3]

2. 腾讯面试题

JavaScript
// 面试题1:实现深拷贝(支持循环引用检测)
function deepClone(obj, seen = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理循环引用
  if (seen.has(obj)) {
    return seen.get(obj);
  }

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

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], seen);
    }
  }

  return clone;
}

// 测试
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
cloned.b.c = 3;
console.log(original.b.c); // 2
console.log(cloned.b.c); // 3

// 面试题2:实现EventEmitter
class EventEmitter {
  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((callback) => callback(data));
    }
  }

  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(
        (cb) => cb !== callback
      );
    }
  }
}

// 测试
const emitter = new EventEmitter();
emitter.on('test', (data) => console.log(data));
emitter.emit('test', { message: 'Hello' });

3. 阿里巴巴面试题

JavaScript
// 面试题1:实现LRU缓存
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) {
      return -1;
    }

    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);

    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    this.cache.set(key, value);
  }
}

// 测试
const lru = new LRUCache(2);
lru.put(1, 1);
lru.put(2, 2);
console.log(lru.get(1)); // 1
lru.put(3, 3);
console.log(lru.get(2)); // -1

// 面试题2:实现发布订阅模式
class PubSub {
  constructor() {
    this.subscribers = {};
  }

  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }
    this.subscribers[event].push(callback);

    return () => {  // 箭头函数:简洁的函数语法
      this.unsubscribe(event, callback);
    };
  }

  publish(event, data) {
    if (this.subscribers[event]) {
      this.subscribers[event].forEach((callback) => callback(data));
    }
  }

  unsubscribe(event, callback) {
    if (this.subscribers[event]) {
      this.subscribers[event] = this.subscribers[event].filter(  // map转换每个元素;filter筛选;reduce累积
        (cb) => cb !== callback
      );
    }
  }
}

// 测试
const pubsub = new PubSub();
const unsubscribe = pubsub.subscribe('event', (data) => console.log(data));
pubsub.publish('event', { message: 'Hello' });
unsubscribe();

📝 练习题

1. 基础题

题目1:实现两数之和

JavaScript
// 实现两数之和
function twoSum(nums, target) {
  // 实现逻辑
}

// 测试
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

2. 进阶题

题目2:实现LRU缓存

JavaScript
// 实现LRU缓存
class LRUCache {
  constructor(capacity) {
    // 实现逻辑
  }

  get(key) {
    // 实现逻辑
  }

  put(key, value) {
    // 实现逻辑
  }
}

// 测试
const lru = new LRUCache(2);
lru.put(1, 1);
lru.put(2, 2);
console.log(lru.get(1)); // 1
lru.put(3, 3);
console.log(lru.get(2)); // -1

3. 面试题

题目3:解释React的虚拟DOM原理

JavaScript
// 答案要点:
// 1. 虚拟DOM是真实DOM的JavaScript对象表示
// 2. 通过diff算法比较新旧虚拟DOM
// 3. 只更新变化的部分到真实DOM
// 4. 提升渲染性能

// 示例代码
const virtualDOM = {  // const不可重新赋值;let块级作用域变量
  type: 'div',
  props: {
    className: 'container',
    children: [
      { type: 'h1', props: { children: 'Hello' } },
      { type: 'p', props: { children: 'World' } },
    ],
  },
};

🎯 本章总结

本章节全面介绍了前端面试的准备策略,包括算法题、系统设计、项目经验、大厂面试题等。关键要点:

  1. 面试准备:理解前端面试的类型和准备策略
  2. 算法题:掌握常见算法题和解法
  3. 系统设计:掌握系统设计题和思路
  4. 项目经验:掌握项目经验准备方法
  5. 大厂面试题:熟悉大厂面试题和解答技巧
  6. 面试技巧:掌握面试技巧和注意事项

下一步将创建实战项目和面试准备目录。