前端面试复习资料包

前端面试复习资料包

适合:3~5 年 Vue 全栈前端工程师

总篇幅:约 5 万字 / 5 个文档

复习周期:建议 4 周冲刺


📚 文档结构


前端面试复习/

├── README.md            ← 本文件(导航)

├── 01-基础篇.md          ← JS/TS/CSS/HTTP/浏览器/算法/手写题

├── 02-框架篇.md          ← Vue3/Pinia/Vite/uni-app/微前端/SSR/测试

├── 03-项目篇.md          ← 实时通信/可视化/地图/工程化/项目话术

├── 04-软实力篇.md        ← 自我介绍/离职/薪资/反向提问/AI 工具

└── 05-自测题库.md        ← 100 道高频题 + 参考答案


🗓 4 周复习路径

第 1 周:基础夯实

主攻:01-基础篇

  • 第 1-2 章(JS / TS)

  • 第 8 章(手写题专项)

  • 第 9 章(算法)

  • 每天刷 2 道 LeetCode(题号见 9.5)

  • 每周末:做 05-自测题库 第一/二部分(25 道)

第 2 周:框架深入

主攻:02-框架篇

  • 第 1-7 章(Vue3 / Pinia / Router / Vite / uni-app)

  • 第 8-12 章(组件 / 微前端 / SSR / 测试 / 设计模式)

  • 每周末:做 05-自测题库 第三/四部分(28 道)

第 3 周:项目实战

主攻:03-项目篇

  • 第 1-5 章(实时通信 / 可视化 / 地图 / 工程化 / 低代码)

  • 第 6 章(Node.js)

  • 重点:第 7-8 章(项目话术 + 5 个项目预设追问)

  • 每周末:做 05-自测题库 第五-八部分(35 道)

第 4 周:软实力 + 模拟

主攻:04-软实力篇

  • 全篇研读

  • 自我介绍背诵到流利(30s/1min/3min 三版本)

  • 离职原因 / 期望薪资话术练习

  • 找朋友模拟面试 3 次

  • 每天:做 05-自测题库 第九部分(12 道)+ 复习薄弱点


📊 自测评分标准

| 答对题数 | 评估 |

|---|---|

| 90+ | 大厂候选,可挑战 P6+ |

| 70-90 | 中厂稳过,大厂有戏 |

| 50-70 | 还需补漏 |

| <50 | 系统复习 |


💡 使用建议

  1. 不要全背:理解原理 > 背答案

  2. 项目深挖:每个项目至少 3 个难点细节 + 数字

  3. 诚信底线:不熟的不写"精通"

  4. 手写题练习:必练 10 道(见 01-基础篇 第 8 章)

  5. 大厂额外:算法(leetcode hot 100)+ 计算机基础


📦 推荐资源

| 类型 | 资源 |

|---|---|

| JS 进阶 | 《JavaScript 高级程序设计》第 4 版 |

| Vue 源码 | 霍春阳《Vue.js 设计与实现》 |

| 算法 | 《代码随想录》 |

| 网络 | 《图解 HTTP》《网络是怎样连接的》 |

| 浏览器 | 极客时间《浏览器工作原理与实践》 |

| 面试题库 | 牛客网、力扣、前端九部 |


✅ 面试前一天 checklist

  • 简历最新版(PDF + 纸质 3 份)

  • 自我介绍背到流利

  • 5 个项目的 STAR 故事

  • 期望薪资 + 谈判区间

  • 反向提问 3-5 个

  • 公司调研(业务、产品、新闻)

  • 装备:笔、本子、充电宝、耳机(视频面)

  • 提前确认面试地址 / 视频链接

  • 早睡,第二天提前 15 分钟到


🎯 终极心态

  1. 面试是技能,可以训练

  2. 失败不丢人,每次都是练习

  3. 保持谦逊但自信

  4. 复盘比刷题更重要

祝你早日拿到心仪 offer!💪

前端面试复习|基础篇

涵盖:JavaScript 核心 / TypeScript / CSS / HTTP / 浏览器原理 / 网络协议 / 性能优化 / 手写题 / 算法
复习时长建议:1 周


📑 目录

  1. JavaScript 核心
  2. TypeScript 进阶
  3. CSS 与样式工程化
  4. HTTP 与浏览器原理
  5. 浏览器原理深挖
  6. 网络协议补全
  7. 性能优化
  8. 手写题专项
  9. 算法与数据结构

一、JavaScript 核心

1.1 数据类型与判断

8 种数据类型Number / String / Boolean / null / undefined / Symbol / BigInt / Object

方法 优点 缺点
typeof 快、判断基本类型准 null 返回 'object',无法区分对象/数组
instanceof 判断引用类型 跨 iframe 失效,不能判断基本类型
Object.prototype.toString.call() 最准 写法繁琐
Array.isArray() 专门判数组 仅判数组
Object.prototype.toString.call(null)      // [object Null]
Object.prototype.toString.call([])        // [object Array]
Object.prototype.toString.call(new Date)  // [object Date]

1.2 闭包

是什么:函数 + 它能访问到的外部变量 = 闭包。

function counter() {
  let count = 0;
  return () => ++count;
}
const add = counter();
add(); // 1
add(); // 2

用途:模块封装、防抖节流、柯里化、私有变量
坑点:内存泄漏、循环里 var 闭包陷阱(用 let 解决)

1.3 原型与原型链

核心三句话

  1. 每个对象都有 __proto__,指向它的"父对象"
  2. 每个函数都有 prototype
  3. 查找属性时,沿 __proto__ 向上查找直到 null
arr.__proto__ === Array.prototype
Array.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

1.4 this 指向(5 种规则)

  1. new → this 指向新对象
  2. bind / call / apply → this 指向第一个参数
  3. obj.fn() → this 指向 obj
  4. 普通 fn() → 严格模式 undefined,非严格 window
  5. 箭头函数没有自己的 this

1.5 事件循环(Event Loop)

同步代码 → 清空所有微任务 → 取一个宏任务 → 清空微任务 → 渲染 → 下一轮
微任务 宏任务
Promise.then setTimeout / setInterval
MutationObserver I/O、UI 渲染
queueMicrotask setImmediate (Node)
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出:1 4 3 2

1.6 Promise / async-await

三状态:pending → fulfilled / rejected(不可逆)

async/await 本质:Generator + 自动执行器的语法糖。

1.7 深浅拷贝

方式 深/浅 缺点
Object.assign / 扩展运算符 嵌套对象仍是引用
JSON.parse(JSON.stringify()) 丢失函数、Symbol、循环引用报错
structuredClone() 现代浏览器最佳选择
手写递归 + WeakMap 能处理循环引用
function deepClone(obj, map = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (map.has(obj)) return map.get(obj);
  const clone = Array.isArray(obj) ? [] : {};
  map.set(obj, clone);
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) clone[key] = deepClone(obj[key], map);
  }
  return clone;
}

二、TypeScript 进阶

2.1 基础类型

let n: number = 1;
let arr: string[] = ['a'];
let tuple: [string, number] = ['a', 1];

type Status = 'success' | 'error';
type Both = A & B;

2.2 interface vs type

interface type
同名合并
联合/交叉
描述对象

经验:定义对象用 interface,定义类型组合用 type。

2.3 泛型

function identity<T>(arg: T): T { return arg; }

interface ApiResponse<T> {
  code: number;
  data: T;
}

function getLen<T extends { length: number }>(arg: T) {
  return arg.length;
}

2.4 内置工具类型(必背)

Partial<T>       // 所有属性变可选
Required<T>      // 所有属性变必填
Readonly<T>      // 所有属性只读
Pick<T, K>       // 挑选指定属性
Omit<T, K>       // 排除指定属性
Record<K, V>     // 构造键值对类型
ReturnType<T>    // 取函数返回值类型
Parameters<T>    // 取函数参数类型

2.5 类型守卫

function pad(value: string | number) {
  if (typeof value === 'string') return value.padStart(2, '0');
}

function isString(x: unknown): x is string {
  return typeof x === 'string';
}

三、CSS 与样式工程化

3.1 盒模型

* { box-sizing: border-box; }

3.2 BFC(块级格式化上下文)

触发overflow: hidden / display: flex/grid / position: absolute / float
用途:清除浮动、防止 margin 重叠、自适应两栏布局

3.3 Flex 必备

.parent {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.child { flex: 1; }

3.4 Grid 必备

.parent {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

3.5 居中 6 种方式

.parent { display: flex; justify-content: center; align-items: center; }
.parent { display: grid; place-items: center; }
.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); }
.child { position: absolute; inset: 0; margin: auto; }

3.6 移动端适配

方案 原理
rem 根据 html 字体大小动态计算
vw/vh 视口百分比
postcss-px-to-viewport px 自动转 vw

3.7 重排 vs 重绘

重排 重绘
触发 几何属性变化 颜色等非几何属性
开销

优化:使用 transform/opacity 触发 GPU 加速


四、HTTP 与浏览器原理

4.1 输入 URL 到页面渲染

1. URL 解析 → 2. DNS → 3. TCP 三次握手 → 4. TLS 握手
→ 5. HTTP 请求 → 6. 返回 HTML → 7. 解析 DOM → 8. 解析 CSS
→ 9. Render Tree → 10. Layout → 11. Paint → 12. Composite

4.2 HTTP 缓存

类型 字段 状态码
强缓存 Cache-Control / Expires 200 (from cache)
协商缓存 ETag / Last-Modified 304

4.3 跨域

  1. CORS(推荐):后端加 Access-Control-Allow-Origin
  2. 代理:开发用 Vite proxy,生产用 Nginx
  3. postMessage:iframe 间通信

4.4 HTTP/1.1 vs HTTP/2 vs HTTP/3

HTTP/1.1 HTTP/2 HTTP/3
传输 文本 二进制 二进制
多路复用
队头阻塞 TCP 层有 解决(QUIC)

4.5 浏览器存储

cookie localStorage sessionStorage IndexedDB
容量 4KB ~5MB ~5MB 无限
生命周期 可设置 永久 标签页 永久
随请求发送

4.6 HTTPS 握手

1. Client Hello(加密套件、随机数1)
2. Server Hello(证书、随机数2)
3. 客户端验证证书 → pre-master-secret 用公钥加密发送
4. 双方生成对称密钥
5. 后续用对称密钥加密通信

五、浏览器原理深挖

5.1 V8 引擎

源代码 → AST → 字节码(Ignition)→ 监控热点 → TurboFan 优化 → 机器码

优化:隐藏类、内联缓存、逃逸分析

给开发者的建议

  • 对象保持相同的属性顺序
  • 不要中途增删属性
  • 数组保持同一类型

5.2 垃圾回收

引用计数 标记清除
原理 引用为 0 即垃圾 从根遍历,未到达即垃圾
缺点 循环引用 STW

V8 分代回收

  • 新生代(Scavenge 复制算法)
  • 老生代(Mark-Sweep + Mark-Compact)

内存泄漏排查

  • 全局变量
  • 未清理的定时器/事件监听
  • 闭包持有大对象
  • DOM 引用未释放

5.3 浏览器多进程架构

浏览器主进程
├── 网络进程
├── GPU 进程
├── 渲染进程(每 Tab 一个,沙箱隔离)
└── 插件进程

5.4 渲染流水线

HTML → DOM 树
CSS → CSSOM 树
DOM + CSSOM → Render Tree
  → Layout(布局)
  → Paint(绘制)
  → Composite(合成)

触发 GPU 加速transform / opacity / filter / will-change

5.5 Service Worker / PWA

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

// sw.js
self.addEventListener('fetch', (e) => {
  e.respondWith(caches.match(e.request).then(r => r || fetch(e.request)));
});

