前端面试题手册

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

前端阅读 05月30日 22:48

whistle 如何捕获请求并快速过滤定位问题?

whistle 捕获请求的核心流程是:启动本地代理,让浏览器、App 或命令行流量经过它,然后在 Network 面板查看请求、响应、耗时、头信息和匹配规则。它不像浏览器 DevTools 只看当前页面,而是能统一观察多端流量,尤其适合排查 H5、客户端内嵌页、Node 服务请求和第三方 SDK 调用。使用前要明确边界:whistle 只能看到走代理的流量,系统代理没配好、App 不信任证书、命令行绕过代理时,Network 面板自然不会出现请求。先启动服务并打开管理页:npm i -g whistlew2 start -p 8899open http://127.0.0.1:8899/浏览器可以直接配置 HTTP/HTTPS 代理为 127.0.0.1:8899,命令行请求可以这样验证:export HTTP_PROXY=http://127.0.0.1:8899export HTTPS_PROXY=http://127.0.0.1:8899curl -I https://example.com如果要看 HTTPS 明文内容,需要安装并信任 whistle 根证书。证书只用于本机调试,别在公共设备或不可信网络里随便安装;调试结束后可以关闭代理,避免正常访问也继续经过 whistle。Network 面板里最常用的是域名、路径、方法、状态码和关键字过滤。排查接口失败时,先用域名缩小范围,再用 status:500、method:POST、接口路径片段继续收窄。遇到请求太多的页面,直接清空列表后复现一次,比在几千条历史记录里搜索更可靠。追问为什么已经配置代理却抓不到请求?先确认流量来源是否真的走了 127.0.0.1:8899,浏览器、系统、App 模拟器和命令行各有自己的代理设置。取舍上,浏览器调试最简单,移动端调试更接近真实场景但证书和网络环境更容易出问题。常见坑是 App 使用了证书锁定、系统代理被 VPN 覆盖,或者 Node 请求库显式禁用了代理。可以先用 curl 走代理验证 whistle 正常,再回到具体客户端排查。Network 里请求太多,怎么快速定位目标接口?不要一上来搜索很短的词,比如 api 或 user,这会把噪音也带进来。更稳的做法是按域名、路径片段、请求方法、状态码逐层过滤,最后再看请求体或响应体。边界是过滤条件越多,越可能把真正的异常请求排除掉,所以每次收窄都要确认列表数量是否合理。踩坑时可以先清屏,再触发一次具体操作,让目标请求出现在最新的几条里。HTTPS 请求显示 CONNECT 或看不到响应体怎么办?这通常是证书没有安装、没有被系统信任,或者客户端不接受用户证书。浏览器调试时安装 whistle 证书一般就够了,Android 7 之后的 App 可能还需要应用允许用户证书,部分金融或安全类 App 会做证书锁定。取舍是:普通业务调试可以信任本机证书,涉及敏感账号和生产数据时应尽量用测试环境。不要为了抓包去关闭应用安全校验并提交到正式包,这是非常典型的安全事故源头。如何用 whistle 判断问题在前端还是后端?先看请求是否发出、URL 和参数是否正确,再看后端返回的状态码、响应体和耗时。若请求没发出,多半是前端逻辑、跨域预检或网络配置问题;若请求正确但响应错误,就要看服务端日志或网关链路。一个实用取舍是临时用规则 mock 一个成功响应,如果页面恢复正常,说明前端渲染链路大概率没问题。反过来,如果 mock 成功响应页面仍异常,就要查前端数据解析或状态管理。过滤和搜索有什么踩坑点?过滤是为了缩小列表,搜索是为了在已有列表里找内容,两者不要混用。很多人只搜响应关键字,却忘了请求还没被捕获或已经被过滤条件排除,结果误以为接口没返回。另一个坑是缓存,请求可能直接走浏览器缓存,Network 里看不到你期待的接口变化。排查时可以禁用缓存、加随机参数,或者在 whistle 里观察状态码和响应头是否真的来自服务端。
前端阅读 05月30日 22:48

whistle Values 如何管理多环境配置和 mock 数据?

