Skip to content

二面追问

第一部分:项目深挖(工程化 + 软技能)

1. 项目中遇到的最大技术难点是什么?

  • 基础认知:难点分两类——技术实现难(不会做)和方案选型难(怎么做更好)。

  • 进阶回答(结合你的IRMP + IAMP全流程打通)

    “最大的难点不是单个技术点,而是**‘数据孤岛’的打通**。检测车采集的点云数据(IAMP)、监控量测的传感器数据(MMMP)、最终出具的PDF报告(IRMP)原本是三个独立的系统,数据格式不统一。我的难点在于设计统一的数据中转层(BFF层),用Node.js做聚合,前端根据业务场景自动切换数据源。 具体解法:设计了一套统一的数据适配器(Adapter),将点云坐标、传感器数值、报告元数据统一映射为 { timestamp, value, type } 标准格式。前端大屏只需要消费这一套标准数据,新增设备时只需增加适配器,无需改动大屏代码。 结果新设备接入时间从2周缩短至2天,系统扩展性大幅提升。”

2. 如何保证代码质量和可维护性?

  • 基础认知:ESLint、Prettier、Code Review。

  • 进阶回答(展现架构师思维)

    “我在团队推行了‘三层质量防线’:

    1. 编码阶段(自动化):配置 husky + lint-staged,提交代码时自动格式化并运行单元测试(Jest/Vitest),不合格代码进不了仓库。
    2. 合并阶段(人工):强制 MR(Merge Request)必须有2位同事 Approve,重点审查业务逻辑和是否复用已有组件。
    3. 长期维护(文档):我在 WeLink 维护 SuitUI 组件库时,强制要求每个公共组件必须写 Storybook 示例和 README.md(含Props表格)。在IRMP项目中,我把复杂的PDF生成逻辑抽成独立的 report-engine 包,发布到公司私有 NPM 仓库,版本号严格遵循语义化(Semver)。”
  • 量化数据:推行后,线上Bug率下降40%,新人接手项目熟悉周期从1个月缩短至1周

3. 团队协作中如何处理技术分歧?

  • 基础认知:沟通、妥协、数据说话。

  • 进阶回答(经典的STAR法则)

    “在MMMP项目中,关于视频流方案,后端同事坚持用 HLS(延迟2-3秒) ,因为他们熟悉;但我坚持 WebRTC(延迟<500ms) 因为业务要求实时监控。 我的处理方式

    1. 不争论,发起 POC(概念验证) 小会,花一天时间分别搭出两套Demo。
    2. 拉上产品经理一起做压力测试(模拟工地弱网环境),数据证明WebRTC在打洞成功率80%情况下延迟远优于HLS。
    3. 提出折中方案:默认用WebRTC,但封装一层降级开关,网络差时自动降级HLS。

    结果:方案全票通过,且我主动把降级逻辑封装成公共组件,其他项目直接复用。”

4. 有没有做过性能监控?指标有哪些?

  • 基础认知:前端监控体系。

  • 进阶回答(结合WeLink和你的项目)

    “在 WeLink IM 模块,我接入过公司的自研监控平台,重点关注以下指标:

    • Web Vitals(核心体验指标):LCP(最大内容绘制,<2.5s)、FID(首次交互延迟,<100ms)、CLS(布局偏移,<0.1)。
    • JS错误率:通过 window.onerrorunhandledrejection 捕获,按 errorType 聚合告警。
    • 接口成功率:拦截 axios 响应,监控 /sendMessage 等核心接口的HTTP状态码和耗时。

    在IRMP平台,我自定义了**‘报告生成耗时’**埋点,通过 performance.mark 记录从点击到PDF下载完成的耗时,发现P50是12s,P95是35s。基于此数据,我推动了后端优化(异步队列),将P95降低至18s。”


第二部分:Vue 框架核心深度(二面必考)

1. Vue响应式原理是什么?

(快速回顾,你之前已经深度准备了track/trigger,这里直接精简有力地抛出来)

  • 回答:“Vue2用Object.defineProperty递归遍历,Vue3用Proxy配合targetMap做懒代理。核心是track收集依赖(存进WeakMap -> Map -> Set),trigger派发更新。Vue3最大的进步是支持了deleteProperty和新增属性的响应式,且初始化性能提升了至少50%。”(如果面试官追问targetMap结构,直接把之前那三层图画出来)。

2. computedwatch 的区别?(高频易混淆)

  • 基础认知

    • computed:计算属性,依赖其他数据,有缓存(依赖不变,直接返回缓存值)。
    • watch:侦听器,侦听特定数据,数据变化时执行副作用(回调函数),无缓存。
  • 源码级深度(必杀技)

    • computed 本质上是一个特殊的 effect,设置了 lazyscheduler。它维护一个 _dirty 标志位,只有依赖变化时才重新计算(缓存机制)。
    • watch 底层是 doWatch,通过遍历 source 触发 getter 收集依赖,变化时执行用户回调,支持 immediate(立即执行)和 deep(深度监听)。
  • 实战选择(结合你的项目)

    “在IRMP报告列表页,我需要根据筛选条件动态计算表格数据,我用 computed 缓存过滤后的列表。而监听路由参数变化重新请求数据,我用 watch(配合 immediate: true)。”