PWA 三大特性:可安装 / 离线可用 / 推送通知


六、网络协议补全

6.1 TCP 三次握手

客户端 ─── SYN(seq=x) ──→ 服务端
客户端 ←── SYN+ACK ── 服务端
客户端 ─── ACK ──→ 服务端

为什么不是两次:防止历史连接干扰新连接。

6.2 TCP 四次挥手

客户端 ─── FIN ──→ 服务端
客户端 ←── ACK ── 服务端
客户端 ←── FIN ── 服务端
客户端 ─── ACK ──→ 服务端
TIME_WAIT (2MSL) → 关闭

TIME_WAIT 作用:等最后 ACK 到达 / 让旧报文消失。

6.3 DNS 解析

浏览器缓存 → 系统缓存 → 本地 DNS → 根 DNS → 顶级 DNS → 权威 DNS

优化

  • DNS 预解析:<link rel="dns-prefetch" href="//cdn.com">
  • HTTP DNS(绕过运营商劫持)

6.4 WebSocket 握手报文

请求:
  GET /chat HTTP/1.1
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Key: ...
响应:
  HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Sec-WebSocket-Accept: ...

6.5 HTTP 状态码

类别 含义 常见
1xx 信息 100 / 101
2xx 成功 200 / 201 / 204
3xx 重定向 301 / 302 / 304
4xx 客户端错误 400 / 401 / 403 / 404 / 429
5xx 服务端错误 500 / 502 / 504

七、性能优化

7.1 首屏优化清单

阶段 优化点
请求 DNS 预解析、CDN、HTTP/2、Gzip、缓存
加载 路由懒加载、组件异步、图片懒加载、骨架屏
渲染 SSR、关键 CSS 内联、虚拟滚动
构建 Tree Shaking、代码分割、按需引入

7.2 长列表优化

虚拟滚动:根据 scrollTop 计算应该渲染哪些行,其他用占位。

7.3 图片优化

  • WebP / AVIF 格式
  • loading="lazy" 原生懒加载
  • srcset + sizes 响应式
  • tinypng、imagemin 压缩

7.4 Web Vitals

指标 含义 目标
FCP 首次内容绘制 < 1.8s
LCP 最大内容绘制 < 2.5s
FID/INP 首次输入延迟 < 100ms
CLS 累积布局偏移 < 0.1
TTFB 首字节时间 < 600ms

7.5 防抖节流

function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

function throttle(fn, delay) {
  let last = 0;
  return function(...args) {
    const now = Date.now();
    if (now - last >= delay) {
      fn.apply(this, args);
      last = now;
    }
  };
}

八、手写题专项

8.1 手写 new

function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype);
  const result = Constructor.apply(obj, args);
  return result instanceof Object ? result : obj;
}

8.2 手写 instanceof

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

8.3 手写 call / apply / bind

Function.prototype.myCall = function (context, ...args) {
  context = context || window;
  const fn = Symbol('fn');
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};

Function.prototype.myApply = function (context, args = []) {
  context = context || window;
  const fn = Symbol('fn');
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};

Function.prototype.myBind = function (context, ...args) {
  const fn = this;
  return function F(...newArgs) {
    if (this instanceof F) return new fn(...args, ...newArgs);
    return fn.apply(context, [...args, ...newArgs]);
  };
};

8.4 手写 Object.create

function myCreate(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

8.5 手写 Promise.all / race / allSettled

Promise.myAll = function (promises) {
  return new Promise((resolve, reject) => {
    const result = [];
    let count = 0;
    promises.forEach((p, i) => {
      Promise.resolve(p).then(
        (val) => {
          result[i] = val;
          if (++count === promises.length) resolve(result);
        },
        reject
      );
    });
  });
};

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

Promise.myAllSettled = function (promises) {
  return Promise.all(
    promises.map((p) =>
      Promise.resolve(p)
        .then((value) => ({ status: 'fulfilled', value }))
        .catch((reason) => ({ status: 'rejected', reason }))
    )
  );
};

8.6 手写 EventEmitter

class EventEmitter {
  constructor() {
    this.events = {};
  }
  on(event, fn) {
    (this.events[event] ||= []).push(fn);
  }
  off(event, fn) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter((f) => f !== fn);
  }
  emit(event, ...args) {
    this.events[event]?.forEach((fn) => fn(...args));
  }
  once(event, fn) {
    const wrapper = (...args) => {
      fn(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

8.7 手写 flat / compose / pipe

function myFlat(arr, depth = 1) {
  return depth > 0
    ? arr.reduce((a, b) => a.concat(Array.isArray(b) ? myFlat(b, depth - 1) : b), [])
    : arr.slice();
}

const compose = (...fns) => fns.reduce((a, b) => (...args) => a(b(...args)));
const pipe = (...fns) => fns.reduce((a, b) => (...args) => b(a(...args)));

8.8 数组去重 5 种

[...new Set(arr)]                                                   // 1
arr.filter((item, i) => arr.indexOf(item) === i)                    // 2
arr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []) // 3
// 4. Map + filter(对象去重)
// 5. 双层循环(最差)

8.9 手写 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, val) {
    if (this.cache.has(key)) this.cache.delete(key);
    else if (this.cache.size >= this.capacity) {
      this.cache.delete(this.cache.keys().next().value);
    }
    this.cache.set(key, val);
  }
}

8.10 手写 Ajax

function ajax({ url, method = 'GET', data, headers = {} }) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);
    Object.keys(headers).forEach(k => xhr.setRequestHeader(k, headers[k]));
    xhr.onreadystatechange = () => {
      if (xhr.readyState !== 4) return;
      xhr.status >= 200 && xhr.status < 300
        ? resolve(JSON.parse(xhr.responseText))
        : reject(xhr.statusText);
    };
    xhr.send(data ? JSON.stringify(data) : null);
  });
}

九、算法与数据结构

9.1 双指针 / 滑动窗口

// 两数之和(有序)
function twoSum(nums, target) {
  let l = 0, r = nums.length - 1;
  while (l < r) {
    const sum = nums[l] + nums[r];
    if (sum === target) return [l, r];
    sum < target ? l++ : r--;
  }
}

// 无重复字符最长子串
function lengthOfLongestSubstring(s) {
  const map = new Map();
  let max = 0, l = 0;
  for (let r = 0; r < s.length; r++) {
    if (map.has(s[r])) l = Math.max(l, map.get(s[r]) + 1);
    map.set(s[r], r);
    max = Math.max(max, r - l + 1);
  }
  return max;
}

9.2 链表

// 反转链表
function reverseList(head) {
  let prev = null, curr = head;
  while (curr) {
    const next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
  }
  return prev;
}

// 链表是否有环
function hasCycle(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) return true;
  }
  return false;
}

9.3 二叉树遍历

function preorder(root) {
  if (!root) return [];
  return [root.val, ...preorder(root.left), ...preorder(root.right)];
}

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

9.4 动态规划

// 爬楼梯
function climbStairs(n) {
  const dp = [1, 1];
  for (let i = 2; i <= n; i++) dp[i] = dp[i - 1] + dp[i - 2];
  return dp[n];
}

// 最大子数组和
function maxSubArray(nums) {
  let max = nums[0], curr = nums[0];
  for (let i = 1; i < nums.length; i++) {
    curr = Math.max(nums[i], curr + nums[i]);
    max = Math.max(max, curr);
  }
  return max;
}

9.5 LeetCode Hot 100 题单(前端必刷 16 道)

难度 题号 题目
1 两数之和
20 有效的括号
21 合并两个有序链表
70 爬楼梯
121 买卖股票最佳时机
136 只出现一次的数字
206 反转链表
3 无重复字符最长子串
5 最长回文子串
15 三数之和
53 最大子数组和
200 岛屿数量
215 数组中第K大
300 最长递增子序列
42 接雨水
76 最小覆盖子串

本篇完。下一篇:02-框架篇

前端面试复习|框架篇

涵盖:Vue3 / Vue2 对比 / Pinia / Vue Router / Vite / uni-app / Element-Plus / 微前端 / SSR / 测试
复习时长建议:1 周


📑 目录

  1. Vue3 深入
  2. Vue2 vs Vue3 对比
  3. 状态管理
  4. Vue Router
  5. Vite & 构建工具
  6. uni-app 与小程序
  7. 小程序进阶
  8. Element-Plus 与组件设计
  9. 微前端
  10. 服务端渲染(SSR)
  11. 测试
  12. 设计模式

一、Vue3 深入

1.1 响应式原理(核心考点)

Vue2 用 Object.defineProperty

  • 缺点:只能监听已存在属性、数组下标修改不响应、需要递归遍历

Vue3 用 Proxy

  • 优点:拦截 13 种操作、支持新增属性、支持数组、性能更好(懒代理)
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key);
      const val = Reflect.get(target, key);
      return typeof val === 'object' ? reactive(val) : val;
    },
    set(target, key, value) {
      Reflect.set(target, key, value);
      trigger(target, key);
      return true;
    }
  });
}

ref vs reactive

  • ref:基本类型,需要 .value
  • reactive:对象/数组,解构会失去响应性(要用 toRefs

1.2 Composition API vs Options API

<script setup>
import { ref, onMounted } from 'vue';
const count = ref(0);
const add = () => count.value++;
onMounted(() => {});
</script>

优势:逻辑复用更好 / 类型推导友好 / 相关逻辑聚合 / Tree-shaking 友好

1.3 生命周期对照

Vue2 Vue3 (Composition) 时机
beforeCreate (setup 本身) 实例初始化
created (setup 本身) data/methods 已注入
beforeMount onBeforeMount 挂载前
mounted onMounted 挂载完成
beforeUpdate onBeforeUpdate 数据更新前
updated onUpdated DOM 更新完
beforeDestroy onBeforeUnmount 销毁前
destroyed onUnmounted 销毁后

1.4 computed vs watch vs watchEffect

computed watch watchEffect
有返回值
缓存
立即执行 调用时 默认否
知道新旧值
依赖收集 自动 显式 自动

1.5 nextTick

Vue 更新 DOM 是异步批量的。nextTick 把回调推到当前 tick 的微任务队列。

this.count++;
console.log(dom.innerText);              // 旧值
nextTick(() => console.log(dom.innerText)); // 新值

1.6 diff 算法(Vue3 优化)

  1. 静态提升(hoistStatic):静态节点提升到 render 外
  2. PatchFlag:编译时给动态节点打标记
  3. 块(Block):缩小 diff 范围
  4. 最长递增子序列:减少 DOM 移动

1.7 v-if vs v-show

v-if v-show
原理 删/建 DOM 切换 display
初始开销
切换开销
场景 切换频率低 频繁切换

1.8 自定义 Hook

export function useCounter(initial = 0) {
  const count = ref(initial);
  const inc = () => count.value++;
  return { count, inc };
}

1.9 Vue3 新特性

<Teleport to="body">
  <div class="modal">弹窗</div>
</Teleport>

<Suspense>
  <template #default><AsyncComp /></template>
  <template #fallback>加载中...</template>
</Suspense>

<UserForm v-model:name="name" v-model:age="age" />

二、Vue2 vs Vue3 对比

维度 Vue2 Vue3
响应式 Object.defineProperty Proxy
API Options API Composition API
模板根节点 必须单根 Fragment(多根)
生命周期名 created/mounted onMounted...
TypeScript 支持有限 原生设计
包体积 ~30KB ~10KB
性能 baseline +30~50%
新特性 - Teleport / Suspense

三、状态管理

3.1 Pinia

export const useUserStore = defineStore('user', {
  state: () => ({ name: '', token: '' }),
  getters: {
    isLogin: (state) => !!state.token,
  },
  actions: {
    async login(payload) {
      const res = await api.login(payload);
      this.token = res.token;
    },
  },
});

3.2 Pinia vs Vuex

Vuex Pinia
mutation 必须 取消,直接改 state
TypeScript 体验差 完美
模块化 嵌套 modules 多 store 平级
体积 ~1KB

3.3 持久化

const useStore = defineStore('app', {
  state: () => ({ token: '' }),
  persist: true,  // pinia-plugin-persistedstate
});

四、Vue Router

4.1 hash vs history

hash history
URL /#/home /home
原理 onhashchange history.pushState
服务器配置 不需要 需要
SEO

4.2 路由守卫

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token');
  if (to.meta.requireAuth && !token) next('/login');
  else next();
});

