文章目录

腾讯 PCG 前端暑期一面整理笔记(三十三)

2. 项目为什么选择 Vite,辨析 Vite 与 Webpack,Vite 热更新原理是什么

答案

这题其实在同时考 3 件事:

  1. 你是否理解为什么项目选 Vite
  2. 你是否理解 Vite 和 Webpack 的差异
  3. 你是否理解 Vite 的 HMR 原理

推荐回答:

如果是现代前端项目,选择 Vite 的核心原因通常有:

  1. 启动更快
  2. 热更新更快
  3. 配置更轻
  4. 对 Vue / React / TS 友好
  5. 新项目开发体验更好

面试表达:

我们选择 Vite,主要是因为它在开发阶段基于原生 ESM 按需加载模块,不需要像传统 bundler 那样先全量打包,所以冷启动和热更新都更快,配置也更轻,更适合现代前端项目。

可以从 5 个维度回答:

1. 开发阶段策略不同

  1. Webpack 通常是先构建依赖图并打包
  2. Vite 开发阶段基于浏览器原生 ESM,按需提供模块

2. 启动速度不同

  1. Webpack 项目越大,启动越容易变慢
  2. Vite 启动通常更快

3. 热更新方式不同

  1. Webpack 更偏 bundle 级构建流程
  2. Vite 更偏模块级更新

4. 配置复杂度不同

  1. Webpack 更灵活、生态更深
  2. Vite 更轻、更开箱即用

5. 适用场景不同

  1. Webpack 更适合历史复杂工程或高度定制项目
  2. Vite 更适合现代新项目

核心主线:

  1. 浏览器通过原生 ESM 请求模块
  2. 开发服务器和客户端建立 WebSocket 连接
  3. 文件变化后,Vite 只定位受影响模块
  4. 服务端通过 WebSocket 通知客户端哪个模块更新了
  5. 客户端重新拉取这个模块
  6. 如果模块支持 HMR,就只替换对应模块,而不是整页刷新

面试表达:

Vite 的热更新本质是原生 ESM + WebSocket 的模块级 HMR。改动发生后,服务端只通知受影响的模块,客户端重新请求该模块并替换运行,因此热更新更快。

推荐顺序:

  1. 先讲为什么选 Vite
  2. 再讲和 Webpack 的本质区别
  3. 最后讲 HMR 原理

3. Node.js 相较于其他后端的优势是什么

答案

这题不是要你说 Node.js 比 Java、Go、PHP 都强,而是看你是否理解:

  1. Node.js 的定位
  2. 它的优势场景
  3. 它的短板

Node.js 的核心优势主要有:

  1. 前后端语言统一,协作效率高
  2. 事件驱动、非阻塞 I/O,适合高并发 I/O 场景
  3. npm 生态丰富
  4. JSON 处理天然友好
  5. 很适合做 BFF、网关、SSR、中台服务、实时通信服务

Node.js 不适合:

  1. 重 CPU 计算
  2. 特别复杂的强事务型重后端系统

Node.js 的优势主要在于事件驱动和非阻塞 I/O,特别适合高并发 I/O 密集型场景,比如 BFF、SSR、网关和实时通信服务。另外前后端都能用 JavaScript/TypeScript,团队协作和代码复用效率也更高。它的短板是 CPU 密集型任务不占优。


4. 高并发场景:1s 内对数据库进行 10w 次写入,如何优化

答案

这题不是考“SQL 怎么写更快”,而是考:

高并发写入场景下,系统如何削峰、异步化、批量化。

  1. 不能让 10w 次请求直接同步打数据库
  2. 要削峰填谷
  3. 要异步处理
  4. 要批量写入

1. 消息队列削峰

请求先写 MQ:

  1. Kafka
  2. RabbitMQ
  3. RocketMQ

后台消费者再异步落库。

2. 批量写入

不要单条 insert,改成:

  1. batch insert
  2. 批量提交事务

3. 缓存或内存队列暂存

先写:

  1. Redis
  2. 内存队列

再异步刷库。

4. 分库分表

单库单表扛不住时,做水平拆分。

5. 限流和降级

如果写入流量超过系统能力,要:

  1. 限流
  2. 排队
  3. 降级

6. 数据库层优化

  1. 索引控制
  2. 连接池优化
  3. 主从架构
  4. 写入热点拆分

