<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>前端面试题 on Last Stand</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/</link><description>Recent content in 前端面试题 on Last Stand</description><generator>Hugo</generator><language>zh-cn</language><atom:link href="https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/index.xml" rel="self" type="application/rss+xml"/><item><title>Agent 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/agent/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/agent/</guid><description>&lt;p&gt;共 119 道 Agent 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-7cd93d99-ef11-40d4-b0fb-d53441bc5cc1"&gt;1. 如何设计一个企业级 AI Agent 平台？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3.5 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;企业级 AI Agent 平台的设计重点不是“接一个大模型接口”，而是围绕业务任务构建完整体系：前端提供可解释、可配置、可监控的交互体验；中间层负责 Agent 编排、工具调用和知识增强；底层统一管理模型、数据和企业系统；治理层保障权限、安全、审计、评测和成本控制。真正可落地的平台，一定是在智能能力和工程确定性之间取得平衡。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;企业级 AI Agent 平台不能只设计成一个“聊天入口”，更应该设计成一个可治理、可扩展、可观测的智能任务执行平台。它的核心目标是让企业可以安全地把大模型能力接入真实业务流程，例如客服、知识检索、数据分析、代码辅助、审批流、运营自动化等场景。&lt;/p&gt;
&lt;p&gt;从架构上看，可以分为四个关键层次：前端体验层、Agent 编排层、能力接入层和企业治理层。&lt;/p&gt;
&lt;p&gt;前端体验层不应该只有 Chat UI，而要支持多种交互形态。对于普通用户，可以提供对话式入口、任务面板、历史记录、引用来源、执行进度和人工确认节点；对于业务管理员，需要有 Agent 配置台，可以配置角色、Prompt、工具权限、知识库、工作流、触发条件和发布版本；对于运维和安全人员，则需要看到调用链路、成本消耗、失败原因、审计日志和风险拦截记录。&lt;/p&gt;
&lt;p&gt;Agent 编排层是平台的核心。这里不能简单地把用户输入直接发给模型，而是要经过意图识别、上下文构建、计划拆解、工具选择、执行反馈和结果汇总。复杂任务通常需要支持多 Agent 协作，例如一个 Agent 负责理解需求，一个 Agent 负责查知识库，一个 Agent 负责调用业务系统，还有一个 Agent 负责校验输出质量。对于企业场景，工作流式 Agent 和自主规划式 Agent 往往要结合使用：高风险业务用确定性流程约束，低风险探索任务可以给 Agent 更高自主度。&lt;/p&gt;
&lt;p&gt;能力接入层主要解决模型、知识和工具的问题。模型层需要做统一网关，屏蔽不同模型厂商差异，支持模型路由、降级、限流、重试和成本统计。知识层通常采用 RAG 架构，需要处理文档解析、切片、向量化、权限过滤、召回排序和答案引用，避免 Agent 编造来源。工具层则要把企业内部系统封装成标准 Tool，例如 CRM、工单、审批、BI、代码仓库、飞书、邮件、数据库等，同时每个工具都要有清晰的输入输出 Schema、权限边界和幂等设计。&lt;/p&gt;
&lt;p&gt;企业级平台最容易被低估的是治理能力。Agent 一旦可以调用工具，就不再只是“生成文本”，而是在执行真实操作，所以必须有权限控制、数据脱敏、敏感词策略、操作审计、人工确认和回滚机制。例如查询类工具可以自动执行，但转账、删除、发公告、修改生产配置这类操作必须进入 human-in-the-loop 流程。平台还需要支持租户隔离、部门权限、知识库权限继承和操作留痕。&lt;/p&gt;
&lt;p&gt;前端设计上，要重点解决“可解释”和“可控”。用户不能只看到一个最终答案，而应该能看到 Agent 做了哪些步骤、用了哪些知识、调用了哪些工具、哪些地方失败了、是否需要用户确认。对于长任务，要有状态机式的任务视图，而不是让用户一直等在输入框前。对于企业用户来说，信任感往往来自透明度，而不是更拟人的回复。&lt;/p&gt;
&lt;p&gt;另外，平台需要内置评测体系。Agent 发布前要经过离线评测，例如准确率、召回率、幻觉率、工具调用成功率、越权风险和响应时延；上线后要持续收集用户反馈、人工纠错、失败样本和成本数据，再反向优化 Prompt、知识库、工具描述和模型路由。没有评测闭环的 Agent 平台，很难在企业里长期稳定运行。&lt;/p&gt;</description></item><item><title>AI相关 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-26-b14403bc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-26-b14403bc/</guid><description>&lt;p&gt;共 43 道 AI相关 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-f58b9f85-153d-47b9-851f-f65abe8ec3e0"&gt;1. 如何实现 ChatGPT 类似的流式输出？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1.5 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;流式输出的核心是服务端通过 &lt;code&gt;SSE&lt;/code&gt;、&lt;code&gt;fetch stream&lt;/code&gt; 或 &lt;code&gt;WebSocket&lt;/code&gt; 持续返回增量内容，前端通过 &lt;code&gt;ReadableStream&lt;/code&gt; 边读边渲染。实现时要注意网络 chunk 不等于完整消息，需要做缓冲和协议解析。前端渲染要做节流，避免频繁状态更新导致卡顿。Agent 场景下建议使用结构化事件协议，把文本增量、工具调用、状态、错误、完成信号分开处理。最后还要支持取消、错误恢复、最终 Markdown 校正和滚动体验，才能达到接近 ChatGPT 的交互质量。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;类似 ChatGPT 的流式输出，本质是：服务端不要等完整答案生成后再返回，而是把模型生成的 token 或文本片段持续写入 HTTP 响应；前端边读边渲染，并在完成、取消、异常时维护好状态。&lt;/p&gt;
&lt;p&gt;实际项目里最常见的是 &lt;code&gt;SSE&lt;/code&gt; 或 &lt;code&gt;fetch + ReadableStream&lt;/code&gt;。如果只是服务端向前端单向推送，&lt;code&gt;SSE&lt;/code&gt; 足够稳定；如果需要双向实时交互，比如语音、多人协作、实时 Agent 控制，可以用 &lt;code&gt;WebSocket&lt;/code&gt;。在 ChatGPT 这类文本生成场景中，&lt;code&gt;fetch&lt;/code&gt; 读取流也很常用，因为它支持 &lt;code&gt;POST&lt;/code&gt;、请求体、鉴权、&lt;code&gt;AbortController&lt;/code&gt;，控制能力比原生 &lt;code&gt;EventSource&lt;/code&gt; 更强。&lt;/p&gt;
&lt;p&gt;前端核心代码大致是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;streamChat&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;any&lt;/span&gt;[], &lt;span style="color:#a6e22e"&gt;onDelta&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;controller&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AbortController&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/api/chat/stream&amp;#39;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;JSON.stringify&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;signal&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;controller.signal&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#39;stream request failed&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;reader&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getReader&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decoder&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextDecoder&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;done&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;reader&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;read&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;done&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decoder&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;decode&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;, { &lt;span style="color:#a6e22e"&gt;stream&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;events&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;split&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;\n\n&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;pop&lt;/span&gt;() &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;line&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;split&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;find&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;startsWith&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;data:&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;line&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;line&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;replace&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;/^data:\s*/&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;[DONE]&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;payload&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;payload&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;delta&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;onDelta&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;payload&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;controller&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;abort&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;服务端返回时通常使用类似这样的协议：&lt;/p&gt;</description></item><item><title>CSS 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/css/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/css/</guid><description>&lt;p&gt;共 114 道 CSS 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-88f58c02-0b7c-4706-9e7e-08d7509205c5"&gt;1. Tailwind 的响应式断点（如 &lt;code&gt;md:&lt;/code&gt;）底层如何实现？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;Tailwind 的响应式断点基于构建期的变体系统实现。&lt;code&gt;md:&lt;/code&gt; 在编译阶段被解析为 screen 变体，并根据 &lt;code&gt;screens&lt;/code&gt; 配置生成对应的 &lt;code&gt;@media (min-width)&lt;/code&gt; 包裹规则。所有响应式逻辑依赖原生 CSS media query，遵循 mobile-first 原则，通过规则生成管线支持多变体组合，不涉及运行时计算。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;这个问题的关键在于理解：
&lt;code&gt;md:&lt;/code&gt; 不是运行时逻辑，而是 &lt;strong&gt;构建期的变体（variant）展开机制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 Tailwind CSS 中，响应式系统建立在三个核心之上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;screens 配置映射&lt;/li&gt;
&lt;li&gt;变体解析机制&lt;/li&gt;
&lt;li&gt;JIT 构建阶段规则生成&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="一md-本质是什么"&gt;一、&lt;code&gt;md:&lt;/code&gt; 本质是什么？&lt;/h2&gt;
&lt;p&gt;当写：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;md:text-red-500&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tailwind 在构建阶段会将其解析为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;variant：&lt;code&gt;md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;utility：&lt;code&gt;text-red-500&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后生成如下 CSS：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@&lt;span style="color:#66d9ef"&gt;media&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#f92672"&gt;min-width&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;768px&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;md&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;:&lt;span style="color:#a6e22e"&gt;text-red-500&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;color&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;#ef4444&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;md:&lt;/code&gt; 只是一个前缀标记&lt;/li&gt;
&lt;li&gt;构建阶段转换成 &lt;code&gt;@media&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;冒号会被转义为合法 CSS 选择器&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二断点从哪里来"&gt;二、断点从哪里来？&lt;/h2&gt;
&lt;p&gt;断点定义在配置文件中：&lt;/p&gt;</description></item><item><title>ES6 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/es6/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/es6/</guid><description>&lt;p&gt;共 51 道 ES6 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-86483690-5e1e-4dae-911e-0aba1644bd2f"&gt;1. 说说 Class 语法糖的底层实现&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;class 是基于函数构造器和原型链的语法封装，实例方法本质挂载在 prototype 上，继承通过 Object.create 构建原型链，同时通过构造函数原型链继承静态方法。super 依赖内部 [[HomeObject]] 机制定位父类方法。class 并未引入新的对象模型，而是在原型继承基础上增加语义约束与语法增强。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;这个问题的关键在于理解：&lt;code&gt;class&lt;/code&gt; 并不是一种新的面向对象机制，而是对 &lt;strong&gt;原型继承（prototype-based inheritance）&lt;/strong&gt; 的语法封装。底层仍然是函数构造器 + 原型链。&lt;/p&gt;
&lt;p&gt;如果把 &lt;code&gt;class&lt;/code&gt; 当作 Java 那种基于类的模型去理解，就会产生误解。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;先看一个简单例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;constructor&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;say&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它在底层大致等价于：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;prototype&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;say&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;一、实例方法的实现&lt;/p&gt;</description></item><item><title>HTML 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/html/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/html/</guid><description>&lt;p&gt;共 77 道 HTML 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-1c3850f1-2fdf-46a8-8b6e-ff9a6f1abf08"&gt;1. SPA和MPA的区别？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;SPA 通过前端路由在单一 HTML 中完成页面切换，交互流畅但首屏和 SEO 成本较高；MPA 每次导航请求新页面，SEO 友好且隔离性强，但交互体验有限；两者本质差异在于前后端渲染与路由职责的划分；现代项目常通过 SSR、SSG 等方式在两种模式之间折中。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;SPA 和 MPA 的区别，表面看是“页面跳转方式不同”，本质上是&lt;strong&gt;前端与服务器在渲染职责、路由控制以及资源加载策略上的分工差异&lt;/strong&gt;。理解这一点，才能在具体业务中做出合理选择。&lt;/p&gt;
&lt;p&gt;SPA（Single Page Application）只有一个 HTML 入口，首屏加载时会拉取完整的 JavaScript 应用骨架，之后的页面切换通过前端路由完成。URL 的变化并不会触发浏览器重新请求文档，而是由前端框架根据路由状态切换视图并复用已有资源。这种模式的优势在于页面切换流畅、状态可以在客户端长期保留，复杂交互体验更接近原生应用，非常适合后台系统、强交互工具型产品。但代价也同样明显：首屏资源压力大，SEO 先天不足，对 JS 执行和异常处理的依赖极高，一旦主 bundle 出现问题，整个应用都无法正常工作。&lt;/p&gt;
&lt;p&gt;MPA（Multi Page Application）则以“页面”为核心单元。每一次导航都会向服务器请求一个新的 HTML 文档，路由和渲染主要由服务端控制。页面之间天然隔离，资源和状态会随页面刷新而重置。这种模式的优势在于首屏直观、SEO 友好、容错性强，适合内容型站点、营销页和对搜索引擎依赖较高的业务场景。但在交互复杂度上，MPA 往往需要付出更多成本，例如重复加载公共资源、跨页面状态共享困难，整体体验也更接近传统 Web。&lt;/p&gt;
&lt;p&gt;从工程角度看，SPA 和 MPA 的差异还体现在构建和部署方式上。SPA 更强调前端工程化和运行时能力，构建目标是一个可长期运行的客户端应用；MPA 更强调页面级拆分和服务端模板渲染，前端更多是增强层而非控制中心。在现代实践中，两者并非对立关系，常见的折中方案包括 SSR、SSG 以及“多入口 SPA”，试图在交互体验和首屏性能、SEO 之间取得平衡。&lt;/p&gt;
&lt;p&gt;因此，选择 SPA 还是 MPA，并不存在绝对优劣，关键在于业务目标：是以复杂交互和状态管理为核心，还是以内容分发和搜索可见性为核心。&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-ae93b62f-d9ef-46c2-8151-da164426f49f"&gt;2. 常见的H5标签有哪些 ，你是怎么用的&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点-1"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;常见 H5 标签包括结构语义标签、文本语义标签、表单标签和媒体标签；实际使用中优先考虑语义表达和可访问性，而非样式；充分利用表单和媒体标签的原生能力以减少 JS 复杂度；&lt;code&gt;canvas&lt;/code&gt; 与 &lt;code&gt;svg&lt;/code&gt; 需根据场景选择；合理使用语义化标签能显著提升页面质量和长期可维护性。&lt;/p&gt;</description></item><item><title>JavaScript 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/javascript/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/javascript/</guid><description>&lt;p&gt;共 407 道 JavaScript 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-f142c5fe-17fd-444e-8daf-bd03d085ce69"&gt;1. &lt;code&gt;requestAnimationFrame&lt;/code&gt; 与 &lt;code&gt;requestIdleCallback&lt;/code&gt; 在渲染优化中的执行时机差异？谁优先触发？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt; 在浏览器即将进行下一帧渲染之前执行，用于动画与视觉更新，优先级高于渲染阶段后的任务。&lt;code&gt;requestIdleCallback&lt;/code&gt; 只会在当前帧所有渲染任务完成且仍有剩余时间时触发，属于低优先级空闲调度机制。因此在同一帧中，rAF 必然先于 rIC 执行。rAF 用于保证帧同步，rIC 用于利用空闲时间执行非关键任务。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;这个问题本质是理解 &lt;strong&gt;浏览器一帧的调度模型&lt;/strong&gt;。
不搞清楚一帧里发生了什么，很容易回答成“谁快谁慢”这种表层结论。&lt;/p&gt;
&lt;p&gt;核心结论先给出：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在有下一帧渲染需求的情况下，&lt;code&gt;requestAnimationFrame&lt;/code&gt; 一定优先于 &lt;code&gt;requestIdleCallback&lt;/code&gt; 执行。
&lt;code&gt;requestIdleCallback&lt;/code&gt; 只会在当前帧还有“空闲时间”时才会触发。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面从浏览器帧模型拆解。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="一浏览器一帧的基本流程"&gt;一、浏览器一帧的基本流程&lt;/h1&gt;
&lt;p&gt;以 60fps 为例，一帧约 16.6ms。&lt;/p&gt;
&lt;p&gt;一次完整渲染循环大致是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;执行宏任务（例如 setTimeout）&lt;/li&gt;
&lt;li&gt;清空微任务队列&lt;/li&gt;
&lt;li&gt;触发 requestAnimationFrame 回调&lt;/li&gt;
&lt;li&gt;进行样式计算（Style）&lt;/li&gt;
&lt;li&gt;布局计算（Layout）&lt;/li&gt;
&lt;li&gt;绘制（Paint）&lt;/li&gt;
&lt;li&gt;合成（Composite）&lt;/li&gt;
&lt;li&gt;如果还有时间 → 执行 requestIdleCallback&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;需要明确一点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rAF 是“下一帧渲染前”的回调&lt;/li&gt;
&lt;li&gt;rIC 是“当前帧剩余时间”的回调&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id="二requestanimationframe-的执行时机"&gt;二、requestAnimationFrame 的执行时机&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt; 的设计目标是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在浏览器即将进行下一次重绘之前执行回调。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每帧最多执行一次&lt;/li&gt;
&lt;li&gt;与刷新率同步&lt;/li&gt;
&lt;li&gt;在样式计算之前触发&lt;/li&gt;
&lt;li&gt;如果标签页不可见，会暂停&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行时机可以理解为：&lt;/p&gt;</description></item><item><title>leetcode 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/leetcode/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/leetcode/</guid><description>&lt;p&gt;共 38 道 leetcode 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-8dcdf90d-46d4-4466-a96b-c684806e90f3"&gt;1. 最大公共前缀&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;编写一个函数，接收一个字符串数组作为输入，找出这些字符串的最大公共前缀。如果没有公共前缀，则返回空字符串。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;输入输出要求&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入：一个字符串数组。&lt;/li&gt;
&lt;li&gt;输出：一个字符串，表示最大公共前缀。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;input&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;flower&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;flow&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;flight&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;longestCommonPrefix&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;input&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// 输出：&amp;#39;fl&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;最大公共前缀问题的核心是逐步缩小候选前缀；横向扫描通过不断用后续字符串修剪前缀，实现简单且高效；在最坏情况下时间复杂度为 O(n·m)，空间复杂度为 O(1)，非常适合在实际工程和面试中使用。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;这个问题本质是在&lt;strong&gt;多个字符串之间求一个公共的、连续的前缀子串&lt;/strong&gt;，并且要求尽可能长。关键不在于字符串操作技巧，而在于&lt;strong&gt;如何逐步收敛搜索空间&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一整体思路说明横向扫描"&gt;一、整体思路说明（横向扫描）&lt;/h2&gt;
&lt;p&gt;横向扫描的核心思想是：
&lt;strong&gt;先假设第一个字符串是公共前缀，然后不断用后续字符串去“修剪”它&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;执行过程可以概括为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;取第一个字符串作为初始前缀&lt;/li&gt;
&lt;li&gt;从第二个字符串开始，逐个比较&lt;/li&gt;
&lt;li&gt;如果当前字符串不以该前缀开头，就不断缩短前缀&lt;/li&gt;
&lt;li&gt;一旦前缀缩短为空，说明不存在公共前缀，可以提前结束&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个过程的好处是逻辑直观，且在工程中可读性和可维护性都很好。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二示例代码实现横向扫描"&gt;二、示例代码实现（横向扫描）&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;longestCommonPrefix&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;strs&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strs&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;prefix&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strs&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strs&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;startsWith&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;prefix&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;prefix&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;prefix&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;slice&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;prefix&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;prefix&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="示例验证"&gt;示例验证&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;input&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;flower&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;flow&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;flight&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;longestCommonPrefix&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;input&lt;/span&gt;)); &lt;span style="color:#75715e"&gt;// &amp;#34;fl&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="三时间与空间复杂度分析"&gt;三、时间与空间复杂度分析&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;时间复杂度&lt;/strong&gt;：
最坏情况下为 &lt;code&gt;O(n * m)&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Node.js 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/nodejs/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/nodejs/</guid><description>&lt;p&gt;共 40 道 Node.js 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-e35e43bb-714f-4071-9d8d-6ef850213099"&gt;1. NestJs、Nust.js、Next.js 这几个框架有什么区别&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;NestJS 是 Node.js 的企业级服务端框架，强调模块化、依赖注入和 TypeScript，适合构建 API 和微服务；&lt;/li&gt;
&lt;li&gt;Nuxt.js 是 Vue 生态的全栈框架，在 Vue 基础上提供 SSR、SSG、文件路由等能力；&lt;/li&gt;
&lt;li&gt;Next.js 是 React 生态的全栈框架，除了 SSR、SSG 外，还支持 React Server Components、Server Actions 等现代特性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三者最大的区别在于定位不同：NestJS 负责后端服务，Nuxt.js 和 Next.js 负责全栈 Web 应用开发，分别服务于 Vue 和 React 技术栈。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;这几个框架名字非常相似，但定位完全不同。简单来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NestJS&lt;/strong&gt;：Node.js 服务端开发框架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nuxt.js&lt;/strong&gt;：Vue 生态的全栈应用框架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt;：React 生态的全栈应用框架。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三者分别解决的是不同层面的问题，并不存在直接竞争关系。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;先来看 &lt;strong&gt;NestJS&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;NestJS 是基于 Node.js 构建的服务端框架，底层默认使用 Express，也可以切换到 Fastify。它借鉴了很多 Java Spring 的设计思想，例如依赖注入（DI）、模块化、装饰器以及 IoC 容器，因此对于大型团队开发来说，代码组织会更加规范。&lt;/p&gt;</description></item><item><title>React.js 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/react/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/react/</guid><description>&lt;p&gt;共 148 道 React.js 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-7e56d9a7-fe8f-4b9f-819f-68247725f673"&gt;1. 说说 Hooks 的依赖数组原理&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;Hooks 的依赖数组本质是一次浅层引用比较机制。React 在 commit 阶段通过 Object.is 逐项比较新旧依赖数组，决定是否重新执行 effect，并在变化时先执行 cleanup。依赖数组不会监听变量变化，也不负责触发 render，只用于声明副作用与状态之间的关系。依赖缺失会导致闭包捕获旧值，依赖不稳定会导致 effect 频繁执行，因此依赖必须完整且保持引用稳定。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;这个问题的核心，不是“依赖数组怎么写”，而是：&lt;strong&gt;React 是如何判断副作用是否需要重新执行的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;依赖数组的存在，本质是为了让 React 在两次 render 之间进行一次“浅层比较（shallow compare）”，从而决定是否跳过本次 effect 的执行。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一从-useeffect-的执行时机说起"&gt;一、从 useEffect 的执行时机说起&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;useEffect&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// effect
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}, [&lt;span style="color:#a6e22e"&gt;a&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;React 在每次 render 结束后，会把 effect 收集到 Fiber 节点的 effect 链表中。&lt;/p&gt;
&lt;p&gt;当进入 commit 阶段时，React 会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;取出当前 Fiber 上一次保存的依赖数组&lt;/li&gt;
&lt;li&gt;与本次 render 生成的新依赖数组进行比较&lt;/li&gt;
&lt;li&gt;决定是否标记该 effect 需要执行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果依赖变化，则：&lt;/p&gt;</description></item><item><title>Typescript 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/typescript/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/typescript/</guid><description>&lt;p&gt;共 61 道 Typescript 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-6af04565-f0bf-45dc-8ab0-c43ac8a1078d"&gt;1. 如何管理和优化 tsconfig？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;tsconfig 应被视为工程规范而非临时配置；通过分层与继承降低维护成本；类型严格性需要渐进式收紧而非一次性激进开启；合理控制编译范围以优化性能；在复杂工程中利用 tsconfig 约束依赖边界，使类型系统服务于长期可维护性。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;tsconfig 的管理与优化，本质上是在&lt;strong&gt;类型安全、开发体验与工程效率之间做长期平衡&lt;/strong&gt;。它并不是一次性配置文件，而是随着项目规模、团队协作方式和构建体系不断演进的工程资产。&lt;/p&gt;
&lt;p&gt;首先需要明确 tsconfig 在工程中的角色定位。它既是 TypeScript 编译器的输入约束，也是 IDE 类型分析与提示能力的基础配置。如果把 tsconfig 仅当作“让项目能跑起来的必要文件”，往往会在项目中后期暴露出类型失控、编译缓慢或不同环境行为不一致的问题。因此，治理 tsconfig 的第一步，是将其视为&lt;strong&gt;工程规范的一部分&lt;/strong&gt;，而不是单纯的工具配置。&lt;/p&gt;
&lt;p&gt;在实际管理中，通常会通过“分层”的方式来降低复杂度。将通用且稳定的配置抽离为基础 tsconfig，由不同运行环境或子项目通过 &lt;code&gt;extends&lt;/code&gt; 继承，再在局部覆盖差异化选项。这种方式可以避免在多个 tsconfig 中重复维护同一组 compilerOptions，也能清晰表达哪些约束是全局共识，哪些是特定场景的取舍。例如，类型严格性、模块解析策略往往属于全局约束，而是否生成声明文件、是否开启 sourceMap 则更偏向构建阶段需求。&lt;/p&gt;
&lt;p&gt;在优化层面，类型严格性的管理尤为关键。一次性开启所有严格选项，在历史项目中通常不可行，反而会导致大量噪音，削弱团队对类型系统的信任度。更可持续的做法，是以 &lt;code&gt;strict&lt;/code&gt; 为目标方向，但通过逐步引入单项严格规则，让类型质量随时间提升，而不是通过一次激进配置制造阻力。tsconfig 在这里承担的是“收紧边界”的角色，而不是制造阻断。&lt;/p&gt;
&lt;p&gt;编译性能同样是 tsconfig 优化中不可忽视的一环。随着项目体量增大，合理控制 &lt;code&gt;include&lt;/code&gt;、&lt;code&gt;exclude&lt;/code&gt; 范围，避免将无关文件纳入类型分析，是最直接也最有效的手段。同时，明确区分“类型检查”与“代码转译”的职责，可以避免在开发阶段承担不必要的编译成本。在一些工程中，会通过不同 tsconfig 分别服务于 IDE 类型检查与构建流程，从而兼顾体验与效率。&lt;/p&gt;
&lt;p&gt;在 Monorepo 或组件库场景下，tsconfig 还承担着依赖边界约束的职责。通过 project references 明确包之间的依赖关系，不仅可以加速增量编译，也能在类型层面阻止不合法的跨包引用。这类配置一旦稳定下来，往往比 lint 规则更可靠，因为它直接作用于编译阶段。&lt;/p&gt;
&lt;p&gt;长期来看，tsconfig 的优化不是“调参数”，而是通过持续审视哪些约束是必要的、哪些已经不再适用，让类型系统始终贴合真实的工程状态。当 tsconfig 能够稳定表达团队的工程共识时，它的价值才真正体现出来。&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-7aad6bf7-246a-4725-a614-cfcc5945cb96"&gt;2. Boolean 和 boolean 有什么区别？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;</description></item><item><title>Vue.js 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/vue/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/vue/</guid><description>&lt;p&gt;共 123 道 Vue.js 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-0eb08245-5227-41ee-8dc4-f35a8e3b3748"&gt;1. 为何&lt;code&gt;setup()&lt;/code&gt;中直接解构&lt;code&gt;props&lt;/code&gt;会丢失响应性？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;setup&lt;/code&gt; 中直接解构 &lt;code&gt;props&lt;/code&gt; 会丢失响应性，是因为解构会把属性值拷贝为普通变量，打断了与 &lt;code&gt;props&lt;/code&gt; Proxy 的引用关系，导致 Vue 无法进行依赖收集。正确做法是直接使用 &lt;code&gt;props.xxx&lt;/code&gt;，或通过 &lt;code&gt;toRefs / toRef&lt;/code&gt; 将属性转换为保持响应性的 &lt;code&gt;ref&lt;/code&gt;。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;在 Vue 3 中，&lt;code&gt;setup()&lt;/code&gt; 里&lt;strong&gt;直接解构 &lt;code&gt;props&lt;/code&gt; 会丢失响应性&lt;/strong&gt;，根本原因在于：&lt;strong&gt;响应式是依赖引用关系实现的，而解构会打断这种引用关系&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;下面从原理、示例和正确做法三个层面说明。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一根本原因解构会拷贝值不再是响应式引用"&gt;一、根本原因：解构会“拷贝值”，不再是响应式引用&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;setup(props)&lt;/code&gt; 中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;props&lt;/code&gt; 本身是一个 &lt;strong&gt;浅只读的响应式对象（Proxy）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Vue 的响应式系统是 &lt;strong&gt;基于 getter / setter（Proxy 拦截）+ 依赖收集&lt;/strong&gt; 实现的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解构会把属性的当前值“取出来”，赋值给一个普通变量&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一旦解构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt; 是一个 &lt;strong&gt;普通变量&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;它不再通过 &lt;code&gt;props.title&lt;/code&gt; 访问&lt;/li&gt;
&lt;li&gt;Vue 无法拦截 &lt;code&gt;title&lt;/code&gt; 的读取&lt;/li&gt;
&lt;li&gt;依赖收集链路断裂 → 响应性丢失&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二对比示例为什么一个会更新一个不会"&gt;二、对比示例：为什么一个会更新，一个不会&lt;/h2&gt;
&lt;h3 id="-错误示例丢失响应性"&gt;❌ 错误示例（丢失响应性）&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; String
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setup&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;父组件更新 &lt;code&gt;title&lt;/code&gt; 时：&lt;/p&gt;</description></item><item><title>前端安全 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-12-28b9d64b/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-12-28b9d64b/</guid><description>&lt;p&gt;共 35 道 前端安全 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-bbf8b7ee-035e-4f2b-b64c-40d560e6dc19"&gt;1. 如果发现JWT Token泄露，如何快速响应？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定位&lt;/strong&gt;：确认泄露的是单个用户 Token 还是全局密钥。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拉黑&lt;/strong&gt;：立即将泄露的 Token &lt;code&gt;jti&lt;/code&gt; 加入 Redis 禁用名单。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阻断&lt;/strong&gt;：如果是密钥泄露，立即更新环境变量中的 &lt;code&gt;JWT_SECRET&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通知&lt;/strong&gt;：提醒受影响用户，并建议其修改密码（以防攻击者通过其他渠道获取了账号控制权）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审计&lt;/strong&gt;：检查该 Token 泄露期间产生的异常操作日志，进行回滚或补偿。&lt;/li&gt;
&lt;/ol&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;由于 JWT 是&lt;strong&gt;无状态&lt;/strong&gt;的（服务器默认不存储 Token，无法像 Session 那样轻易在服务端作废），快速响应的核心思路在于&lt;strong&gt;阻断该 Token 的有效性&lt;/strong&gt;或&lt;strong&gt;强制变更鉴权环境&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以下是针对不同场景的快速响应方案：&lt;/p&gt;
&lt;h3 id="1-服务端拦截引入黑名单机制最快响应"&gt;1. 服务端拦截：引入“黑名单”机制（最快响应）&lt;/h3&gt;
&lt;p&gt;这是最直接的补救措施。虽然 JWT 旨在去中心化校验，但在紧急情况下，必须引入中心化检查。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;操作&lt;/strong&gt;：将泄露的 Token 唯一标识（通常是 &lt;code&gt;jti&lt;/code&gt; 载荷）或整个 Token 字符串存入 Redis 等极速缓存中，并设置过期时间（等于该 Token 的剩余有效期）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逻辑更新&lt;/strong&gt;：修改后端的鉴权中间件，在校验 JWT 签名通过后，额外多一步检查：&lt;strong&gt;“该 Token 是否在 Redis 黑名单中？”&lt;/strong&gt;。如果在，则拒绝请求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：秒级生效，精准拦截。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-变更签名密钥最彻底但代价大"&gt;2. 变更签名密钥（最彻底但代价大）&lt;/h3&gt;
&lt;p&gt;如果怀疑是大规模泄露，或者加密私钥（Secret Key）本身已暴露：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;操作&lt;/strong&gt;：立即在服务端更换用于签署 JWT 的 &lt;strong&gt;Secret Key&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后果&lt;/strong&gt;：由于密钥变了，之前所有基于旧密钥生成的 Token（包括合法用户的）都会瞬间失效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：密钥泄露或遭受系统性攻击。虽然这会导致全服用户强制下线（需重新登录），但在极端安全风险下是必要的“熔断”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-强制用户重新登录变更-user-secret"&gt;3. 强制用户重新登录：变更 User Secret&lt;/h3&gt;
&lt;p&gt;如果你的 JWT 签名逻辑中引入了用户特有的变量（例如：&lt;code&gt;HMAC256(payload, base_secret + user_password_hash)&lt;/code&gt;）：&lt;/p&gt;</description></item><item><title>场景题 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-25-b5d632a4/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-25-b5d632a4/</guid><description>&lt;p&gt;共 84 道 场景题 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-58605bf6-530e-4b56-973a-a0147b3e3f7e"&gt;1. 现在大模型已经能高效生成界面代码，有人认为前端会被AI颠覆，也有人认为这只是工具革新。你更倾向哪种观点？你认为前端未来3-5年真正的机会点或转型方向是什么？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;更倾向于“工具革新”而非“彻底颠覆”；大模型替代的是低层实现工作，而前端正在向工程化、系统设计以及 AI 应用方向演进，未来机会集中在高阶工程能力与“前端 + AI”结合的领域。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;更倾向于“工具革新”的判断，但需要补充一个前提：&lt;strong&gt;被替代的不是前端岗位，而是低抽象层的前端工作&lt;/strong&gt;。大模型把“写代码”这一步极大压缩，但没有消解前端在系统中的职责。&lt;/p&gt;
&lt;p&gt;从本质上看，前端的价值不在于把设计稿转成代码，而在于&lt;strong&gt;如何在浏览器这个受限运行时中构建复杂系统并保证体验、性能与稳定性&lt;/strong&gt;。大模型擅长生成局部正确的代码，但在跨模块一致性、状态复杂性、长期可维护性等方面仍然依赖工程设计能力。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一为什么更接近工具革新"&gt;一、为什么更接近“工具革新”&lt;/h2&gt;
&lt;p&gt;大模型改变的是生产方式，而不是问题本身：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码生成 → 自动化&lt;/strong&gt;：组件、页面、样板代码的产出成本趋近于零&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;问题复杂度 → 上移&lt;/strong&gt;：从“怎么写”转为“怎么设计、怎么约束、怎么演进”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人机协作 → 常态化&lt;/strong&gt;：开发流程变成“设计 + 约束 + 校验 + 修正”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这和当年从 jQuery 到框架、再到工程化工具的演进类似，本质是抽象层不断上移。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二未来-35-年的核心变化"&gt;二、未来 3–5 年的核心变化&lt;/h2&gt;
&lt;p&gt;可以用一句话概括：&lt;strong&gt;前端从“实现层”走向“系统层 + AI 协同层”&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="1-前端工程能力会被放大而不是削弱"&gt;1. 前端工程能力会被放大，而不是削弱&lt;/h3&gt;
&lt;p&gt;大模型生成的代码，如果没有约束，很容易出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;状态混乱（状态源不清晰）&lt;/li&gt;
&lt;li&gt;性能问题（重复渲染、无效请求）&lt;/li&gt;
&lt;li&gt;架构失控（组件边界混乱）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此需要更强的工程能力去“约束 AI”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设计组件规范、状态模型、数据流&lt;/li&gt;
&lt;li&gt;统一代码风格与约束（lint / schema / contract）&lt;/li&gt;
&lt;li&gt;构建可持续演进的架构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本质上：&lt;strong&gt;人负责“系统正确性”，AI 负责“实现效率”&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2-前端--ai的应用能力成为新分水岭"&gt;2. “前端 + AI”的应用能力成为新分水岭&lt;/h3&gt;
&lt;p&gt;未来不是“用 AI 写代码”，而是“用前端做 AI 产品”：&lt;/p&gt;</description></item><item><title>小程序 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-14-68fba79b/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-14-68fba79b/</guid><description>&lt;p&gt;共 15 道 小程序 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-92e09b66-a9fc-435d-ad28-99011b2ddeaa"&gt;1. 小程序可以做哪些性能优化？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;小程序性能优化围绕逻辑层与视图层分离架构展开，重点是减少 setData 频率与数据体积，控制列表节点数量，优化首包体积与启动链路，并通过分包加载、WXS 与懒加载等手段降低渲染压力。核心原则是减少跨线程通信与避免无意义重渲染，从架构层面解决性能瓶颈。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;小程序的性能优化，不能只从“前端渲染”角度看。它的运行模型和 Web 不同，存在 &lt;strong&gt;逻辑层（JSCore）与视图层（WebView）分离&lt;/strong&gt; 的架构特征，核心瓶颈往往出在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;跨线程通信成本&lt;/li&gt;
&lt;li&gt;setData 传输体积&lt;/li&gt;
&lt;li&gt;渲染节点数量&lt;/li&gt;
&lt;li&gt;包体与启动链路&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优化必须围绕这些机制展开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一理解运行架构是前提"&gt;一、理解运行架构是前提&lt;/h2&gt;
&lt;p&gt;以 微信小程序 为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;逻辑层：JS 执行环境&lt;/li&gt;
&lt;li&gt;视图层：WebView 渲染&lt;/li&gt;
&lt;li&gt;两层通过 JSON 序列化通信&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每一次 &lt;code&gt;setData&lt;/code&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据序列化&lt;/li&gt;
&lt;li&gt;线程间传输&lt;/li&gt;
&lt;li&gt;视图层 diff&lt;/li&gt;
&lt;li&gt;真实节点更新&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以小程序性能优化的第一原则是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;减少跨线程通信的数据量与次数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二控制-setdata-的粒度与频率"&gt;二、控制 setData 的粒度与频率&lt;/h2&gt;
&lt;h3 id="1-避免大对象全量更新"&gt;1. 避免大对象全量更新&lt;/h3&gt;
&lt;p&gt;错误方式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;setData&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;newFormObject&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果 form 很大，每次都会整体传输。&lt;/p&gt;
&lt;p&gt;正确方式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;setData&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;form.username&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用路径更新，最小化数据传输。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2-合并多次-setdata"&gt;2. 合并多次 setData&lt;/h3&gt;
&lt;p&gt;多次调用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;setData&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;setData&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;会触发多次通信。&lt;/p&gt;</description></item><item><title>工具 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-20-5ca6730d/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-20-5ca6730d/</guid><description>&lt;p&gt;共 40 道 工具 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-32b2812f-241b-4d0a-9da6-3ff8556b2df1"&gt;1. 你了解 Git 的 Worktree 吗？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;Git Worktree 允许多个工作目录共享同一个仓库对象库，从而同时检出多个分支，避免频繁 checkout 带来的上下文切换问题。其核心依赖 Git 的对象数据库与工作区分离设计，相比 clone 具有更低的磁盘与网络成本，适用于并行开发、hotfix、版本对比和 CI 场景，本质是同一仓库的多工作视图机制。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;Git Worktree 是 Git 提供的一种 &lt;strong&gt;在同一个仓库中同时检出多个工作目录（working directory）&lt;/strong&gt; 的机制，本质目的是解决“一个仓库只能对应一个当前分支”的限制。&lt;/p&gt;
&lt;p&gt;在传统 Git 使用方式下，一个仓库目录只能 checkout 一个分支。如果正在开发 feature 分支，此时需要紧急修复线上 bug，就必须经历：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stash 当前修改&lt;/li&gt;
&lt;li&gt;切换分支&lt;/li&gt;
&lt;li&gt;修复问题&lt;/li&gt;
&lt;li&gt;再切回原分支恢复现场&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个过程不仅低效，而且容易产生冲突或遗漏。Worktree 的出现，就是为了避免这种上下文切换成本。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="一worktree-的核心原理"&gt;一、Worktree 的核心原理&lt;/h3&gt;
&lt;p&gt;Git 仓库实际上由两部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.git&lt;/code&gt;：对象数据库（commit、tree、blob、refs）&lt;/li&gt;
&lt;li&gt;working directory：当前检出的文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Worktree 允许 &lt;strong&gt;多个 working directory 共享同一个 &lt;code&gt;.git&lt;/code&gt; 对象库&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;commit history 只有一份&lt;/li&gt;
&lt;li&gt;objects 只有一份&lt;/li&gt;
&lt;li&gt;但可以有多个独立的代码目录，每个目录对应不同分支&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Git 内部会在 &lt;code&gt;.git/worktrees/&lt;/code&gt; 下维护额外工作区的元信息，每个 worktree 都拥有自己的 HEAD、index 和 checkout 状态。&lt;/p&gt;</description></item><item><title>工程化 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-19-539fb4b4/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-19-539fb4b4/</guid><description>&lt;p&gt;共 87 道 工程化 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-7d4e4120-8c99-4e2e-a2c7-98d186c57289"&gt;1. webpack 中的 Loader ，链式调用顺序会影响编译结果吗？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;Webpack Loader 是一个按流水线执行的源码转换系统，Loader 的链式顺序直接决定源码被处理的阶段；配置顺序为从左到右，但执行顺序为从右到左，每个 Loader 都处理前一个 Loader 的输出，因此顺序错误会导致语义阶段错乱甚至编译失败；通常应遵循“靠近源码的 Loader 放右侧，靠近运行时的 Loader 放左侧”的原则，同时 Loader 还存在 pitch 与 normal 两阶段执行机制，使顺序对最终编译结果具有决定性影响。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;会，而且这是 Webpack Loader 机制中&lt;strong&gt;非常关键且经常被误解的一点&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Loader 的链式调用顺序不仅会影响编译结果，而且很多语法是否能正确工作，完全取决于 Loader 的执行顺序。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;理解这个问题的关键在于：&lt;strong&gt;Loader 本质是一个“源码转换流水线（transform pipeline）”。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一loader-本质源码的逐步转换"&gt;一、Loader 本质：源码的逐步转换&lt;/h2&gt;
&lt;p&gt;在 Webpack 中 hookup 的 Loader，并不是同时运行的，它们会对模块源码进行&lt;strong&gt;一层一层的转换&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;原始源码 → loaderA → loaderB → loaderC → 最终 JS 模块
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个 Loader：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接收上一个 Loader 的输出&lt;/li&gt;
&lt;li&gt;返回新的源码字符串（或 AST 转换结果）&lt;/li&gt;
&lt;li&gt;交给下一个 Loader&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此：&lt;/p&gt;</description></item><item><title>性能优化 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-11-50b3ce31/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-11-50b3ce31/</guid><description>&lt;p&gt;共 52 道 性能优化 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-cf82f359-6b3e-494b-ba24-11d79fabb8b8"&gt;1. 响应式开发中，如何避免窗口大小监听导致的重排抖动？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;响应式开发中 resize 导致抖动的本质原因是窗口变化会高频触发事件，从而反复触发布局计算。常见优化方式包括对 resize 事件进行节流或防抖以降低执行频率，避免在回调中混合 DOM 读写以减少强制同步布局，利用 &lt;code&gt;requestAnimationFrame&lt;/code&gt; 控制更新时机，以及使用 &lt;code&gt;ResizeObserver&lt;/code&gt;、媒体查询或容器查询等浏览器能力，让布局响应更多地交给浏览器完成，从而降低重排和重绘的成本。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;在响应式开发中，如果直接监听 &lt;code&gt;resize&lt;/code&gt; 事件并在回调中执行布局计算或 DOM 操作，很容易引发频繁的 &lt;strong&gt;重排（reflow）和重绘（repaint）&lt;/strong&gt;。原因在于浏览器在拖动窗口尺寸时会持续触发 &lt;code&gt;resize&lt;/code&gt; 事件，如果每次触发都进行样式读取和 DOM 更新，就会造成布局计算不断被打断，从而出现明显的抖动或性能下降。&lt;/p&gt;
&lt;p&gt;实际工程中通常会从 &lt;strong&gt;事件触发频率控制、布局计算方式优化、浏览器能力利用&lt;/strong&gt; 三个层面进行处理。&lt;/p&gt;
&lt;p&gt;首先是对 &lt;code&gt;resize&lt;/code&gt; 事件进行频率控制。浏览器在拖拽窗口时可能每秒触发几十到上百次 &lt;code&gt;resize&lt;/code&gt;，如果每次都重新计算布局，主线程压力会非常大。常见做法是通过 &lt;strong&gt;节流（throttle）或防抖（debounce）&lt;/strong&gt; 将计算频率降低。例如在节流策略下，每隔一段时间才执行一次布局更新，从而避免在连续拖动过程中频繁触发布局计算。对于布局实时性要求较低的场景，防抖也比较适合，即在窗口停止变化后再执行计算。&lt;/p&gt;
&lt;p&gt;其次是避免在 &lt;code&gt;resize&lt;/code&gt; 回调中混合 &lt;strong&gt;DOM 读写操作&lt;/strong&gt;。浏览器在读取布局信息（例如 &lt;code&gt;offsetWidth&lt;/code&gt;、&lt;code&gt;getBoundingClientRect&lt;/code&gt;）时，如果之前存在未提交的样式修改，会强制触发布局计算，这被称为 &lt;strong&gt;强制同步布局（Forced Reflow）&lt;/strong&gt;。因此更好的方式是将读取和写入操作进行分离，例如先批量读取尺寸，再统一修改样式，或者借助 &lt;code&gt;requestAnimationFrame&lt;/code&gt; 将 DOM 更新安排到浏览器下一帧执行，避免频繁打断渲染流程。&lt;/p&gt;
&lt;p&gt;另外一个更现代的方案是尽量减少对 &lt;code&gt;window.resize&lt;/code&gt; 的依赖，而是使用 &lt;strong&gt;元素级尺寸监听机制&lt;/strong&gt;。浏览器已经提供了 &lt;code&gt;ResizeObserver&lt;/code&gt; API，可以直接监听某个容器尺寸变化。当布局响应依赖的是容器宽度而不是窗口宽度时，这种方式更加精确，同时也减少无关 resize 触发带来的性能损耗。&lt;/p&gt;
&lt;p&gt;在 CSS 层面也可以减少 JavaScript 的参与。例如使用 &lt;strong&gt;媒体查询（media query）&lt;/strong&gt; 或 &lt;strong&gt;容器查询（container query）&lt;/strong&gt; 来处理布局变化，让浏览器在样式计算阶段直接完成响应式适配。CSS 驱动的响应式布局通常比 JavaScript 监听 resize 更高效，因为浏览器可以对样式计算进行统一调度。&lt;/p&gt;</description></item><item><title>算法 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-6-af84cfb6/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-6-af84cfb6/</guid><description>&lt;p&gt;共 22 道 算法 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-0c2e2466-54ef-47b7-99f1-1064ea325a0b"&gt;1. 不重复最大子串&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;给定一个字符串，请实现一个函数来找到其中的不重复最大子串。例如，对于字符串&amp;quot;abcabcbb&amp;quot;，不重复最大子串是&amp;quot;abc&amp;quot;，长度为3。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;请写出实现该功能的代码，并说明其时间复杂度。&lt;/li&gt;
&lt;li&gt;考虑到性能优化，你认为还有哪些改进空间？请提出优化思路并实现优化后的代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;滑动窗口&lt;/strong&gt; 是解决最长不重复子串问题的核心思想。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set 方法&lt;/strong&gt;简单直观，但每遇到重复字符可能多次移动左指针。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Map 优化&lt;/strong&gt;通过记录字符索引，&lt;strong&gt;直接跳过重复区域&lt;/strong&gt;，减少不必要操作。&lt;/li&gt;
&lt;li&gt;时间复杂度 O(n)，空间复杂度 O(Σ)。&lt;/li&gt;
&lt;/ol&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;h2 id="一滑动窗口实现"&gt;一、滑动窗口实现&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lengthOfLongestSubstring&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;s&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Set&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;left&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;maxLen&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;right&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;right&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;s&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;right&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;has&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;s&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;right&lt;/span&gt;])) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;delete&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;s&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;left&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;left&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;add&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;s&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;right&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;maxLen&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Math.&lt;span style="color:#a6e22e"&gt;max&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;maxLen&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;right&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;left&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;maxLen&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 测试
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;lengthOfLongestSubstring&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;abcabcbb&amp;#34;&lt;/span&gt;)); &lt;span style="color:#75715e"&gt;// 输出 3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="思路"&gt;思路&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;strong&gt;滑动窗口&lt;/strong&gt; &lt;code&gt;[left, right]&lt;/code&gt; 遍历字符串。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;Set&lt;/code&gt; 存储当前窗口内字符。&lt;/li&gt;
&lt;li&gt;当遇到重复字符时，移动左指针，直到窗口内无重复字符。&lt;/li&gt;
&lt;li&gt;每次窗口扩大时更新最大长度。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="时间复杂度"&gt;时间复杂度&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;每个字符 &lt;strong&gt;最多进出窗口一次&lt;/strong&gt; → O(n)&lt;/li&gt;
&lt;li&gt;空间复杂度：O(min(n, Σ))，Σ 是字符集大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二性能优化"&gt;二、性能优化&lt;/h2&gt;
&lt;p&gt;上面方法每遇到重复字符，需要 &lt;strong&gt;逐个删除左边字符&lt;/strong&gt;。可以进一步优化为 &lt;strong&gt;直接跳过重复字符的索引&lt;/strong&gt;，使用 &lt;strong&gt;Map 存储字符上次出现的索引&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>编程题 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-17-98dde3d4/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-17-98dde3d4/</guid><description>&lt;p&gt;共 136 道 编程题 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-0a146449-b17c-42f8-9029-7c56402adc98"&gt;1. 实现发布订阅模式&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;发布订阅模式通过事件中心解耦消息发送者与接收者，核心实现是维护事件名与回调函数列表之间的映射关系。订阅阶段注册回调函数，发布阶段遍历执行所有监听函数，取消订阅则从列表中移除对应回调。工程实践中通常会扩展一次性订阅、事件隔离等能力，但在大型应用中需要避免滥用事件总线，以免造成事件链复杂和内存管理问题。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;发布订阅模式（Publish–Subscribe Pattern）是一种典型的 &lt;strong&gt;消息通信模式&lt;/strong&gt;。其核心思想是将消息的发送者（Publisher）与接收者（Subscriber）进行解耦，消息不会直接发送给某个具体对象，而是发布到一个事件或主题（Topic）上，由所有订阅该事件的监听者统一接收。&lt;/p&gt;
&lt;p&gt;在前端应用中，这种模式常用于 &lt;strong&gt;组件通信、事件总线、状态变化通知、插件系统等场景&lt;/strong&gt;。例如一个模块只负责发布事件，而其他模块只需要订阅事件即可响应变化，双方不需要知道彼此的存在。&lt;/p&gt;
&lt;p&gt;实现发布订阅模式通常需要三个核心能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注册订阅（subscribe / on）&lt;/li&gt;
&lt;li&gt;发布事件（publish / emit）&lt;/li&gt;
&lt;li&gt;取消订阅（unsubscribe / off）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本质上可以通过维护一个 &lt;strong&gt;事件名 → 回调函数列表&lt;/strong&gt; 的映射结构来实现。&lt;/p&gt;
&lt;p&gt;一个简单实现如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EventEmitter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;constructor&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;events&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Object.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;on&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;push&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;emit&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;, ...&lt;span style="color:#a6e22e"&gt;args&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handlers&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;handlers&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;handlers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;forEach&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt; =&amp;gt; &lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt;(...&lt;span style="color:#a6e22e"&gt;args&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;off&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handlers&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;handlers&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;events&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;eventName&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handlers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;filter&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt; =&amp;gt; &lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用方式如下：&lt;/p&gt;</description></item><item><title>计算机基础 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-21-e009ecee/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-21-e009ecee/</guid><description>&lt;p&gt;共 19 道 计算机基础 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-566e4e90-c81e-4fe0-82bc-27ca19b835e7"&gt;1. 进程、线程、协程分别是什么概念？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进程&lt;/strong&gt; 是独立的资源分配单位，每个进程有自己的内存和资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程&lt;/strong&gt; 是进程中的执行单元，线程之间共享进程的资源，但每个线程有自己的执行栈。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协程&lt;/strong&gt; 是一种用户级的轻量级线程，通过程序控制的方式在任务之间进行切换，适合需要高效并发的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;进程、线程和协程是计算机程序设计中不同层次的执行单元，各自有不同的概念和特点。以下是它们的详细解释：&lt;/p&gt;
&lt;h3 id="1-进程process"&gt;&lt;strong&gt;1. 进程（Process）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;概念&lt;/strong&gt;：进程是操作系统分配资源的基本单位，是正在执行的程序的实例。每个进程都有自己的地址空间、内存、文件描述符等资源。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;独立性&lt;/strong&gt;：进程是相互独立的，互不干扰。一个进程的崩溃不会直接影响到其他进程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源分配&lt;/strong&gt;：每个进程有独立的内存空间和系统资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开销大&lt;/strong&gt;：由于需要独立的资源和内存，进程之间的切换（上下文切换）开销相对较大。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;应用&lt;/strong&gt;：常用于需要高隔离性和独立性的场景，如多进程服务器、操作系统服务等。&lt;/p&gt;
&lt;h3 id="2-线程thread"&gt;&lt;strong&gt;2. 线程（Thread）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;概念&lt;/strong&gt;：线程是进程中的执行单元，是程序执行的最小单位。一个进程可以包含多个线程，这些线程共享进程的资源（如内存）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;共享资源&lt;/strong&gt;：线程之间共享进程的内存和资源，这使得线程间通信更加高效，但也带来了同步和竞争的问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开销小&lt;/strong&gt;：线程的创建和销毁比进程要快，线程之间的切换也比进程切换更高效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协作&lt;/strong&gt;：线程之间可以进行协作，适合进行多任务处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;应用&lt;/strong&gt;：适用于需要并发执行的场景，如多线程应用程序、并行计算等。&lt;/p&gt;
&lt;h3 id="3-协程coroutine"&gt;&lt;strong&gt;3. 协程（Coroutine）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;概念&lt;/strong&gt;：协程是一种轻量级的线程，允许在执行过程中挂起和恢复，支持非抢占式的任务切换。协程可以在单线程中并发执行多个任务，但它们之间的切换由程序控制而不是操作系统。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;协作式切换&lt;/strong&gt;：协程通过显式的挂起和恢复操作进行切换，不需要操作系统的调度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轻量级&lt;/strong&gt;：协程的创建和切换开销非常小，通常比线程更高效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：适用于需要大量并发操作但不需要多线程资源的场景，如异步编程、事件驱动编程等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;应用&lt;/strong&gt;：广泛用于异步编程、游戏开发、网络编程等领域。许多现代编程语言（如 Python 的 &lt;code&gt;asyncio&lt;/code&gt;、JavaScript 的 &lt;code&gt;async/await&lt;/code&gt;）都支持协程。&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-5296602e-3318-4ad0-81aa-023ab8454343"&gt;2. 解释性语言和编译型语言有什么区别？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点-1"&gt;题目要点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解释性语言&lt;/strong&gt;：逐行解释执行，适合快速开发和跨平台，但执行速度较慢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编译型语言&lt;/strong&gt;：预先编译成机器码，执行速度较快，适合性能要求高的应用，但开发周期较长。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现代编程语言和环境往往结合这两种方式，以兼顾开发效率和执行性能。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;解释性语言和编译型语言是两种不同的编程语言执行方式，它们在代码执行、编译过程、执行效率等方面有显著差异。以下是它们的主要区别：&lt;/p&gt;
&lt;h3 id="1-解释性语言interpreted-languages"&gt;1. &lt;strong&gt;解释性语言（Interpreted Languages）&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：解释性语言的代码在运行时由解释器逐行解释和执行。解释器将源代码逐步翻译成机器码或中间代码，然后立即执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;执行方式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码在每次执行时都会被解释器逐行翻译。&lt;/li&gt;
&lt;li&gt;不需要预先编译成机器码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;开发和测试灵活性&lt;/strong&gt;：支持即时运行和调试，便于快速开发和修改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨平台性&lt;/strong&gt;：代码可以在不同平台上运行，只要有相应的解释器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;</description></item><item><title>计算机网络 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-7-38746af8/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-7-38746af8/</guid><description>&lt;p&gt;共 104 道 计算机网络 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-2c32c460-d90d-45a6-b480-bb592fa9caa9"&gt;1. 说说流式输出的原理及其应用场景&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;流式输出的本质是将结果按时间拆分为连续数据流，实现边生成、边传输、边消费；它通过降低首字节时间和提升反馈及时性来改善用户体验；常见于大模型推理、长耗时任务、实时数据和媒体传输等场景；但同时也带来更高的工程复杂度，需要在体验收益与系统成本之间进行权衡。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;流式输出并不是一种新的计算模型，而是一种&lt;strong&gt;结果传递与消费方式的改变&lt;/strong&gt;：从“一次性生成、一次性返回”，变为“边生成、边传输、边消费”。理解它的关键，不在于某个具体 API，而在于&lt;strong&gt;数据生产方、传输层和消费方三者如何协同工作&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一流式输出的基本原理"&gt;一、流式输出的基本原理&lt;/h2&gt;
&lt;h3 id="1-传统非流式模式"&gt;1. 传统非流式模式&lt;/h3&gt;
&lt;p&gt;在非流式模式下，系统的执行流程是严格串行的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务端完整计算结果&lt;/li&gt;
&lt;li&gt;将结果一次性写入响应&lt;/li&gt;
&lt;li&gt;客户端在接收完全部数据后再进行处理或渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种模式的特点是实现简单，但&lt;strong&gt;首字节时间（TTFB）和用户感知延迟较高&lt;/strong&gt;，尤其当计算过程本身很慢时，用户在很长一段时间内得不到任何反馈。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2-流式输出模式"&gt;2. 流式输出模式&lt;/h3&gt;
&lt;p&gt;流式输出的核心变化在于：&lt;strong&gt;结果不再作为一个整体返回，而是被拆分为多个连续的数据块&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;典型流程是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务端在计算过程中，阶段性地产生部分结果&lt;/li&gt;
&lt;li&gt;每当有新数据可用，就立刻写入响应流并 flush&lt;/li&gt;
&lt;li&gt;客户端持续读取数据流，并逐步消费、渲染或处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在传输层，这通常依赖于&lt;strong&gt;长连接 + 分块传输&lt;/strong&gt;，例如 HTTP chunked encoding、Server-Sent Events 或 WebSocket。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="3-从系统视角看流式输出"&gt;3. 从系统视角看流式输出&lt;/h3&gt;
&lt;p&gt;从系统设计角度，流式输出具备以下特征：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据是&lt;strong&gt;按时间顺序增量产生的&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;消费方不需要等待生产方完全结束&lt;/li&gt;
&lt;li&gt;生产与消费之间形成一种弱同步关系&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这使得系统整体从“请求-响应”模型，转变为一种更接近“发布-订阅”的交互方式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二前端视角下的实现机制"&gt;二、前端视角下的实现机制&lt;/h2&gt;
&lt;p&gt;在前端，流式输出通常体现在&lt;strong&gt;如何读取并渲染增量数据&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以 HTTP 流为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;浏览器通过 Fetch API 获取一个 ReadableStream&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;getReader()&lt;/code&gt; 持续读取字节块&lt;/li&gt;
&lt;li&gt;将字节解码为文本或结构化数据&lt;/li&gt;
&lt;li&gt;逐步更新 UI，而不是等全部完成&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种方式要求前端具备更细粒度的状态管理能力，例如处理中间态、取消、错误恢复等问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三典型应用场景"&gt;三、典型应用场景&lt;/h2&gt;
&lt;h3 id="1-大模型推理与对话系统"&gt;1. 大模型推理与对话系统&lt;/h3&gt;
&lt;p&gt;这是当前最典型的应用场景之一。
模型在生成文本时是逐 token 产生的，流式输出可以让用户几乎立即看到内容开始出现，显著降低等待焦虑，同时也便于中途打断和重试。&lt;/p&gt;</description></item><item><title>设计模式 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-18-1729148f/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-18-1729148f/</guid><description>&lt;p&gt;共 7 道 设计模式 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-252664a5-1524-46f0-b6ae-ae7e2874f6e2"&gt;1. 开发的过程中你用到过哪些设计模式？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;设计模式是一种被广泛接受并经过验证的面向对象软件开发中的最佳实践。它们提供了一套解决常见问题的可重用设计方案。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;设计模式是一种被广泛接受并经过验证的面向对象软件开发中的最佳实践。它们提供了一套解决常见问题的可重用设计方案。&lt;/p&gt;
&lt;p&gt;以下是一些常用的设计模式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;单例模式（Singleton）&lt;/strong&gt;：确保一个类只有一个实例，并提供全局访问点来获取该实例。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;工厂模式（Factory）&lt;/strong&gt;：通过工厂方法创建对象，而不是直接使用&lt;code&gt;new&lt;/code&gt;操作符。这样可以隐藏具体实现，并根据需要创建所需类型的对象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;观察者模式（Observer）&lt;/strong&gt;：定义了一种一对多的依赖关系，当一个对象状态发生改变时，它的所有依赖者（观察者）都会收到通知并自动更新。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;装饰器模式（Decorator）&lt;/strong&gt;：动态地将责任附加到对象上。通过将对象包装在装饰器对象中，可以在运行时为对象添加新的行为。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略模式（Strategy）&lt;/strong&gt;：定义了一系列算法，将每个算法封装起来并使它们可以相互替换。策略模式可以让算法独立于客户端而变化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;适配器模式（Adapter）&lt;/strong&gt;：将一个类的接口转换成客户端所期望的另一个接口。适配器模式使得原本由于接口不匹配而无法一起工作的类可以协同工作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每个设计模式都有其特定的应用场景和优缺点，可以根据具体情况来选择使用。设计模式可以提高代码结构的灵活性、可维护性和可扩展性，并促进重用和解耦。然而，需要根据实际需求慎重选择和应用设计模式，避免过度设计或不必要的复杂性。&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-c79bd75a-aa6d-4228-9e55-c9fb41e97253"&gt;2. 什么是 MVVM？比之 MVC 有什么区别？什么又是 MVP ？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：3 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点-1"&gt;题目要点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MVC（Model-View-Controller）&lt;/strong&gt;：将应用程序分为模型、视图和控制器。控制器处理用户输入并更新模型和视图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MVVM（Model-View-ViewModel）&lt;/strong&gt;：将应用程序分为模型、视图和视图模型。视图模型处理视图和模型之间的交互，通过数据绑定简化状态管理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MVP（Model-View-Presenter）&lt;/strong&gt;：将应用程序分为模型、视图和展示者。展示者处理视图和模型之间的交互，并负责更新视图和模型。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每种模式都有其适用场景和优缺点，选择哪一种模式通常取决于应用程序的需求和团队的开发习惯。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;MVC、MVP 和 MVVM 是三种常见的软件架构设计模式，主要通过分离关注点的方式来组织代码结构，优化我们的开发效率。&lt;/p&gt;
&lt;p&gt;比如说我们实验室在以前项目开发的时候，使用单页应用时，往往一个路由页面对应了一个脚本文件，所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取，对用户事件的响应所有的应用逻辑都混合在一起，这样在开发简单项目时，可能看不出什么问题，当时一旦项目变得复杂，那么整个文件就会变得冗长，混乱，这样对我们的项目开发和后期的项目维护是非常不利的。&lt;/p&gt;
&lt;p&gt;MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑，Model 负责存储页面的业务数据，以及对相应数据的操作。并且 View 和 Model 应用了观察者模式，当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带，它主要负责用户与应用的响应操作，当用户与页面产生交互的时候，Co
ntroller 中的事件触发器就开始工作了，通过调用 Model 层，来完成对 Model 的修改，然后 Model 层再去通知 View 层更新。&lt;/p&gt;</description></item><item><title>趣味题 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-8-ff7de42b/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-8-ff7de42b/</guid><description>&lt;p&gt;共 35 道 趣味题 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-482d976d-6697-4456-b0f5-8af4dd060dfc"&gt;1. 过桥问题&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在一个夜晚，同时有4人需要过一桥，一次最多只能通过两个人，且只有一只手电筒，而且每人的速度不同。A，B，C，D需要时间分别为：1，2，5，10分钟。问：在17分钟内这四个人怎么过桥？&lt;/p&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;第一步：A、B过花时间2分钟。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;第一步：A、B过花时间2分钟。&lt;/p&gt;
&lt;p&gt;第二步：B回花时间2分钟。&lt;/p&gt;
&lt;p&gt;第三步：C、D过花时间10分钟。&lt;/p&gt;
&lt;p&gt;第四步：A回花时间1分钟。&lt;/p&gt;
&lt;p&gt;第五步：A、B再过花时间2分钟。&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-d131bd64-4531-4903-bb82-76aa277c7cb8"&gt;2. 圆环转圈问题&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;两个圆环，半径分别是1和2，小圆在大圆内部绕大圆圆周一周，问小圆自身转了几周？如果在大圆的外部，小圆自身转几周呢？&lt;/p&gt;
&lt;h3 id="题目要点-1"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;小圆所转动的路程便是小圆圆心走过的距离&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;小圆所转动的路程便是小圆圆心走过的距离&lt;/p&gt;
&lt;p&gt;所以在大圆外部转动，小圆圆心饶半径为3的大圆心转动，走了三圈&lt;/p&gt;
&lt;p&gt;所以在大圆内部转动，小圆圆心饶半径为1的大圆心转动，走了一圈&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-4f3611dc-0446-4d49-95ae-1798d1194c9b"&gt;3. 推理问题（二）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;赵女士买了一些水果和小食品准备去看望一个朋友，谁知，这些水果和小食品被他的儿子们偷吃了，但她不知道是哪个儿子。为此，赵女士非常生气，就盘问4个儿子谁偷吃了水果和小食品。老大说道：“是老二吃的。”老二说道：“是老四偷吃的。”老三说道：“反正我没有偷吃。”老四说道：“老二在说谎。”这4个儿子中只有一个人说了实话，其他的3个都在撒谎。那么，到底是谁偷吃了这些水果和小食品？&lt;/p&gt;
&lt;h3 id="题目要点-2"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;是老三偷吃了水果和小食品，只有老四说了实话。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;是老三偷吃了水果和小食品，只有老四说了实话。&lt;/p&gt;
&lt;p&gt;用假设法分别假设老大、老二、老三、老四都说了实话，看是否与题意矛盾，就可以得出答案。&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-4eea1bf3-0d20-454c-ae40-a83914d92828"&gt;4. 最后剩下谁？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;1～50 号运动员按顺序排成一排。教练下令：“单数运动员出列！”剩下的运动员重新排队编号。教练又下令：“单数运动员出列！”如此下去，最后只剩下一个人，他是几号运动员？如果教练下的令是“双数运动员出列！”最后剩下的又是谁？&lt;/p&gt;
&lt;h3 id="题目要点-3"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;教练下令“单数”运动员出列时，教练只要下5次命令，就能知道剩下的那个人。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;教练下令“单数”运动员出列时，教练只要下5次命令，就能知道剩下的那个人。&lt;/p&gt;
&lt;p&gt;此人在下第五次令之前排序为2，在下4次令之前排序为4，在下3次令之前排序为8，在下2次令之前排序为16，在下1次令之前排序为32，即32位运动员，因此答案是32号。&lt;/p&gt;
&lt;p&gt;如果教练下的令是“双数运动员出列！”，最后剩下的是1号。&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="question-74a00836-f5ab-4666-893c-00c62af412a3"&gt;5. 推理问题（一）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1.5 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;甲、乙、丙三个人在一起做作业，有一道数学题比较难，当他们三个人都把自己的解法说出来以后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;甲说：“我做错了。”&lt;/li&gt;
&lt;li&gt;乙说：“甲做对了。”&lt;/li&gt;
&lt;li&gt;丙说：“我做错了。”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在一旁的丁看到他们的答案并听了她们的意见后说：“你们三个人中有一个人做对了，有一个人说对了。”&lt;/p&gt;
&lt;p&gt;请问，他们三人中到底谁做对了？&lt;/p&gt;
&lt;h3 id="题目要点-4"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;假设丙做对了，那么甲、乙都做错了，这样，甲说的是正确的，乙、丙都说错了，符合条件，因此，丙做对了。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;假设丙做对了，那么甲、乙都做错了，这样，甲说的是正确的，乙、丙都说错了，符合条件，因此，丙做对了。&lt;/p&gt;</description></item><item><title>跨端技术 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-24-88e618f1/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-24-88e618f1/</guid><description>&lt;p&gt;共 19 道 跨端技术 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-da6f0b14-5108-4b3d-8949-b34d7f746509"&gt;1. 跨平台的开发中，如何处理不同平台的 API 差异？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：2 · 类型：QA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;条件编译：在编译阶段，通过特定注释（如 // #ifdef MP-WEIXIN）来区分不同平台，编写差异化代码。&lt;/p&gt;
&lt;p&gt;适配器模式（Adapter Pattern）：在运行时，封装一个统一的工具类（Adapter），在内部判断环境并调用对应的原生 API。例如，封装一个 StorageAdapter，内部判断环境并分别调用 uni.setStorageSync 或浏览器的 localStorage，对业务代码提供统一的 Promise 接口 。&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;处理不同平台的 API 差异，是跨平台开发中的核心挑战。其目标是在享受跨平台开发效率的同时，确保应用在各平台上的功能完整、性能稳定、体验原生。关键在于通过良好的架构设计来&lt;strong&gt;隔离与抽象&lt;/strong&gt;平台细节。&lt;/p&gt;
&lt;h3 id="核心原则与架构思想"&gt;核心原则与架构思想&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;抽象与统一接口层&lt;/strong&gt;：这是最根本的策略。不要在各处业务代码中直接调用平台特定的 API，而是&lt;strong&gt;封装一个统一的接口层&lt;/strong&gt;。业务逻辑只与这个抽象层交互，由它来屏蔽底层的平台差异。例如，一个文件操作模块，可以定义统一的 &lt;code&gt;readFile&lt;/code&gt; 和 &lt;code&gt;writeFile&lt;/code&gt; 方法，然后在内部根据平台调用不同的原生实现 。这种设计使得核心业务代码保持平台无关性，极大提升了可维护性 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;渐进增强与平稳退化&lt;/strong&gt;：在设计功能时，采用“渐进增强”思想。即，&lt;strong&gt;优先保证核心功能在所有平台都能运行&lt;/strong&gt;，然后为高版本平台或特定平台增强更优的体验。同时，做好“平稳退化”，对于不支持某些高级功能的平台，提供合适的降级方案，而不是让功能崩溃 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="关键技术方案与手段"&gt;关键技术方案与手段&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;条件编译&lt;/strong&gt;：这是在&lt;strong&gt;编译时&lt;/strong&gt;处理差异的高效方式。通过预定义的宏或配置，编译器只会将当前目标平台的代码打包到最终产物中 。例如，在 C++ 或前端构建工具中，可以使用 &lt;code&gt;#ifdef _WIN32&lt;/code&gt; 或类似语法，为不同平台编写不同的代码路径。这种方式打包结果纯净，但需要构建工具支持 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;运行时检测与分支判断&lt;/strong&gt;：这是在&lt;strong&gt;代码运行时&lt;/strong&gt;根据当前所处的平台环境，动态选择执行不同的逻辑。这是最常见和灵活的方式 。各框架通常提供全局的 API 用于获取平台信息，例如 React Native 的 &lt;code&gt;Platform.OS&lt;/code&gt; 和 &lt;code&gt;Platform.select()&lt;/code&gt; 方法 ，或 UniApp 中的 &lt;code&gt;uni.getSystemInfoSync().platform&lt;/code&gt; 。这种方式灵活直观，但代码中会存在一些分支判断。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文件/模块约定&lt;/strong&gt;：一些框架支持通过特定的文件命名约定来区分平台代码。例如，在 React Native 中，可以创建 &lt;code&gt;Component.ios.js&lt;/code&gt; 和 &lt;code&gt;Component.android.js&lt;/code&gt; 文件。在导入时，只需 &lt;code&gt;import Component from './Component'&lt;/code&gt;，框架会自动根据平台加载正确的文件 。这是一种将平台差异隔离在文件级别的优雅实践。&lt;/p&gt;</description></item><item><title>选择题 面试题</title><link>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-23-18275a1e/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://amatsuzero.github.io/LastStand/posts/frontend/interview-questions/tag-23-18275a1e/</guid><description>&lt;p&gt;共 154 道 选择题 面试题。答案默认折叠，便于先自行作答。&lt;/p&gt;
&lt;h2 id="question-fa065548-704b-4428-b784-6a038acde5b4"&gt;1. 对以下代码说法正确的是？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1 · 类型：Choice&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;arr&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;arr2&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, , &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;A. 执行 arr.length = 3，此时数组为 [1,2,3]&lt;/li&gt;
&lt;li&gt;B. 执行 arr[10] = 11，此时 arr.length 为 6&lt;/li&gt;
&lt;li&gt;C. 执行 delete arr[2]，此时 arr.length 为 4，数组为 [1,2,4,5]&lt;/li&gt;
&lt;li&gt;D. arr2.length 的长度为 2&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="题目要点"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;考察的是数组的length属性：&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;正确答案：A&lt;/strong&gt;
考察的是数组的length属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置数组的length值为小于当前值的非负整数n，则任何索引大于或等于n的数组元素都会从数组中被删除&lt;/li&gt;
&lt;li&gt;如果给一个索引为i​的数组元素赋值，而i大于或等于数组当前的length，则数组的length属性会被设 置为i+1​&lt;/li&gt;
&lt;li&gt;对数组元素使用delete操作符不会修改length属性，使用splice()​会&lt;/li&gt;
&lt;li&gt;如果省略数组字面量中的一个值，被省略的元素是不存在的，但它的索引是存在的&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h2 id="question-96eb9035-e189-4403-a370-eccca4a16345"&gt;2. 下列哪些不可以实现浏览器存储数据？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;难度：1 · 类型：Choice&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A. cookie&lt;/li&gt;
&lt;li&gt;B. localStorage&lt;/li&gt;
&lt;li&gt;C. session&lt;/li&gt;
&lt;li&gt;D. sessionStorage&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="题目要点-1"&gt;题目要点&lt;/h3&gt;
&lt;p&gt;session 不是浏览器端 API，指的是服务端会话机制，不能直接用于浏览器存储&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;参考答案&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;正确答案：C&lt;/strong&gt;
session 不是浏览器端 API，指的是服务端会话机制，不能直接用于浏览器存储&lt;/p&gt;</description></item></channel></rss>