← 已是第一轮 · 返回本次面经 · 已是最后一轮 →

本轮共 22 道题。答案默认折叠,便于先自行作答。

1. 编写一个函数解析URL,提取其中的协议、域名、路径和查询参数

题目要点

URL 结构 / URL API 使用 / 查询参数处理 / 错误处理

参考答案

考察点

● 理解 URL 结构及其组成部分

面试官关注候选人是否熟悉 URL 的协议(scheme)、域名(host)、路径(path)、查询参数(query)等基本组成。

● 掌握字符串处理和正则表达式技巧

是否能用合适的方法提取各部分信息。

● 能写出健壮且兼容性好的解析函数

考虑边界条件和异常输入。


参考答案

原理说明

URL 由以下几部分构成(简化版):


协议://域名\[:端口]/路径\[?查询参数]\[#锚点]
  • 协议(scheme):如 httphttpsftp 等;
  • 域名(host):主机名或 IP 地址;
  • 路径(path):服务器资源路径;
  • 查询参数(query)key=value 形式,多个参数用 & 分隔。

解析时重点提取协议、域名、路径和查询参数。


核心代码示例(JavaScript)

function parseURL(url) {
  try {
    const urlObj = new URL(url);

    // 提取协议,去掉末尾冒号
    const protocol = urlObj.protocol.replace(':', '');

    // 域名(包含端口,如果有)
    const host = urlObj.host;

    // 路径
    const path = urlObj.pathname;

    // 查询参数转成对象
    const queryParams = {};
    urlObj.searchParams.forEach((value, key) => {
      queryParams[key] = value;
    });

    return {
      protocol,
      host,
      path,
      queryParams,
    };
  } catch (error) {
    throw new Error('Invalid URL');
  }
}
使用示例
const url = 'https://www.example.com:8080/path/to/page?name=alice&age=25';
const result = parseURL(url);
console.log(result);

/*
{
  protocol: "https",
  host: "www.example.com:8080",
  path: "/path/to/page",
  queryParams: {
    name: "alice",
    age: "25"
  }
}
*/

常见误区或面试陷阱

误区一:使用正则解析复杂 URL
  • 正则难以覆盖各种边界情况,不建议复杂场景使用;
  • 推荐使用浏览器内置的 URL 类。
误区二:忽略端口号
  • 域名部分可能包含端口,直接取 hostname 会缺失端口。
误区三:查询参数未统一处理
  • 查询参数可能重复,需根据需求选择保存方式(这里简单取最后一个值)。

总结回顾

解析 URL 推荐使用原生 URL API,简洁且兼容性好。重点提取协议、域名(含端口)、路径和查询参数,并把查询参数转换为对象方便后续使用。

2. 给定4个异步函数A、B、C、D

○ 实现串行执行A、B、C、D的代码。

○ 实现并行执行A、B、C,然后执行D的代码。

○ 实现并行执行A、B、C,B、C执行完毕后执行D的代码。

题目要点

async/await 基础 / Promise.all 使用 / 串行与并行区别 / 依赖控制

参考答案

考察点

● 理解异步函数的执行模型

面试官关注候选人对 Promise、async/await 的掌握程度。

● 掌握串行与并行的区别及实现方式

是否能根据需求灵活控制异步执行流程。

● 能合理处理异步依赖关系

理解并行和串行混合场景的解决思路。


参考答案

假设异步函数 A、B、C、D 均返回 Promise,如:

function A() { return new Promise(resolve => setTimeout(() => {console.log('A'); resolve('A done')}, 1000)); }
function B() { return new Promise(resolve => setTimeout(() => {console.log('B'); resolve('B done')}, 800)); }
function C() { return new Promise(resolve => setTimeout(() => {console.log('C'); resolve('C done')}, 1200)); }
function D() { return new Promise(resolve => setTimeout(() => {console.log('D'); resolve('D done')}, 500)); }

1. 串行执行 A、B、C、D

async function serialExecute() {
  await A();
  await B();
  await C();
  await D();
}

serialExecute();

解释:使用 await 顺序等待每个异步函数完成,后一个函数在前一个完成后开始执行。


2. 并行执行 A、B、C,然后执行 D

async function parallelABCThenD() {
  await Promise.all([A(), B(), C()]); // A、B、C 并行
  await D(); // D 在 A、B、C 全部完成后执行
}

parallelABCThenD();

解释Promise.all 等待 A、B、C 同时执行完成,才开始执行 D。


3. 并行执行 A、B、C,B、C 执行完毕后执行 D

async function parallelBCThenDWithA() {
  const promiseA = A();               // A 单独执行,不影响 D
  await Promise.all([B(), C()]);     // 等待 B、C 执行完毕
  await D();                         // B、C 完毕后执行 D
  await promiseA;                    // 最后等待 A 完成(如果 D 前 A 未完成)
}

parallelBCThenDWithA();

解释

  • A 独立执行,无需等待 B、C;
  • B 和 C 并行,且必须完成后才执行 D;
  • D 执行完毕后再等待 A(防止程序提前退出或确保全部完成)。

常见误区或面试陷阱

  • 误区一Promise.all([A(), B(), C(), D()]) 不能满足“D 在 ABC 完毕后执行”,因为此写法是所有同时并行。
  • 误区二:未使用 await 导致异步未按顺序执行。
  • 误区三:忽视异步函数可能抛错,未做异常捕获处理。

总结回顾

  • 串行执行 用连续 await
  • 并行执行后续执行Promise.all 等待前面并行任务完成后再继续;
  • 复杂依赖关系 可拆分 Promise,组合 Promise.allawait 灵活控制顺序。

3. 实现Promise.all()方法

题目要点

Promise.resolve / 并行执行 / 结果顺序 / 异常短路

参考答案

考察点

● 理解 Promise.all 的核心功能和行为

面试官关注候选人是否掌握 Promise 并行执行、结果收集及错误处理机制。

● 能够手写 Promise 相关工具函数

考察对 Promise 构造、状态管理和异步执行原理的理解。

● 掌握边界条件及异常场景处理

是否能处理空数组、非 Promise 输入、任意一个 Promise 拒绝等情况。


参考答案

原理说明

Promise.all(iterable) 接收一个可迭代对象(通常是数组)包含多个 Promise 或值,返回一个新的 Promise,满足以下条件:

  • 所有 Promise 全部成功,返回结果数组,顺序与输入顺序对应;
  • 任意一个 Promise 失败,立即返回该拒绝理由,其他 Promise 结果忽略;
  • 支持非 Promise 值,会自动转换为 resolved Promise。

核心代码实现

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }
    const results = [];
    let completedCount = 0;
    const total = promises.length;

    if (total === 0) {
      return resolve([]);
    }

    promises.forEach((p, index) => {
      // 保证即使非 Promise 值,也转换成 Promise 处理
      Promise.resolve(p)
        .then(value => {
          results[index] = value; // 按输入顺序存储结果
          completedCount++;
          if (completedCount === total) {
            resolve(results);
          }
        })
        .catch(err => {
          // 任何一个失败,立即 reject
          reject(err);
        });
    });
  });
}

使用示例

const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = 3; // 非 Promise,自动包装成 Promise.resolve(3)

myPromiseAll([p1, p2, p3])
  .then(results => {
    console.log(results); // [1, 2, 3]
  })
  .catch(err => {
    console.error('Error:', err);
  });

常见误区或面试陷阱