4.3 动态路由

const menus = await api.getMenus();
menus.forEach(m => router.addRoute({
  path: m.path,
  component: () => import(`@/views/${m.component}.vue`),
}));

4.4 路由懒加载

{ path: '/home', component: () => import('@/views/Home.vue') }

五、Vite & 构建工具

5.1 Vite 为什么快

Webpack:先打包整个项目 → 再启动 dev server
Vite

  1. 开发:浏览器原生 ESM,不打包,按需编译
  2. 预构建:用 esbuild 把第三方依赖打成 ESM
  3. HMR:精准热更新
  4. 生产:Rollup 打包
冷启动 HMR
Webpack 10~30s 1~3s
Vite <1s <100ms

5.2 Vite 配置

export default defineConfig({
  plugins: [vue()],
  resolve: { alias: { '@': path.resolve(__dirname, 'src') } },
  server: {
    port: 3000,
    proxy: { '/api': { target: 'http://backend.com', changeOrigin: true } },
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vue: ['vue', 'vue-router', 'pinia'],
          element: ['element-plus'],
        },
      },
    },
  },
});

5.3 Webpack 核心概念

  • Entry / Output
  • Loader(vue-loader、css-loader、babel-loader)
  • Plugin(HtmlWebpackPlugin、MiniCssExtractPlugin)
  • Tree Shaking
  • Code Splitting

六、uni-app 与小程序

6.1 uni-app 原理

一份代码 → 多端编译,编译时通过条件编译 #ifdef MP-WEIXIN ... #endif 区分平台。

6.2 小程序双线程模型

┌─────────────┐  setData  ┌─────────────┐
│  逻辑层 JS   │ ───────→ │  渲染层 WXML │
│ (JsCore/V8) │ ←─────── │  (WebView)   │
└─────────────┘  事件     └─────────────┘

为什么这样设计:安全(不能直接操作 DOM)、性能(双线程并发)

性能要点

  • setData 不要传太大对象、不要频繁调用
  • data- 而不是闭包传参
  • 长列表用 recycle-view

6.3 uni-app vs Taro

uni-app Taro
语法 Vue React / Vue
学习成本 Vue 0 成本 需熟悉 React

6.4 分包

{
  "subpackages": [
    { "root": "pkgA", "pages": ["page1"] }
  ],
  "preloadRule": {
    "pages/index/index": { "packages": ["pkgA"] }
  }
}

主包 2MB,单分包 2MB,总包 20MB。

6.5 条件编译

<!-- #ifdef MP-WEIXIN -->
<view>仅微信小程序</view>
<!-- #endif -->

<!-- #ifndef H5 -->
<view>除 H5 外都显示</view>
<!-- #endif -->

七、小程序进阶

7.1 原生 vs uni-app 对比

原生 uni-app
启动速度 最快 略慢
包体积 最小 略大(runtime)
多端支持
学习成本

7.2 自定义组件

Component({
  properties: { title: { type: String, value: '' } },
  data: { count: 0 },
  methods: {
    onTap() {
      this.setData({ count: this.data.count + 1 });
      this.triggerEvent('change', { count: this.data.count });
    },
  },
});

7.3 跨页面通信

// 1. globalData
getApp().globalData.user = user;

// 2. EventBus
uni.$emit('userUpdate', user);
uni.$on('userUpdate', (user) => {});

// 3. 页面栈通信
const pages = getCurrentPages();
const prev = pages[pages.length - 2];
prev.setData({ result: 'hello' });

7.4 setData 优化

// ❌ 错误
this.setData({ list: largeList });

// ✅ 正确
this.setData({ 'list[0].name': 'new' });

// 分片
function chunkSetData(list) {
  const size = 50;
  for (let i = 0; i < list.length; i += size) {
    setTimeout(() => {
      this.setData({ [`list[${i}]`]: list.slice(i, i + size) });
    }, 0);
  }
}

八、Element-Plus 与组件设计

8.1 二次封装

<template>
  <el-table :data="data" v-bind="$attrs">
    <el-table-column
      v-for="col in columns"
      :key="col.prop"
      v-bind="col"
    >
      <template #default="scope" v-if="col.slot">
        <slot :name="col.slot" v-bind="scope" />
      </template>
    </el-table-column>
  </el-table>
</template>

封装原则

  1. 透传属性 v-bind="$attrs"
  2. 提供具名插槽 + 作用域插槽
  3. 配置驱动(columns 配置化)

8.2 表单生成器(Schema)

const schema = [
  { type: 'input', prop: 'name', label: '姓名', rules: [...] },
  { type: 'select', prop: 'gender', label: '性别', options: [...] },
];

8.3 按需引入

import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';

plugins: [
  AutoImport({ resolvers: [ElementPlusResolver()] }),
  Components({ resolvers: [ElementPlusResolver()] }),
]

体积可从 ~3MB 降到 ~200KB。


九、微前端

9.1 qiankun 原理

核心三件事

  1. HTML Entry:以 HTML 为入口,解析 JS/CSS
  2. JS 沙箱
    • Proxy 沙箱(默认)
    • Snapshot 沙箱(不支持 Proxy 时)
  3. CSS 隔离:Shadow DOM / Scoped CSS
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'app1',
    entry: '//localhost:8081',
    container: '#container',
    activeRule: '/app1',
  },
]);
start();

9.2 Module Federation

// 提供方
new ModuleFederationPlugin({
  name: 'app1',
  filename: 'remoteEntry.js',
  exposes: { './Button': './src/Button' },
});

// 消费方
new ModuleFederationPlugin({
  name: 'host',
  remotes: { app1: 'app1@http://localhost:8081/remoteEntry.js' },
});

9.3 iframe vs 微前端

iframe 微前端
隔离 完美 需要框架支持
通信 postMessage 多种
URL 同步 麻烦 友好
性能

十、服务端渲染(SSR)

10.1 CSR / SSR / SSG 对比

CSR SSR SSG
渲染时机 浏览器 每次请求服务端 构建时
首屏 极快
SEO
服务器压力 极低
适用 SPA 后台 内容站 博客、文档

10.2 Nuxt 3 基础

<script setup>
const route = useRoute();
const { data: user } = await useFetch(`/api/users/${route.params.id}`);
</script>

10.3 Hydration(同构)

服务端渲染 HTML → 浏览器显示静态 HTML(首屏快)
        ↓
        下载 JS Bundle
        ↓
        hydrate:附加事件监听器
        ↓
        变成可交互应用

水合不匹配:服务端和客户端结果不一致 → 报错。常见原因:使用 Date.now()Math.random()、浏览器 API。


十一、测试

11.1 测试金字塔

        /\
       /E2E\        少而慢
      /─────\
     /集成测试\
    /─────────\
   /  单元测试  \   多而快
  /─────────────\

11.2 Vitest

import { describe, it, expect } from 'vitest';
import { sum } from './sum';

describe('sum', () => {
  it('1 + 2 = 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
});

11.3 Vue Test Utils

import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';

it('点击 +1', async () => {
  const wrapper = mount(Counter);
  await wrapper.find('button').trigger('click');
  expect(wrapper.text()).toContain('1');
});

11.4 Playwright (E2E)

test('登录流程', async ({ page }) => {
  await page.goto('/login');
  await page.fill('#username', 'admin');
  await page.fill('#password', '123456');
  await page.click('button[type=submit]');
  await expect(page).toHaveURL('/dashboard');
});

11.5 覆盖率

vitest run --coverage

四种覆盖率:行 / 分支 / 函数 / 语句,团队一般 70%+。


十二、设计模式

12.1 观察者 vs 发布订阅

观察者 发布订阅
是否有中介 ✅(事件中心)
例子 Vue 响应式 EventBus

12.2 单例

class Singleton {
  static instance = null;
  static getInstance() {
    if (!this.instance) this.instance = new Singleton();
    return this.instance;
  }
}

12.3 工厂

class UserFactory {
  static create(type) {
    switch (type) {
      case 'admin': return new AdminUser();
      case 'guest': return new GuestUser();
    }
  }
}

12.4 装饰器

function log(target, key, descriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args) {
    console.log(`Call ${key}`);
    return original.apply(this, args);
  };
}

class Service {
  @log
  fetch() {}
}

12.5 策略模式

const strategies = {
  vip: (p) => p * 0.8,
  svip: (p) => p * 0.6,
  normal: (p) => p,
};
const calc = (type, price) => strategies[type](price);

本篇完。下一篇:03-项目篇

前端面试复习|项目篇

涵盖:实时通信 / ECharts / 地图 SDK / 工程化 / 项目难点话术 / 简历项目 Q&A
复习时长建议:1 周


📑 目录

  1. 实时通信(WebSocket / WebRTC / MQTT)
  2. ECharts 数据可视化
  3. 地图 SDK 与电子围栏
  4. 前端工程化
  5. 低代码 / 中后台脚手架
  6. Node.js / BFF
  7. 项目难点话术(STAR)
  8. 简历项目延伸 Q&A

一、实时通信

1.1 WebSocket

为什么需要:HTTP 是请求-响应模式,服务器无法主动推送。WebSocket 是全双工长连接

const ws = new WebSocket('wss://api.com');
ws.onopen = () => ws.send('hello');
ws.onmessage = (e) => console.log(e.data);
ws.onclose = () => reconnect();
ws.onerror = () => {};

生产级要点

  1. 心跳:定时发 ping,30s 没收到 pong 就重连
  2. 断线重连:指数退避(1s、2s、4s、8s...)
  3. 消息队列:断线期间消息暂存,重连后补发
  4. 鉴权:URL 带 token 或第一条消息发 auth

完整封装

class SocketClient {
  constructor(url) {
    this.url = url;
    this.reconnectTimes = 0;
    this.heartTimer = null;
    this.connect();
  }
  connect() {
    this.ws = new WebSocket(this.url);
    this.ws.onopen = () => {
      this.reconnectTimes = 0;
      this.startHeartbeat();
    };
    this.ws.onclose = () => this.reconnect();
  }
  startHeartbeat() {
    this.heartTimer = setInterval(() => {
      this.ws.send(JSON.stringify({ type: 'ping' }));
    }, 30000);
  }
  reconnect() {
    clearInterval(this.heartTimer);
    const delay = Math.min(1000 * 2 ** this.reconnectTimes, 30000);
    setTimeout(() => {
      this.reconnectTimes++;
      this.connect();
    }, delay);
  }
}

1.2 WebRTC

用途:浏览器间 P2P 音视频传输,1V1 通话、屏幕共享、实时直播。

三大核心 API

API 作用
getUserMedia() 获取摄像头/麦克风流
RTCPeerConnection 建立 P2P 连接
RTCDataChannel 任意数据通道

信令流程

A → 创建 Offer → 信令服务器 → B
B → 创建 Answer → 信令服务器 → A
A、B 互换 ICE Candidate → P2P 通道建立

