<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>荆仕航的博客站</title><description>荆仕航的个人技术博客，分享前端开发、AI应用等技术经验与见解</description><link>https://blog.jingshihang.cn/</link><language>zh-cn</language><item><title>前端工程师转 AI Agent 开发，我的学习路线</title><link>https://blog.jingshihang.cn/posts/frontend-ai-agent-learning-route-final/</link><guid isPermaLink="true">https://blog.jingshihang.cn/posts/frontend-ai-agent-learning-route-final/</guid><description>一名前端工程师转向 AI Agent / AI 应用开发过程中的阶段性思考和学习计划</description><pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;这篇文章不是一份“权威路线图”，而是我作为一名前端工程师，在转向 AI Agent / AI 应用开发过程中的阶段性思考和学习计划。&lt;/p&gt;
&lt;p&gt;我希望把这个过程记录下来，一方面方便自己回顾，另一方面也给同样想从前端转向 AI 应用开发的同学一个参考。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;section&gt;&lt;h2 id=&quot;一为什么要转-ai-agent-开发&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#一为什么要转-ai-agent-开发&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;一、为什么要转 AI Agent 开发？&lt;/h2&gt;&lt;p&gt;让我决定拓宽方向的原因很直接：现在已经进入 AI 时代了，前端开发不会消失，但如果只会做前端，未来的路会越来越窄。&lt;/p&gt;&lt;p&gt;我过去 4 年主要做前端开发。以前，前端开发是一件很具体的事情，产品给出需求，设计给出页面，而我只需要负责把页面实现出来，把交互做好，接上接口，把异常处理掉，最后让用户可以稳定地使用这个功能。后来，前端开始涉及更多工程化、性能优化、组件设计和项目架构相关的内容，前端开发也不再只是单纯把页面写出来。而现在，AI 又把这个变化往前推了一步，各大小公司都在前赴后继地转向 AI，它不仅开始影响软件开发的方式，也开始影响前端工程师未来的能力边界。&lt;/p&gt;&lt;p&gt;我开始关注 AI Agent。AI Agent 是一种应用形态，它把大模型接入真实工具和业务流程，变成一个能帮人干活的系统。相比单纯调用大模型生成一段回答，AI Agent 更像是一个能理解任务、结合上下文、调用工具并完成具体工作的智能助手。它不只是“会聊天”，而是可以围绕一个目标去检索资料、分析问题、生成结果，甚至协助推进后续动作。&lt;/p&gt;&lt;p&gt;对我来说，在 AI 时代里，主动把自己的能力从单纯的前端开发，扩展到更完整的 AI Agent 开发，也算是在追热点。但这个热点不是短期噱头，而是一个正在改变软件开发和职业方向的长期趋势。&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cos.jingshihang.cn/1779207026752-gwrgeoiz6b.png&quot; alt=&quot;开头&quot;&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;二前端转-ai-agent-的跨度&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#二前端转-ai-agent-的跨度&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;二、前端转 AI Agent 的跨度&lt;/h2&gt;&lt;p&gt;一开始，我也想过先从 Python 学起。后来我想明白自己想做的到底是哪一层。学习 AI Agent 并不意味着放弃自己熟悉的技术栈，因为各大模型调用方式和各种工具，基本都有支持 Node.js 的开发方式。&lt;/p&gt;&lt;p&gt;在还没有接触机器学习、数据处理、模型训练这些更底层内容的时候，Python 并不是必须的。等后面熟悉 AI 应用开发之后，如果需要接触更底层的内容，再补 Python 会更合适。只是做 AI 应用，TypeScript + Node.js 完全可以作为起点。&lt;/p&gt;&lt;p&gt;AI 应用开发的重点，不是从零训练一个模型，而是如何把现有模型能力接入真实业务，让它在具体场景里产生价值。&lt;/p&gt;&lt;p&gt;调用大模型接口、实现流式输出、处理工具调用、接入数据库、做 RAG 检索、封装业务 API、做权限控制，这些事情 Node.js 都能完成。&lt;/p&gt;&lt;p&gt;对我来说，先用熟悉的 TypeScript 把 AI 应用开发的主线跑通，比一开始同时学习新语言、新框架、新概念要现实得多。现在很多主流开源项目，也有不少是用 TypeScript 做出来的。&lt;/p&gt;&lt;p&gt;所以我的选择是：先用自己熟悉的技术栈进入这个方向。后面遇到确实需要 Python 的场景，再补 Python，给自己找一个更容易启动的入口。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;三理解-ai-agent&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#三理解-ai-agent&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;三、理解 AI Agent&lt;/h2&gt;&lt;p&gt;我现在对 AI Agent 的理解是：&lt;strong&gt;AI Agent 是一个能够理解用户目标、结合上下文、调用工具，并在一定流程中自主完成任务的 AI 应用。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;对比普通聊天机器人偏向的&lt;strong&gt;回答&lt;/strong&gt;，Agent 更偏向&lt;strong&gt;做事&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;比如用户问：“帮我整理一下这个项目最近的风险点，并列出每个风险对应的负责人。”&lt;/p&gt;&lt;p&gt;如果只是普通聊天，模型可能会给出一段很通用的建议，比如建议关注进度风险、技术风险、沟通风险。但这对真实工作帮助有限，因为它没有团队上下文，也不知道这个项目最近发生了什么。&lt;/p&gt;&lt;p&gt;一个更接近 Agent 的系统，应该能够去查项目文档、会议纪要、任务状态、历史问题，然后基于这些真实信息进行总结。如果它发现某个风险没有负责人，甚至可以继续提示需要补充责任人。再进一步，它还可以创建待办、发通知、生成日报，甚至接入更多工具，完成一些自动化操作。现在很多主流 Agent 产品，也都在快速增强这类能力。&lt;/p&gt;&lt;p&gt;当然，第一阶段我更关心的是先把几个核心问题弄明白：大模型如何接入，Prompt 如何设计，流式输出怎么做，模型如何调用工具，RAG 如何让回答基于知识库等。&lt;/p&gt;&lt;p&gt;这些看起来像一个个技术点，但串起来以后，其实就是 AI Agent 的基本骨架。当模型调用工具、检索知识、执行任务这些能力逐步串起来之后，AI Agent 才会从一个普通聊天入口，慢慢变成一个真正能参与工作流程的系统。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;四学习路线&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#四学习路线&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;四、学习路线&lt;/h2&gt;&lt;p&gt;学习 AI Agent 很容易被各种方向带着跑。&lt;/p&gt;&lt;p&gt;一会儿看到模型训练，一会儿看到微调；一会儿看到 RAG，一会儿又看到 Tool Calling、MCP、LangChain、LangGraph、向量数据库、工作流编排。每个概念都很重要，每个方向也都能继续深入。&lt;/p&gt;&lt;p&gt;但对我来说，现阶段最重要的不是一上来什么都学，而是先明确边界。如果不先划清边界，很容易什么都想学，最后什么都学得很浅。&lt;/p&gt;&lt;p&gt;我更想先解决的是：如何使用现有大模型能力，把大模型接入真实业务系统，让 AI 能力变成一个用户可以使用、工程上可以维护、后续可以扩展的应用能力。&lt;/p&gt;&lt;p&gt;所以，我给自己定的学习路线，会围绕 AI Agent 应用开发逐步展开。&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cos.jingshihang.cn/1779207026750-aqf3v0i8mxl.png&quot; alt=&quot;学习路线&quot;&gt;&lt;/p&gt;&lt;section&gt;&lt;h3 id=&quot;1-大模型调用和-prompt-engineering&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#1-大模型调用和-prompt-engineering&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1. 大模型调用和 Prompt Engineering&lt;/h3&gt;&lt;p&gt;第一个阶段，先学会和大模型打交道。&lt;/p&gt;&lt;p&gt;这里不只是简单调一次接口，而是要理解大模型在应用里到底怎么工作。比如 system、user、assistant 这些消息角色分别起什么作用，模型参数会怎样影响输出。&lt;/p&gt;&lt;p&gt;同时，这一阶段也要学习 Prompt Engineering。&lt;/p&gt;&lt;p&gt;学习 Prompt 后会发现，Prompt 不只是“提示词”，而是一种和模型协作的任务说明书。它需要告诉模型当前任务是什么、输入内容是什么、输出格式是什么、有哪些限制、不确定时应该怎么处理。&lt;/p&gt;&lt;p&gt;很多时候模型回答不好，并不是模型笨，而是没有把任务、上下文和边界讲清楚。&lt;/p&gt;&lt;p&gt;这一阶段的目标，是先把大模型基础调用和 Prompt 设计跑通。至少能做出一个完整的聊天链路：用户输入问题，后端调用模型，能让模型按照相对稳定的格式返回内容。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;2-调用工具-tool-calling&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#2-调用工具-tool-calling&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2. 调用工具 Tool Calling&lt;/h3&gt;&lt;p&gt;第二个阶段，学习 Tool Calling。&lt;/p&gt;&lt;p&gt;通俗来说，调用工具一方面是让模型判断自己要使用哪个工具，另一方面是把模型返回的指定格式数据作为参数传递给工具函数，最后把结果再回传给模型，以此给模型添加使用工具的能力。&lt;/p&gt;&lt;p&gt;模型本身并不知道外部系统里的实时数据，也不能凭空访问业务接口。如果希望它完成更具体的任务，就需要通过 Tool Calling 把模型和外部工具连接起来。&lt;/p&gt;&lt;p&gt;这一阶段需要重点理解：如何定义工具，如何描述工具能力，如何设计工具参数，如何校验模型传来的参数，如何执行工具，如何把工具结果再交还给模型。&lt;/p&gt;&lt;p&gt;工具调用看起来像是模型能力，但背后其实是工程设计。工具定义得不清楚，模型可能不知道什么时候该调用；参数设计得不合理，后端执行起来就会很混乱。&lt;/p&gt;&lt;p&gt;所以 Tool Calling 是 AI Agent 开发里非常核心的一步。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;3-检索增强生成-rag&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#3-检索增强生成-rag&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;3. 检索增强生成 RAG&lt;/h3&gt;&lt;p&gt;第三个阶段，学习 RAG。&lt;/p&gt;&lt;p&gt;大模型自身的知识是有限的。它不知道本地文档，不知道公司内部资料，也不知道某些实时变化的信息。如果直接让模型回答，它回答得可能会比较笼统，或者只是告诉我们可以去哪里查看。&lt;/p&gt;&lt;p&gt;RAG 的意义就在于：先从外部知识库检索相关内容，再让模型基于这些内容回答。&lt;/p&gt;&lt;p&gt;这一阶段需要理解 Embedding 是什么，为什么要把文本变成向量，向量数据库解决什么问题，文档如何切分，除了向量检索还有哪些检索方式，检索到的内容又如何放进上下文里交给模型。&lt;/p&gt;&lt;p&gt;RAG 看起来只是“检索 + 生成”，但真正做起来会遇到很多细节。比如 chunk 太大或太小都会影响效果，检索结果不准确会影响回答质量，上下文太长会增加成本和时间，没有检索到内容时也要控制模型的回答。&lt;/p&gt;&lt;p&gt;这一阶段的重点，不只是把 RAG 跑通，还要理解它在 AI Agent 里的位置：负责给模型补充外部知识，让回答不只依赖模型自身记忆。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;4-agent-工作流和任务编排&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#4-agent-工作流和任务编排&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;4. Agent 工作流和任务编排&lt;/h3&gt;&lt;p&gt;第四个阶段，学习 Agent 的工作流和任务编排。&lt;/p&gt;&lt;p&gt;当模型可以调用工具，也可以基于外部知识回答之后，下一步就是让它围绕一个目标完成多步骤任务。&lt;/p&gt;&lt;p&gt;这时就会涉及任务拆解、步骤规划、状态管理、上下文传递、工具调用顺序、失败重试等问题。&lt;/p&gt;&lt;p&gt;简单来说，AI Agent 不应该只是“调用一次模型，然后返回结果”。更完整的 Agent 往往需要经历一个过程：理解目标，判断需要哪些信息，决定是否调用工具，读取工具结果，再继续分析，最后生成结果。&lt;/p&gt;&lt;p&gt;这一阶段可以开始理解 Workflow、Planning、Memory、Multi-step Reasoning 这些概念。&lt;/p&gt;&lt;p&gt;对我来说，先理解一个 Agent 如何完成一个简单的多步骤任务，比一上来研究多个 Agent 怎么协作更重要。&lt;/p&gt;&lt;p&gt;走到这一步之后，就已经可以尝试做一个真实项目了。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;5-ai-agent-工程化能力&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#5-ai-agent-工程化能力&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;5. AI Agent 工程化能力&lt;/h3&gt;&lt;p&gt;第五个阶段，是补齐 AI Agent 的工程化能力。&lt;/p&gt;&lt;p&gt;真正做一个 AI Agent 应用，不只是写 Prompt，也不只是调用模型。它需要有完整的工程系统来支撑。&lt;/p&gt;&lt;p&gt;比如服务端要负责模型调用、会话管理、工具编排、上下文管理、数据存储、错误处理、日志记录和接口封装。前端要负责输入交互、流式展示、结果渲染、状态反馈和用户体验。&lt;/p&gt;&lt;p&gt;这一阶段我会以实践为主，继续用 TypeScript / Node.js 作为主要起点。后端服务可以选择 Hono、Koa，或者直接使用 NestJS；数据库选择 PostgreSQL，ORM 可以考虑 Prisma 或 Drizzle，向量数据库可以选择 Qdrant，异步任务后面可以通过 Worker 结合 Redis 和 BullMQ。&lt;/p&gt;&lt;p&gt;但我不会一开始就把所有技术都堆上去。&lt;/p&gt;&lt;p&gt;第一版最重要的是先跑通核心链路。等基础能力稳定之后，再逐步补上任务队列、日志、监控、安全和部署这些工程能力。&lt;/p&gt;&lt;p&gt;AI Agent 最终还是一个软件系统，所以工程化能力一定绕不开。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;6-langchain-和-langgraph&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#6-langchain-和-langgraph&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;6. LangChain 和 LangGraph&lt;/h3&gt;&lt;p&gt;等大模型调用、Prompt、Tool Calling、RAG 和基础 Agent 工作流跑通之后，我会开始学习 &lt;a href=&quot;https://docs.langchain.com/oss/javascript/langchain/overview&quot;&gt;LangChain&lt;/a&gt; 和 &lt;a href=&quot;https://docs.langchain.com/oss/javascript/langgraph/overview&quot;&gt;LangGraph&lt;/a&gt;。这两个框架刚好也支持 TypeScript。&lt;/p&gt;&lt;p&gt;LangChain 更像是一个 AI 应用开发工具库，它把模型调用、Prompt、工具调用、RAG、记忆、链式调用等能力做了封装，可以帮助开发者更快搭建 AI 应用。&lt;/p&gt;&lt;p&gt;LangGraph 则更偏向 Agent 工作流编排。相比简单的一次模型调用，它更适合处理多步骤任务、状态流转、条件判断、循环执行和复杂 Agent 流程。&lt;/p&gt;&lt;p&gt;先用原生方式把核心流程跑通，再学习 LangChain 和 LangGraph。这样看框架时，就能更清楚它们到底解决了什么问题，也能更快上手和理解。&lt;/p&gt;&lt;p&gt;我的计划是：在真正需要 LangChain 或 LangGraph 之前，先进行下面的项目实践。等项目发展到确实需要框架能力时，再把它们逐步引入进来。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;五用项目实践检验学习路线&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#五用项目实践检验学习路线&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;五、用项目实践检验学习路线&lt;/h2&gt;&lt;p&gt;学习 AI Agent 最好的方式，肯定不是只看概念，而是做一个真实项目。&lt;/p&gt;&lt;p&gt;我目前要做的项目，是一个 &lt;strong&gt;AI 助理&lt;/strong&gt;，同时也是在工作中需要的一个团队 AI 助理。&lt;/p&gt;&lt;p&gt;这个项目对我来说比较合适，因为它既有实际业务价值，又能把 AI 应用开发中的关键能力串起来。后续也能接入自己的知识库，以及为我的 blog 添加一个 AI 助理。&lt;/p&gt;&lt;p&gt;第一版团队 AI 助理先解决几个简单但真实的问题。&lt;/p&gt;&lt;p&gt;比如团队知识库问答。很多团队都有大量文档，但真正找起来很痛苦。业务规则、技术方案、流程规范、历史问题，可能散落在不同系统里。AI 助理如果能基于知识库回答，并且给出来源，就已经能节省很多重复沟通成本。&lt;/p&gt;&lt;p&gt;再比如日常工作跟进。团队每天都会产生进度、风险、待办、负责人这些信息。如果 AI 能帮助汇总这些内容，就可以减少一些机械整理工作。&lt;/p&gt;&lt;p&gt;会议辅助也是一个很自然的场景。会议结束后，AI 可以帮助提取结论、待办和责任人。现在的会议基本都能自动生成相关会议纪要，&lt;strong&gt;但这些纪要后续能不能被持续跟踪、快速检索，我想 AI 助理可以很好地解决这个问题。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我选择这个项目，不是因为它听起来最酷，而是因为它足够贴近真实工作。它会让我逐步思考数据来源、权限控制、回答可信度和用户体验这些问题。&lt;/p&gt;&lt;p&gt;能够做一个真实解决这些问题的项目，比单纯做一个聊天 Demo 更有意义，也更能得到成长。&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cos.jingshihang.cn/1779207026754-f2xlw927twn.png&quot; alt=&quot;团队AI助理下&quot;&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;六总结&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#六总结&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;六、总结&lt;/h2&gt;&lt;p&gt;以上是我现阶段对 AI Agent 开发的学习路线。对我来说，这条路既不会完全脱离已有的工程经验，又能让我从单纯的前端开发，逐步扩展到更完整的 AI Agent 应用开发。&lt;/p&gt;&lt;p&gt;它可能会随着实践不断调整，但至少现在，我已经知道自己要从哪里开始了。&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;如果你也是前端开发，你肯定知道前端技术的更新迭代很快，学习新技术是一种常态 :)&lt;/p&gt;
&lt;p&gt;这篇文章算是一个起点。后面我会继续记录学习 AI Agent / AI 应用开发的过程，更希望每篇都解决一个真实问题。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/section&gt;</content:encoded><category>AI Agent</category><category>AI 应用</category><category>前端开发</category><category>学习路线</category><category>AI Agent</category></item><item><title>用 Astro 搭建个人博客：从零到上线的完整实践</title><link>https://blog.jingshihang.cn/posts/build-my-blog-with-astro/</link><guid isPermaLink="true">https://blog.jingshihang.cn/posts/build-my-blog-with-astro/</guid><description>一个前端工程师的博客搭建血泪史：为什么选 Astro、踩了哪些坑、以及最后如何真香了</description><pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2 id=&quot;为什么选择-astro&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#为什么选择-astro&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;为什么选择 Astro&lt;/h2&gt;&lt;p&gt;首先排除SPA模式，思考了以下几种：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt; —— 太重，我只是写博客，不需要服务端渲染那套&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nuxt&lt;/strong&gt; —— Vue 技术栈，但我更想试试新东西&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VitePress&lt;/strong&gt; —— 适合文档，博客功能不够灵活&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt; —— 静态生成、按需加载、支持 Vue/React 组件&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;最后选了 Astro，原因很简单：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;够轻&lt;/strong&gt; —— 默认零 JavaScript，页面加载快，SEO 友好&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;够灵活&lt;/strong&gt; —— 可以用 Vue 写组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;够新&lt;/strong&gt; —— 2024 年之后火起来的，新技术意味着有探索空间，值得试试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署简单&lt;/strong&gt; —— 静态站点，扔哪都能跑&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;实际用下来，挺满意的。下面记录一下搭建过程。包括我踩过的每一个坑。希望你的博客之路能比我顺畅一些。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;项目架构&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#项目架构&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;项目架构&lt;/h2&gt;&lt;p&gt;最初只是普通的静态博客，后来加了”个人动态”功能，就需要后端了，考虑到项目并不复杂，没必要分多个仓，于是就搞了个 Monorepo。后续有其他想法也会加入其他动态功能。&lt;/p&gt;&lt;p&gt;我有两个选择：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;纯前端方案&lt;/strong&gt; —— 用第三方服务（例如文章的评论使用的 Giscus，后续有空会自己实现）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前后端分离&lt;/strong&gt; —— 自己写个简单的 API 服务&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;我选了第二个。原因很简单：我想掌握全部控制权，随意改动。&lt;/p&gt;&lt;p&gt;于是我的博客变成了这样：&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;blog/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── apps/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── web/         # 前端 — Astro 静态博客&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── server/      # 后端 — Hono API 服务&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── pnpm-workspace.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── pnpm-lock.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;pnpm&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;pnpm 现代前端开发优选。它用硬链接的方式共享依赖，节省空间，速度快。对于 Monorepo 来说，pnpm 几乎是唯一选择。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;技术栈&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#技术栈&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;技术栈&lt;/h2&gt;&lt;p&gt;我的技术选型原则是：&lt;strong&gt;权衡利弊，刚刚好，不过度追求，不强行炫技&lt;/strong&gt;。&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;技术&lt;/th&gt;&lt;th&gt;用途&lt;/th&gt;&lt;th&gt;选择理由&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Astro 5.x&lt;/td&gt;&lt;td&gt;静态站点框架&lt;/td&gt;&lt;td&gt;核心理由：零 JS 默认，快&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vue 3.x&lt;/td&gt;&lt;td&gt;交互组件（个人动态页）&lt;/td&gt;&lt;td&gt;日常使用，生态丰富&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Swup&lt;/td&gt;&lt;td&gt;页面无刷新过渡动画&lt;/td&gt;&lt;td&gt;让网站有点 SPA 的感觉，提升体验&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CSS Variables&lt;/td&gt;&lt;td&gt;主题系统，使用 hsl()&lt;/td&gt;&lt;td&gt;原生支持，不用额外库，暗色模式轻松搞&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;有尝试过 &lt;code&gt;Tailwind CSS&lt;/code&gt;，但觉得太重了，有些地方需要去了解默认的各种样式，然后选择原生 &lt;code&gt;CSS + Less&lt;/code&gt; 简单方便。&lt;/p&gt;&lt;section&gt;&lt;h3 id=&quot;目录结构&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#目录结构&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;目录结构&lt;/h3&gt;&lt;p&gt;根据开发过程逐渐需要新的结构并参考优秀项目目录，最后形成了自己的规范：&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apps/web/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── public/                  # 静态资源（图片、字体）— 直接复制不构建&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── src/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── components/          # 组件 — 按功能模块分组&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   │   ├── aside/           # 侧边栏（作者卡片、分类、标签）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   │   ├── blog/            # 博客组件（文章卡片、详情）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   │   ├── header/          # 页头（导航、搜索、主题切换）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   │   └── moments/         # 个人动态（Vue 组件，因为交互多）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── content/             # 内容集合 — Astro 的核心特性&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   │   └── blog/            # 博客文章（Markdown 文件都放这里）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── layouts/             # 页面布局 — 统一 header/footer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── pages/               # 页面路由 — 文件即路由&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── plugins/             # Remark/Rehype 插件 — Markdown 增强&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── styles/              # 全局样式 — CSS Variables 定义&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── astro.config.mjs         # Astro 配置&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;几个设计原则：&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;按功能分组，而不是按类型&lt;/strong&gt; —— 比如 &lt;code&gt;components/blog/&lt;/code&gt; 里放所有博客相关的组件，而不是把所有组件平铺&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown 和代码分离&lt;/strong&gt; —— &lt;code&gt;content/blog/&lt;/code&gt; 只放 Markdown 文件，组件和逻辑放 &lt;code&gt;src/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路由即文件&lt;/strong&gt; —— Astro 的 &lt;code&gt;pages/&lt;/code&gt; 目录结构直接对应 URL 路径，直观&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;核心功能实现&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#核心功能实现&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;核心功能实现&lt;/h2&gt;&lt;section&gt;&lt;h3 id=&quot;1-markdown-内容管理&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#1-markdown-内容管理&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1. Markdown 内容管理&lt;/h3&gt;&lt;p&gt;Astro 的 Content Collections 是我决定用它的重要原因之一。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;我的文章 schema 定义：&lt;/strong&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; blog&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; defineCollection&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  type: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;content&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  schema: z.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    title: z.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 标题必填&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    pubDate: z.coerce.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 发布日期，自动解析&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    updatedDate: z.coerce.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 可选&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    description: z.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    tags: z.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(z.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;([]),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    category: z.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    draft: z.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 草稿模式&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;每篇文章的 Frontmatter 长这样：&lt;/strong&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;title: &apos;文章标题&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;pubDate: &apos;2026-03-12&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;description: &apos;文章描述&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;tags: [&apos;Astro&apos;, &apos;博客&apos;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;category: &apos;前端&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;draft: false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;正文内容...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;2-阅读时间统计&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#2-阅读时间统计&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2. 阅读时间统计&lt;/h3&gt;&lt;p&gt;我注意到很多博客都有”预计阅读时间”这个功能。&lt;/p&gt;&lt;p&gt;我的做法如下：&lt;strong&gt;核心依赖是两个插件&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mdast-util-to-string&lt;/code&gt; —— 提取 Markdown 的纯文本内容&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reading-time&lt;/code&gt; —— 计算阅读时间&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;插件代码：&lt;/strong&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// src/plugins/remark-reading-time.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { toString } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;mdast-util-to-string&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; readingTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;reading-time&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; remarkReadingTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;tree&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; textContent&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; toString&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(tree) &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 提取纯文本&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; stats&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; readingTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(textContent) &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 计算阅读时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 把结果写入 Frontmatter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    file.data.astro.frontmatter.readingTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      words: stats.words,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      minutes: Math.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;ceil&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(stats.minutes),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;最后将 &lt;code&gt;remarkReadingTime&lt;/code&gt; 插件添加到 Astro 配置中。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;3-markdown-样式从-tailwind-到自定义&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#3-markdown-样式从-tailwind-到自定义&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;3. Markdown 样式：从 Tailwind 到自定义&lt;/h3&gt;&lt;p&gt;Markdown 的渲染样式，我走过一段弯路。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;最开始&lt;/strong&gt;，我直接用了 &lt;code&gt;@tailwindcss/typography&lt;/code&gt; 插件，几行配置就能让 Markdown 有不错的默认样式。但用了一段时间后发现：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Tailwind 的类名太多，调试起来麻烦&lt;/li&gt;
&lt;li&gt;有些细节样式不符合我的设计&lt;/li&gt;
&lt;li&gt;为了自定义一个颜色，得覆盖一堆类&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;后来&lt;/strong&gt;，我干脆自己写了一套 Markdown 样式，用 Less 组织，按模块拆分：&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;styles/markdown/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── markdown.css      # 基础排版（标题、段落、列表、表格）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── markdown-code.css # 代码块样式（行号、复制按钮、滚动条）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;代码块&lt;/strong&gt;，自定义复制按钮：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;用 CSS 画图标（SVG mask），不用额外图片&lt;/li&gt;
&lt;li&gt;悬停时显示，点击后变勾选动画&lt;/li&gt;
&lt;li&gt;复制成功有视觉反馈&lt;/li&gt;
&lt;/ul&gt;&lt;note&gt;&lt;p&gt;提示&lt;/p&gt;&lt;p&gt;自定义样式虽然花时间，但完全可控，后期维护反而更轻松。&lt;/p&gt;&lt;/note&gt;&lt;p&gt;&lt;strong&gt;Markdown 插件方面&lt;/strong&gt;，我加了这些：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;remark-gfm&lt;/strong&gt; —— GitHub Flavored Markdown，支持表格、删除线等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;remark-github-admonitions-to-directives&lt;/strong&gt; —— GitHub 风格的提示块（例如上方的提示 —— :::note、:::warning）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rehype-autolink-headings&lt;/strong&gt; —— 标题自动生成锚点，点击可跳转&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rehype-code-copy&lt;/strong&gt; —— 代码块复制按钮（配合自定义样式）&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;4-vue-组件集成astro-的-island-架构&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#4-vue-组件集成astro-的-island-架构&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;4. Vue 组件集成：Astro 的 Island 架构&lt;/h3&gt;&lt;p&gt;个人动态页（类似朋友圈）是我博客里动态数据的部分。有发布动态、图片上传、删除。&lt;/p&gt;&lt;p&gt;所以我用 Vue 写了这个页面。这也是 Astro “Island 架构”的典型应用——只有需要交互的部分才加载 JavaScript。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;在 Astro 中使用 Vue 组件：&lt;/strong&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; MomentsPage &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;../components/moments/MomentsPage.vue&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;MomentsPage&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; client:load&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;注意 &lt;code&gt;client:load&lt;/code&gt; —— 这是 Astro 的指令，告诉它这个组件需要在客户端加载和渲染。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Astro 的几种客户端指令：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;client:load&lt;/code&gt; —— 页面加载时立即加载（适合首屏交互）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client:idle&lt;/code&gt; —— 浏览器空闲时加载（适合非关键交互）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client:visible&lt;/code&gt; —— 滚动到可见区域时加载（适合长页面）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client:only&lt;/code&gt; —— 只在客户端渲染，构建时跳过（适合依赖浏览器 API 的组件）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;坑：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;一开始我忘了加客户端指令，部署后直接报错：“window is not defined”。&lt;/p&gt;&lt;p&gt;原因是 Astro 默认是服务端渲染（SSR），在构建时执行。而 &lt;code&gt;window&lt;/code&gt;、&lt;code&gt;document&lt;/code&gt; 这些浏览器 API 在服务端不存在。&lt;/p&gt;&lt;p&gt;加上 &lt;code&gt;client:load&lt;/code&gt; 后，Astro 就知道这个组件要在客户端跑，构建时会跳过它。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;5-页面无刷新过渡swup-让体验更流畅&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#5-页面无刷新过渡swup-让体验更流畅&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;5. 页面无刷新过渡：Swup 让体验更流畅&lt;/h3&gt;&lt;p&gt;点击一个链接，页面一下切换，没有白屏，没有闪烁，像 SPA 一样流畅，这就是 Swup 做的事。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;配置非常简单：&lt;/strong&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// astro.config.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; swup &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;@swup/astro&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; defineConfig&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  integrations: [&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;swup&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({ containers: [&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;#swup&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;] })],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;然后在布局文件里给会变化的区域加个 &lt;code&gt;id=&quot;swup&quot;&lt;/code&gt;：&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;swup&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; data-swup&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;slot&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;过渡动画用 CSS 写，我用的默认淡入淡出，也可以自定义更复杂的效果。&lt;/p&gt;&lt;p&gt;使用上可能说不清哪里不一样，但会觉得”这个网站很流畅”。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;6-主题切换hsl-让调色变得简单&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#6-主题切换hsl-让调色变得简单&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;6. 主题切换：HSL 让调色变得简单&lt;/h3&gt;&lt;p&gt;暗色模式现在是标配了。我用 CSS Variables 实现，而且用 &lt;strong&gt;HSL&lt;/strong&gt; 定义颜色。&lt;/p&gt;&lt;p&gt;HSL（色相、饱和度、亮度）比 RGB 更直观。比如我想让主题色饱和度高一点，在 HSL 里只需要调饱和度（S），而在 RGB 里不够直观，需要多次尝试。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;想换主题色？只需要改 &lt;code&gt;--hue&lt;/code&gt; 一个值，全站颜色自动更新&lt;/li&gt;
&lt;li&gt;调亮度？改 &lt;code&gt;--lig&lt;/code&gt; 就行&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;亮暗模式切换逻辑：优先使用用户上次选择的主题，如果没有，就跟随系统偏好。&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; theme&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  localStorage.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;getItem&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;theme&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (window.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;matchMedia&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;(prefers-color-scheme: dark)&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;).matches &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;dark&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;light&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;document.documentElement.classList.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;toggle&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;dark&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, theme &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;dark&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 我把这段代码放在 &lt;code&gt;&amp;#x3C;head&gt;&lt;/code&gt; 里，并使用 &lt;code&gt;&amp;#x3C;script is:inline&gt;&amp;#x3C;/script&gt;&lt;/code&gt;，避免页面加载时闪烁（先亮后暗）。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;构建优化&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#构建优化&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;构建优化&lt;/h2&gt;&lt;p&gt;博客性能是我非常在意的。我用了几种优化手段：&lt;/p&gt;&lt;section&gt;&lt;h3 id=&quot;1-代码压缩&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#1-代码压缩&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1. 代码压缩&lt;/h3&gt;&lt;p&gt;用 &lt;code&gt;astro-compress&lt;/code&gt; 一站式压缩 CSS、JS、HTML。配置里我开启了所有选项：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;CSS 压缩 + 移除注释&lt;/li&gt;
&lt;li&gt;HTML 移除空白 + 移除注释&lt;/li&gt;
&lt;li&gt;JS 压缩 + 移除 console.log&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;最后再加上 EdgeOne Pages 对网站的优化，gzip/brotli，效果明显。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;2-图片优化&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#2-图片优化&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2. 图片优化&lt;/h3&gt;&lt;p&gt;图片通常是网站最大的资源。Astro 的 &lt;code&gt;Image&lt;/code&gt; 组件会自动把图片转换成 WebP 格式（现代浏览器支持），并且按需加载。&lt;/p&gt;&lt;p&gt;或者可以通过 &lt;code&gt;getImage&lt;/code&gt; API 手动处理图片压缩为 WebP/Avif 格式，也支持配置质量。&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { Image } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;astro:assets&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; cover &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;../images/cover.jpg&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Image&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;={cover} &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;封面&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&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;li&gt;WebP 格式比 JPEG 小 30% 左右&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;3-vite-配置优化&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#3-vite-配置优化&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;3. Vite 配置优化&lt;/h3&gt;&lt;p&gt;Astro 底层用 Vite，所以可以配置 Vite 的构建选项：&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;vite&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  build&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    minify&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;esbuild&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,      &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 快速压缩&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    cssCodeSplit&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,     &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// CSS 分割，按需加载&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    target&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;esnext&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,       &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 只针对现代浏览器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;4-seo-与分享卡片优化&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#4-seo-与分享卡片优化&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;4. SEO 与分享卡片优化&lt;/h3&gt;&lt;p&gt;博客写好了。微信、QQ、Twitter 上看到的链接预览卡片，都是靠 SEO 标签实现的。&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;&amp;#x3C;!-- src/pages/blog/[slug].astro --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;og:title&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;={frontmatter.title} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;og:description&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;={frontmatter.description} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;og:image&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;={ogImage} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;og:url&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;={Astro.url} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;og:type&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;article&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;article:published_time&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;={frontmatter.pubDate} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;article:author&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;={frontmatter.author} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;效果：&lt;/strong&gt; 分享链接时，自动显示文章标题、描述和封面图。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;后端-api-服务简单的力量&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#后端-api-服务简单的力量&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;后端 API 服务：简单的力量&lt;/h2&gt;&lt;p&gt;个人动态需要登录、发布、删除，所以需要一个后端。&lt;/p&gt;&lt;p&gt;我的原则是：&lt;strong&gt;够用就行，不搞复杂&lt;/strong&gt;。&lt;/p&gt;&lt;section&gt;&lt;h3 id=&quot;技术选型&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#技术选型&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;技术选型&lt;/h3&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;技术&lt;/th&gt;&lt;th&gt;用途&lt;/th&gt;&lt;th&gt;选择理由&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Hono&lt;/td&gt;&lt;td&gt;Web 框架&lt;/td&gt;&lt;td&gt;轻量、快速、TypeScript 友好&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;better-sqlite3&lt;/td&gt;&lt;td&gt;数据库&lt;/td&gt;&lt;td&gt;单文件、零配置、性能够用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;jose&lt;/td&gt;&lt;td&gt;JWT 鉴权&lt;/td&gt;&lt;td&gt;现代、安全、API 简单&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Hono 是我第一次用，体验很好。它的设计理念是”小”，整个框架只有几十 KB，但功能完整。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;核心代码结构&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#核心代码结构&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;核心代码结构&lt;/h3&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;apps/server/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── src/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── db/           # 数据库连接&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── middleware/   # JWT 鉴权中间件&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── routes/       # API 路由&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── index.ts      # 入口&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;入口文件简单：&lt;/strong&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { Hono } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;hono&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { cors } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;hono/cors&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Hono&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;/api/*&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;cors&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()) &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 允许跨域&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;/api/auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, authRoutes) &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 登录接口&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;/api/moments&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, momentRoutes) &lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 动态接口&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;JWT 鉴权中间件：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;原理是：用户登录后发一个 JWT token，之后每次请求都带上这个 token。服务端验证 token 有效性，有效才允许操作。&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; authMiddleware&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; token&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; c.req.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;Authorization&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)?.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;replace&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;Bearer &apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;token) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({ error: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;Unauthorized&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;401&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  try&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; payload&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; verify&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(token, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;JWT_SECRET&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    c.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, payload)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({ error: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;Invalid token&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;401&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;这段代码保护了所有需要登录的接口。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;部署流程从本地到线上&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#部署流程从本地到线上&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;部署流程：从本地到线上&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;部署策略：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;前端（web）：静态部署到腾讯云 EdgeOne Pages，CDN 加速，足够快而且免费够用&lt;/li&gt;
&lt;li&gt;后端（server）：跑在腾讯云轻量应用服务器，包年也便宜&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这样设计的好处是：前端完全静态，几乎零成本；后端按需部署，资源占用低。&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;&lt;code&gt;blog.jingshihang.cn&lt;/code&gt;：博客主站&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.jingshihang.cn&lt;/code&gt;：API 服务&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;图片/文件&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;图片和文件都放到腾讯云的 COS 对象存储里，访问速度快，便宜够用。&lt;/p&gt;&lt;p&gt;&lt;em&gt;开启图片防盗链，只允许指定域访问图片&lt;/em&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;# 用 PM2 守护进程&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;pm2&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; dist/index.js&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; --name&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; blog-server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;PM2 是个进程管理工具，可以保证服务挂了自动重启。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Nginx 反向代理：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我用 Nginx 做反向代理，把 &lt;code&gt;server.jingshihang.cn&lt;/code&gt; 的请求转发到本地 3001 端口。&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;nginx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    listen &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;443&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; ssl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    server_name &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;server.jingshihang.cn;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    location&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;        proxy_pass &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;http://localhost:3001;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;        proxy_set_header &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;Host $host;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;        proxy_set_header &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;X-Real-IP $remote_addr;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;踩坑记录都是真金白银换来的&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#踩坑记录都是真金白银换来的&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;踩坑记录：都是真金白银换来的&lt;/h2&gt;&lt;section&gt;&lt;h3 id=&quot;坑-1swup-页面切换后组件失效&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#坑-1swup-页面切换后组件失效&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;坑 1：Swup 页面切换后组件失效&lt;/h3&gt;&lt;p&gt;用 Swup 做页面无刷新切换时，遇到一个问题：切换页面后，代码块复制按钮、评论系统、统计脚本都不工作了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; Swup 只替换页面内容区域（&lt;code&gt;&amp;#x3C;main id=&quot;swup&quot;&gt;&lt;/code&gt;），但不会重新执行 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 标签。那些依赖 DOM 加载后初始化的脚本，在切换页面后就失效了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;解决：&lt;/strong&gt; 监听 Swup 的 &lt;code&gt;swup:content:replace&lt;/code&gt; 事件，在内容替换后重新初始化：&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 代码块复制按钮、评论系统、统计脚本、标题锚点滚动&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;swup:content:replace&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, initSomething)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;关键点：&lt;/strong&gt; 把需要初始化的逻辑封装成函数，首次加载时执行一次，每次 &lt;code&gt;swup:content:replace&lt;/code&gt; 时再执行一次。&lt;/p&gt;&lt;p&gt;这个事件是 Swup 内容替换完成后触发的，是重新绑定事件、重新初始化组件的最佳时机。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;坑-2shiki-代码高亮主题切换&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#坑-2shiki-代码高亮主题切换&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;坑 2：Shiki 代码高亮主题切换&lt;/h3&gt;&lt;p&gt;Astro 默认用 Shiki 做代码高亮，支持双主题（亮色/暗色）。但自定义样式时踩了个坑：&lt;strong&gt;样式覆盖不了&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt; 我想让代码块的背景色和网站主题更搭，但无论怎么写 CSS，Shiki 的内联样式都优先生效。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;原因：&lt;/strong&gt; Shiki 会在 &lt;code&gt;&amp;#x3C;pre&gt;&lt;/code&gt; 和 &lt;code&gt;&amp;#x3C;span&gt;&lt;/code&gt; 标签上直接写内联样式（&lt;code&gt;style=&quot;background-color: #xxx&quot;&lt;/code&gt;），优先级比外部 CSS 高。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;解决：&lt;/strong&gt; 必须用 &lt;code&gt;!important&lt;/code&gt; 强制覆盖，而且暗色模式要用 Shiki 提供的 CSS 变量：&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;css&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;/* 亮色模式 - Shiki 默认用 --shiki-light */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.markdown-body&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt; pre&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.astro-code&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.markdown-body&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt; pre&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.astro-code&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt; span&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;--shiki-light-bg&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;!important&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;/* 暗色模式 - 必须用 --shiki-dark */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.dark&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; .markdown-body&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt; pre&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.astro-code&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.dark&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; .markdown-body&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt; pre&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.astro-code&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt; span&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  color&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;--shiki-dark&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;!important&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;--shiki-dark-bg-custom&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;!important&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;strong&gt;关键点：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;!important&lt;/code&gt; 是必须的，不然优先级不够&lt;/li&gt;
&lt;li&gt;暗色模式要用 &lt;code&gt;var(--shiki-dark)&lt;/code&gt;，这是 Shiki 生成的变量&lt;/li&gt;
&lt;li&gt;亮色模式默认用 &lt;code&gt;var(--shiki-light)&lt;/code&gt;，可以不写&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这个坑让我明白了：&lt;strong&gt;Shiki 的双主题机制是靠 CSS 变量 + 类名切换实现的&lt;/strong&gt;，要覆盖样式就得用同样的方式，而且必须加 &lt;code&gt;!important&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;最后&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#最后&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;最后&lt;/h2&gt;&lt;p&gt;博客搭好了，地址：就是当前看到的页面 &lt;a href=&quot;https://blog.jingshihang.cn&quot;&gt;https://blog.jingshihang.cn&lt;/a&gt;&lt;/p&gt;&lt;p&gt;整体用下来，Astro 给我的感觉是很友好的。&lt;/p&gt;&lt;p&gt;不做多余的事，不强迫用它的生态，不绑死在固定技术栈上。这种反而让它变得更灵活。&lt;/p&gt;&lt;p&gt;Monorepo 架构让前后端分离更清晰，部署也方便。虽然一开始配置麻烦了点，但方便后续维护，长期来看是值得的。&lt;/p&gt;&lt;section&gt;&lt;h3 id=&quot;如果你也想搭博客&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#如果你也想搭博客&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;如果你也想搭博客&lt;/h3&gt;&lt;p&gt;我的建议是：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先想清楚需求&lt;/strong&gt; —— 你需要一个什么样的博客？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选对工具&lt;/strong&gt; —— Astro 适合博客，Next.js 适合复杂应用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别过度设计&lt;/strong&gt; —— 够用就行，后续可以迭代，重要的是执行，不能空想&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;未来规划&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#未来规划&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;未来规划&lt;/h3&gt;&lt;p&gt;博客不是一蹴而就的，我还有一些计划：&lt;/p&gt;&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 文章目录（TOC）组件和目录跳转&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 照片墙页面&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 评论系统集成（当前使用的 Giscus）&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; 国际化支持（如果有外国朋友想看）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;参考资源：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hono.dev/&quot;&gt;Hono 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pnpm.io/workspaces&quot;&gt;pnpm Workspace 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;hr&gt;&lt;p&gt;&lt;em&gt;如果这篇文章对你有帮助，欢迎分享和交流！也欢迎来我的博客留言～&lt;/em&gt;&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded><category>Astro</category><category>博客</category><category>前端开发</category><category>前端开发</category></item><item><title>JavaScript 实现防抖和节流</title><link>https://blog.jingshihang.cn/posts/debounce-throttle/</link><guid isPermaLink="true">https://blog.jingshihang.cn/posts/debounce-throttle/</guid><description>JavaScript 实现防抖和节流（含参数 / 返回值 / 取消功能）</description><pubDate>Thu, 17 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2 id=&quot;1-防抖debounce&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#1-防抖debounce&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1. 防抖(debounce)&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;当事件触发时，相应的函数并不会立即触发，而是会等待一定的时间；&lt;/li&gt;
&lt;li&gt;当事件密集触发时，函数的触发会被频繁的推迟；&lt;/li&gt;
&lt;li&gt;只有等待了一段时间也没有事件触发，才会真正的执行响应函数；
&lt;img src=&quot;https://cos.jingshihang.cn/contents/debounce.png?imageMogr2/thumbnail/1200x/format/webp/quality/80&quot; alt=&quot;debounce.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;section&gt;&lt;h3 id=&quot;11-应用场景&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#11-应用场景&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1.1 应用场景&lt;/h3&gt;&lt;p&gt;例如: &lt;code&gt;输入框中频繁的输入内容&lt;/code&gt;
&lt;code&gt;频繁的点击按钮，触发某个事件&lt;/code&gt;
&lt;code&gt;监听浏览器滚动事件，完成某些特定操作&lt;/code&gt;
&lt;code&gt;用户缩放浏览器的resize事件&lt;/code&gt;等等&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;12-简单实现&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#12-简单实现&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1.2 简单实现&lt;/h3&gt;&lt;p&gt;第一个参数为函数 fn, 第二个参数延迟时间 delay&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; debounce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.定义一个定时器, 保存上一次的定时器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _debounce&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 取消上一次的定时器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 延迟执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 外部传入的真正要执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;      fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _debounce&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;至此, 已经实现了一个简单的防抖函数&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;13-绑定this和参数args&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#13-绑定this和参数args&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1.3 绑定this和参数args&lt;/h3&gt;&lt;p&gt;当原始函数有参数和需要用到this时, 可以给 &lt;code&gt;_debounc&lt;/code&gt; 添加args接收参数并在调用函数fn时使用 &lt;code&gt;fn.apply(this, args)&lt;/code&gt;&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; debounce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 原始函数的参数args&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _debounce&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 绑定this&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      fn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, args)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _debounce&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;14-第一次是否执行&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#14-第一次是否执行&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1.4 第一次是否执行&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;首次触发时是否立即执行
接收一个参数 immediate, 定义一个默认值, 这里为false, 声明一个是否第一次激活的变量 isInvoke = false, 如果&lt;code&gt;immediate &amp;#x26;&amp;#x26; !isInvoke&lt;/code&gt;就执行函数并将 isInvoke = true, 如下代码&lt;/p&gt;
&lt;/blockquote&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; debounce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;immediate&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.定义一个定时器, 保存上一次的定时器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 是否激活了立即执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _debounce&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 取消上一次的定时器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 判断是否需要立即执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (immediate &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;isInvoke) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      fn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, args)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;     // 已经立即执行, 阻止下次触发的立即执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 延迟执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;        // 外部传入的真正要执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        fn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, args)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 将isInvoke初始化&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      }, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _debounce&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;15-增加取消方法&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#15-增加取消方法&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1.5 增加取消方法&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;如果触发了事件又不需要了, 比如直接离开此位置, 增加取消功能防止事件触发带来意外不当&lt;/p&gt;
&lt;/blockquote&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; debounce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;immediate&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.定义一个定时器, 保存上一次的定时器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _debounce&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 封装取消功能&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  _debounce.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;cancel&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _debounce&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;ul&gt;
&lt;li&gt;使用时便可以直接使用cancel方法&lt;/li&gt;
&lt;/ul&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; debounceAfterFn&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; debounce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(fn, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// 使用取消方法&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;debounceAfterFn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;cancel&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;16-函数返回值&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#16-函数返回值&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;1.6 函数返回值&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;解决返回值问题, _debounce返回一个Promise, 调用原始函数fn时拿到返回值&lt;code&gt;const result = fn.apply(this, args)&lt;/code&gt;, 再调用 &lt;code&gt;resolve(result)&lt;/code&gt; , 使用时通过&lt;code&gt;.then&lt;/code&gt;获取&lt;strong&gt;返回值&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;// debounce.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; debounce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;immediate&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.定义一个定时器, 保存上一次的定时器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _debounce&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 取消上一次的定时器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 判断是否需要立即执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (immediate &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;isInvoke) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; fn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, args)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;        resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(result)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;        // 延迟执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;          // 外部传入的真正要执行的函数, 拿到函数返回值并调用resolve&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;          const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; fn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, args)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;          resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(result)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;          isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;          timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        }, delay)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 封装取消功能&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  _debounce.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;cancel&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    isInvoke &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _debounce&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;防抖的使用&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#防抖的使用&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;防抖的使用&lt;/h3&gt;&lt;p&gt;在html中引入写好的防抖函数的文件debounce.js&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;input&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;cancel&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;取消&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;debounce.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; inputEl&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; counter &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; inputChange&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;`触发第${&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}次`&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, event)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 返回值&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &quot;aaaaaaaaaaaa&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 防抖处理 将原本函数放入 debounce 作为参数, 之后直接使用 debounceChange 即可&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; debounceChange&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; debounce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(inputChange, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  //文本框事件触发&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  inputEl.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;oninput&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 这种调用方法需要重新绑定this, 所写的debounce函数是没有问题的, 在实际开发中使用相对这里会简单一点&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    debounceChange.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(inputEl, args).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;Promise的返回值结果:&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, res)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 取消功能&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; cancelBtn&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;#cancel&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  cancelBtn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;onclick&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    debounceChange.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;cancel&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;2-节流throttle&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#2-节流throttle&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2. 节流(throttle)&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;如果这个事件会被频繁触发，那么节流函数会按照一定的频率来执行函数；&lt;/li&gt;
&lt;li&gt;不管在这个中间有多少次触发这个事件，执行函数的频率总是固定的；
&lt;img src=&quot;https://cos.jingshihang.cn/contents/throttle.png?imageMogr2/thumbnail/1200x/format/webp/quality/80&quot; alt=&quot;throttle.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;section&gt;&lt;h3 id=&quot;21-应用场景&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#21-应用场景&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2.1 应用场景&lt;/h3&gt;&lt;p&gt;例如: &lt;code&gt;鼠标移动事件&lt;/code&gt; &lt;code&gt;王者荣耀攻击键, 点击再快也是以一定攻速(频率)进行攻击&lt;/code&gt;等等&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;22-简单实现&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#22-简单实现&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2.2 简单实现&lt;/h3&gt;&lt;p&gt;时间间隔: &lt;code&gt;interval&lt;/code&gt;; 当前时间: &lt;code&gt;nowTime&lt;/code&gt;; 上次执行时间: &lt;code&gt;lastTime&lt;/code&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;主要算出每次点击后剩余的时间&lt;/li&gt;
&lt;li&gt;时间差 = 当前时间 - 上次执行时间&lt;/li&gt;
&lt;li&gt;剩余时间 = 时间间隔 - 时间差
&lt;code&gt;const remainTime = interval - (nowTime - lastTime)&lt;/code&gt;
判断当剩余时间 &amp;#x3C;= 0 时执行函数&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;每次执行完之后将 lastTime = nowTime&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; throttle&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.记录上一次的开始时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.事件触发时, 真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _throttle&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 2.1.获取当前事件触发时的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; nowTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; remainTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; interval &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (nowTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (remainTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.3.真正触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;      fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.4.保留上次触发的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; nowTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _throttle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;23-首次不触发leading和最后结束触发trailing&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#23-首次不触发leading和最后结束触发trailing&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2.3 首次不触发leading和最后结束触发trailing&lt;/h3&gt;&lt;section&gt;&lt;h4 id=&quot;231-leading&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#231-leading&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2.3.1 leading&lt;/h4&gt;&lt;p&gt;初始值lastTime = 0, 所以第一次的&lt;code&gt;nowTime - lastTime&lt;/code&gt;也会很大, &lt;code&gt;interval&lt;/code&gt;减去很大的正数, 就会成为负数 &amp;#x3C; 0  &lt;strong&gt;所以默认第一次是执行的&lt;/strong&gt;&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;如果 interval 不是特别大, 第一次就是执行的, 除非你设置的 interval 大于当前时间. 也就是从计算机元年(1970)到如今, 五十多年的频率, 这个节流可不简单 :)&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;如果想要第一次不执行, 拿到remainTime之前将 &lt;code&gt;lastTime = nowTime&lt;/code&gt; 即可&lt;/p&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; throttle&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { leading: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, trailing: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.记录上一次的开始时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.事件触发时, 真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _throttle&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 2.1.获取当前事件触发时的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; nowTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;leading) lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; nowTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; remainTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; interval &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (nowTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (remainTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.3.真正触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;      fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.4.保留上次触发的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; nowTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _throttle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h4 id=&quot;232-trailing&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#232-trailing&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2.3.2 trailing&lt;/h4&gt;&lt;p&gt;最后是否执行, 需要使用到定时器 setTimeout&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;首先定义一个定时器 timer = null&lt;/li&gt;
&lt;li&gt;如果&lt;code&gt;remainTime &amp;#x3C;= 0&lt;/code&gt; 判断 timer 不为空是就清除定时器并置空, 执行完函数后直接 return&lt;/li&gt;
&lt;li&gt;否则判断 trailing 为 true 且 timer 为 null 时, 设置一个定时器, 延迟时间就是剩余时间 remainTime&lt;/li&gt;
&lt;li&gt;通过 timer 执行函数后关于 lastTime 最后应该设置为什么, 取决于 leading 的值
&lt;ul&gt;
&lt;li&gt;当 leading 为 true 时, 相当于&lt;strong&gt;2.2简单实现&lt;/strong&gt;, 将lastTime赋值当前时间, 这里重新获取当前时间 &lt;code&gt;lastTime = new Date().getTime()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;当 leading 为 false 时, 直接将 lastTime 初始化为0, 相当于&lt;strong&gt;2.3.1 leading&lt;/strong&gt;, 为0时下次执行将会进入&lt;code&gt;if (!lastTime &amp;#x26;&amp;#x26; !leading) lastTime = nowTime&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;因此 &lt;code&gt;lastTime = !leading ? 0 : new Date().getTime()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; throttle&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { leading: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, trailing: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.记录上一次的开始时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.事件触发时, 真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _throttle&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 2.1.获取当前事件触发时的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; nowTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;leading) lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; nowTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; remainTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; interval &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (nowTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (remainTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;        clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.3.真正触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;      fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.4.保留上次触发的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; nowTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (trailing &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;timer) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;leading &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;        fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      }, remainTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _throttle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;24-绑定原本函数的this和参数args--增加取消方法和函数返回值&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#24-绑定原本函数的this和参数args--增加取消方法和函数返回值&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;2.4 绑定原本函数的this和参数args &amp;#x26;&amp;#x26; 增加取消方法和函数返回值&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;与防抖实现相同, 使用apply绑定this和传参数, 返回Promise来解决返回值问题 最终代码如下:&lt;/p&gt;
&lt;/blockquote&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; throttle&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { leading: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, trailing: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 1.记录上一次的开始时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;leading&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;trailing&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;resultCallback&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 2.事件触发时, 真正执行的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; _throttle&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.1.获取当前事件触发时的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; nowTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;leading) lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; nowTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;      // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; remainTime&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; interval &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (nowTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lastTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (remainTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (timer) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;          clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;          timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;        // 2.3.真正触发函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; fn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, args)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;        resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(result)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;        // 2.4.保留上次触发的时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; nowTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (trailing &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;timer) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;          timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;          lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;leading &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;          const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; fn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, args)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;          resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(result)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        }, remainTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  _throttle.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;cancel&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(timer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    timer &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    lastTime &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; _throttle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;p&gt;&lt;em&gt;也可使使用回调函数callback来处理返回值&lt;/em&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3 id=&quot;节流的使用&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#节流的使用&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;节流的使用&lt;/h3&gt;&lt;section class=&quot;code-block&quot;&gt;&lt;span class=&quot;copy-btn&quot;&gt;&lt;/span&gt;&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;input&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;cancel&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;取消&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;throttle.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; inputEl&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; counter &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; inputChange&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;`触发第${&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}次`&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, event)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 11111111111&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 节流处理&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; _throttle&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; throttle&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(inputChange, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    leading: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    trailing: &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  inputEl.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;oninput&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    _throttle.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(inputEl, args).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#FFAB70&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;Promise的返回值结果:&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, res)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;  // 取消功能&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; cancelBtn&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;#cancel&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  cancelBtn.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;onclick&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    _throttle.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;cancel&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A;--shiki-dark:#85E89D&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2 id=&quot;结语&quot;&gt;&lt;a class=&quot;anchor&quot; href=&quot;#结语&quot;&gt;&lt;span class=&quot;anchor-icon&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;display: block;&quot;&gt;&lt;g fill=&quot;none&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot;&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M6 9h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;14&quot; stroke-dashoffset=&quot;14&quot; d=&quot;M5 15h13&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.2s&quot; dur=&quot;0.2s&quot; values=&quot;14;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M10 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.5s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;path stroke-dasharray=&quot;16&quot; stroke-dashoffset=&quot;16&quot; d=&quot;M16 5l-2 14&quot;&gt;&lt;animate fill=&quot;freeze&quot; attributeName=&quot;stroke-dashoffset&quot; begin=&quot;0.7s&quot; dur=&quot;0.2s&quot; values=&quot;16;0&quot;&gt;&lt;/animate&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;结语&lt;/h2&gt;&lt;p&gt;学习自 &lt;code&gt;coderwhy(王红元)&lt;/code&gt; 老师, 感谢老师&lt;/p&gt;&lt;/section&gt;</content:encoded><category>JavaScript</category><category>前端开发</category></item></channel></rss>