误区一:忽略结果顺序
  • 直接 push 结果导致顺序错乱,必须使用下标保存结果。
误区二:未处理非 Promise 值
  • Promise.all 会自动调用 Promise.resolve,自定义实现也应支持。
误区三:未实现短路失败机制
  • 任何一个 Promise 拒绝后,应立即调用 reject,不再等待其它 Promise。

总结回顾

手写 Promise.all 关键点是:

  • 返回一个新的 Promise;
  • 使用 Promise.resolve 包装每个输入项,保证兼容非 Promise 值;
  • 维护计数器,所有 Promise 成功时返回结果数组,保证顺序一致;
  • 任意拒绝则立即失败。

4. 简介你的项目经历,重点介绍一个印象最深刻的项目,包括项目背景、技术栈、功能模块和遇到的挑战

题目要点

结合自己简历,挖掘亮点项目,提前准备,STAR法则

参考答案

经典面试方向,选择简历中的亮点项目深入介绍

5. Vue是如何实现数据绑定的?为什么Object.defineProperty拦截不到数组的变化?数组的响应式原理是什么

题目要点

Object.defineProperty 响应式原理 / 数据劫持 / 依赖收集 / 数组变异方法重写 / 数组响应式限制

参考答案

考察点

● 理解 Vue 响应式数据绑定的核心机制

面试官关注候选人对 Vue 数据劫持、依赖收集、视图更新机制的理解。

● 理解 Object.defineProperty 在数据响应式中的作用与局限

是否能明确其对对象和数组的不同处理方式。

● 掌握数组响应式的特殊处理机制

是否了解 Vue 如何拦截数组变异方法以实现响应式。


参考答案

1. Vue 数据绑定原理(基于 Object.defineProperty)

核心概念
  • 数据劫持:Vue 通过 Object.defineProperty 拦截对象属性的 getter 和 setter,实现数据变化时通知视图更新。
  • 依赖收集:在 getter 中收集依赖(如 Watcher),setter 中触发依赖更新。
  • 视图更新:数据变化触发依赖对应的视图更新函数,实现数据与视图同步。
过程简述
  1. Vue 初始化时递归遍历数据对象,使用 Object.defineProperty 转换所有属性的 getter 和 setter。
  2. 在 getter 访问时收集依赖,存储对应 Watcher。
  3. 在 setter 赋值时通知依赖 Watcher,执行视图更新。
  4. 通过这种“发布-订阅”模式实现双向绑定。

2. 为什么 Object.defineProperty 拦截不到数组变化?

原因分析
  • Object.defineProperty 只能劫持对象的属性访问,对 数组索引(例如 arr[0] = x)的赋值拦截困难且性能低下,无法覆盖所有数组变异操作。
  • 数组的长度变化和方法调用(如 pushpopsplice)不会触发单个索引的 setter,因此无法通过劫持单个索引实现响应式。
  • JS 数组的原生方法直接修改内部结构,无法被 Object.defineProperty 劫持。

3. Vue 数组响应式的实现原理

核心思路:重写数组变异方法
  • Vue 通过 原型链继承,将数组的原生变异方法(pushpopshiftunshiftsplicesortreverse)替换为响应式版本。
  • 替换后的方法在执行原生操作的同时,通知视图更新。
  • 例如,push 会先执行原生的 push,然后触发依赖通知。
实现细节
  • Vue 创建一个 arrayMethods 对象,包含重写的数组变异方法。
  • 在响应式数组上将 __proto__ 指向该 arrayMethods 对象。
  • 变异方法内部调用 dep.notify(),通知订阅者数据变化。
数组内部对象的响应式
  • 对数组中新增的对象元素,Vue 会递归调用 observe,使其属性也变成响应式。

总结

说明
数据绑定核心利用 Object.defineProperty 递归劫持对象属性 getter/setter,收集依赖,触发视图更新。
数组响应式问题Object.defineProperty 无法直接监听数组索引和长度变化。
数组响应式解决方案重写数组的七个变异方法,通过原型链继承替换,实现变异时触发依赖通知。

常见误区或面试陷阱

  • 认为 Object.defineProperty 可以劫持数组所有变化(索引、长度等);
  • 忽视数组新增元素的响应式处理;
  • 忽略 Vue 2 和 Vue 3 响应式机制差异(Vue 3 使用 Proxy 解决了数组拦截问题)。

代码示例(简化版重写数组方法)

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);

['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  arrayMethods[method] = function(...args) {
    const result = arrayProto[method].apply(this, args);
    // 触发更新通知
    console.log(`调用了数组方法: ${method}`);
    // 假设有 dep.notify() 机制
    // dep.notify();
    return result;
  }
});

// 使用时
const arr = [1, 2, 3];
arr.__proto__ = arrayMethods;
arr.push(4); // 控制台打印 “调用了数组方法: push”

6. 请阐述Vue中Diff算法的原理,包括其优化策略和应用场景

题目要点

虚拟 DOM / Diff 算法原理 / 双端指针 / key 优化 / 静态节点 / 异步批量更新

参考答案

考察点

● 理解虚拟 DOM 与 Diff 算法的基本原理

考察候选人对 Vue 渲染机制和性能优化关键技术的掌握。

● 掌握 Vue Diff 算法的实现细节及优化策略

是否能讲解 Vue 如何高效比较新旧虚拟 DOM 节点,避免全量重绘。

● 知晓 Diff 算法的应用场景及限制

能结合实际项目场景说明何时Diff算法发挥关键作用。


参考答案

1. Vue Diff 算法原理

1.1 什么是 Diff 算法?

Diff 算法是一种比较两个树(通常是虚拟 DOM 树)差异的算法,找出最小的变更集合,以高效更新真实 DOM。

1.2 Vue 的虚拟 DOM

Vue 将模板编译成虚拟 DOM(VNode),每次数据变化产生新的虚拟 DOM 树,通过 Diff 算法对比旧树与新树,计算出需要更新的最小操作,最后批量应用到真实 DOM。

1.3 Vue Diff 过程
  • 逐层比较:从根节点开始,比较新旧节点的类型、属性、子节点。
  • 节点类型判断
    • 不同类型节点,直接销毁旧节点,创建新节点。
    • 同类型节点,复用节点,仅更新属性和事件。
  • 子节点比较
    • 对比子节点列表,找出增删改的节点。
    • Vue 采用双端指针算法快速比对首尾节点,减少遍历次数。

2. Vue Diff 算法的优化策略

2.1 双端指针算法
  • 维护四个索引指针(新旧前后端索引),分别从两端向中间遍历;
  • 快速处理节点增删,减少复杂度。
2.2 通过 key 优化节点重用
  • 对列表节点设置唯一 key,Vue 能根据 key 精准复用节点,避免全量重新渲染;
  • key 的存在让 Diff 能快速定位变更,减少移动、删除操作。
2.3 静态节点标记优化
  • Vue 编译时会标记静态节点,静态节点不会发生变化,Diff 过程直接跳过,提高效率。
2.4 异步更新和批处理
  • Vue 通过异步队列和批量更新机制,将多次数据变更合并为一次 DOM 更新,减少重绘重排。

3. 应用场景

  • 动态列表渲染:如表格、菜单、聊天记录等频繁变动的场景,通过 Diff 最小化 DOM 操作。
  • 复杂组件更新:避免整个组件 DOM 重新渲染,仅针对变化部分更新。
  • 频繁交互界面:如输入框联想、分页切换、拖拽排序等高性能要求场景。