whistle 的 Values 可以理解成一份可复用的变量仓库,用来存放域名、端口、token、mock JSON、响应片段等配置。规则里用 {key} 引用后,同一份值可以被多条规则复用,切环境时只改 Values,不用把规则逐行改一遍。它最适合处理“规则稳定但数据经常变”的场景,比如本地、测试、预发三套接口地址,或者同一个接口在不同用例下返回不同响应。边界也要先说清楚:Values 不是数据库,不适合放大量动态数据,更不要放生产密钥;它解决的是代理调试里的配置复用问题。常见启动方式如下,先确认 whistle 正常运行,再进入 http://127.0.0.1:8899/ 管理界面。npm i -g whistlew2 start -p 8899w2 status在 Values 面板里可以创建 env.json,内容保持简单、稳定:{ "apiHost": "http://127.0.0.1:3000", "testHost": "https://test.example.com", "userId": "10001", "profileMock": { "name": "Tom", "role": "admin" }}规则中再引用这些值:^https://api.example.com/(.*) {apiHost}/$1https://api.example.com/user?id={userId} resBody://{profileMock}这个写法的好处是规则仍然表达“流量怎么转发”,Values 只负责“具体值是什么”。如果团队多人共用,建议把 key 命名成 env.apiHost、mock.profile.admin 这类层级语义,避免 host1、data2 这种几天后没人敢动的名字。追问Values 和 Rules 应该怎么分工?Rules 管流量匹配、转发、替换和注入,Values 管可复用的数据。取舍标准很简单:如果一段内容会被多条规则引用,或者经常随环境变化,就放到 Values;如果它只服务一条规则,直接写在 Rules 里反而更直观。踩坑最多的是把整套规则都“变量化”,最后打开规则只看到一堆 {xxx},排查时必须来回切面板。Values 应该降低重复,不应该降低可读性。多环境配置怎么切换最稳?建议为不同环境建独立 Values,例如 local.json、test.json、pre.json,每份保持相同 key,只替换 value。这样规则文件不用变,切换时只启用对应 Values,团队成员也容易对齐。边界是同一时间不要启用多份同名 key 的 Values,否则后加载或更高优先级的值会覆盖前面的值,表现像“规则偶尔失灵”。如果必须临时覆盖,最好在 key 名里写清楚 override,用完立刻关闭。Values 里能不能放接口 mock 响应?可以,尤其适合体积不大的 JSON 响应、错误码、空列表、权限状态等固定场景。取舍在于维护成本:几十行以内的 mock 放 Values 很方便,几百行甚至带复杂逻辑时,应该改用文件、插件或脚本处理。常见踩坑是 JSON 少了引号或多了尾逗号,规则没有明显报错,但响应内容就是不对。写 mock 时先用 JSON 校验工具检查,再用 Network 面板确认最终响应体。Values 适合保存 token 和账号信息吗?临时调试 token 可以放,但不建议长期保存真实生产凭据。whistle 是本地代理工具,团队共享配置、截图或导出时很容易把敏感值带出去。更稳的做法是只保存占位符,例如 {debugToken},真实值由个人本地单独维护,或者在脚本里从环境变量读取。边界是测试环境的低权限 token 可以接受,生产管理员 token 不应该进入 Values。Values 不生效时怎么排查?先看 key 是否拼错,再看对应 Values 是否启用,最后看规则命中是否符合预期。可以把响应临时改成固定文本验证引用是否成功,例如 resBody://{profileMock},比直接排查完整代理链更快。另一个坑是值里包含特殊字符、换行或 URL 参数时没有正确转义,导致规则解析和预期不同。排查时一次只改一个变量,并在 Network 里查看实际请求 URL、响应头和响应体。
前端阅读 05月30日 22:48

whistle 支持哪些协议?HTTP、HTTPS、WebSocket 怎么代理?

