前端面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

前端阅读 05月30日 22:48

whistle 如何和浏览器、IDE、构建工具一起调试?

Whistle 和开发工具集成时,核心思路不是“装越多插件越好”,而是把它放在请求链路中最容易观察和改写的位置。DevTools 看页面行为,IDE 编辑规则,构建工具跑本地服务,whistle 统一接住请求。同一个接口问题可以从页面、代理、服务端交叉验证。取舍也很明显:链路越完整,排查能力越强,但配置层级越多,误配概率也越高。最基础的集成是浏览器代理。Chrome、Edge、Firefox 都可配置系统代理,也可只给调试浏览器加启动参数。w2 start -p 8899/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir=/tmp/chrome-whistle --proxy-server=http://127.0.0.1:8899这种方式适合排查登录、Cookie、CORS、缓存和重定向。DevTools 是页面侧视角,适合看瀑布图和前端报错;whistle 是代理侧视角,适合改响应、替换 host、保存规则。HTTPS 调试要先安装 whistle 根证书,否则只能看到 CONNECT,无法看到明文请求。IDE 集成更简单,重点是让规则文件进项目。VS Code 可以用普通文本编辑规则,WebStorm 可以同时配置 HTTP Client 和项目代理。团队协作时,建议把常用规则放进仓库:# local-api.rulesapi.example.com host 127.0.0.1:3000static.example.com file:///Users/me/project/mock-statichttps://api.example.com/debug reqHeaders://{"x-debug":"1"}构建工具不要盲目再代理一次。Vite、Webpack、Next.js 都能转发 /api,如果它们和 whistle 同时改 host,排查会很绕。稳妥方案是:构建工具只跑前端页面,跨域、Mock、弱网和接口替换交给 whistle。// vite.config.jsexport default { server: { proxy: { '/api': { target: 'http://127.0.0.1:8899', changeOrigin: true } } }}追问浏览器 DevTools 已经能看 Network,为什么还要 whistle?DevTools 擅长观察,但不擅长批量改写和复用规则。whistle 可以把 host 替换、响应 Mock、请求头注入、弱网模拟保存成规则,下一次不用重新点一遍。两者不是替代关系,DevTools 看页面表现,whistle 控制网络入口。边界是性能分析仍然优先看 DevTools,因为它能关联渲染、脚本和资源加载时间。VS Code 插件是必须的吗?不是必须,规则本质上是文本,能编辑、能保存、能被团队复用就够了。插件的价值主要是语法高亮和减少拼写错误,不应该依赖插件才能跑通流程。真正容易踩坑的是规则只存在某个人本机 whistle 里,换机器就复现不了。建议把关键规则文件放进项目,并说明启动端口和适用环境。Vite 或 Webpack 的 proxy 和 whistle 会冲突吗?会,尤其是两边都配置了 /api 转发或 host 替换时,请求路径会变得不直观。一般建议本地开发服务器只服务前端资源,把接口层交给 whistle;如果项目已有 devServer proxy,就让它转到 whistle 统一分流。这样牺牲了一点配置简洁度,但换来更稳定的调试入口。排查时按浏览器、devServer、whistle、后端的顺序逐层确认。和 Postman、curl 这类接口工具怎么配合?接口工具可以直接把代理设成 127.0.0.1:8899,或者临时使用环境变量。这样请求会经过 whistle,便于复用同一套 Mock 和 header 规则。命令行可以这样跑:HTTPS_PROXY=http://127.0.0.1:8899 curl https://api.example.com/user -k。注意 -k 只适合本地调试,真实证书问题不能靠它掩盖。团队共用 whistle 规则有什么风险?最大风险是规则过期但没人知道,导致新人调试时命中了旧环境。另一个坑是规则过宽,例如只写 example.com host 127.0.0.1,把静态资源、登录和接口全改掉。团队规则应该小而明确,按场景拆分,并在注释里写清楚用途和失效条件。需要经常变化的个人规则,不要混进公共规则。
前端阅读 05月30日 22:48

whistle 如何接入自动化测试并稳定 Mock 网络请求?