4. 常见误区或面试陷阱

  • 误以为 Diff 算法是全量对比所有节点,忽视双端指针和 key 优化策略。
  • 忽视 key 的重要性,导致 Diff 退化为低效的无 key 算法。
  • 不了解静态节点标记,误判静态节点也参与 Diff 流程。
  • 误解 Diff 是实时执行,实际上 Vue 采用异步更新合并多次变化。

总结

Vue 的 Diff 算法基于虚拟 DOM,通过递归逐层对比新旧节点,利用双端指针和 key 优化减少复杂度,结合静态节点标记和异步批量更新,实现高性能视图渲染。它广泛应用于动态列表和复杂组件更新中,极大提升前端渲染效率。

7. 重绘和回流的区别是什么?如何避免重绘和回流以优化前端性能

题目要点

回流与重绘区别 / 触发条件 / 优化策略 / 性能影响

参考答案

考察点

● 理解浏览器渲染流程中重绘(Repaint)和回流(Reflow)的概念

面试官关注候选人是否能区分两者及其性能开销。

● 掌握导致回流和重绘的常见操作

是否了解哪些 CSS 或 DOM 变更会触发回流或重绘。

● 能提出合理的性能优化策略

针对回流和重绘的特性提出有效的避免和优化手段。


参考答案

1. 重绘(Repaint)和回流(Reflow)的区别

区别回流(Reflow)重绘(Repaint)
定义当元素的几何属性(如尺寸、位置)发生变化,导致页面布局需要重新计算和渲染。当元素的外观发生变化(如颜色、背景色),但布局未变,只需重新绘制。
触发条件改变元素尺寸、位置、内容(影响布局)时,如修改宽高、边距、字体大小。修改元素的颜色、背景色、可见性(不影响布局)时。
影响范围影响页面布局及其子树,影响较大,消耗性能高。只影响元素本身的绘制,消耗相对较小。
性能消耗性能开销大,尽量避免频繁触发。性能开销较小,但频繁也会影响性能。

2. 导致回流的常见操作

  • 修改元素的几何属性:widthheightpaddingmarginbordertopleft 等;
  • DOM 结构的添加、删除、修改;
  • 字体样式的改变(如 font-size);
  • 浏览器窗口大小调整;
  • 触发同步读取布局属性(如 offsetWidthscrollTop 等)时,浏览器会先执行回流。

3. 导致重绘的常见操作

  • 改变元素颜色、背景色、可见性(如 colorbackground-colorvisibility);
  • 阴影、渐变等视觉效果改变。

4. 避免重绘和回流的优化策略

4.1 减少回流操作
  • 批量修改样式:将多次修改合并,避免逐条修改触发多次回流。
  • 使用 class 替代直接修改样式:一次性切换样式,减少回流次数。
  • 脱离文档流操作(使用 position: absolute/fixed:避免影响其他元素布局。
  • 避免频繁读取导致回流的属性:如 offsetWidthclientHeight,可以先缓存数据。
  • 使用文档碎片(DocumentFragment)进行批量 DOM 操作,减少回流。
  • 使用 CSS3 硬件加速:通过 transformopacity 等属性修改,避免触发布局计算。
4.2 减少重绘操作
  • 避免频繁修改颜色、背景等视觉属性;
  • 使用 visibility: hidden 代替 display: none(后者触发回流,前者仅重绘);
  • 利用合成层(GPU 加速)减少重绘开销。

5. 额外优化建议

  • 使用防抖节流控制频繁触发的事件(如窗口 resize、scroll);
  • 采用虚拟 DOM 技术(如 React、Vue)减少直接 DOM 操作;
  • 使用 will-change CSS 属性告知浏览器提前做优化(慎用,防止内存占用过高)。

常见误区或面试陷阱

  • 误将所有样式变化都归为回流,忽视重绘的存在;
  • 认为重绘没有性能影响,实际上频繁重绘也会降低性能;
  • 忽视同步读取布局属性会强制回流,导致性能瓶颈。

总结

  • 回流:布局变化,影响范围广,消耗大,尽量减少;
  • 重绘:样式视觉变化,影响较小,但频繁也需优化;
  • 通过批量操作、脱离文档流、减少同步读取、使用 GPU 加速等手段,有效降低重绘与回流带来的性能损耗。

8. 实现登录功能时,前端和后端分别需要进行哪些关键步骤和安全考虑

题目要点

输入校验 / HTTPS / 密码加密 / 令牌管理 / 防御攻击 / 会话安全

参考答案

考察点

● 理解登录流程的完整实现步骤

考察候选人是否掌握前后端协作完成登录的关键流程。

● 掌握登录过程中的安全风险及防护手段

是否熟悉密码安全、数据传输、认证与会话管理等安全措施。

● 能提出合理的设计和实现方案

包括用户体验、安全性、扩展性等方面。


参考答案

1. 前端关键步骤及安全考虑

1.1 关键步骤
  • 输入校验:在提交前对用户名、密码格式做合法性校验,避免无效请求。
  • 密码加密(可选):可在前端对密码做哈希或加盐处理(但仍需后端加密),提高安全性。
  • 请求发送:使用 HTTPS 发送登录请求,保证数据传输加密。
  • 错误处理:根据后端返回的错误信息,给出合理的用户提示。
  • 会话管理:登录成功后,存储服务器返回的令牌(如 JWT 或 Session ID),一般存储在 HttpOnly cookie 或 localStorage(需考虑安全风险)。
  • 登出及过期处理:提供登出功能,处理登录状态过期或失效时的重定向。
1.2 安全考虑
  • 使用 HTTPS:避免明文传输,防止中间人攻击。
  • 避免密码明文存储:即使前端做了加密,也必须由后端最终加密存储。
  • 防止 XSS 攻击:避免通过脚本窃取令牌,尽量使用 HttpOnly Cookie。
  • 防止 CSRF:使用 SameSite Cookie 属性,或结合 CSRF Token 机制。
  • 输入防注入:防止通过登录输入导致后端 SQL 注入或脚本注入。

2. 后端关键步骤及安全考虑

2.1 关键步骤
  • 接收请求并校验参数:严格验证用户名、密码格式和长度。
  • 密码验证:使用安全的哈希算法(如 bcrypt、argon2)对存储的密码进行验证。
  • 用户身份验证:判断用户是否存在,密码是否匹配。
  • 生成令牌
    • 使用 JWT 或 Session 机制管理登录状态。
    • JWT 应包含必要的用户信息和过期时间。
  • 会话存储与管理:Session 存储需安全、可扩展。
  • 响应客户端:成功返回令牌或 Cookie,失败返回统一且不泄露过多信息的错误。
  • 登录限制:防止暴力破解,如失败次数限制、验证码。
2.2 安全考虑
  • 密码加密存储:密码绝不以明文存储,使用加盐哈希。
  • 防止暴力攻击:登录失败次数限制、IP 黑名单、验证码等。
  • 防止会话固定攻击:登录后重新生成 Session ID。
  • 数据传输安全:强制使用 HTTPS。
  • 审计与日志:记录登录行为,方便安全审计和异常检测。

3. 前后端协作要点

  • 明确接口协议和数据格式;
  • 令牌管理机制的统一(Cookie vs Token);
  • 统一错误码和消息,方便前端处理;
  • 统一登录状态过期处理和刷新策略。

常见误区或面试陷阱

  • 前端以为只需简单表单提交,忽略安全传输和存储;
  • 后端存储明文密码或自造弱加密算法;
  • 令牌未设置合理过期时间,导致安全隐患;
  • 登录失败时泄露过多用户信息。

总结

实现登录功能,前端需关注输入校验、安全传输、会话管理和用户体验,后端则重点在身份验证、密码安全、会话安全及防护攻击。两端协同设计,构建安全可靠的登录体系。

9. 如何在接口调用中实现鉴权?常用的鉴权方式有哪些

题目要点

鉴权流程 / Cookie+Session / Token(JWT) / OAuth / API Key / 安全风险防护

参考答案

考察点

● 理解鉴权(Authorization)在接口调用中的作用

面试官关注候选人是否清楚鉴权流程与安全保障的重要性。

● 掌握常用鉴权方式及其适用场景

是否能合理选择和应用不同鉴权方案。

● 能描述具体的实现方法和细节

包括前后端如何协作完成鉴权。


参考答案

1. 鉴权在接口调用中的实现原理

  • 鉴权是指验证请求者是否有权访问某个接口或资源,防止未授权访问。
  • 一般流程:
    1. 用户登录成功后,服务器生成凭证(如令牌 Token、Session ID)。
    2. 客户端在后续接口调用中带上该凭证。
    3. 服务器接收请求时验证凭证有效性及权限。
    4. 根据验证结果允许或拒绝访问。

2. 常用鉴权方式及实现细节

  • 用户登录后,后端创建 Session 并在响应中通过 Set-Cookie 设置 Session ID。
  • 浏览器自动带上 Cookie,后端通过 Session ID 查询登录状态和权限。
  • 优点:简单,支持自动管理 Session 生命周期。
  • 缺点:不适合跨域场景,依赖 Cookie,易受 CSRF 攻击。
2.2 Token 鉴权(常用 JWT)
  • 用户登录成功,服务器返回 Token(如 JWT)。
  • 前端在请求头(如 Authorization: Bearer <token>)中携带 Token。
  • 服务器通过校验 Token 的签名、过期时间、权限信息完成鉴权。
  • 优点:无状态,适合跨域、分布式架构,扩展性好。
  • 缺点:Token 需要妥善存储(防止 XSS),不能随意暴露。
2.3 OAuth / OAuth2.0
  • 适用于第三方授权,用户允许应用访问自己资源。
  • 通过授权码、访问令牌实现鉴权和权限控制。
  • 复杂度高,适合大型系统和开放平台。
2.4 API Key
  • 由服务提供方发放给调用方的唯一标识。
  • 请求时携带 API Key,服务器验证是否有效。
  • 适用于开放接口或服务间简单鉴权。
2.5 其他方式
  • 基于证书的鉴权(如 mTLS);
  • IP 白名单鉴权;
  • 自定义签名算法(如阿里云、AWS 签名机制)。

3. 具体实现示例(Token 鉴权)

// 前端请求示例
fetch('/api/data', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})
.then(res => res.json())
.then(data => { /* 处理数据 */ });
  • 服务器中间件验证请求头 Token;
  • 校验通过后执行业务逻辑,失败返回 401 Unauthorized。