Whistle 常用于 HTTP 调试,但它能处理的并不只有普通 HTTP 请求。前端和移动端排查里,常见的是 HTTP、HTTPS、WebSocket、HTTP/2 以及 SOCKS 或上游代理。理解这些协议的差异,比背规则更重要:HTTP 明文好改,HTTPS 需要证书信任,WebSocket 建连后是长连接,HTTP/2 可能在代理链路里被降级。协议边界没弄清,规则看似正确,请求也可能不按预期走。HTTP 最直接,可以替换 host、改请求头和响应体,适合本地联调和 Mock。www.example.com host 127.0.0.1:8080http://www.example.com/api/user resBody://{"code":0}http://www.example.com/api/user reqHeaders://{"x-debug":"1"}HTTPS 多了一层证书。浏览器先和代理建立 CONNECT,再由 whistle 解密转发;没有信任根证书时,只能看到隧道,看不到路径和响应。调试前先启动服务并按页面提示安装证书:w2 start -p 8899# 浏览器访问 http://127.0.0.1:8899,下载并信任根证书HTTPS 的坑主要有三个:移动端证书信任策略不同;部分 App 做了证书固定;生产登录、支付链路不该随便关闭校验。调试时可以只匹配目标域名,不要用过宽规则影响全部流量。WebSocket 也是 whistle 很有价值的场景。它先通过 HTTP Upgrade 建立连接,之后变成双向消息流,因此排查时既要看握手,也要看消息内容。ws://socket.example.com host 127.0.0.1:7001wss://socket.example.com host 127.0.0.1:7001HTTP/2 和 SOCKS 更像链路兼容问题。很多代理工具会把 HTTP/2 在中间链路降到 HTTP/1.1,这通常不影响接口语义,但会影响多路复用、头压缩和性能判断。SOCKS 适合把流量再转给公司代理、远程调试机或特定网络出口,改包能力仍然发生在 whistle 这一层。api.example.com socks://127.0.0.1:1080api.example.com proxy://http://127.0.0.1:8888追问HTTP 和 HTTPS 代理规则写法一样吗?大部分匹配和改写规则看起来一样,但 HTTPS 能否生效取决于证书信任和解密权限。HTTP 请求是明文,whistle 可以直接看到路径、header 和 body;HTTPS 如果只建立了 CONNECT 隧道,就只能看到域名和连接信息。取舍是安全性和可调试性:本地排查可以信任证书,生产或敏感链路不要随意解密。遇到规则不生效,先确认是不是根本没拿到 HTTPS 明文。WebSocket 代理为什么有时只能看到连接,看不到消息?可能是规则只命中了握手域名,没有命中实际的 wss 地址,也可能是客户端走了不同端口或备用域名。WebSocket 建连成功后是长连接,页面刷新、重连和心跳都会影响观察结果。另一个常见坑是消息被业务层压缩或加密,代理能看到帧,但看不懂业务内容。排查时先验证 Upgrade 状态,再看消息方向和重连次数。HTTP/2 被代理后降级会不会影响测试结论?如果只是验证接口返回、Mock 数据和 header,通常影响不大。若你在排查性能、连接复用、服务端推送或 gRPC 类场景,降级就可能改变结论。whistle 更适合做功能调试和流量改写,不适合替代专门的协议性能分析工具。边界看测试目标:测业务语义可以接受,测协议特性就要谨慎。SOCKS、proxy 和 host 规则怎么选?host 适合把目标域名指到本地或测试机,最直观也最常用。proxy 适合把请求交给另一个 HTTP 代理,socks 适合走公司网络、远程环境或特定出口。不要把三者叠太多层,否则失败时很难判断是哪一层断了。建议先用 host 跑通,再按网络限制加上游代理。多协议混合项目应该怎么写规则?不要用一个大规则覆盖所有域名,应该按协议和业务域名拆开。HTTP API、HTTPS 登录、WebSocket 推送、静态资源最好分别写,必要时加注释说明用途。这样做的成本是规则文件稍长,但排查时能快速关闭某一段,不会影响其他流量。尤其是移动端项目,登录和长连接经常共享域名,规则过宽很容易制造假故障。
前端阅读 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
前端阅读 05月30日 20:13

Appium 如何测试混合应用并稳定切换 WebView?

Appium 测混合应用,关键是先分清当前操作发生在原生视图还是 WebView。原生导航栏、权限弹窗、底部 Tab 通常留在 NATIVE_APP;H5 页面里的按钮、输入框、DOM 文案要切到 WEBVIEW 后再用 CSS、XPath 或 WebDriver 定位。难点不在 API,而在 WebView 什么时候出现、Chromedriver 是否匹配、页面是否加载完成。追问什么时候需要切换到 WebView?只有目标元素属于 H5 DOM 时才切换。原生控件不要硬切 WebView 找,系统弹窗、原生返回按钮、底部导航栏通常仍在 NATIVE_APP。WebView 上下文找不到怎么排查?先确认 Android 应用开启 WebView 调试,再检查页面是否加载完成。iOS 还要确认 Web Inspector、证书和真机调试权限。Chromedriver 版本不匹配会怎样?可能能看到上下文,但切换后查 DOM 或执行 JS 报 session 错误。要配置 chromedriverExecutableDir 或自动下载策略。定位元素优先用什么?WebView 内优先稳定 CSS 或 data-testid,原生侧优先 accessibility id 或 resource-id。不要依赖易变层级 XPath。切换后为什么还要等待?上下文切换只说明进入执行环境,不代表页面元素可交互。要显式等待目标元素可见或可点击。写段代码const contexts = await driver.getContexts();const webview = contexts.find(c => c.includes('WEBVIEW'));if (webview) await driver.context(webview);
前端阅读 05月30日 20:13