多路视频卡顿优化

  • SFU 架构(服务器转发不合流)
  • 自适应码率
  • 关键帧请求(PLI)
  • 硬件解码

1.3 MQTT

为什么用 MQTT 而不用 WebSocket

  • 极轻量(最小 2 字节头)
  • 发布订阅模型,适合海量设备
  • 支持 QoS(0/1/2 三级)
  • 遗嘱消息

核心概念

概念 解释
Broker 消息中转服务器(EMQX、Mosquitto)
Topic 主题(如 farm/cow1/temperature
QoS 0 最多一次(可能丢)
QoS 1 至少一次(可能重复)
QoS 2 仅一次(最可靠最慢)
import mqtt from 'mqtt';
const client = mqtt.connect('wss://broker.com');
client.subscribe('farm/+/temperature');     // + 单层通配
client.on('message', (topic, payload) => {});
client.publish('farm/cow1/cmd', 'feed');

二、ECharts 数据可视化

2.1 基础使用

import * as echarts from 'echarts';
const chart = echarts.init(document.getElementById('main'));
chart.setOption({
  xAxis: { type: 'category', data: ['周一', '周二'] },
  yAxis: { type: 'value' },
  series: [{ type: 'bar', data: [120, 200] }],
});

2.2 性能优化

场景 方案
大数据(>1万) appendData 增量加载
多图表 共享主题 + 懒加载
体积大 按需引入
resize 不灵 监听 resize + chart.resize()

按需引入

import * as echarts from 'echarts/core';
import { BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]);

体积从 ~1MB 降到 ~200KB。

2.3 常踩坑

  • setOption 默认合并,要清空用 chart.clear()
  • 数据切换时 series 数量变化要 setOption(option, true)
  • v-if 销毁后要重新 init
  • 容器宽高为 0 时初始化失败

2.4 Vue3 封装

<template>
  <div ref="chartRef" :style="{ width, height }"></div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
import * as echarts from 'echarts';

const props = defineProps(['option', 'width', 'height']);
const chartRef = ref();
let chart = null;

onMounted(() => {
  chart = echarts.init(chartRef.value);
  chart.setOption(props.option);
  window.addEventListener('resize', () => chart.resize());
});

watch(() => props.option, (val) => chart?.setOption(val), { deep: true });

onBeforeUnmount(() => {
  chart?.dispose();
});
</script>

2.5 大屏适配

.bigscreen {
  width: 1920px;
  height: 1080px;
  transform: scale(var(--scale));
  transform-origin: left top;
}
function setScale() {
  const scale = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
  document.documentElement.style.setProperty('--scale', scale);
}
window.addEventListener('resize', setScale);

2.6 主题定制

echarts.registerTheme('myDark', {
  color: ['#5470c6', '#91cc75'],
  backgroundColor: '#1a1a2e',
  textStyle: { color: '#fff' },
});
echarts.init(dom, 'myDark');

三、地图 SDK 与电子围栏

3.1 高德 vs 腾讯

高德 腾讯
国内数据 ★★★★★ ★★★★
海外 ★★
文档 优秀 一般

3.2 核心能力

const map = new AMap.Map('container', { zoom: 11, center: [116.4, 39.9] });

// 标记
new AMap.Marker({ position: [lng, lat], map });

// 路径规划
new AMap.Driving().search([from, to], (status, result) => {});

// 地理编码
new AMap.Geocoder().getLocation(address, callback);

// 电子围栏判定
AMap.GeometryUtil.isPointInRing([lng, lat], polygonPath);

3.3 电子围栏

是什么:在地图上画一个虚拟"圈",判断设备/用户进入或离开 → 触发动作。

业务流程

  1. 商家用 MouseTool.polygon() 绘制多边形
  2. 后端持久化围栏数据
  3. 用户下单 → 取地址坐标 → 用 isPointInRing 判断

完整代码

// 1. 商家绘制
const mouseTool = new AMap.MouseTool(map);
mouseTool.polygon({
  fillColor: '#1791fc',
  strokeColor: '#1791fc',
});
mouseTool.on('draw', (event) => {
  const path = event.obj.getPath();
  const coords = path.map(p => [p.lng, p.lat]);
  saveToBackend(coords);
});

// 2. 用户下单判定
const isInside = AMap.GeometryUtil.isPointInRing(
  [userLng, userLat],
  polygonPath
);
if (!isInside) alert('超出服务范围');

坐标系坑

坐标系 用于
WGS84 GPS 原始
GCJ02 高德、腾讯(火星坐标)
BD09 百度

后端拿到 GPS 坐标要先转 GCJ02 再画到高德地图,否则偏移几百米。

应用场景

  • 外卖配送范围
  • 共享单车运营区/禁停区
  • 门店服务范围(上门取件)
  • 车辆调度
  • 儿童老人定位手表

四、前端工程化

4.1 模块化历史

CommonJS(Node)→ AMD/CMD(已弃)→ ES Module

// CommonJS
const fs = require('fs');
module.exports = {};

// ESM
import fs from 'fs';
export default {};

4.2 代码规范

# ESLint:代码错误
# Prettier:代码格式
# Husky:Git hooks
# lint-staged:只检查暂存区
# Commitlint:commit 规范

.husky/pre-commit

npx lint-staged

4.3 Git 协作

命令 用途
git rebase -i 合并/修改历史 commit
git cherry-pick 挑某个 commit
git stash 暂存当前修改
git reset --soft/--hard 软/硬重置
git revert 反向 commit

Git Flow

master ←─ release ←─ develop ←─ feature/xxx
                              ←─ hotfix/xxx

4.4 Monorepo

工具:pnpm workspace、Turborepo、Nx、Lerna
好处:多包共享依赖、统一版本、原子提交

4.5 CI/CD 流程

name: Deploy
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - run: pnpm install
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build
      - name: Deploy
        run: rsync -avz dist/ user@server:/var/www/

五、低代码 / 中后台脚手架

5.1 表单生成器(Schema 驱动)

const schema = [
  {
    type: 'input',
    field: 'username',
    label: '用户名',
    rules: [{ required: true, message: '必填' }],
  },
  {
    type: 'select',
    field: 'role',
    label: '角色',
    options: [{ label: '管理员', value: 'admin' }],
  },
];

function renderForm(schema) {
  return schema.map(item => {
    switch (item.type) {
      case 'input': return h(ElInput, { ... });
      case 'select': return h(ElSelect, { ... });
    }
  });
}

5.2 RBAC 权限设计

用户 ─── 多对多 ─── 角色 ─── 多对多 ─── 权限

前端三层权限

  1. 路由级:动态注册可访问路由
  2. 菜单级:根据权限渲染菜单
  3. 按钮级:自定义指令 v-permission="'user:delete'"
const permission = {
  mounted(el, binding) {
    const { value } = binding;
    const userPerms = useUserStore().perms;
    if (!userPerms.includes(value)) {
      el.parentNode?.removeChild(el);
    }
  },
};

5.3 多租户

模式 隔离 复杂度
独立数据库 完全
共享库独立 schema
共享库共享表(tenant_id)

前端要点:登录确定租户 + 请求带 X-Tenant-Id 头 + 子域名区分。


六、Node.js / BFF

6.1 事件循环(与浏览器差异)

Node.js Event Loop 6 个阶段:
timers → pending → idle → poll → check → close

与浏览器差异

  • Node 11.x+ 微任务穿插在每个阶段(和浏览器一致)
  • 多了 setImmediateprocess.nextTick
  • process.nextTick 优先级最高

6.2 Buffer / Stream

const buf = Buffer.from('hello');
console.log(buf.toString('hex'));

fs.createReadStream('big.txt').pipe(fs.createWriteStream('copy.txt'));

4 种流:Readable / Writable / Duplex / Transform

6.3 Express 中间件

const app = express();

app.use((req, res, next) => {
  console.log('日志');
  next();
});

app.get('/users', (req, res) => res.json([]));
app.listen(3000);

Koa 洋葱模型:请求 → mw1 前 → mw2 前 → 处理 → mw2 后 → mw1 后 → 响应

6.4 BFF(Backend For Frontend)

app.get('/api/dashboard', async (req, res) => {
  const [user, orders, stats] = await Promise.all([
    fetch('/user-service/info').then(r => r.json()),
    fetch('/order-service/recent').then(r => r.json()),
    fetch('/stat-service/today').then(r => r.json()),
  ]);
  res.json({ user, orders, stats });
});

6.5 npm 包发布

npm init
npm login
npm publish

npm version patch    # 1.0.0 → 1.0.1
npm version minor    # 1.0.0 → 1.1.0
npm version major    # 1.0.0 → 2.0.0

七、项目难点话术(STAR)