4. 常见误区或面试陷阱

  • 将鉴权和认证(Authentication)混淆;
  • 在前端暴露敏感凭证,导致 XSS 被盗用;
  • 忽视 Token 过期和刷新机制;
  • 依赖 Cookie 鉴权但未防护 CSRF 攻击。

总结

接口鉴权关键是验证请求者权限,保证资源安全。常见方式包括 Cookie+Session、Token(JWT)、OAuth、API Key 等。合理选择鉴权方案,并配合安全存储和防护措施,保障接口调用安全。

10. Vue中有哪些路由守卫?它们各自的用途和使用场景是什么

题目要点

全局前置/解析/后置守卫 / 路由独享守卫 / 组件内守卫(3 种) / 执行时机 / 使用场景

参考答案

考察点

● 理解 Vue Router 中路由守卫的种类和执行时机

考察候选人对路由导航流程的掌握。

● 掌握如何通过路由守卫控制路由访问和权限管理

是否能合理使用守卫实现权限校验、数据预处理等功能。

● 熟悉各守卫的适用场景和编写方法


参考答案

1. Vue 路由守卫分类及用途

守卫类型触发时机用途典型使用场景
全局前置守卫
router.beforeEach
路由跳转开始前,所有导航都会触发验证用户权限、登录状态,阻止不符合条件的导航登录拦截、权限校验、统一统计埋点
全局解析守卫
router.beforeResolve
在所有组件内守卫和异步路由组件被解析后触发最后阶段的异步数据准备,确保路由完全解析后再进行操作数据预加载、复杂异步操作,确保所有守卫执行完毕后执行
全局后置守卫
router.afterEach
导航成功完成后触发统计页面访问、修改页面标题、不支持阻止导航页面埋点、修改标题、关闭加载动画
路由独享守卫
beforeEnter
路由配置中定义,只针对当前路由生效针对单个路由的权限校验或逻辑控制某个路由特殊权限控制
组件内守卫
beforeRouteEnter
组件渲染前触发,无法访问 this异步数据获取、跳转前逻辑,延迟访问组件实例组件级数据加载、依赖导航结果的初始化
组件内守卫
beforeRouteUpdate
路由参数变化时触发,组件复用时调用响应路由参数变化,更新组件数据同一个组件不同参数下更新数据
组件内守卫
beforeRouteLeave
组件即将离开当前路由时触发提示用户保存修改、取消导航等操作离开前确认操作、防止数据丢失

2. 守卫执行流程简述

  • 导航触发时,先执行 全局前置守卫 → 路由独享守卫 → 组件内守卫 beforeRouteEnter → 异步组件解析 → 全局解析守卫 → 导航确认 → 组件渲染 → 全局后置守卫

3. 使用场景示例

3.1 登录权限控制
router.beforeEach((to, from, next) => {
  const isLoggedIn = Boolean(localStorage.getItem('token'));
  if (to.meta.requiresAuth && !isLoggedIn) {
    next('/login');
  } else {
    next();
  }
});
3.2 组件数据预加载
export default {
  beforeRouteEnter(to, from, next) {
    fetchData(to.params.id).then(data => {
      next(vm => {
        vm.data = data;
      });
    });
  }
}
3.3 离开确认
export default {
  beforeRouteLeave(to, from, next) {
    if (this.hasUnsavedChanges) {
      const answer = window.confirm('You have unsaved changes. Leave anyway?');
      if (answer) next();
      else next(false);
    } else {
      next();
    }
  }
}

4. 常见误区或面试陷阱

  • 误认为全局后置守卫可以阻止导航(后置守卫无法阻止导航)。
  • 不清楚组件内守卫 beforeRouteEnter 中无法访问 this,需通过 next(vm => {}) 获取实例。
  • 忽略路由复用场景,未处理 beforeRouteUpdate 导致参数变更不刷新组件。
  • 忘记调用 next() 导致导航卡死。

总结

Vue 路由守卫分为全局守卫、路由独享守卫和组件内守卫,分别在不同导航阶段触发,支持权限校验、数据预加载、导航拦截等功能。合理运用各类守卫,结合路由元信息,可以高效管理路由访问和组件状态。

11. 你是否有过开发小程序的经验?分享一个你参与过的小程序项目,包括所用技术、实现功能和遇到的问题