Whistle 可以接入自动化测试,但它更适合作为“可控网络层”,而不是替代测试框架。Jest、Playwright、Puppeteer 负责断言和驱动浏览器,whistle 负责代理、Mock 和改包。这样测试代码不用侵入业务接口,也能复现弱网、跨域和接口异常。边界也要说清楚:如果只是单元测试,直接 mock 函数更轻;只有涉及浏览器真实网络、HTTPS 或 WebSocket 时,whistle 的价值才明显。先把 whistle 作为测试前置服务启动,端口固定,避免 CI 和本机不一致。npm i -D jest puppeteernpm i -g whistlew2 start -p 8899w2 stop浏览器侧只需要把代理指向 whistle。Puppeteer 的配置通常放在测试 setup 里,Playwright 也有类似的 proxy 选项。const browser = await puppeteer.launch({ headless: 'new', args: ['--proxy-server=http://127.0.0.1:8899']})Mock 规则建议单独维护,不要散落在测试用例里。比如把线上 API 改到本地服务,或者直接返回固定 JSON:api.example.com host 127.0.0.1:3000https://api.example.com/user resBody://{"code":0,"name":"mock-user"}https://api.example.com/user resHeaders://{"content-type":"application/json"}在 CI 里要额外处理两个细节:第一,等待 whistle 真正启动后再跑测试,不能只 sleep 一秒就赌运气;第二,HTTPS 场景需要提前安装并信任 whistle 根证书。测试环境可临时加 ignoreHTTPSErrors,但它会掩盖证书链问题,不适合验证支付、登录链路。追问Jest、Playwright 和 Puppeteer 该选哪个?如果测试重点是函数和组件逻辑,Jest 足够,没必要引入 whistle。只要验证真实页面请求、跳转、Cookie 或跨域行为,就应该用 Playwright 或 Puppeteer 驱动浏览器,再让 whistle 接管网络。Playwright 在多浏览器和等待机制上更省心,Puppeteer 更贴近 Chrome 调试。取舍点是维护成本:端到端测试越真实,运行越慢,也越容易受环境影响。Mock 数据应该写在 whistle 规则里还是测试代码里?稳定的接口替身适合写成 whistle 规则,因为规则可以被开发、测试和 CI 复用。和断言强相关的一次性数据,可以放在测试代码里,避免规则文件变成没人敢改的“黑盒”。踩坑最多的是两边都写,最后不知道哪个 Mock 生效。建议给规则文件加命名约定,例如 e2e-login.rules。HTTPS 自动化测试为什么经常失败?最常见原因是证书没有被系统或浏览器信任,whistle 能看到请求,但页面侧直接阻断。另一个坑是某些客户端启用了证书固定,代理解密会失败,这类场景只能做旁路抓包或在测试包关闭 pinning。CI 容器里还要注意证书安装路径,不同镜像的信任库不一样。不要把 ignoreHTTPSErrors 当万能药,它只能让浏览器继续跑,不能证明 HTTPS 链路真的没问题。如何判断 whistle 规则真的被测试用例命中了?不要只看页面断言通过,还要检查网络侧是否经过预期域名和规则。可以在 whistle 界面查看请求,也可以让被 Mock 的响应返回测试专用字段,例如 x-mock-source: whistle-e2e。如果接口被浏览器缓存,规则可能根本没走,所以测试前最好禁用缓存或给请求加唯一 query。定位失败时先验证代理配置,再验证规则匹配,最后看业务断言。什么时候不该把 whistle 放进自动化测试?纯算法、纯组件渲染和后端接口契约测试,不需要 whistle,直接 mock 或启动本地服务更快。whistle 适合解决“网络路径不可控”的问题,不适合承担所有测试隔离职责。规则过多会让测试变慢,也会让新人很难理解真实请求链路。一个实用边界是:只有当问题必须在浏览器代理层复现时,才把它纳入 whistle 自动化测试。
前端阅读 05月30日 21:29

SameSite Cookie 如何防护 CSRF?Strict、Lax、None 怎么选?

SameSite Cookie 防护 CSRF 的关键是让浏览器在跨站请求里少带或不带 Cookie。CSRF 成立,是因为用户登录后,攻击站能诱导浏览器向目标站发请求,而浏览器会自动附带登录 Cookie。SameSite 改变的就是这个默认行为。追问Strict、Lax、None 怎么选?Strict 最严格,适合后台、支付、改密等安全优先页面,但会影响从外部链接进入的登录体验。Lax 是多数业务默认选择;None 只给确实需要跨站携带 Cookie 的场景,并必须配 Secure。SameSite=Lax 能完全替代 CSRF Token 吗?不能。Lax 能挡住大量跨站 POST,但覆盖不了所有业务边界。资金、权限、账号安全仍应保留 Token。SameSite=None 为什么必须配 Secure?因为 None 允许跨站携带 Cookie,现代浏览器要求它只能在 HTTPS 下使用。少了 Secure,很多浏览器会拒收。子域名之间算跨站吗?通常不算。a.example.com 和 b.example.com 在 SameSite 语义下属于同站,因此子域名安全会影响整体站点。写段配置Set-Cookie: sid=abc; Path=/; HttpOnly; Secure; SameSite=LaxSet-Cookie: admin_sid=def; Path=/admin; HttpOnly; Secure; SameSite=Strict
前端阅读 05月30日 21:21

RxJS 中 Subject、BehaviorSubject、ReplaySubject 和 AsyncSubject 怎么选?