7.1 STAR 法则

  • S Situation:业务背景
  • T Task:你要解决什么
  • A Action:你做了什么(技术细节
  • R Result:结果(有数字

7.2 难点 1:多端架构统一

场景:云护项目要同时支持 Web 后台 + 小程序 + 公众号 H5
问题:三端代码大量重复
方案:抽象业务层(API、Store、工具函数)成 npm 包,UI 层用 uni-app + 条件编译适配差异
结果:跨端复用率 70%+,新功能开发效率提升 40%

7.3 难点 2:长列表性能优化

场景:监控页面要展示 1000+ 设备实时状态
问题:直接渲染浏览器卡死
方案:虚拟滚动(el-table-v2)+ requestAnimationFrame 节流 + Web Worker 处理数据
结果:FPS 从 25 提升到稳定 60,内存占用降低 60%

7.4 难点 3:首屏加载优化

场景:长慈系统首屏 4.2s 用户投诉
排查:Lighthouse + Performance 面板定位,发现 ECharts 全量引入 + 图表组件同步加载
方案:路由懒加载 + ECharts 按需引入 + Gzip + 关键 CSS 内联
结果:首屏 4.2s → 1.3s,Lighthouse 58 → 92

7.5 难点 4:WebRTC 多路视频卡顿

场景:云护值守端要同屏看 16 路 1080P 视频
问题:客户端 CPU 跑满
方案:协商时降低非焦点画面分辨率(动态码率)+ 启用硬件解码 + 滚动可视区外暂停解码
结果:CPU 占用降低 50%,16 路同屏稳定运行

7.6 难点 5:MQTT 长连接稳定性

场景:牛轻松 App 在弱网环境下 MQTT 频繁断连
方案:心跳间隔动态调整(弱网延长)+ 指数退避重连 + 断线期间消息本地暂存 + QoS 1
结果:弱网下指令到达率从 78% 提升到 99.5%


八、简历项目延伸 Q&A

8.1 通用追问 10 个角度

每个项目准备这 10 个角度:

  1. 业务背景:to B 还是 to C?多少用户?
  2. 你的角色:独立做的?团队几个人?
  3. 技术选型理由:为什么用 Vue3 不用 React?
  4. 架构设计:项目目录结构?模块划分?
  5. 难点:最大技术挑战?
  6. 数据:提升数据怎么测的?工具?
  7. 协作:和后端/UI 怎么协作?
  8. 上线:怎么发布?灰度?回滚?
  9. 复盘:如果重做你会怎么改?
  10. 延伸:最自豪的一行代码/设计?

8.2 高频问题

Q:为什么用 Vue3 不用 React?

三个角度:

  1. 团队成本:团队 Vue 经验更多,React Hooks 学习曲线对部分成员陡峭
  2. 生态匹配:项目用了大量 Element-Plus 组件,Vue 生态更顺
  3. 维护成本:Vue 双向绑定和模板语法对业务开发者更直观

高交互复杂应用(Figma 这种),React + 类型系统可能更适合。

Q:为什么用 uni-app 不用 Taro?

团队是 Vue 技术栈,uni-app 0 学习成本。Taro 虽然 React 写法更现代,但需要培训。
项目主要面向小程序和 App,uni-app 微信生态适配更成熟。

Q:WebRTC 你做到什么深度?

  • 业务层:完整对接信令、SDP 协商、ICE 流程
  • 优化层:处理过多路视频卡顿
  • 底层原理:理解 STUN/TURN/ICE
  • 不擅长:底层协议修改、自研信令服务器(团队有专人)

诚实地标记自己的边界。

Q:项目的并发上限?

前端:1000+ 表格行 + 10 路视频流 + 实时数据推送时仍能保持 60FPS
后端:单接口 QPS 在 2000 左右(团队压测报告)

Q:项目的代码量?

  • 我主导的项目代码量约 X 万行(cloc 统计)
  • X 个核心模块,X 个通用组件
  • 我个人贡献者占 70%+

8.3 5 个项目预设追问

云护值守

  • WebRTC 信令服务器是谁搭的?
  • 16 路视频同屏的硬件配置?
  • 损盗率 0.003% 怎么测出来的?

长慈系统

  • ECharts 按需引入加了哪些模块?
  • 一对一客服 WebSocket 怎么解决断线重连?
  • Lighthouse 92 分怎么测的?

牛轻松

  • MQTT QoS 用的几级?为什么?
  • iOS WebView 内存泄漏怎么定位的?
  • App 上架审核遇到过什么坑?

犀团

  • Vite 重构具体迁移了哪些 Webpack 配置?
  • SKU 五维规格怎么管理 UI 复杂度?
  • Web Worker 处理 Excel 用什么库?

通洗

  • Vue-cli 升级 Vite 怎么保证零中断?
  • 电子围栏多边形坐标怎么持久化?
  • 商家端模板复用怎么实现?

本篇完。下一篇:04-软实力篇

前端面试复习|软实力篇

涵盖:自我介绍 / 离职原因 / 期望薪资 / 反向提问 / AI 工具 / 简历优化 / 面试礼仪
复习时长建议:2~3 天


📑 目录

  1. 自我介绍三版本
  2. 离职原因话术
  3. 期望薪资谈判
  4. "你最大的缺点"
  5. 团队冲突处理
  6. 反向提问加分
  7. AI 编程工具加分项
  8. 面试前准备清单
  9. 面试当天注意事项
  10. Offer 谈判技巧

一、自我介绍三版本

1.1 30 秒版(开场)

您好,我是 XXX,X 年前端开发经验,主要技术栈是 Vue3 + TypeScript + Vite。最近一段在 X 公司,主要负责多端业务(Web + 小程序 + App)的开发与架构。期待加入贵司从事更有挑战的工作。

1.2 1 分钟版(标准)

您好,我是 XXX,X 年前端经验。

技术栈:精通 Vue3/Vue2 + TS + Vite,熟悉 uni-app 跨端开发、ECharts 数据可视化、WebSocket/MQTT 实时通信。

项目亮点:在云护项目中,独立负责三端架构,跨端复用率达 70%,并集成 WebRTC 解决了 16 路视频同屏卡顿问题;在长慈项目中,将首屏从 4.2s 优化到 1.3s。

个人特点:喜欢深挖原理(最近在啃 Vue3 源码)、关注 AI 编程趋势(Cursor 重度用户)。期待加入贵司。

1.3 3 分钟版(深聊)

在 1 分钟基础上加:

  • 1 个最有成就感的项目(用 STAR 详细讲)
  • 团队协作经历
  • 未来 3 年规划

3 年规划话术

短期 1 年:在贵司平台快速上手核心业务,承担 1-2 个完整项目
中期 2 年:往技术架构方向发展,能独立设计中型系统
长期 3 年:成为团队技术骨干,能带团队完成大型项目


二、离职原因话术

2.1 ✅ 正确版本(积极向上)

  • "上家公司业务方向调整,前端团队规模变小,可成长空间有限"
  • "想从业务前端往架构方向发展,贵司平台更适合"
  • "希望加入更有技术深度的团队,做更有挑战的事"
  • "上家公司主要做后台系统,想在 C 端产品上有更多积累"
  • "希望加入业务和技术双轮驱动的团队"

2.2 ❌ 避免

  • 抱怨领导/同事/加班
  • 钱少(即使是真的,1 面也不要说)
  • 上家公司不行(贬低前公司 = 贬低自己的选择)
  • "公司不重视前端" → 暴露负面情绪
  • "项目没意思" → 显得挑剔

2.3 不同情境的应对

情境 话术
公司倒闭/裁员 实话实说,强调主动找下家、行业内常见
跳槽频繁 提前准备解释,强调每次跳槽的成长曲线
空窗期长 解释做了什么(学习、自由职业、家庭原因)
主动离职 强调正向追求(成长、平台、方向)

三、期望薪资谈判

3.1 三步话术

  1. 先反问:贵司这个岗位的预算范围是多少?
  2. 如果对方坚持你先说:根据我的经验和市场情况,期望是 X-Y 区间,最终看 offer 综合(base、月数、股票、五险一金、福利)
  3. 抛锚:报价比目标高 20%,留出谈判空间

3.2 报价依据

  • 上一份薪资 +20~30%
  • 同岗位市场价(拉钩、Boss 直聘均值)
  • 自身核心竞争力溢价
  • 行业景气度(互联网寒冬要保守)

3.3 综合 Offer 评估

维度 权重 备注
Base 月薪 40% 最基础
月数(13/14/16) 15% 年终影响大
股票/期权 15% 上市公司硬通货
五险一金基数 10% 长期影响大
福利(餐补/通勤/医疗) 10% 隐性收入
调薪/晋升机制 10% 未来发展

3.4 谈判禁区

  • ❌ 不要在第一轮面试就谈钱
  • ❌ 不要给出"死"的数字("我必须 30K")
  • ❌ 不要拿 offer 互相比价(除非真有)
  • ❌ 不要威胁 HR("不到 X 我就走")

四、"你最大的缺点"

4.1 ✅ 正确策略

说一个真缺点,但不致命,并展示改进意识

我有时会过于追求代码完美,比如一个组件会反复重构 2-3 次。我意识到这会影响交付节奏,最近开始主动设定"够用就好"的边界,先交付再迭代。

4.2 备选回答

版本 A(执行偏完美)

写代码时容易陷入细节,会反复打磨一个组件直到自己满意。最近在学着平衡——MVP 优先,迭代优化。

版本 B(沟通偏含蓄)

面对意见冲突时容易先妥协,事后才发现自己的方案更合适。最近在练习用文档/数据来表达观点,让讨论更高效。

版本 C(视野偏技术)

之前过于关注技术实现,对业务理解不够深。最近主动参与产品评审,带着业务视角去做技术决策。

4.3 ❌ 避免

  • "我没缺点" → 自大
  • "我太追求完美了" → 套话/装
  • "我执行力太强容易加班" → 装
  • "我太热爱学习" → 假
  • 真致命缺点(沟通差、不会团队协作)

五、团队冲突处理

5.1 STAR 模板答法

S:上次我和后端在接口设计上有分歧——他想用 RESTful,我觉得 GraphQL 更适合复杂查询场景
T:需要在 1 周内对齐方案
A:我没强推自己的观点,而是写了一份对比文档(性能、学习成本、生态、迁移成本),约了一次评审会,邀请技术负责人一起决策
R:最终采纳了 RESTful(团队学习成本低 + 现有工具链支持),我也认可了这个决定。事后我发现合作关键不是"谁对谁错",而是"谁能让团队对齐"

5.2 关键原则

  1. 对事不对人:避免用"他怎么样",用"这个方案怎么样"
  2. 数据说话:拿出文档、benchmark、用户数据
  3. 承认对方合理性:先肯定对方观点中的合理部分
  4. 以团队目标为锚:不是为了赢,而是为了项目好

5.3 与不同角色冲突的应对

角色 常见冲突 应对要点
后端 接口设计 / 字段命名 提前对齐文档,用 OpenAPI
UI 还原度 / 交互细节 早期介入设计阶段
产品 需求变更 评审时锁定,变更走流程
测试 Bug 定级 / 边界情况 复现步骤共建,规则透明
老板 工期 / 优先级 提供选项 + 风险评估,让老板决策

六、反向提问(加分)

6.1 技术维度

  1. 团队的技术栈和最近一年的演进方向?
  2. 团队的代码 review 流程是怎样的?
  3. 团队怎么平衡技术债和业务交付?
  4. 团队最近一次技术升级 / 重构是什么?
  5. 团队的测试覆盖率要求?

6.2 团队维度

  1. 团队规模和构成?前后端比例?
  2. 团队 onboarding 流程?
  3. 团队氛围怎么样?加班情况?
  4. 团队最近一年人员流动如何?
  5. 团队的技术分享 / 学习机制?

6.3 岗位维度

  1. 这个岗位 3-6 个月希望我做出什么成果?
  2. 这个岗位最大的挑战是什么?
  3. 您觉得我哪些经历最匹配这个岗位?
  4. 团队对这个岗位的预期是什么?
  5. 入职后第一个项目是什么?

6.4 公司维度(HR 面)

  1. 公司的晋升机制是怎样的?
  2. 调薪频率和依据?
  3. 公司的福利体系?
  4. 公司未来 1-2 年的方向?
  5. 公司前端团队整体规模?

6.5 ❌ 避免的提问

  • "贵司加班吗?"(直接显得怕加班)
  • "工资是多少?"(应等到 HR 主动谈)
  • "面我的人是谁?"(面试官自己会介绍)
  • 任何能从官网搜到的问题

七、AI 编程工具加分项

7.1 工具对比

工具 强项 适合
Cursor IDE 集成、Agent 能力 日常开发
Claude Code 终端 / CLI、长上下文 重构、文档生成
Codex 命令行 一次性脚本
GitHub Copilot 行内补全 代码补全

7.2 实践方向

  1. 代码补全:日常 60% 代码 AI 生成,自己负责架构和审核
  2. 代码重构:选中复杂函数让 AI 提取 hook
  3. Bug 排查:把报错和上下文丢给 AI
  4. 单元测试:组件 + AI = 完整测试用例
  5. 技术调研:让 AI 对比方案 + 输出代码示例
  6. 文档生成:组件 props / 接口文档自动化

7.3 加分话术

我在日常工作中已经把 Cursor + Claude Code 深度集成到工作流。比如最近做的一个数据看板项目,我用 Claude Code 一次性生成了 8 个 ECharts 配置 + 对应的 TS 类型,省掉了大量重复劳动。但我始终保持代码 review 的习惯——AI 生成的代码我会逐行 review,确保符合团队规范和性能要求。我的理解是:AI 是杠杆,但杠杆的支点是工程师本身的技术判断力。

7.4 注意事项

  • 不要说"我所有代码都让 AI 写"(显得自己没价值)
  • 不要说"AI 还不够好我不用"(显得保守)
  • 强调 AI + 人 的协同模式
  • 准备一个具体案例(比如某个组件用 AI 生成后如何 review/调整)

八、面试前准备清单

8.1 简历准备

  • 简历最新版(PDF + Word 备用)
  • 5 个项目的 STAR 故事(每个 3-5 个难点)
  • 自我介绍 30s/1min/3min 三版本
  • 期望薪资 + 谈判区间
  • 离职原因话术

8.2 公司调研

  • 公司主营业务、近期新闻
  • 公司产品(亲自体验)
  • 公司技术博客 / GitHub
  • 招聘岗位 JD 逐条对应
  • 面试官背景(脉脉、领英)

8.3 技术准备

  • 复习手写题(10 道核心)
  • 算法题保持手感(每天 1-2 道)
  • 项目代码自己再过一遍
  • 面试当天的高频题

8.4 装备准备

  • 西装/正装 / 商务休闲(按公司氛围)
  • 简历纸质版 3 份
  • 笔 + 笔记本
  • 提前到达,预留交通时间
  • 手机充满电
  • 喝水、上厕所

九、面试当天注意事项

9.1 时间

  • 提前 15 分钟到,不要太早(半小时显得焦虑)
  • 视频面试提前 10 分钟开测试
  • 面试中查看时间不要太频繁

9.2 形象

  • 整洁干净 > 多贵
  • 互联网公司:商务休闲(衬衫 + 西裤即可)
  • 国企/银行:正装西装
  • 视频面试:注意背景、灯光、收音

9.3 沟通

  • 听完问题再答(不要打断)
  • 不会就承认"这块我不熟,但我理解是..."(思考过程很重要)
  • 答题用"总分总"结构
  • 答不上来时承认 + 反问 ("这块您能给我点提示吗?")

9.4 心态

  • 面试是双向选择
  • 失败不丢人,是面试官也在被你评估
  • 准备好"今天可能会失败"的心理预期
  • 一面后立刻复盘,准备下一面

9.5 视频面试加分项

  • 提前测试网络、麦克风、摄像头
  • 用耳机(避免回音)
  • 屏幕分享前清理桌面
  • 准备一个干净的物理背景
  • 笔记本电源插好(防断电)

十、Offer 谈判技巧

10.1 谈判时机

  • ✅ HR 主动给出口头 offer 后
  • ❌ 不要在面试中谈
  • ❌ 不要在 offer letter 发出后才提

10.2 谈判筹码

  • 多家 offer 在手(最有力)
  • 当前公司即将调薪(加薪通知截图)
  • 关键技能稀缺度(你刚好补这个空缺)
  • 行业平均水平数据

10.3 谈判话术模板

软性争取

非常感谢贵司的 offer!整体方案我很满意,但 base 部分能否再争取一下?我看了一下市场同岗位的水平,希望能在 X-Y 之间。其他方面我都没问题。

有竞争 offer

我目前还有另一家公司给到 X 的 offer,但贵司的业务方向和团队我更感兴趣。如果能在薪资上对齐或接近,我会优先选择贵司。

正面拒绝

很抱歉,目前的 offer 比我预期低了一些,需要再考虑一下。如果还有调整空间,期待您的回复。

10.4 谈判禁忌

  • ❌ 不要威胁("不到 X 我就走")
  • ❌ 不要假装有 offer(HR 可能会查)
  • ❌ 不要谈到一半态度变差
  • ❌ 不要漫天要价(开出超出市场太多的数字)
  • ❌ 接了 offer 又反悔(行业很小,名声会传)

10.5 综合权衡(不只看薪资)

优先级 考虑维度
团队/老板(直接决定 1-2 年体验)
业务前景(决定 2-3 年发展)
技术成长(决定 3-5 年深度)
薪资福利
通勤/工作时间
公司大小/品牌

🎯 终极心态建议

  1. 面试是技能,是可以训练的:不要害怕,多面才能进步
  2. 失败 ≠ 你不行:可能是 JD 不匹配、面试官风格、或者纯运气
  3. 保持谦逊:对每个面试官都尊重,行业很小
  4. 保持自信:不卑不亢,你在选公司公司也在选你
  5. 复盘比刷题更重要:每次面完写复盘,哪里没答好下次准备

📦 附:高频问题速查

问题 重点
你最近的项目 准备 1 个详细 STAR
项目难点 至少 3 个技术深度点
为什么离职 正向、不抱怨
期望薪资 反问 → 区间 → 综合
你的优点 真实 + 与岗位匹配
你的缺点 不致命 + 改进
5 年规划 有方向,不要太具体
你的不足 在学什么补
业余时间做什么 体现持续学习
怎么了解技术 体现信息渠道

本系列复习文档完。
配套:05-自测题库(100 道高频题)

前端面试自测题库(100 道)

适合:3~5 年 Vue 全栈前端工程师
使用方法:先看题不看答案,自己答,然后对照参考答案
评分:80 分以上可以投大厂 / 60-80 中厂 / 60 以下补漏


📑 题目分布

模块 题数
JavaScript 核心 20
TypeScript 5
Vue3 / Vue2 20
Vite / 工程化 8
CSS 7
HTTP / 浏览器 12
性能优化 8
实时通信 / 可视化 / 地图 8
项目 + 软实力 12
合计 100

第一部分:JavaScript 核心(20 题)

1. 说说 JS 的数据类型

<details>
<summary>参考答案</summary>

8 种:Number / String / Boolean / null / undefined / Symbol / BigInt / Object

  • 7 种基本类型 + 1 种引用类型
  • Symbol 用于唯一标识,BigInt 用于大整数
  • typeof null === 'object' 是历史遗留 Bug
    </details>

2. typeof 和 instanceof 的区别

<details>
<summary>参考答案</summary>

  • typeof:判断基本类型,对引用类型只能区分 function 和 object
  • instanceof:判断对象是否在某构造函数原型链上
  • 最准确:Object.prototype.toString.call(obj) 返回 [object Type]
    </details>

3. 闭包是什么?有什么用?有什么坑?

<details>
<summary>参考答案</summary>

是什么:函数 + 它能访问到的外部变量

用途:模块封装、防抖节流、柯里化、私有变量

  • 内存泄漏(DOM 引用未释放)
  • 循环 var 陷阱
    </details>

4. 解释 this 的 5 种指向规则

<details>
<summary>参考答案</summary>

优先级从高到低:

  1. new → 新对象
  2. bind/call/apply → 第一个参数
  3. obj.fn() → obj
  4. 普通 fn() → 严格模式 undefined / 非严格 window
  5. 箭头函数 → 没有自己的 this,用外层作用域
    </details>

5. 事件循环输出顺序

console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => {
  console.log(3);
  setTimeout(() => console.log(4));
});
console.log(5);

<details>
<summary>参考答案</summary>

1 → 5 → 3 → 2 → 4

执行流程:

  • 同步:1, 5
  • 微任务:3(执行时往宏队列 push 4)
  • 宏任务:2
  • 宏任务:4
    </details>

6. 原型链是什么?

<details>
<summary>参考答案</summary>

每个对象都有 __proto__,指向构造函数的 prototype
访问属性时,沿 __proto__ 一路向上查找,直到 Object.prototype.__proto__ === null
</details>

7. 深浅拷贝的实现

<details>
<summary>参考答案</summary>

Object.assign、扩展运算符

  • JSON.parse(JSON.stringify()) (有缺陷)
  • structuredClone() (现代浏览器)
  • 手写递归 + WeakMap (能处理循环引用)
    </details>

8. Promise 三状态?为什么不可逆?

<details>
<summary>参考答案</summary>

pending → fulfilled / rejected

不可逆是为了保证 Promise 的可预测性——一旦确定结果,所有 then 回调都能拿到一致的值。
</details>

9. 手写 Promise.all

<details>
<summary>参考答案</summary>

Promise.myAll = (promises) => new Promise((resolve, reject) => {
  const result = [];
  let count = 0;
  promises.forEach((p, i) => {
    Promise.resolve(p).then(val => {
      result[i] = val;
      if (++count === promises.length) resolve(result);
    }, reject);
  });
});

</details>

10. async/await 本质?

<details>
<summary>参考答案</summary>

Generator + 自动执行器的语法糖。
await 后面的代码相当于放进 .then 回调中。
</details>

11. 手写防抖和节流

<details>
<summary>参考答案</summary>

const debounce = (fn, delay) => {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
};

const throttle = (fn, delay) => {
  let last = 0;
  return function(...args) {
    const now = Date.now();
    if (now - last >= delay) {
      fn.apply(this, args);
      last = now;
    }
  };
};

</details>

12. 手写 new

<details>
<summary>参考答案</summary>

function myNew(C, ...args) {
  const obj = Object.create(C.prototype);
  const result = C.apply(obj, args);
  return result instanceof Object ? result : obj;
}

</details>

13. 手写 call / apply / bind

<details>
<summary>参考答案</summary>

Function.prototype.myCall = function(ctx, ...args) {
  ctx = ctx || window;
  const fn = Symbol('fn');
  ctx[fn] = this;
  const r = ctx[fn](...args);
  delete ctx[fn];
  return r;
};

Function.prototype.myBind = function(ctx, ...args) {
  const fn = this;
  return function F(...newArgs) {
    if (this instanceof F) return new fn(...args, ...newArgs);
    return fn.apply(ctx, [...args, ...newArgs]);
  };
};

</details>

14. 数组去重 5 种方式

<details>
<summary>参考答案</summary>

[...new Set(arr)]
arr.filter((v, i) => arr.indexOf(v) === i)
arr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], [])
// Map 对象去重 / 双层循环