题目要点

  • 清晰描述技术栈及功能
  • 分析具体遇到的问题和解决办法
  • 展示项目经验和思考能力
  • 避免空泛,具体且系统
参考答案

考察点

● 评估候选人对小程序开发技术栈的熟悉度

考察是否掌握微信小程序框架、API及开发工具。

● 了解项目实现的功能和技术细节

是否能清晰描述项目需求、技术选型和实现思路。

● 分析遇到的问题及解决方案

体现解决问题的思路和能力。


参考答案

1. 项目背景及技术栈

  • 项目名称:线上问诊小程序
  • 主要技术:微信小程序原生框架 + WXML + WXSS + JavaScript
  • 辅助技术:云开发(云函数、数据库)、WebSocket 实时通信、ES6+语法支持

2. 实现功能

  • 用户注册登录(微信授权及手机号绑定)
  • 医生和患者端的身份区分
  • 实时在线问诊聊天(基于 WebSocket 或微信的实时通信能力)
  • 订单管理、支付功能
  • 历史记录查询与评价系统
  • 基础消息通知与推送

3. 遇到的问题与解决

3.1 实时通信性能瓶颈
  • 问题:实时聊天消息延迟大,用户体验不佳。
  • 解决:引入云开发的 WebSocket 服务,减少长轮询请求,保证消息快速双向传递。
3.2 网络不稳定时的数据同步
  • 问题:网络波动导致消息发送失败或重复。
  • 解决:实现消息本地缓存和发送状态管理,支持断线重连机制及消息重发。
3.3 小程序页面跳转限制
  • 问题:跳转页面数有限制,复杂导航导致跳转失败。
  • 解决:合理规划路由结构,使用 redirectTo 替代 navigateTo,避免过多页面堆积。
3.4 微信接口权限及审核问题
  • 问题:涉及支付和授权功能时,接口权限申请复杂,审核周期长。
  • 解决:提前准备完整资料,规范功能设计,积极沟通微信官方。

4. 总结与经验

  • 小程序开发需要深入理解微信生态特性及限制,合理利用云开发等新技术提升开发效率。
  • 实时通信、页面跳转及权限审核是常见难点,需结合项目实际需求设计方案。
  • 注意网络状态管理与用户体验,保证稳定流畅的服务。

12. 你是否有过使用Node.js开发的经历?你熟悉哪些Node.js框架和模块

题目要点

Node.js 事件驱动 / Express / Koa / Nest / 核心模块 / 常用第三方库 / 项目实践

参考答案

考察点

● 了解候选人对 Node.js 的实际使用经验

考察是否具备 Node.js 开发背景,能够胜任相关工作。

● 熟悉主流框架及常用模块

判断候选人对生态圈技术的掌握深度和广度。

● 能结合项目经验说明技术选型理由

体现对技术栈的理解与应用能力。


参考答案

1. Node.js 使用经历

  • 曾参与多个基于 Node.js 的项目开发,涉及后端 API 设计、服务器搭建、接口实现等。
  • 熟悉 Node.js 的事件驱动和异步非阻塞模型,能编写高效的服务端代码。
  • 具备调试、性能优化和安全加固的实践经验。

2. 熟悉的 Node.js 框架

  • Express.js:最常用的轻量级 Web 框架,熟悉中间件机制、路由管理和错误处理。
  • Koa.js:基于 ES6 generator 的新型框架,理解 async/await 异步处理方式。
  • Nest.js:企业级 Node.js 框架,采用模块化设计,支持依赖注入,适合构建大型应用。
  • Egg.js:阿里巴巴开源,适合企业级应用,支持插件机制和多进程管理。

3. 熟悉的 Node.js 核心模块和第三方模块

  • 核心模块fs(文件系统)、http(HTTP 服务器)、path(路径操作)、crypto(加密)、events(事件驱动)、stream(流处理)等。
  • 第三方模块
    • axios / node-fetch:HTTP 请求客户端。
    • jsonwebtoken:JWT 令牌生成与验证。
    • mongoose / sequelize:MongoDB 和关系数据库 ORM。
    • socket.io:实现实时通信。
    • pm2:进程管理和负载均衡。
    • winston / log4js:日志管理。
    • bcrypt:密码加密。

4. 项目中使用的典型场景

  • 使用 Express + Mongoose 开发 RESTful API,实现用户认证与权限控制。
  • 利用 Socket.io 实现实时消息推送功能。
  • 通过 PM2 实现应用的多进程管理和自动重启。
  • 使用 JWT 进行无状态认证,配合中间件完成权限校验。

常见误区或面试陷阱

  • 只会使用框架的基础功能,不了解其内部机制和异步原理。
  • 不了解 Node.js 的单线程特性,误用同步 API 导致性能问题。
  • 框架版本混淆,未能正确掌握新旧版本差异。

总结

具备丰富的 Node.js 开发经验,熟悉多种框架和核心模块,能够根据项目需求合理选型,编写高效、健壮的后端服务。

13. 平时在进行前端开发时,你如何组织代码结构、管理状态和优化性能

题目要点

模块化 / 目录规范 / 组件职责 / 局部和全局状态管理 / 代码分割 / 渲染优化 / 资源和网络优化

参考答案

考察点

● 理解良好代码组织的重要性

考察候选人是否具备清晰、可维护的代码架构设计能力。

● 掌握前端状态管理方法

是否能根据项目复杂度合理选择和实现状态管理方案。

● 具备前端性能优化的系统思路

能从多个维度提出有效的性能提升措施。


参考答案

1. 代码结构组织

1.1 模块化设计
  • 按业务功能划分模块,避免单文件臃肿。
  • 使用 ES6 模块(import/export)进行依赖管理。
  • 保持文件职责单一,如组件、工具函数、API 请求分离。
1.2 目录规范
  • 通常包含 components/pages/services/(API接口)、store/(状态管理)、utils/(工具函数)、assets/(静态资源)等目录。
  • 统一命名规范,方便团队协作和代码查找。
1.3 组件设计
  • 遵循“单一职责原则”,组件职责明确,避免过大组件。
  • 提倡组合复用,通过 props 和事件通信实现组件解耦。

2. 状态管理

2.1 局部状态
  • 组件内部使用 useState(React)或 data(Vue)管理局部状态。
  • 利用组件通信(props、事件、Context)传递数据。
2.2 全局状态
  • 复杂项目引入状态管理库,如 Redux、MobX、Vuex、Pinia。
  • 按业务模块划分 store,确保状态结构清晰,便于维护。
  • 使用中间件(如 Redux Thunk、Saga)处理异步状态。
2.3 数据流设计
  • 遵循单向数据流原则,避免状态混乱。
  • 结合接口设计,保证状态与后台数据同步一致。

3. 性能优化

3.1 资源优化
  • 静态资源压缩、合并和缓存,使用 CDN 加速。
  • 图片按需加载、懒加载和 WebP 格式替代。
3.2 渲染优化
  • 合理拆分组件,避免不必要的重渲染。
  • 使用虚拟列表技术优化长列表渲染。
  • React 中使用 React.memouseMemo,Vue 中使用 computedwatch 控制计算。
3.3 代码优化
  • 代码分割,按需加载,减少首屏包体积。
  • Tree shaking 去除无用代码。
  • 减少内存泄漏和不必要的事件监听。
3.4 网络优化
  • 接口请求合并,避免重复请求。
  • 使用 HTTP/2、多路复用减少延迟。
  • 缓存策略(Service Worker、浏览器缓存)优化。