Appium 如何进行数据驱动测试才稳定?

Appium 做数据驱动测试,核心是把测试步骤和测试数据拆开:脚本负责打开页面、输入、点击、断言,账号、密码、预期文案、设备差异、边界值放到 JSON、CSV、Excel、数据库或接口里。这样新增场景时通常只改数据,不改自动化逻辑,回归覆盖会稳定很多。追问数据源选 JSON、CSV 还是 Excel?JSON 适合层级结构,CSV 适合简单二维数据,Excel 对测试同学友好但 CI 里容易遇到空值类型、日期格式问题。最容易踩什么坑?测试数据之间互相污染,比如同一手机号被多条用例注册。另一个坑是只换输入不换断言,跑很多数据却没有真正覆盖业务规则。一条数据失败后要不要继续?回归场景建议继续跑,并记录 caseId、设备、输入数据、截图和关键日志。冒烟测试可以关键链路失败就中止。动态生成数据好不好?手机号、订单号适合动态生成;空密码、特殊字符等边界值最好显式写在文件里。动态数据还要有清理逻辑。和 Page Object 怎么配合?Page Object 管页面操作,数据驱动管输入和预期。用例层读取数据后调用页面方法,断言也要带 caseId。写段代码for (const tc of require('./login-cases.json').testCases) { it(`${tc.id} ${tc.description}`, async () => { await loginPage.login(tc.username, tc.password); expect(await loginPage.resultText()).toBe(tc.expected); });}
前端阅读 05月30日 19:58

Chrome 的 V8 引擎如何执行 JavaScript?

V8 执行 JavaScript 不是直接把源码一路翻成机器码。大致流程是:源码先被解析成 AST,再由 Ignition 生成并执行字节码;运行中如果某段代码频繁执行,V8 会收集类型反馈,把它交给 TurboFan 优化成机器码。优化依赖“假设”,比如对象形状稳定、函数参数类型稳定;一旦假设被打破,就会发生去优化。追问隐藏类和内联缓存是什么?V8 会给结构相似的对象建立隐藏类,用来快速定位属性。内联缓存会记录某个属性访问曾经遇到的对象形状。为什么不建议频繁 delete 属性?delete 可能改变对象形状,让原本稳定的隐藏类失效。热点路径里更常见做法是置为 null 或创建新对象。V8 的垃圾回收怎么影响页面?大量临时对象会增加 minor GC 频率,长期被闭包、全局数组、事件监听器引用的对象会进入老生代,严重时页面会间歇卡顿。代码怎么写更友好?让对象结构稳定、避免热路径混用类型、减少循环里的临时对象。先用 Performance 和 Memory 确认瓶颈,再优化热点。写段代码function makeUser(id, name) { return { id, name, active: true }; }const u = makeUser(1, 'A');u.active = false;
前端阅读 05月30日 19:58

Chrome 缓存机制有哪些?强缓存和协商缓存怎么选?

Chrome 缓存是一组从近到远的命中链路。页面请求资源时,可能先被 Service Worker 接管,再命中内存缓存或磁盘缓存;HTTP 层又分强缓存和协商缓存。强缓存看 Cache-Control: max-age,没过期就不问服务器;协商缓存会带 If-None-Match 或 If-Modified-Since 去问服务器,没变就返回 304。追问强缓存和协商缓存怎么取舍?带内容 hash 的 JS、CSS、图片适合强缓存很久;HTML 入口更适合短缓存或协商缓存,避免用户一直拿旧入口。ETag 和 Last-Modified 有什么区别?Last-Modified 依赖修改时间,精度受构建流程影响;ETag 通常基于内容或版本生成,更精确但有计算成本。memory cache 和 disk cache 有什么区别?memory cache 快,通常跟当前页面生命周期相关;disk cache 更持久。开发者主要通过 HTTP 头影响策略。Service Worker 缓存会不会覆盖 HTTP 缓存?会,它能拦截 fetch 并决定读 Cache Storage 还是走网络。缓存策略写错会导致用户长期拿旧数据。写段配置Cache-Control: public, max-age=31536000, immutableETag: "app-v42"
前端阅读 05月30日 19:58