</details>

15. 手写数组扁平 flat

<details>
<summary>参考答案</summary>

function flat(arr, depth = 1) {
  return depth > 0
    ? arr.reduce((a, b) => a.concat(Array.isArray(b) ? flat(b, depth-1) : b), [])
    : arr.slice();
}

</details>

16. let、const、var 区别

<details>
<summary>参考答案</summary>

var let const
作用域 函数
提升 ✅(值为 undefined) ✅(暂时性死区) ✅(暂时性死区)
重复声明
重新赋值

</details>

17. == 和 === 的区别

<details>
<summary>参考答案</summary>

  • ==:会进行类型转换(容易踩坑)
  • ===:严格相等,类型不同直接 false

推荐永远用 ===,除了判 null/undefined:x == null
</details>

18. 解释事件委托

<details>
<summary>参考答案</summary>

利用事件冒泡,把事件监听器绑在父元素上,通过 e.target 判断实际触发的元素。

好处:少绑定监听器,动态添加的子元素也能响应
</details>

19. 手写 EventEmitter

<details>
<summary>参考答案</summary>

class EventEmitter {
  events = {};
  on(e, fn) { (this.events[e] ||= []).push(fn); }
  off(e, fn) { this.events[e] = this.events[e]?.filter(f => f !== fn); }
  emit(e, ...args) { this.events[e]?.forEach(fn => fn(...args)); }
  once(e, fn) {
    const w = (...args) => { fn(...args); this.off(e, w); };
    this.on(e, w);
  }
}

</details>

20. 内存泄漏的常见场景

<details>
<summary>参考答案</summary>

  1. 全局变量
  2. 未清理的定时器/事件监听
  3. 闭包持有大对象
  4. DOM 引用(变量持有已删除 DOM)
  5. console.log 持有大对象
    </details>

第二部分:TypeScript(5 题)

21. interface 和 type 的区别

<details>
<summary>参考答案</summary>

  • interface 可同名合并,type 不行
  • type 支持联合/交叉,interface 不擅长
  • 经验:对象用 interface,组合类型用 type
    </details>

22. 写出 5 个常用工具类型

<details>
<summary>参考答案</summary>

Partial<T>     // 所有可选
Required<T>    // 所有必填
Pick<T, K>     // 挑选
Omit<T, K>     // 排除
Record<K, V>   // 键值对

</details>

23. 泛型怎么写?

<details>
<summary>参考答案</summary>

function identity<T>(x: T): T { return x; }
function getLen<T extends { length: number }>(x: T) { return x.length; }
interface ApiRes<T> { code: number; data: T; }

</details>

24. 手写 Pick

<details>
<summary>参考答案</summary>

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

</details>