Subject 是既能订阅又能 next 的 Observable,常用来把外部事件推给多个订阅者。Subject 不保存历史值;BehaviorSubject 保存当前值,新订阅者立刻拿到;ReplaySubject 可以回放一段历史;AsyncSubject 只在 complete 时发出最后一个值。选型时先问:新订阅者要不要旧值,要几个旧值,结果是不是只有完成后才有意义。追问BehaviorSubject 为什么适合状态?状态通常需要当前值,比如用户信息、主题配置、表单快照。它要求初始值,所以你必须明确空状态是什么。ReplaySubject 有什么边界?ReplaySubject 可以保留多个值或一段时间窗口。如果不设置 bufferSize 或 windowTime,历史值可能越攒越多。AsyncSubject 现在还常用吗?比较少,因为很多只取最终结果的场景已被 Promise 或 lastValueFrom 覆盖。它适合只关心完成后最后值的过程。Subject 当事件总线有什么坑?全局事件总线会让数据流来源变模糊。更稳的做法是封装 Subject,只暴露 asObservable。写段代码const currentUser$ = new BehaviorSubject(null);currentUser$.next(user);currentUser$.subscribe(render);
前端阅读 05月30日 21:21

RxJS 中 switchMap、mergeMap、concatMap 该怎么选?

switchMap、mergeMap、concatMap 都是把外层值映射成内部 Observable,再把结果摊平,但它们处理“新任务来了,旧任务怎么办”的策略不同。switchMap 会取消旧任务,只要最新结果;mergeMap 会并发执行,所有结果都要;concatMap 会排队,一个完成后再做下一个。追问为什么搜索框通常用 switchMap?用户连续输入时,旧关键词请求已经没有展示价值。switchMap 会退订上一次内部流,避免慢请求晚返回覆盖新结果。mergeMap 有什么风险?mergeMap 默认不限制并发,外层值很多时会同时打出大量请求。实际项目常用第二个参数限制并发数。concatMap 为什么慢但常用?它故意排队,前一个内部 Observable 不 complete,后一个不会开始。适合顺序保存、支付步骤、分片上传。和 exhaustMap 怎么区分?exhaustMap 忙的时候忽略新任务,适合防重复提交。switchMap 抛弃旧的保留新的,exhaustMap 保留旧的忽略新的。写段代码searchText$.pipe( debounceTime(300), distinctUntilChanged(), switchMap(q => apiSearch(q).pipe(catchError(() => of([]))))).subscribe(renderList);
前端阅读 05月30日 21:21

RxJS 6 升级到 RxJS 7 时要注意哪些变化?