1 秒 10 万次写入不能直接同步打数据库,核心思路是削峰填谷。常见方案是请求先进入消息队列,后台异步批量写库,同时配合 Redis 暂存、分库分表和连接池优化。如果系统承压过大,还要加限流和降级。也就是说,重点不是单次写得多快,而是把同步直写改成异步批量写。


5. Git:本地 commit 5 次,未 push,回滚到第 3 次,如何找回第 4、5 次提交

答案

考察:

  1. 你是否知道 Git 里 HEAD 移动历史可以找回
  2. 你是否知道 reflog

第一步:查看引用历史

git reflog

第二步:找到第 4、5 次提交的 commit hash

第三步:恢复方式

如果想直接恢复到第 5 次提交:

git reset --hard <commit-hash>

如果只想把第 4、5 次提交重新摘回来:

git cherry-pick <hash4>
git cherry-pick <hash5>

虽然本地已经回滚到了第 3 次,但只要第 4、5 次提交曾经存在过,一般都可以通过 git reflog 找回。reflog 会记录 HEAD 的移动历史,找到对应提交 hash 后,可以 reset 回去,或者 cherry-pick 把它们摘回来。


6. 辨析 XSS 与 CSRF 攻击

答案

XSS 的本质是:

攻击者把恶意脚本注入页面,并在用户浏览器中执行。

常见危害:

  1. 窃取 Cookie / Token
  2. 篡改页面
  3. 冒充用户操作

常见类型:

  1. 存储型 XSS
  2. 反射型 XSS
  3. DOM 型 XSS

CSRF 的本质是:

攻击者诱导用户在已登录状态下,对目标站点发起非本人意愿的请求。

它成立的关键前提:

  1. 用户已经登录目标站点
  2. 浏览器会自动携带身份信息,比如 Cookie

XSS

重点是:

  1. 恶意脚本注入
  2. 在页面中执行

CSRF

重点是:

  1. 冒用用户身份
  2. 伪造请求

XSS 防御

  1. 输入校验
  2. 输出转义
  3. 避免 innerHTML 直插
  4. CSP
  5. Cookie 设置 HttpOnly

CSRF 防御

  1. CSRF Token
  2. SameSite Cookie
  3. 校验 Referer / Origin
  4. 关键操作二次确认

XSS 是把恶意脚本注入页面并执行,核心风险是脚本执行;CSRF 是利用用户已登录状态伪造请求,核心风险是请求伪造。XSS 重点防注入和脚本执行,CSRF 重点防请求伪造。


7. 自动化脚本向输入框中填入 1000w 个手机号,发起获取验证码功能,前端如何防范或规避

答案

一定要先说:

前端只能提高攻击成本,不能作为核心安全防线,真正的防御一定在后端。

  1. 按钮防抖和节流

  2. 倒计时限制重复发送

  3. 图形验证码 / 滑块验证码

  4. 风险行为识别

  5. 禁止高频切换手机号

  6. 异常操作埋点上报

  7. IP 限流

  8. 设备指纹

  9. 用户维度限频

  10. 验证码校验

  11. 风控模型

  12. 黑名单机制

前端可以通过按钮节流、倒计时、图形验证码、滑块验证、异常行为识别等方式提高脚本攻击成本,但前端无法真正防住伪造请求。核心还是要依赖后端限流、设备指纹、验证码校验和风控策略。面试里要明确说“前端只能辅助,后端才是主防线”。


8. 算法题:硬币找零问题

答案

给定硬币数组 coins 和金额 amount,求凑出该金额所需的最少硬币数;如果无法凑出,返回 -1

这是经典动态规划题。

状态定义

dp[i] 表示凑出金额 i 所需的最少硬币数。

转移方程

如果当前使用一枚 coin

dp[i] = min(dp[i], dp[i - coin] + 1)

初始化

  1. dp[0] = 0
  2. 其余初始化为 Infinity

返回值

如果 dp[amount] 仍然是无穷大,返回 -1

function coinChange(coins, amount) {
  const dp = new Array(amount + 1).fill(Infinity)
  dp[0] = 0

  for (const coin of coins) {
    for (let i = coin; i <= amount; i++) {
      dp[i] = Math.min(dp[i], dp[i - coin] + 1)
    }
  }

  return dp[amount] === Infinity ? -1 : dp[amount]
}
  1. 时间复杂度:O(coins.length * amount)
  2. 空间复杂度:O(amount)

可以主动补一句:

这题本质是完全背包问题,因为每种硬币都可以重复使用。