3.5 其他
  • 使用性能分析工具(Chrome DevTools、Lighthouse)监控瓶颈。
  • 采用防抖节流控制频繁触发事件(如 scroll、input)。

常见误区或面试陷阱

  • 组件设计过于臃肿,职责不清。
  • 状态管理滥用全局状态,导致维护困难。
  • 性能优化单点考虑,缺乏系统性思路。

总结

良好的代码结构基于模块化和职责分明,状态管理结合局部与全局设计,性能优化从资源、渲染、代码和网络多角度综合施策,形成系统化的前端开发流程。

14. 比较4字节的int和4字节的float表示的数据个数,哪个更多?为什么

题目要点

  • int是连续整数编码,数值个数=2³²。

  • float采用IEEE 754编码,存在特殊值且数值非连续,有效数值个数<2³²。

  • 因此4字节int表示的数据个数更多。

参考答案

考察点

● 理解计算机中数据类型的存储原理

考察候选人对整数和浮点数在内存中的存储方式及编码机制的理解。

● 掌握整数和浮点数表示范围及精度的差异

是否能清楚区分两者能表达的数值个数和分布特点。


参考答案

1. 基础概念及定义

  • 4字节(32位)int:通常指32位有符号整数,采用补码表示法,取值范围是 -2³¹ 到 2³¹-1,连续的整数个数为2³²(约42亿)个。

  • 4字节(32位)float:遵循IEEE 754单精度浮点数格式,由1位符号位、8位指数位和23位尾数(有效数字)组成。它表示的是一个浮点数集合,数值分布不连续,存在无穷大、非数(NaN)等特殊值。

2. 数据个数比较

  • int的取值个数:2³²(约4.29亿)个,表示的是所有可能的32位二进制组合,每个组合对应一个唯一整数。

  • float的取值个数:虽然float也是32位存储,但由于采用科学计数法编码,不同的二进制组合会表示特殊值(如NaN、±∞),且浮点数表示的值是非连续的,且许多bit组合代表相同的数值(比如0的正负零),导致可表示的不同数值个数实际上小于2³²。

3. 为什么int的“数据个数”更多?

  • int表示的是离散且连续的整数集合,32位二进制编码直接映射到2³²个不同整数值,且没有重复或特殊编码。

  • float采用科学计数法编码数值,同一个数值可能对应多个二进制组合(例如正负零),部分编码用于表示特殊值,且数值分布稀疏、非连续。因此,虽然float有2³²种可能的位模式,但实际能表示的“不同有效数值”比int少。

4. 总结

  • 4字节int能表示约42亿个连续整数,数量更多且连续。

  • 4字节float的有效数值个数少于42亿,因为编码包括特殊值、重复表示、且数值分布非连续。


常见误区或面试陷阱

  • 误以为float和int位数相同就能表示相同数量的数据。

  • 忽略浮点数特殊值(NaN、±∞)对有效数值个数的影响。

  • 将float的数值范围与数值个数混淆。

15. 进程和线程的区别是什么?线程有哪些优势和劣势

题目要点

进程定义及资源 / 线程定义及资源共享 / 两者区别 / 线程优势 / 线程劣势 / 多线程风险

参考答案

考察点

● 理解操作系统中进程和线程的基本概念

考察候选人对多任务执行机制的理解。

● 掌握进程与线程的区别及联系

能清晰区分两者在资源、调度等方面的异同。

● 了解线程的优缺点及适用场景

是否能结合实际开发或系统设计说出线程的优势和局限。


参考答案

1. 进程和线程的区别

维度进程(Process)线程(Thread)
定义操作系统分配资源的基本单位,是程序的执行实例进程内的执行单元,依赖于进程存在,执行任务的最小单位
资源拥有拥有独立的内存空间(代码段、数据段、堆、栈)共享进程的内存空间(堆和全局变量),拥有独立栈和寄存器
创建开销较大,涉及资源分配和管理较小,线程切换开销小,创建销毁速度快
调度单位由操作系统内核调度由操作系统调度,也可能由用户级线程库调度
通信方式进程间通信(IPC),如管道、消息队列、共享内存等线程间通信简单,共享内存直接访问
崩溃影响一个进程崩溃通常不会直接影响其他进程一个线程崩溃可能导致整个进程崩溃

2. 线程的优势

  • 资源利用率高
    线程间共享进程资源,避免重复分配内存和资源。

  • 切换开销小
    线程切换只需保存和恢复部分寄存器状态,无需切换内存页表。

  • 响应速度快
    适合处理高并发任务,提升程序响应能力和并行度。

  • 便于实现异步和并发操作
    可在同一进程中同时执行多个任务,提高CPU利用率。


3. 线程的劣势

  • 线程安全问题
    共享内存导致数据竞争和同步问题,需使用锁、信号量等机制防护。

  • 调试复杂
    多线程程序容易出现死锁、竞态条件,调试和维护难度大。

  • 线程崩溃风险
    线程异常可能导致整个进程崩溃,影响系统稳定性。

  • 不适合CPU密集型多线程(依赖硬件核心数)
    线程数量过多会导致上下文切换频繁,反而降低性能。


常见误区或面试陷阱

  • 将线程和进程混淆,认为线程拥有独立内存空间。

  • 忽略线程共享资源带来的安全隐患。

  • 误以为多线程一定带来性能提升,忽视上下文切换成本。


总结

进程是操作系统资源分配和调度的基本单位,拥有独立内存空间;线程是进程内的执行单位,资源共享但调度开销低。线程适合提高并发和响应速度,但带来同步安全和调试复杂性,需要权衡使用。

16. 进程通信有哪些方式?它们各自的原理和适用场景是什么

题目要点

管道 / 有名管道 / 消息队列 / 共享内存 / 信号量 / 套接字 / 信号 / 适用场景 / 同步机制

参考答案

考察点

● 理解进程间通信(IPC)的必要性及基本方式

考察候选人对多进程协作机制的掌握。

● 掌握常见 IPC 方式的工作原理

能描述不同 IPC 的通信机制和实现细节。

● 能结合场景合理选用 IPC 方式


参考答案

1. 进程通信的概念

  • 进程间通信(IPC,Inter-Process Communication)是不同进程之间交换数据和信息的机制。
  • 由于进程拥有独立地址空间,IPC 需要借助操作系统提供的通信渠道。

2. 常见的进程通信方式及原理

通信方式原理描述适用场景及特点
管道(Pipe)单向数据流,父子进程或兄弟进程之间传递数据,通过内核缓冲区实现。适合父子进程通信,简单且高效,但只能单向通信,不支持无亲缘关系进程。
有名管道(Named Pipe/FIFO)具名文件路径,允许无亲缘关系的进程通信,数据流同样为单向。跨进程且无亲缘关系,通信简单,但依赖文件系统,性能稍逊。
消息队列(Message Queue)以消息为单位传输数据,消息有优先级,支持异步通信。适合复杂异步通信,消息有序且支持优先级控制,多进程间灵活通信。
共享内存(Shared Memory)不同进程映射同一块内存区域,进程可直接读写共享内存,速度最快。大量数据传输时性能优越,但需额外同步机制(如信号量)保证数据一致性。
信号量(Semaphore)进程间用于控制共享资源访问的计数器,防止竞争条件。常配合共享内存使用,实现进程间同步和互斥。
套接字(Socket)基于网络协议(TCP/UDP),即使在同一台机器上也可以用来通信,支持全双工。适合跨机器或同机不同进程通信,灵活但开销较大,支持网络通信。
信号(Signal)软中断机制,进程发送信号通知另一进程执行特定操作,如终止、暂停。用于简单的事件通知和进程控制,数据传递量有限。