RxJS 7 不是把 RxJS 6 推倒重来,而是在保持 pipe 操作符模型的基础上,重点修了类型、弃用 API、Promise 转换和多播写法。项目从 6 升到 7,通常不用大面积重写业务流,但要重点检查 throwError、toPromise、combineLatest/concat/merge 的静态调用、shareReplay 配置,以及 TypeScript 版本和严格类型报错。追问最大的破坏性变化是什么?toPromise 被废弃,应该改成 firstValueFrom 或 lastValueFrom。前者拿第一个值后退订,后者等流 complete 后取最后一个值。throwError 为什么改成工厂函数?RxJS 7 推荐 throwError(() => error),错误对象会在订阅时创建,堆栈更准确。导入路径需要怎么改?多数项目仍从 rxjs 和 rxjs/operators 导入。更重要的是清理 rxjs/internal/* 深层导入。shareReplay 有什么坑?缓存 HTTP 时常写 shareReplay(1),但要考虑 refCount 和重置策略,否则长期服务可能一直持有缓存。写段代码const source$ = throwError(() => new Error('request failed'));const value = await firstValueFrom(apiResult$);
前端阅读 05月30日 21:21

RxJS 性能优化应该从哪些地方下手?

RxJS 性能优化先看三件事:有没有重复订阅,有没有处理过多无效事件,有没有订阅生命周期失控。多数慢不是操作符本身慢,而是同一个 HTTP Observable 被订阅多次、输入框每个字符都打接口、组件销毁后流还在跑。优化时不要一上来堆操作符,先用浏览器 Network、Performance 和简单日志确认瓶颈在哪里。追问share 和 shareReplay 应该怎么选?share 只共享当前订阅期,晚来的订阅者拿不到历史值。shareReplay(1) 会回放最近一次结果,适合 HTTP 缓存,但要注意 refCount 和生命周期。为什么搜索框通常用 switchMap?用户连续输入时,旧关键词的请求结果已经没价值了。switchMap 会取消上一次内部订阅,避免慢请求晚返回覆盖新结果。mergeMap 限制并发有什么意义?批量请求如果不限制并发,浏览器连接数、服务端限流和内存都会被打满。可以用第二个参数控制同时执行数量。怎么避免内存泄漏?模板里优先用 async pipe;必须手动订阅时,用 takeUntilDestroyed() 或明确的 takeUntil(destroy$)。写段代码from(ids).pipe( mergeMap(id => api.load(id), 3)).subscribe();
前端阅读 05月30日 20:38

Appium 的核心特性是什么?为什么适合跨平台移动自动化?

Appium 是基于 W3C WebDriver 协议的移动端自动化框架,核心价值是用同一套测试思路覆盖 Android、iOS、移动 Web 和混合应用。它不是自己去“点屏幕”,而是由客户端把命令发给 Appium Server,再交给 UiAutomator2、XCUITest 等平台驱动执行。适合黑盒回归、跨平台主流程验证和 CI 冒烟测试,但不适合替代单元测试或追求极限执行速度的白盒 UI 测试。追问Appium 和 Espresso、XCUITest 怎么取舍?只测 Android 且能接触源码时,Espresso 通常更快更稳;只测 iOS 时,XCUITest 的系统集成更直接。Appium 的优势是跨平台和黑盒测试。Appium 为什么不需要重新编译 App?它通过系统自动化框架驱动已安装应用,不要求在业务代码里埋测试 SDK。边界是安全控件、系统弹窗或 WebView 调试开关可能仍要测试包配合。元素定位最容易踩什么坑?最常见的是依赖 XPath 全路径,页面层级变化就全挂。更稳的是让客户端补 accessibility id 或 resource-id。混合应用测试要注意什么?进入 WebView 前要确认调试已开启,并等待 context 出现后再切换。切回原生时也要显式切到 NATIVE_APP。CI 里跑 Appium 有哪些边界?CI 适合跑登录、下单、关键链路等少量稳定用例。设备占用、模拟器启动和网络抖动都会放大失败率。写段命令npm i -g appiumappium driver install uiautomator2appium driver install xcuitestappium --base-path /wd/hub
前端阅读 05月30日 20:13

Appium 测试运行慢时如何定位和优化?

Appium 测试慢,通常是定位、等待、会话创建、设备资源和网络链路叠在一起变慢。优化前先量化:哪一步耗时最长,是启动应用、找元素、切 WebView、执行手势,还是每条命令都慢。没有数据就调参数,很容易把稳定性也一起调没。追问元素定位为什么会拖慢 Appium?每次定位都要跨进程和设备通信,复杂 XPath 还会遍历 UI 树。优先用 id、accessibility id,其次平台特定选择器。隐式等待和显式等待怎么取舍?隐式等待影响几乎每次查找,定位失败尤其拖时间;显式等待只等关键条件,更容易控制成本。少用固定 sleep。会话复用一定更快吗?会话复用能省安装、启动和授权时间,但会带来脏数据、登录态残留和用例污染。冒烟可用 noReset,首次启动类用例要干净环境。并行为什么有时更慢?瓶颈可能在 Appium Server、设备 CPU、USB、模拟器资源和后端环境。Android 并行要给每台设备独立 udid 和 systemPort。性能监控看什么?先看 Appium 命令耗时,再看应用 CPU、内存、网络和日志。所有命令都慢查网络和设备负载,只有定位慢就查选择器。写段配置const capabilities = { automationName: 'UiAutomator2', noReset: true, disableWindowAnimation: true, newCommandTimeout: 60};
前端阅读 05月30日 20:13

Appium 元素找不到或启动失败时如何排查?

Appium 排查不要一上来就改脚本,先把链路拆开:Server 是否可连、设备是否在线、应用是否成功安装和启动、元素是否真的在当前上下文里。很多“找不到元素”不是定位写错,而是页面没加载、弹窗挡住、WebView 没切上下文,或测试连到另一台设备。追问Appium Server 连不上先看什么?先确认端口和服务状态,检查 Appium 版本、4723 是否监听、URL 是否还在用旧的 /wd/hub,以及防火墙或代理。设备连接失败怎么区分?先脱离 Appium,用 adb devices 或 Xcode 设备列表验证设备是否在线。多设备时必须显式写 udid。元素找不到为什么不能只换 XPath?XPath 复杂且慢,层级变化就失效。先用 Appium Inspector 确认元素是否存在,再优先用 id、accessibility id 或平台选择器。应用安装或启动失败卡在哪里?安装失败查路径、包损坏、设备空间和签名冲突;启动失败看 appPackage、appActivity、权限弹窗和 logcat 崩溃日志。点击滑动失败怎么判断?先看元素是否可见、可点击、是否被浮层遮挡,再考虑坐标点击。滑动建议用屏幕比例而非固定像素。写段命令lsof -i :4723appium --log-level debug -p 4723adb devices