面灵AI→

得物秋招客户端一面:前端八股与实习项目深挖

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 请做一下自我介绍。
  2. 详细讲一讲你的实习项目。
  3. 事件循环(Event Loop)是什么?
  4. Node.js 是如何实现高并发的?
  5. JavaScript 为什么是单线程的?如果是多线程会怎么样?
  6. TypeScript 和 JavaScript 有什么区别?平时开发中怎么使用 TypeScript?
  7. 谈谈 Vue 和 React 的区别。
  8. 一张图片从页面打开到渲染完成,中间经历了哪些过程?
  9. 你的技术栈是自学的吗?学习过程是怎样的?
  10. 项目里做过性能优化,讲讲背景、过程和结果。
  11. 项目中的后端是怎么做的?
  12. 业务开发中碰到问题,你会如何排查?
  13. 设计一个日志库组件,讲讲你的思路。
  14. 工作中碰到过的难点是什么?是怎么解决的?
  15. 反问:得物内部的客户端是否偏原生开发?

《参考解析》

事件循环与 JavaScript 的单线程模型

浏览器里只有一个 JS 执行线程,靠事件循环调度:先跑完同步代码清空调用栈,再清空微任务队列(Promise.then、queueMicrotask、MutationObserver),然后取一个宏任务(setTimeout、I/O 回调、用户交互)执行,如此往复;渲染时机卡在一个宏任务结束后、下一个宏任务开始前,所以主线程被长任务占住就会直接掉帧。Node 的分层不同:timers → pending callbacks → poll → check(setImmediate)→ close,微任务在每个阶段之间清空,且 process.nextTick 优先于 Promise。

JS 之所以是单线程,跟它的出身有关:主要工作是操作 DOM 和响应交互,多线程同时改同一棵树需要锁与同步,竞态和死锁的成本远高于收益,单线程加事件驱动反而让模型足够简单。真需要并行时规范给的是受限方案:Web Worker 跑独立线程但没有 DOM、只能靠 postMessage 通信,GPU 计算走 WebGL/WebGPU。多线程会带来的问题主要是共享状态的竞态与锁开销、死锁、线程创建的内存成本,以及调试难度上升——这也是 Node 选择「单线程事件循环 + 线程池」而不是「一请求一线程」的原因。

Node.js 的高并发从哪来

关键不是「快」,而是同一时间能挂住大量连接而不为每个连接开线程:网络 I/O 交给内核的 epoll/kqueue,文件、DNS、加密这类会阻塞的操作交给 libuv 线程池(默认 4 个,可用 UV_THREADPOOL_SIZE 调),回调回到事件循环继续执行。所以业务代码里只要不出现同步阻塞调用(readFileSync、大循环、CPU 密集计算、JSON.parse 超大字符串),上万并发连接的内存与上下文切换成本都远低于线程模型。

CPU 密集的任务必须拆出去:worker_threads 或 child_process 单开进程,服务端一般用 cluster 起满 CPU 核数、前面挂 Nginx 做转发;进程保持无状态,会话与共享数据放 Redis 等外部存储,才能水平扩容。追问常延伸到这里:Node 适合 IO 密集、不适合长计算,以及多进程下的连接数、优雅重启和内存泄漏排查。

TypeScript 与 JavaScript

TS 是 JS 的超集,增加的静态类型系统只存在于编译期,产物仍是 JS。开发中真正的收益有三块:IDE 的补全与跳转、重构时能把受影响的调用点全部找出来、把接口契约直接写进代码(后端返回结构、组件 props、状态取值),配合 strict、noUncheckedIndexedAccess 能把大量 undefined 与拼写错误挡在提交之前。

工程上要守住两条边界:外部数据(接口、本地存储、URL 参数)是不可信的,要用运行时校验(zod 一类)收口,而不是一句 as 断言过去;泛型和类型体操控制在团队能维护的复杂度内,类型是给人看的,不是炫技。平时开发的用法可以照着「新代码全量 TS + 老代码渐进迁移 + CI 里跑类型检查」讲。

Vue 与 React 的差异

