哔哩哔哩-大会员中心-前端二面

14 天前(已编辑)
/ , ,
16

阅读此文章之前,你可能需要首先阅读以下的文章才能更好的理解上下文。

哔哩哔哩-大会员中心-前端二面

📖 面试问题

1. 浏览器从输入到显示页面的全过程

  • 用户在浏览器地址栏输入 URL,浏览器通过 DNS 解析获取 IP 地址。
  • 浏览器与服务器建立 TCP 连接,发起 HTTP 请求。
  • 服务器返回 HTML 文件,浏览器解析 HTML、CSS、JavaScript,构建 DOM 树、CSSOM 树,合并成 Render 树。
  • 浏览器渲染页面,进行布局、绘制,最后显示页面。

2. TCP 三次握手过程

  • 第一次握手:客户端向服务器发送 SYN 包,表示请求建立连接。
  • 第二次握手:服务器收到请求后,发送 SYN-ACK 包,表示同意建立连接。
  • 第三次握手:客户端收到 SYN-ACK 包后,发送 ACK 包,确认连接建立。

3. 为什么要 TCP 四次挥手

  • 第一次挥手:客户端发送 FIN 包,表示没有数据发送了。
  • 第二次挥手:服务器收到 FIN 包后,确认连接关闭,返回 ACK 包。
  • 第三次挥手:服务器发送 FIN 包,表示服务器端也没有数据发送了。
  • 第四次挥手:客户端确认收到服务器的 FIN 包后,发送 ACK 包,连接完全关闭。

4. 浏览器缓存策略

  • 强缓存:通过 Cache-ControlExpires 控制,缓存文件有效期内不会向服务器发送请求。
  • 协商缓存:通过 ETagLast-Modified 等进行验证,决定是否从缓存中读取文件,或重新向服务器请求。

5. 讲一下 HOC(高阶组件)

  • 高阶组件(HOC):是 React 的一种模式,指的是一个函数,它接受一个组件并返回一个新的组件,通常用于逻辑复用和组件装饰。

6. 登录权限判断逻辑

  • 检查用户是否已经登录:查看存储的 token 或 session。
  • 如果没有登录,重定向到登录页面;如果已登录,检查用户权限,决定是否允许访问某些页面。
  • localStorage:存储的数据不随每次请求自动发送,适合存储不需要随请求一起发送的数据(如 token)。
  • cookie:会随着每次请求自动发送,适合存储会话信息,但会增加每次请求的负担,且存在一定的安全隐患(如 CSRF 攻击)。

8. React 动态路由怎么实现的

  • 使用 React Router 的 <Route> 配置动态路径,可以通过 useParams 获取动态参数。
  • 例如,<Route path="/user/:id" component={User} />useParams 获取 id

9. Suspense 的实现原理

  • Suspense 是 React 用于处理异步渲染的组件,它会在异步操作完成之前渲染一个备用 UI(如加载动画)。React 会通过 React.lazy 动态加载组件并配合 Suspense 进行渲染。

10. 首屏加载优化做了哪些

  • 减少首屏渲染的 JavaScript 和 CSS 文件大小,使用代码分割(Code Splitting)。
  • 使用懒加载(Lazy Loading)和图片懒加载(Lazy Image)。
  • 优化 WebFont 加载,使用字体显示策略(如 font-display: swap)。
  • 使用服务端渲染(SSR)或静态生成(SSG)提高首屏加载速度。

✍️ 手撕题目

1. 事件总线(EventBus)

  • EventBus 是一个自定义的事件管理系统,用于不同组件之间通信。
  • 它通过 on 订阅事件、emit 触发事件、off 移除事件监听来实现松耦合的组件间通信。
class EventBus {
  constructor() {
    this.events = {};
  }

  on(event, fn) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(fn);
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(fn => fn(...args));
    }
  }

  off(event, fn) {
    if (!this.events[event]) return;
    const index = this.events[event].indexOf(fn);
    if (index !== -1) this.events[event].splice(index, 1);
  }
}

2. 版本号排序

  • 给定版本号 v1.0.1, v1.0.2, v1.1.0 等,要求按照版本号大小排序。
  • 可以通过拆分版本号字符串并逐个比较各部分大小来进行排序。
function compareVersion(v1, v2) {
  const v1Parts = v1.split('.').map(Number);
  const v2Parts = v2.split('.').map(Number);

  for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
    const v1Part = v1Parts[i] || 0;
    const v2Part = v2Parts[i] || 0;

    if (v1Part > v2Part) return 1;
    if (v1Part < v2Part) return -1;
  }
  return 0;
}

const versions = ['v1.0.1', 'v1.1.0', 'v1.0.2'];
versions.sort(compareVersion);
console.log(versions); // ['v1.0.1', 'v1.0.2', 'v1.1.0']

3. 版本号排序 plus(如 v1.0.1-beta, v1.0.1-alpha)

  • 在版本号中存在附加的后缀(如 alpha 或 beta),需要先比较数字版本部分,再比较后缀。
function compareVersionWithTag(v1, v2) {
  const [v1Version, v1Tag] = v1.split('-');
  const [v2Version, v2Tag] = v2.split('-');

  const versionComparison = compareVersion(v1Version, v2Version);
  if (versionComparison !== 0) return versionComparison;

  // 若版本号相同,按后缀比较
  const tags = ['alpha', 'beta', 'rc', 'stable'];
  return tags.indexOf(v1Tag || 'stable') - tags.indexOf(v2Tag || 'stable');
}

const versionsWithTag = ['v1.0.1-beta', 'v1.0.1-alpha', 'v1.0.1-stable'];
versionsWithTag.sort(compareVersionWithTag);
console.log(versionsWithTag); // ['v1.0.1-alpha', 'v1.0.1-beta', 'v1.0.1-stable']

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...