3. 适用场景示例

  • 管道和有名管道:shell 命令间数据传递,简单父子进程通信。
  • 消息队列:任务调度系统,异步消息处理。
  • 共享内存 + 信号量:高性能大数据交换,如多媒体处理。
  • 套接字:客户端-服务器架构,分布式系统间通信。
  • 信号:进程生命周期管理、异步事件通知。

4. 常见误区或面试陷阱

  • 混淆管道和共享内存的通信效率和复杂度。
  • 忽视共享内存需要同步机制,导致数据竞争。
  • 认为套接字只能用于网络通信,忽略本地进程通信。
  • 低估信号只能传递有限信息,非数据传输手段。

总结

进程通信方式多样,从简单的管道到高效的共享内存和灵活的套接字,各有优缺点和适用场景。选择合适的 IPC 方式需结合数据量、通信复杂度、性能要求及进程关系。

17. 线程的数据安全问题可能出现在哪些场景?如何保证线程的数据安全

题目要点

竞态条件 / 共享变量冲突 / 加锁同步 / 原子操作 / 线程局部存储 / 内存可见性 / 死锁预防

参考答案

考察点

● 理解线程共享内存带来的数据安全隐患

考察候选人对多线程并发问题的认知。

● 掌握常见的线程安全问题场景

能举例说明具体情形及产生原因。

● 熟悉线程安全保障的常用手段和机制


参考答案

1. 线程数据安全问题可能出现的场景

  • 共享变量的读写冲突
    多个线程同时读写同一个变量,导致数据竞态(Race Condition),结果不确定。

  • 非原子操作导致的数据不一致
    复杂操作(如 ++、赋值、多步运算)非原子执行,中断导致数据状态异常。

  • 指令重排和缓存可见性问题
    编译器或 CPU 优化重排序,导致其他线程看到的数据不一致。

  • 死锁和活锁
    线程因互斥资源争用进入死锁,影响程序正常执行。

  • 竞态条件(Race Condition)
    不同线程执行顺序不可控,影响程序逻辑正确性。


2. 保障线程数据安全的方法

2.1 同步机制(加锁)
  • 使用互斥锁(Mutex)、读写锁(ReadWriteLock)等,确保同一时刻只有一个线程访问共享资源。
  • 通过 synchronized(Java)、lock(C++)、mutex(POSIX)等关键字或API实现。
2.2 原子操作
  • 利用硬件支持的原子指令(如 Compare-And-Swap),保证操作不可分割。
  • 语言层面提供原子变量类(如 Java 的 AtomicInteger)。
2.3 线程安全的数据结构
  • 使用设计为线程安全的集合或数据结构(如 Java 的 ConcurrentHashMap)。
2.4 使用线程局部存储(Thread Local)
  • 将变量限定在线程内部,避免共享,消除竞态。
2.5 内存屏障与可见性保证
  • 利用内存屏障(Memory Barrier)防止指令重排序。
  • 使用 volatile 关键字保证变量的可见性。
2.6 避免死锁设计
  • 合理设计锁顺序,减少锁粒度,避免嵌套锁。

常见误区或面试陷阱

  • 认为简单加锁能解决所有线程安全问题,忽视锁的性能开销和死锁风险。
  • 忽略可见性问题,只关注同步导致的数据完整性。
  • 误用锁导致线程饥饿或活锁。
  • 忽略复合操作的原子性,导致隐蔽的竞态条件。

总结

线程数据安全问题多源于共享资源的并发访问,解决方案包含加锁、原子操作、线程局部变量及内存可见性控制等多种技术。设计时应结合性能和安全,避免死锁和竞态,保障程序稳定正确。

18. 什么时候会出现进程的数据安全问题?如何解决这类问题

题目要点

  • 进程间共享资源访问冲突
  • 同步机制(信号量、互斥锁)
  • 文件锁和原子操作
  • 进程间通信协议设计
参考答案

考察点

● 理解多进程环境下数据安全问题的来源

考察候选人对进程间资源共享及数据一致性挑战的认识。

● 掌握常见数据安全问题场景

是否能说明多进程并发访问共享资源时可能导致的冲突。

● 熟悉解决进程数据安全的常用技术手段


参考答案

1. 进程数据安全问题出现的场景

  • 多个进程访问共享内存时数据竞争
    多个进程同时读写共享内存区域,若无同步机制,可能产生脏读、写覆盖等问题。

  • 文件读写冲突
    不同进程同时操作同一文件(写入或读写混合),导致文件内容损坏或不一致。

  • 消息队列或信号量操作不当
    进程间通过消息队列通信时,若未妥善处理同步,会发生消息丢失或重复处理。

  • 进程同步不严格导致状态不一致
    多个进程间缺乏协调,导致操作顺序错乱,出现数据错误。


2. 解决进程数据安全的常用方法

2.1 同步机制
  • 信号量(Semaphore)
    控制多个进程对共享资源的访问权限,避免竞争条件。

  • 互斥锁(Mutex)
    进程间加锁,确保同一时刻只有一个进程访问共享资源。

2.2 原子操作
  • 依赖操作系统或硬件提供的原子指令,保证共享数据操作不可被中断。
2.3 共享内存的访问控制
  • 配合信号量或互斥锁同步共享内存的读写,避免脏数据和数据冲突。
2.4 文件锁(File Lock)
  • 通过文件锁机制(如fcntl锁)防止多个进程同时写入同一文件。
2.5 进程间通信设计规范
  • 合理设计消息队列、管道或套接字通信协议,确保消息完整性和有序性。

3. 总结

进程间数据安全问题主要出现在多个进程并发访问共享资源时。解决方案基于同步机制(信号量、互斥锁)、原子操作及访问控制等手段,确保数据一致性和完整性,避免竞争和冲突。


常见误区或面试陷阱

  • 忽视进程间同步,直接共享内存导致数据混乱。
  • 认为进程间通信天然安全,不考虑消息丢失和重复。
  • 文件操作缺少锁机制,造成数据损坏。

19. 智力题:9个小球中有一个质量不一样,如何用最少的比较次数找到这个球

题目要点

  • 9球异常轻重两种状态共18种情况。

  • 每次称重三分法分组,最大化信息量。

  • 3次称重足够判定异常球及轻重。

参考答案

考察点

● 测试候选人的逻辑思维与问题分析能力

考察解题思路的条理性和优化思考能力。

● 了解候选人如何设计最优方案

检验是否能利用分治和枚举技巧找到最少比较次数。


参考答案

1. 题目描述

有9个小球,其中只有一个小球的质量与其他不同(可能更轻或更重),用天平称重,如何通过最少次数的比较(称重)确定这个异常小球,并判断它是轻还是重?


2. 原理说明

  • 比较次数:利用分治思想,每次称重都尽量将可能的异常球分成几个等份,排除大量正常球。

  • 三分法思想:每次称重将球分成三组,比较两组重量,判断异常球在哪组或是两组之外。

  • 3次称重可区分最多 \(3^3=27\) 种状态,足以覆盖9个球异常且轻重两种可能(9×2=18种情况)。


3. 具体步骤