25. 联合类型 vs 交叉类型

<details>
<summary>参考答案</summary>

  • 联合 A | B:要么 A 要么 B
  • 交叉 A & B:同时是 A 和 B(合并属性)
    </details>

第三部分:Vue3 / Vue2(20 题)

26. Vue3 响应式原理

<details>
<summary>参考答案</summary>

Proxy 代替 Object.defineProperty,可拦截 13 种操作,支持新增属性、数组、嵌套对象懒代理。

new Proxy(obj, {
  get(t, k) { track(t, k); return Reflect.get(t, k); },
  set(t, k, v) { Reflect.set(t, k, v); trigger(t, k); return true; }
});

</details>

27. Vue2 响应式有什么缺陷?

<details>
<summary>参考答案</summary>

  1. 无法监听新增/删除属性(要 Vue.set)
  2. 无法监听数组下标修改和长度
  3. 需要递归遍历,性能开销大
    </details>

28. ref vs reactive

<details>
<summary>参考答案</summary>

ref reactive
数据类型 任意(推荐基本类型) 对象/数组
访问 .value 直接访问
解构 保持响应 失去响应(要 toRefs)

</details>

29. computed 和 watch 区别

<details>
<summary>参考答案</summary>

  • computed:有返回值、有缓存、依赖自动收集
  • watch:无返回值、无缓存、显式声明依赖、知道新旧值

场景:computed 用于派生数据,watch 用于副作用
</details>

30. nextTick 是什么?

<details>
<summary>参考答案</summary>

Vue 更新 DOM 是异步批量的。nextTick 把回调推到当前 tick 的微任务队列尾部,确保 DOM 更新完后再执行。
</details>

31. v-if 和 v-show 区别

<details>
<summary>参考答案</summary>

  • v-if:DOM 增删,初始开销低,切换开销高
  • v-show:CSS display,初始开销高,切换开销低

频繁切换用 v-show,条件渲染用 v-if
</details>

32. Vue3 的 diff 算法做了哪些优化

<details>
<summary>参考答案</summary>

  1. 静态提升(hoistStatic)
  2. PatchFlag 标记动态节点
  3. Block 块缩小 diff 范围
  4. 最长递增子序列减少 DOM 移动
    </details>

33. Composition API 优势

<details>
<summary>参考答案</summary>

  1. 逻辑复用(自定义 hook)
  2. 类型推导更好
  3. 相关逻辑聚合
  4. Tree-shaking 更友好
    </details>

34. v-for 为什么要 key

<details>
<summary>参考答案</summary>

帮助 diff 算法识别哪些节点改变了,避免不必要的 DOM 操作。

禁忌:用 index 作 key(删除/插入会出错)
</details>

35. Vue 组件通信方式

<details>
<summary>参考答案</summary>

  1. props / emit(父子)
  2. provide / inject(跨层级)
  3. v-model(双向)
  4. ref(父访问子)
  5. EventBus(已不推荐)
  6. Pinia / Vuex(全局)
    </details>

36. 自定义 Hook 怎么写

<details>
<summary>参考答案</summary>

export function useCounter(initial = 0) {
  const count = ref(initial);
  const inc = () => count.value++;
  return { count, inc };
}

</details>

37. Pinia 和 Vuex 区别

<details>
<summary>参考答案</summary>

  • Pinia 取消 mutation,直接改 state
  • Pinia TS 体验完美
  • Pinia 多 store 平级,Vuex 嵌套 modules
  • Pinia ~1KB,Vuex 较大
    </details>

38. Vue Router 守卫种类

<details>
<summary>参考答案</summary>

  • 全局:beforeEach / beforeResolve / afterEach
  • 路由级:beforeEnter
  • 组件级:beforeRouteEnter / Update / Leave
    </details>

39. hash vs history 模式

<details>
<summary>参考答案</summary>

  • hash:URL 带 #,原理 onhashchange,无需服务器配置
  • history:原理 pushState,URL 干净,但需服务器配置 404 转 index.html
    </details>

40. Vue3 新特性有哪些

<details>
<summary>参考答案</summary>

  1. Composition API
  2. Teleport(传送)
  3. Suspense(异步加载)
  4. Fragment(多根节点)
  5. 多 v-model
  6. emits 选项
  7. 性能提升 30-50%
    </details>

41. 解释 Teleport

<details>
<summary>参考答案</summary>

把组件内容渲染到 DOM 的指定位置(如 body)。常用于 Modal、Toast 跳出当前 DOM 层级。

<Teleport to="body">
  <div class="modal">...</div>
</Teleport>

</details>

42. 异步组件怎么写

<details>
<summary>参考答案</summary>

import { defineAsyncComponent } from 'vue';

const AsyncComp = defineAsyncComponent(() => import('./Big.vue'));

// 高级版
const AsyncComp = defineAsyncComponent({
  loader: () => import('./Big.vue'),
  loadingComponent: Loading,
  errorComponent: Error,
  delay: 200,
  timeout: 3000,
});

</details>

43. Vue3 setup 中的 props 怎么解构?

<details>
<summary>参考答案</summary>

直接解构会失去响应性,要用 toRefs

const props = defineProps(['name']);
const { name } = toRefs(props);

或 Vue 3.5+ 用 reactive props 解构(实验)
</details>

44. Vue3 的 ref 自动解包规则

<details>
<summary>参考答案</summary>

  • 模板中:自动解包(不用 .value)
  • reactive 内部:自动解包
  • 普通 JS 中:需要 .value
  • ref 数组中:不解包
    </details>

45. 项目里你封装过哪些通用组件

<details>
<summary>参考答案</summary>

至少能讲 3 个:

  • BaseTable(基于 ElTable 二次封装,配置驱动 + 插槽)
  • BaseForm(Schema 驱动表单)
  • BaseDialog(统一 Modal 风格 + Promise 化调用)
  • BaseChart(ECharts 封装 + 自动 resize)
  • BasePermission(权限按钮指令 + 组件)
    </details>

第四部分:Vite / 工程化(8 题)

46. Vite 为什么比 Webpack 快

<details>
<summary>参考答案</summary>

  1. 利用浏览器原生 ESM,不打包直接服务
  2. 第三方依赖用 esbuild 预构建(Go 写的)
  3. HMR 精准更新
  4. 生产用 Rollup
    </details>

47. Vite 中怎么配 alias 和 proxy

<details>
<summary>参考答案</summary>

export default defineConfig({
  resolve: { alias: { '@': path.resolve(__dirname, 'src') } },
  server: {
    proxy: {
      '/api': { target: 'http://backend.com', changeOrigin: true }
    }
  }
});

</details>

48. 什么是 Tree Shaking

<details>
<summary>参考答案</summary>

打包时删除未使用代码,要求:

  • 用 ESM(import/export)
  • 设置 sideEffects: false
  • 生产环境 mode: 'production'
    </details>

49. Webpack 中 loader 和 plugin 区别

<details>
<summary>参考答案</summary>

  • loader:处理文件转换(如 css-loader 把 CSS 变成 JS 模块)
  • plugin:扩展构建流程(如 HtmlWebpackPlugin 生成 HTML)
    </details>

50. ESLint + Prettier 怎么配合

<details>
<summary>参考答案</summary>

  • ESLint:检查代码错误(语法、规范)
  • Prettier:自动格式化代码
  • 配合:用 eslint-config-prettier 关闭 ESLint 中和 Prettier 冲突的规则
  • 提交时:husky + lint-staged 检查暂存区
    </details>

51. Git rebase 和 merge 区别

<details>
<summary>参考答案</summary>

  • merge:保留所有分支历史,产生 merge commit
  • rebase:把分支基础变到目标分支,历史是直线
  • 团队协作建议:feature → main 用 rebase 保持干净,公共分支别 rebase
    </details>

52. CI/CD 流程

<details>
<summary>参考答案</summary>

代码提交 → 触发 CI → Lint → Test → Build → 部署

工具:GitHub Actions / GitLab CI / Jenkins
</details>

53. Monorepo 用过哪些工具

<details>
<summary>参考答案</summary>

  • pnpm workspace(轻量、快)
  • Turborepo(任务编排好)
  • Nx(大型项目)
  • Lerna(老牌)
    </details>

第五部分:CSS(7 题)

54. 盒模型有几种

<details>
<summary>参考答案</summary>

  • content-box(默认):width 不含 padding/border
  • border-box:width 包含 padding/border

* { box-sizing: border-box; } 是开发常用配置
</details>

55. BFC 是什么?怎么触发?

<details>
<summary>参考答案</summary>

块级格式化上下文,独立的渲染区域。

触发:overflow ≠ visible / display: flex/grid/inline-block / position: absolute/fixed / float ≠ none

用途:清浮动、防 margin 重叠、自适应两栏
</details>

56. Flex 布局核心属性

<details>
<summary>参考答案</summary>

容器:

  • display: flex
  • flex-direction
  • justify-content(主轴)
  • align-items(交叉轴)
  • flex-wrap

子元素:

  • flex(grow shrink basis 缩写)
  • align-self
    </details>

57. 写出 5 种垂直居中

<details>
<summary>参考答案</summary>

/* 1 */ display: flex; align-items: center;
/* 2 */ display: grid; place-items: center;
/* 3 */ position: absolute; top: 50%; transform: translateY(-50%);
/* 4 */ position: absolute; inset: 0; margin: auto;
/* 5 */ line-height (仅文字)

</details>

58. 移动端适配方案

<details>
<summary>参考答案</summary>

  • rem:根据 html 字体大小动态计算
  • vw/vh:视口百分比
  • postcss-px-to-viewport:px 自动转 vw
  • 1px 问题:transform: scale(0.5)
    </details>

59. 重排和重绘

<details>
<summary>参考答案</summary>

  • 重排(Reflow):几何属性变化触发,开销大
  • 重绘(Repaint):颜色等非几何变化,开销小

优化:用 transform/opacity 触发 GPU 加速避免重排
</details>

60. CSS 选择器优先级

<details>
<summary>参考答案</summary>

!important > 内联 > id > class/属性/伪类 > 标签 > 通配符

权重:1000 / 100 / 10 / 1 / 0
</details>


第六部分:HTTP / 浏览器(12 题)

61. 输入 URL 到页面渲染过程

<details>
<summary>参考答案</summary>

  1. URL 解析
  2. DNS 查询
  3. TCP 三次握手
  4. TLS 握手
  5. 发 HTTP 请求
  6. 服务器返回 HTML
  7. 解析 DOM 和 CSSOM
  8. 构建 Render Tree
  9. Layout、Paint、Composite
    </details>

62. HTTP 缓存机制

<details>
<summary>参考答案</summary>

  • 强缓存:Cache-Control / Expires,命中返回 200 (from cache)
  • 协商缓存:ETag / Last-Modified,命中返回 304

强缓存失败 → 走协商缓存 → 协商失败 → 重新请求
</details>

63. 跨域解决方案

<details>
<summary>参考答案</summary>

  1. CORS(推荐):后端加响应头
  2. 代理(Vite proxy / Nginx)
  3. JSONP(已淘汰)
  4. postMessage(iframe)
    </details>

64. HTTPS 加密原理

<details>
<summary>参考答案</summary>

非对称加密协商对称密钥,后续用对称密钥通信。

握手:

  1. Client Hello
  2. Server Hello + 证书
  3. 客户端验证证书,生成 pre-master,公钥加密
  4. 双方生成对称密钥
  5. 加密通信
    </details>

65. HTTP/1.1 vs HTTP/2 vs HTTP/3