最本质的差别在更新模型:Vue 用模板 + 响应式系统(Vue 3 是 Proxy 做依赖收集),数据变了知道是哪个组件、哪个依赖用到了它,更新粒度更细、心智负担低;React 走不可变数据 + setState 触发重渲染,靠调度器(Fiber、并发特性)和 diff 决定实际要改哪些 DOM,写起来更接近纯函数、对 JS 功底要求更高。其余差异多在工程习惯上:模板 vs JSX、状态管理(Pinia vs Redux/Zustand)、Hooks 的依赖与闭包陷阱、SSR 方案(Nuxt vs Next)。能力上两者没有明显差距,选型更多看团队与生态;客户端方向的延伸题通常是跨端方案怎么选(React Native、Flutter、Taro、uni-app 的取舍)。

一张图片从打开到渲染完成

网络侧:命中 DNS 缓存 → 建连(HTTP/3 走 QUIC,否则 TCP + TLS)→ 发请求,先查强缓存(Cache-Control 未过期就直接读本地),再走协商缓存(ETag/If-Modified-Since 换 304),都没有才回源。图片请求通常由渲染引擎的预加载扫描器在解析 HTML 阶段就发起,跟 CSS/JS 抢带宽,所以头部资源顺序和优先级提示(fetchpriority、preload)会影响首屏;拿到字节后交给解码器解成位图,WebP/AVIF 比 PNG/JPEG 体积小得多,srcset/sizes 让浏览器按 DPR 选合适尺寸。

渲染侧:HTML 解析成 DOM、CSS 解析成 CSSOM(两者都阻塞渲染,JS 还阻塞解析)→ 合成 Render Tree(display:none 的不在其中)→ Layout 计算几何位置 → Paint 生成绘制指令 → Composite 合成图层交给 GPU 上屏。图片常见的两个坑是没写宽高导致布局抖动(要留 aspect-ratio 或固定尺寸),以及大图解码占住主线程(用 decode()、loading="lazy"、骨架占位缓解)。

性能优化这道题怎么答

按「指标 → 定位 → 手段 → 结果」四步讲,别一上来就报手段。指标层先定清楚:首屏/LCP、交互延迟 INP、包体积、接口 P95;定位靠 Lighthouse、Performance 面板的长任务火焰图加真实用户埋点;手段分四类——资源(体积裁剪、代码分割、图片与字体优化、CDN 与缓存策略)、请求(并发控制、预请求、接口合并与缓存)、渲染(减少重排重绘、长列表虚拟滚动、避免主线程长任务)、构建(tree-shaking、按需引入)。结果必须给数字与口径:从多少降到多少、在什么机型与网络下测的,以及有没有副作用。

日志库组件怎么设计

先定接口:log(level, message, context),级别要能按模块动态开关(线上默认 warn、排查时临时放开 debug)。然后四块展开:结构化(时间、模块、traceId、用户标识、环境信息,方便检索与串联链路);输出可插拔(控制台、文件、远端上报各有 transport,实现上互不耦合);性能与可靠性(异步批量写、队列上限与丢弃策略、采样与限流、页面卸载用 sendBeacon 兜住最后一批,绝不在主流程里同步阻塞);安全(手机号、身份证、密码、token 必须脱敏)。最后要主动讲清取舍:日志量、上报丢失、采样率与排查效率之间的平衡,以及与业务代码的解耦方式。

业务问题怎么排查

先复现和固定证据:用户描述、截图、发生时间点、traceId,能本地复现就本地复现。再分层定位——是数据错、接口错还是渲染错:看 Network 里的请求与响应、看前端状态与埋点、看服务端日志,用二分法缩小范围(换账号/换环境、回滚最近一次发布、注释掉可疑分支),必要时补一段临时埋点把中间值打出来。定位之后先止血(回滚、开关、降级)再修根因,最后补上防复发的监控与用例。这一题考的其实是「有没有一套稳定的排查顺序」,而不是某一次具体的 debug 经历。

反问环节透露的信息

面试官的回答是:客户端方向跨端与原生都有,团队里前端转全栈很常见,没学过的技术栈允许结合 AI 工具上手写原生或跨端。这句话说明岗位对跨栈意愿的接受度比较高,方向不完全对口也不是硬门槛;反问时可以顺着问团队里跨端与原生的比例、新人上手路径、以及转正或培养机制这类具体问题。