第一次称重
  • 将9个球分成三组,每组3个(A组、B组、C组)。

  • 把A组和B组放到天平两边称重。

    • 情况1:天平平衡,异常球在C组。

    • 情况2:天平不平衡,异常球在较重或较轻的一组,且可以判断异常是轻还是重。

第二次称重
  • 根据第一次结果,从确定的异常组中取出3个球,继续分为3个1个球的小组。

  • 选择合适的两球进行称重。

  • 根据称重结果进一步缩小异常球范围。

第三次称重
  • 对剩下的2个或1个球进行最终判定,确认异常球及其轻重。

4. 优势

  • 只需3次称重即可确定唯一异常球和轻重状态,达到理论最优。

  • 利用信息熵最大化原则,快速排除大量正常球。


常见误区或面试陷阱

  • 没有考虑异常球轻重两种可能,误认为只需区分9种。

  • 没有合理分组,称重效率低,导致比较次数增加。

  • 未利用三分法思路,导致多余的比较。

20. 智力题:如果是16个小球,又该如何用最少的比较次数找到质量不一样的球

题目要点

  • 16球异常轻重两种状态共32种情况。

  • \(3^k \geq 32\),最少4次称重。

  • 合理分组与逐步缩小异常球范围。

参考答案

考察点

● 考察候选人面对更大规模问题的分解能力与推理能力

考察能否灵活运用信息理论和分治策略。

● 测试优化称重次数的思路和算法设计能力


参考答案

1. 题目描述

有16个小球,其中一个小球的质量与其他球不同(可能更轻或更重),使用天平称重,如何在最少称重次数内确定这个异常球,并判断其轻重?


2. 原理说明

  • 每次称重的信息量有限,天平每次比较有3种可能(左重、右重、平衡)。

  • 称重次数 \(k\) 可区分的状态数最大为 \(3^k\)。

  • 16个球异常且轻重两种可能共有 \(16 \times 2 = 32\) 种情况。

  • 需要满足 \(3^k \geq 32\),计算得:

    • \(3^3 = 27 < 32\)

    • \(3^4 = 81 \geq 32\)

  • 因此,最少需要4次称重


3. 实现步骤(思路)

第一次称重
  • 将16个球分成3组,尽量均分,如5、5、6。

  • 称重两组5个球:

    • 若平衡,异常球在6个球组。

    • 若不平衡,异常球在较重或较轻的一组,且知道异常是重还是轻。

第二次称重
  • 对异常组(5或6个)继续分组,进行称重缩小范围。

  • 每次称重都基于已知轻重方向合理分组,保证最大排除。

第三次称重
  • 缩小到3个或更少的球,进一步确认。
第四次称重
  • 最终确定唯一异常球及其轻重。

4. 优势

  • 通过数学推导和信息理论,保证称重次数最优。

  • 合理分组和称重策略,确保每次称重信息最大化。


常见误区或面试陷阱

  • 误认为仍能用3次称重解决16球问题,忽视状态数量。

  • 分组不均或不合理,导致称重信息浪费。

  • 未结合轻重状态设计分组,称重判断错误。

21. 手撕代码:实现大数相加功能,输入两个大数字符串,返回相加后的结果字符串

题目要点

  • 字符串倒序遍历模拟加法。

  • 进位处理。

  • 结果反转拼接。

参考答案

考察点

● 理解大数处理的基本思路

考察候选人是否能处理超过语言内置数值范围的大数运算。

● 掌握字符串操作及算法实现能力

测试对字符串倒序遍历、进位处理等细节的掌握。

● 代码规范和边界条件考虑


参考答案

/**
 * 实现两个大数字符串相加,返回结果字符串
 * @param {string} num1 - 大数字符串1
 * @param {string} num2 - 大数字符串2
 * @return {string} - 相加结果字符串
 */
function addBigNumbers(num1, num2) {
  let i = num1.length - 1;
  let j = num2.length - 1;
  let carry = 0;    // 进位
  let result = [];

  while (i >= 0 || j >= 0 || carry) {
    const digit1 = i >= 0 ? Number(num1[i]) : 0;
    const digit2 = j >= 0 ? Number(num2[j]) : 0;

    const sum = digit1 + digit2 + carry;
    carry = Math.floor(sum / 10);
    result.push((sum % 10).toString());

    i--;
    j--;
  }

  // 结果是倒序的,需要反转
  return result.reverse().join('');
}

代码说明

  • 从字符串尾部开始逐位相加,模拟手工加法过程。

  • 处理不同长度的数字,通过判断索引是否越界补0。

  • 使用 carry 保存进位信息。

  • 最后将结果数组反转拼接成字符串。


常见误区或面试陷阱

  • 只处理了相同长度数字,忽略长度差异。

  • 忘记最后一位进位。

  • 误用数字转字符串方法导致拼接错误。

22. 项目中如何实现无限加载功能?如果面对一亿条数据,有哪些优化策略可以提高加载效率和性能

题目要点

滚动触发 / 游标分页 / 虚拟列表 / 防抖节流 / 数据索引 / 缓存机制 / 用户体验

参考答案

考察点

● 理解无限加载(Infinite Scroll)核心实现机制

考察候选人对前端数据分页和懒加载的掌握。

● 掌握大数据量场景下的性能和体验优化方法

检验其对前后端协同、数据处理和性能调优的系统思考。


参考答案

1. 无限加载功能的实现

1.1 核心原理
  • 页面滚动到接近底部时,触发请求加载下一批数据(分页加载)。
  • 通过监听滚动事件或使用Intersection Observer API判断是否需要加载。
  • 新数据追加到列表,实现“无限”加载。
1.2 关键点
  • 分页接口设计:后端返回分批数据,通常使用页码或游标(cursor)方式。
  • 防抖节流:避免滚动事件频繁触发请求。
  • 加载状态管理:显示加载动画,避免重复请求。
  • 错误处理和重试机制

2. 面对一亿条数据的优化策略

2.1 后端优化
  • 游标分页(Cursor-based Pagination)
    避免传统页码分页带来的性能瓶颈,使用唯一排序字段作为游标,高效定位下一批数据。

  • 数据索引优化
    建立合理索引,减少数据库扫描时间。

  • 接口返回精简数据
    只返回前端展示必须字段,减小网络负载。

  • 数据分片与分区
    数据库层面分区,提升查询效率。

2.2 前端优化
  • 虚拟列表(Virtual Scrolling)
    只渲染视口内及附近元素,极大减少 DOM 节点数,提升渲染性能。

  • 预加载和缓存
    适当提前加载下一批数据,缓存已加载数据,减少重复请求。

  • 合理限制加载频率
    结合防抖节流,控制请求次数,避免网络拥堵。

  • 分页大小调优
    根据用户网络环境和设备性能,动态调整单次加载条数。

2.3 网络和用户体验
  • 使用CDN缓存接口响应
    减少请求延迟。

  • 接口压缩和合并
    减少网络传输大小。

  • 用户友好反馈
    加载时显示动画、错误提示,提升体验。


常见误区或面试陷阱

  • 盲目使用页码分页导致数据库性能下降。

  • 不使用虚拟列表,导致页面卡顿和内存溢出。

  • 滚动事件监听未节流,导致请求泛滥。

  • 加载时无提示,用户体验差。


总结

无限加载核心在于分页加载和懒渲染,面对亿级大数据需从后端分页游标、索引优化,到前端虚拟列表和缓存等多方面协同提升,确保性能和体验的平衡。


← 已是第一轮 · 返回本次面经 · 已是最后一轮 →