<details>
<summary>参考答案</summary>

  • 1.1:文本传输,无多路复用
  • 2:二进制,多路复用,HPACK 头压缩,但 TCP 队头阻塞仍存在
  • 3:基于 QUIC(UDP),彻底解决队头阻塞
    </details>

66. TCP 三次握手为什么不能两次

<details>
<summary>参考答案</summary>

防止历史连接干扰新连接。如果只两次握手,历史延迟到达的 SYN 会让服务端误开新连接。
</details>

67. cookie / localStorage / sessionStorage / IndexedDB 区别

<details>
<summary>参考答案</summary>

cookie localStorage sessionStorage IndexedDB
容量 4KB 5MB 5MB 无限
生命 可设置 永久 标签页 永久
随请求

</details>

68. 什么是同源策略

<details>
<summary>参考答案</summary>

协议 + 域名 + 端口 三者都相同才同源。

限制:跨域请求被拦截、Cookie/LocalStorage 不能跨域读、DOM 不能跨域操作
</details>

69. 浏览器多进程架构

<details>
<summary>参考答案</summary>

  • 主进程(浏览器 UI)
  • 网络进程
  • GPU 进程
  • 渲染进程(每 Tab 一个,沙箱)
  • 插件进程
    </details>

70. V8 的垃圾回收

<details>
<summary>参考答案</summary>

分代回收:

  • 新生代:Scavenge 复制算法(处理短命对象)
  • 老生代:Mark-Sweep + Mark-Compact(标记清除 + 整理)
    </details>

71. 解释 Service Worker

<details>
<summary>参考答案</summary>

独立运行的脚本,能拦截网络请求,实现离线缓存、推送通知。

是 PWA 的核心。
</details>

72. 什么是 CORS 预检请求

<details>
<summary>参考答案</summary>

复杂请求(如 PUT、自定义 header)会先发 OPTIONS 请求询问服务器是否允许,服务器返回允许的方法、头、域,然后才发真正的请求。
</details>


第七部分:性能优化(8 题)

73. 首屏优化怎么做

<details>
<summary>参考答案</summary>

  • 请求:DNS 预解析、CDN、HTTP/2、Gzip、缓存
  • 加载:路由懒加载、组件异步、图片懒加载、骨架屏
  • 渲染:SSR、关键 CSS 内联、虚拟滚动
  • 构建:Tree Shaking、代码分割、按需引入
    </details>

74. 怎么优化长列表

<details>
<summary>参考答案</summary>

  • 虚拟滚动(只渲染可视区)
  • 分页加载
  • 时间分片渲染(requestIdleCallback)
  • 用 keep-alive 缓存
    </details>

75. Web Vitals 指标有哪些

<details>
<summary>参考答案</summary>

指标 目标
FCP < 1.8s
LCP < 2.5s
FID/INP < 100ms
CLS < 0.1
TTFB < 600ms

</details>

76. 图片优化方法

<details>
<summary>参考答案</summary>

  • WebP/AVIF 格式
  • loading="lazy"
  • srcset 响应式
  • 雪碧图(小图标)
  • iconfont/SVG 替代位图
  • tinypng 压缩
    </details>

77. JS 加载优化(defer/async)

<details>
<summary>参考答案</summary>

  • 默认:阻塞解析
  • async:异步下载,下载完立即执行(阻塞)
  • defer:异步下载,DOM 解析完后执行(不阻塞)

推荐 defer
</details>

78. 怎么定位前端性能问题

<details>
<summary>参考答案</summary>

  • Lighthouse:综合评分
  • Performance 面板:CPU 火焰图
  • Memory 面板:内存泄漏
  • Network 面板:请求瀑布
  • 真实用户监控:Sentry / 阿里 ARMS
    </details>

79. requestAnimationFrame 和 setTimeout 区别

<details>
<summary>参考答案</summary>

  • raf:跟随浏览器刷新率(60fps),动画专用
  • setTimeout:固定时间,可能丢帧或过密

动画用 raf,定时用 setTimeout
</details>

80. 怎么实现虚拟滚动

<details>
<summary>参考答案</summary>

  1. 占位元素撑满总高度
  2. 根据 scrollTop 计算 startIndex 和 endIndex
  3. 只渲染可视区元素 + buffer
  4. 用 transform 偏移到正确位置
const visibleCount = Math.ceil(containerHeight / itemHeight);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = startIndex + visibleCount + buffer;

</details>


第八部分:实时通信 / 可视化 / 地图(8 题)

81. WebSocket 怎么实现心跳和重连

<details>
<summary>参考答案</summary>

class WS {
  connect() {
    this.ws = new WebSocket(url);
    this.ws.onopen = () => this.startHeart();
    this.ws.onclose = () => this.reconnect();
  }
  startHeart() {
    this.timer = setInterval(() => this.ws.send('ping'), 30000);
  }
  reconnect() {
    clearInterval(this.timer);
    const delay = Math.min(1000 * 2 ** this.times, 30000);
    setTimeout(() => { this.times++; this.connect(); }, delay);
  }
}

</details>

82. WebRTC 三大核心 API

<details>
<summary>参考答案</summary>

  • getUserMedia:获取媒体流
  • RTCPeerConnection:建立 P2P 连接
  • RTCDataChannel:任意数据通道
    </details>

83. WebRTC 信令流程

<details>
<summary>参考答案</summary>

A → 创建 Offer → 信令服务器 → B
B → 创建 Answer → 信令服务器 → A
互换 ICE Candidate → P2P 通道建立

</details>

84. MQTT 为什么适合 IoT

<details>
<summary>参考答案</summary>

  • 极轻量(最小 2 字节头)
  • 发布订阅模型
  • QoS 0/1/2 三级可靠性
  • 遗嘱消息(设备掉线通知)
    </details>

85. ECharts 大数据怎么优化

<details>
<summary>参考答案</summary>

  • appendData 增量加载
  • dataZoom 视窗滚动
  • 按需引入
  • canvas 渲染器(SVG 适合静态/打印)
  • progressive 渐进渲染
    </details>

86. ECharts 怎么按需引入

<details>
<summary>参考答案</summary>

import * as echarts from 'echarts/core';
import { BarChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([BarChart, GridComponent, CanvasRenderer]);

体积从 1MB 降到 200KB。
</details>

87. 什么是电子围栏?怎么实现?

<details>
<summary>参考答案</summary>

地图上画虚拟圈,判断点是否在范围内。

// 商家绘制
new AMap.MouseTool(map).polygon();

// 判定(射线法)
AMap.GeometryUtil.isPointInRing([lng, lat], polygonPath);

应用:外卖配送范围、共享单车运营区、上门服务区域
</details>

88. 高德地图坐标系坑

<details>
<summary>参考答案</summary>

  • WGS84:GPS 原始
  • GCJ02:高德、腾讯(火星坐标)
  • BD09:百度

GPS 坐标要先转 GCJ02 再画到高德地图,否则偏移几百米。
</details>


第九部分:项目 + 软实力(12 题)

89. 介绍一下你最近的项目

<details>
<summary>参考答案要点</summary>

按 STAR 结构:

  • Situation:业务背景(什么公司、什么产品、什么用户)
  • Task:你的角色和目标
  • Action:技术选型、架构、难点解决
  • Result:上线效果、用户数、性能指标

结尾:我从中学到了什么
</details>

90. 项目里最大的难点

<details>
<summary>参考答案要点</summary>

挑一个有技术含量的,避免说"业务复杂"。

模板:

业务上要解决 X 问题 → 技术挑战是 Y → 我用 Z 方案 → 结果是 N

举例:WebRTC 多路视频卡顿、MQTT 弱网重连、虚拟滚动、首屏优化
</details>

91. 为什么从上家公司离职

<details>
<summary>参考答案要点</summary>

✅ 正向:

  • 想往架构方向发展
  • 寻求更有挑战的工作
  • 业务方向调整

❌ 避免:

  • 抱怨领导/同事
  • 钱少(1 面别说)
  • 上家公司不行
    </details>

92. 你最大的优点

<details>
<summary>参考答案要点</summary>

挑 1-2 个 + 案例佐证:

  • 学习能力(举例:3 个月从零学 WebRTC)
  • 解决问题能力(举例:定位 iOS 内存泄漏)
  • 沟通协作(举例:拉齐前后端方案分歧)

不要泛泛而谈"有责任心、勤奋"
</details>

93. 你最大的缺点

<details>
<summary>参考答案要点</summary>

真缺点 + 不致命 + 改进意识:

我有时过于追求代码完美,会反复重构组件。最近开始设定"够用就好"的边界,先交付再迭代。
</details>

94. 你期望的薪资

<details>
<summary>参考答案要点</summary>

三步:

  1. 反问预算
  2. 给区间(高 20%)
  3. 综合考虑(base + 月数 + 福利)

不要给死数字,不要威胁
</details>

94. 5 年规划

<details>
<summary>参考答案要点</summary>

有方向但不要太具体:

1-2 年深耕业务,能独立负责中型项目
3 年成为团队技术骨干
5 年朝架构师方向发展,能从 0 到 1 设计大型系统
</details>

96. 怎么了解新技术

<details>
<summary>参考答案要点</summary>

体现持续学习:

  • 关注 Vue/React/TypeScript 官方更新
  • 订阅前端早早聊、奇舞团等公众号
  • 看技术博客(阮一峰、张鑫旭、ruan-yifeng)
  • 看源码(Vue3、Vite)
  • AI 工具(Claude/Cursor)辅助学习
    </details>

97. 你最自豪的代码

<details>
<summary>参考答案要点</summary>

挑一个你能讲半小时的:

  • 一个解决了团队痛点的工具
  • 一个性能从 X 提升到 Y 的优化
  • 一个被多个项目复用的组件库

讲清楚:背景 → 方案 → 实现 → 影响
</details>

98. 你和同事有过冲突吗?怎么解决

<details>
<summary>参考答案要点</summary>

STAR:

  • S:具体冲突情况
  • T:要达成什么目标
  • A:怎么处理(对事不对人 + 数据说话 + 找共同目标
  • R:结果

避免:说同事不好、说自己永远对
</details>

99. 你怎么看 AI 编程

<details>
<summary>参考答案要点</summary>

AI 是杠杆,但杠杆的支点是工程师本身的技术判断力。

我把 Cursor + Claude Code 集成到工作流——日常 60% 代码 AI 生成,但每行都自己 review。AI 解决重复劳动,工程师专注架构和决策。

这是趋势,不主动拥抱会被淘汰,但也不能完全依赖。
</details>

100. 你有什么要问我的

<details>
<summary>参考答案要点</summary>

至少准备 3 个:

  1. 团队最近一年的技术演进方向?
  2. 这个岗位 3-6 个月的预期?
  3. 您觉得我哪些经历最匹配?

绝对不要说"没有"
</details>


🎯 自测评分

答对题数 评估
90+ 大厂候选,可挑战 P6+
70-90 中厂稳过,大厂有戏
50-70 还需补漏,先扎实基础
<50 系统复习,回看专题文档

📌 复习方法

  1. 第一遍:盖住答案做,记下不会的
  2. 第二遍:只做不会的,对照专题文档查漏
  3. 第三遍:模拟面试,限时 5 分钟答 3 题
  4. 每周一次:随机抽 20 题快速过

📦 配套资料

  • 01-基础篇:JS/TS/CSS/HTTP/浏览器/算法/手写题
  • 02-框架篇:Vue3/Pinia/Vite/uni-app/微前端/SSR/测试
  • 03-项目篇:实时通信/可视化/地图/工程化/项目话术
  • 04-软实力篇:自我介绍/离职/薪资/反向提问/AI 工具
  • 05-自测题库(本篇)

祝你早日拿到心仪 offer!💪

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容