Chrome 事件循环中宏任务和微任务到底怎么执行?

Chrome 的事件循环可以先记一句话:同步代码先进调用栈,栈清空后清空微任务,然后浏览器选择是否渲染,再执行下一个宏任务。宏任务包括 script、setTimeout、用户事件等;微任务包括 Promise.then、queueMicrotask、MutationObserver。微任务优先级高,但太多会让渲染一直没机会发生。追问setTimeout(fn, 0) 为什么不是立刻执行?它只是把回调放进宏任务队列,必须等当前同步代码和本轮微任务执行完。后台标签页和嵌套定时器还可能被节流。Promise.then 和 requestAnimationFrame 谁先执行?Promise.then 属于微任务,会在当前宏任务结束后尽快清空;rAF 通常在下一次渲染前执行。实际项目常见坑是什么?接口返回后连续触发大量 Promise 回调,在里面同步计算列表、改 DOM、再追加微任务,页面就会点击无响应。怎么拆重任务?把非关键计算切片,用 setTimeout、MessageChannel 或 requestIdleCallback 给浏览器处理输入和渲染的机会。写段代码console.log('A');setTimeout(() => console.log('B'), 0);Promise.resolve().then(() => console.log('C'));console.log('D'); // A D C B
前端阅读 05月30日 19:58

Chrome 里如何定位并优化慢网络请求?

优化 Chrome 网络请求,先用 DevTools Network 找慢在哪里。一次请求会经历 DNS、TCP/TLS、排队、TTFB、下载等阶段;不同阶段慢,处理手段完全不同。TTFB 高通常是后端或网关慢,下载慢可能是资源太大,排队多可能是连接数、优先级或主线程阻塞。追问HTTP/2 以后还要合并 JS 和 CSS 吗?不一定。HTTP/2 多路复用降低了小请求连接成本,过度合并反而会让缓存失效范围变大。preload、prefetch、preconnect 怎么选?preconnect 提前建立跨域连接;preload 抢首屏关键资源;prefetch 适合空闲加载下一页资源。Network 面板看哪些指标?先看瀑布图是否有长串阻塞,再看 Timing。TTFB、Content Download、Queueing 分别对应不同问题。缓存策略怎么避免旧资源?HTML 用短缓存或协商缓存;带 hash 的 JS、CSS、图片可设置长强缓存。没有版本号的资源不要随手缓存一年。写段代码<link rel="preconnect" href="https://cdn.example.com" crossorigin><link rel="preload" href="/assets/app.abcd.js" as="script">
前端阅读 05月30日 19:58

Chrome 为什么会拦截跨域请求?前端该怎么解决?

Chrome 拦截跨域是同源策略在保护用户数据。协议、域名、端口任意一个不同就算跨域;前端可以发起请求,但浏览器会在读取响应时检查 CORS 头。真正的解决点通常在服务端:明确允许哪些来源、方法、请求头,是否允许携带 Cookie。开发环境可以用代理绕过限制,生产不要靠关闭安全策略或插件。追问CORS 和 JSONP 有什么区别?CORS 是标准跨域机制,支持多种方法和预检;JSONP 只利用 script 标签,只能 GET,安全边界差。为什么有些请求会先发 OPTIONS?带自定义请求头、非简单方法或特殊 Content-Type 时,Chrome 会先发预检请求确认服务端是否允许。携带 Cookie 时为什么不能写 *?如果 credentials: include,服务端必须回显具体 origin,不能是星号,还要设置 Access-Control-Allow-Credentials: true。开发代理和服务端 CORS 怎么取舍?本地代理适合调试;上线后要让网关或应用服务配置 CORS,因为开发服务器代理不会跟着用户浏览器上线。写段代码res.setHeader('Access-Control-Allow-Origin', 'https://www.example.com');res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');res.setHeader('Access-Control-Allow-Credentials', 'true');