3. 组件通信方式有哪些?(尽量说全)

  • 基础:props / $emit(父子)
  • 跨层级:provide / inject(爷孙,注意响应式要传函数或 reactive 对象)
  • 全局:Vuex / Pinia(状态管理)
  • 事件总线:mitt(替代Vue2的$on,用于任意组件通信,但不推荐滥用)
  • 特殊:$refs / $parent(直接调用子组件方法或属性)
  • 结合你的简历:“在IAMP平台,我使用 provide/inject 向深层子组件注入 Three.js 场景实例,避免层层传递 props。”

4. 路由守卫的使用场景?

  • 基础分类

    • 全局守卫beforeEachafterEach
    • 路由独享守卫beforeEnter
    • 组件内守卫beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
  • 实战场景(结合华为内审中心AI小助理)

    “我在内审中心项目用 router.beforeEach动态权限拦截——从 Pinia store 读取用户角色,如果是‘普通用户’访问‘管理员页面’,直接 next('/403')。另外在 IRMP 报告编辑页,用户可能误触关闭浏览器,我用 beforeRouteLeave 监听未保存状态,弹出确认框‘内容未保存,确定离开吗?’。”

5. Vuex / Pinia 的状态管理流程?(必画图)

  • 基础认知(画图解释)

    • VuexView -> dispatch -> Actions (异步) -> commit -> Mutations (同步) -> State -> View。强调Mutations必须是同步函数
    • Pinia:取消了 Mutations,只有 StateGettersActions(同时支持同步和异步)。更扁平,且完美支持 TypeScript(自动类型推断)。
  • 源码级深度:Pinia 的 store 本质上是 reactive 对象。访问 store.count 触发 get 收集依赖,修改 store.count = 1 触发 settrigger(直接利用了Vue3的响应式)。

  • 结合你的项目

    “IRMP 项目我从 Vue2(Vuex)升级到 Vue3(Pinia)。原本用 Vuex 管理‘用户信息’、‘报告列表’、‘权限菜单’,模块多且繁琐。切 Pinia 后,我拆成三个 defineStoreuseUserStoreuseReportStoreusePermissionStore),代码量减少 40%,配合 storeToRefs 解构赋值,组件内调用极其丝滑。”


第三部分:手撕代码 —— 二叉树的层序遍历(BFS)

题目要求:给一个二叉树,返回其按层序遍历得到的节点值(即逐层地,从左到右访问所有节点)。通常要求输出 [[root], [level1], [level2]] 这样的二维数组。

  • 基础认知:BFS(广度优先搜索)依赖于**队列(Queue)**数据结构(JS中用数组模拟,push + shift)。
  • 进阶要点(面试官想看的)
    1. 不仅要写出来,还要说清楚时间复杂度 O(n)空间复杂度 O(n)
    2. 要能在循环中正确区分每一层(通过记录每层长度 levelSize)。
    3. 处理边界情况root 为空)。

手写完整代码(背这个,一次过)

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

function levelOrder(root) {
  // 【边界处理】面试必写!鲁棒性体现
  if (!root) return [];

  const result = [];
  const queue = [root]; // 初始化队列,放入根节点

  // BFS 主循环
  while (queue.length > 0) {
    // 【核心亮点】这里先记录当前队列的长度,即当前层的节点总数
    const levelSize = queue.length;
    const currentLevel = [];

    // 只循环当前层的节点(注意:queue.length 在循环中会变化,所以必须用 levelSize)
    for (let i = 0; i < levelSize; i++) {
      // 取出队首节点
      const node = queue.shift(); 

      // 处理当前节点(存入当前层结果数组)
      currentLevel.push(node.val);

      // 将下一层的子节点按顺序推入队尾(先左后右)
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }

    // 当前层遍历完毕,将结果存入最终数组
    result.push(currentLevel);
  }

  return result;
}

// --- 测试用例 (方便你验证) ---
// 构造一棵树: 1 -> (2, 3) -> (4, 5, 6, 7)
const tree = new TreeNode(1, 
  new TreeNode(2, new TreeNode(4), new TreeNode(5)),
  new TreeNode(3, new TreeNode(6), new TreeNode(7))
);
console.log(levelOrder(tree)); 
// 输出: [[1], [2, 3], [4, 5, 6, 7]]

面试进阶追问(防卷提示): 如果面试官问“如果不用 shift(因为 shift 时间复杂度是 O(n)),怎么优化?”,你可以立刻回答:

“用两个数组模拟队列交换,或者用索引指针(let head = 0,通过 queue[head++] 取值,避免 shift 带来的数组重排开销。” 手动实现优化版(展示你懂底层)

javascript
function levelOrderOptimized(root) {
  if (!root) return [];
  const result = [];
  const queue = [root];
  let head = 0; // 指针,替代 shift

  while (head < queue.length) {
    const levelSize = queue.length - head;
    const currentLevel = [];
    for (let i = 0; i < levelSize; i++) {
      const node = queue[head++]; // 利用指针取出,不清除数组
      currentLevel.push(node.val);
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
    result.push(currentLevel);
  }
  return result;
}

二面综合备战建议

  1. 软技能要“讲故事”:回答“团队分歧”和“代码质量”时,不要说“我觉得应该怎样”,要说“我牵头拉了一次会议,做了几页PPT/POC,用数据证明了我的方案,最终达成了共识”。这符合华为“力出一孔,用事实说话”的价值观。
  2. 先画图再写算法:拿到二叉树算法题时,先在纸上画一棵简单的树(比如 1->2->3),标明队列每一步的变化。面试官很看重你解决问题的思路可视化能力。
  3. 把二面所有Vue问题连起来:当问到computed时,主动带出watch的对比;当问到组件通信时,主动带出provide/injectPinia的区别。展现你对Vue全家桶的体系化认知
最近更新