Skip to content

RESTful、Git、SEO、HTTP 协议、缓存、Nginx

作者:青见春山

杂七杂八的一些知识点


REST(REpresentational State Transfer,表述性状态转移)是由 Roy Fielding 在其 2000 年的博士论文中提出的一种软件架构风格,RESTful 即"符合 REST 原则的" Web 服务设计方式。


一、核心概念

1. 资源(Resource)

REST 的核心思想是一切皆资源。每个资源用一个唯一的 URI(统一资源标识符)来标识。

Plain
/users          → 用户集合
/users/123      → ID 为 123 的用户
/users/123/orders → 该用户的订单集合

Image

2. 表述(Representation)

资源本身是抽象的,客户端拿到的是资源的表述(如 JSON、XML、HTML 等):

JSON
{
  "id": 123,
  "name": "张三",
  "email": "zhangsan@example.com"
}

3. 状态转移(State Transfer)

客户端通过 HTTP 动词(GET、POST、PUT、DELETE 等)来操作资源的状态

Image

Image

Image

Image

不是协议,不是标准,不是强制规范,只是一种建议的设计风格


二、六大约束

约束说明
客户端-服务器前后端分离,关注点分离
无状态每次请求包含所有必要信息,服务端不保存会话状态
可缓存响应可标记为可缓存或不可缓存
统一接口核心约束,下面详细展开
分层系统客户端无需关心是否直连服务器,中间可以有负载均衡、代理等
按需代码(可选)服务端可向客户端发送可执行代码(如 JS)

Image

Image


三、统一接口(REST 最核心的特征)

统一接口包含四个子约束:

1. 资源标识:URI

每个资源用唯一且一致的 URI 标识,URI 只描述名词,不描述动作。

Image

✅ 推荐❌ 不推荐
GET /users/123GET /getUser?id=123
DELETE /users/123POST /deleteUser

2. 资源表述:通过 HTTP 消息交换

请求和响应中使用标准的表述格式(JSON、XML 等),通过 Content-TypeAccept 头协商。

Image

Image

Image

3. 自描述消息

每个请求/响应包含足够的信息来说明如何处理它:

  • Content-Type: application/json

  • HTTP/1.1 200 OK

  • Cache-Control: no-cache

4. HATEOAS(超媒体即应用状态引擎)

客户端通过服务器返回的链接来发现可用操作:

JSON
{
  "id": 123,
  "name": "张三",
  "links": [
    { "rel": "self", "href": "/users/123", "method": "GET" },
    { "rel": "edit", "href": "/users/123", "method": "PUT" },
    { "rel": "orders", "href": "/users/123/orders", "method": "GET" }
  ]
}

Image

Image

所谓无状态就是每次都要带上所有信息,比如用户登录后,不是服务端记住状态,而是每次发请求客户端携带凭证,每个请求都是独立的,这样可以加多服务器,实现负载均衡

服务端:

Image

Image

Image

Image

Image


Image

四、HTTP 方法与 CRUD 映射

HTTP 方法语义CRUD 操作幂等安全
GET获取资源Read
POST创建资源Create
PUT全量替换资源Update
PATCH部分更新资源Update
DELETE删除资源Delete
  • 幂等性:多次请求结果一致(如 GET、DELETE 多次调用结果相同)

  • 安全性:不修改服务端资源状态(只有 GET 是安全的)


五、HTTP 状态码规范

范围含义常用示例
2xx成功200 OK201 Created204 No Content
3xx重定向301 Moved Permanently304 Not Modified
4xx客户端错误400 Bad Request401 Unauthorized403 Forbidden404 Not Found422 Unprocessable Entity
5xx服务端错误500 Internal Server Error502 Bad Gateway503 Service Unavailable

六、实践要点

URI 设计规范

Plain
✅ /users                  → 用户集合
✅ /users/123              → 单个用户
✅ /users/123/orders       → 用户的订单子资源
✅ /users/123/orders/456   → 某个具体订单

❌ /users/123/get          → 不要用动词
❌ /GetUser                → 不要用大驼峰
❌ /user_list              → 不要用下划线连接

版本控制

Plain
https://api.example.com/v1/users
https://api.example.com/v2/users

过滤、分页、排序

Plain
GET /users?status=active&page=1&per_page=20&sort=-created_at

请求/响应头

Plain
Content-Type: application/json
Accept: application/json
Authorization: Bearer <token>

七、RESTful vs 其他风格

特性RESTGraphQLgRPC
协议HTTP/1.1HTTPHTTP/2
数据格式JSON/XMLJSONProtobuf
接口定义无强制Schema 强类型.proto 文件
适用场景通用 Web API复杂查询/移动端高性能微服务

总结

RESTful 的本质是:

用 URI 命名资源,用 HTTP 方法表达操作,用状态码描述结果,保持无状态通信。

它不是一种协议或标准,而是一种设计哲学——通过统一接口、无状态、资源导向等约束,构建可伸缩、可维护的 Web 服务架构。

Image

实现这种风格restful

Image

ai写,生成接口文档

Image

Vibe coding

Image

git操作

Image

Image

seo优化,搜索引擎优化

Image

1. "什么是SEO?"

SEO 是通过优化网站的技术架构、页面内容和外部权威性,提升网站在搜索引擎自然结果中的排名,从而获取精准流量的系统方法。它不是一次性的操作,而是需要持续优化的长期策略。


2. "SEO 分为哪几类?"

分为三类:技术SEO(确保搜索引擎能抓取和索引,比如网站速度、移动端适配、结构化数据);页面SEO(优化页面本身,比如标题标签、关键词布局、内容质量);站外SEO(建立外部权威性,比如外链建设、品牌曝光)。


3. "怎么做关键词研究?"

首先通过工具(如 Google Keyword Planner、Ahrefs)挖掘目标关键词,然后从搜索量、竞争难度、搜索意图三个维度评估,优先选择搜索意图匹配且竞争度适中的词。特别重视长尾关键词,因为竞争小、转化率高。最后将关键词合理布局到标题、正文、URL 和图片 alt 中。


4. "影响SEO排名的主要因素有哪些?"

主要包括:内容质量与相关性(E-E-A-T 标准)、外链的质量和数量用户体验指标(Core Web Vitals)、移动端适配网站技术架构(爬取性、索引性)、搜索意图匹配度。其中内容和外链是最重要的两个因素。


5. "SEO 多久能见效?"

通常 3-6 个月可以看到明显效果,竞争激烈的行业可能更久。这取决于网站基础、行业竞争程度、内容产出频率和外链建设速度。我会先做技术审计快速修复明显问题,同时制定中长期内容和外链策略。


6. "如何做外链建设?"

核心原则是质量优于数量。主要方法:一是创建可链接资产(原创数据、深度指南、工具);二是客座文章,在行业权威网站发布内容;三是竞品外链分析,挖掘竞品的外链来源;四是断链建设,为别人的失效链接提供替代内容。坚决避免购买链接等黑帽手段。


7. "你用过哪些SEO工具?"

  • Google Search Console:监控索引状态、发现技术问题、查看搜索表现
  • Google Analytics 4:分析流量和用户行为
  • Ahrefs/SEMrush:关键词研究、竞品分析、外链分析
  • Screaming Frog:技术审计,爬取网站发现SEO问题
  • PageSpeed Insights:检测页面速度和 Core Web Vitals

robots.txt 和 Sitemap 的作用

robots.txt

一句话:告诉搜索引擎爬虫"哪些能爬,哪些不能爬"。

它是一个放在网站根目录下的文本文件(如 https://example.com/robots.txt),搜索引擎爬虫访问网站时首先读取它。

示例:

Plain
User-agent: *
Disallow: /admin/
Disallow: /private/
Allow: /public/
Sitemap: https://example.com/sitemap.xml

常见用途:

用途示例
禁止爬取后台页面Disallow: /admin/
禁止爬取敏感目录Disallow: /private/
禁止爬取某些文件Disallow: *.pdf$
允许特定爬虫User-agent: Googlebot + Allow: /
全站允许User-agent: * + Disallow:(空值表示全部允许)

⚠️ 注意事项:

  • robots.txt 是"君子协议",不是安全机制,恶意爬虫可以忽略它

  • 不要用它来隐藏敏感信息(应该用服务器权限控制)

  • 错误配置可能导致整站被搜索引擎屏蔽


Sitemap(站点地图)

一句话:给搜索引擎提供一份"网站所有重要页面的清单"。

它是 XML 格式的文件(如 https://example.com/sitemap.xml),列出网站中希望被搜索引擎索引的页面。

示例:

XML
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2026-05-14</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://example.com/seo-guide</loc>
    <lastmod>2026-05-10</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.8</priority>
  </url>
</urlset>

核心字段说明:

字段作用
<loc>页面完整 URL
<lastmod>最后修改日期
<changefreq>内容更新频率(daily/weekly/monthly)
<priority>页面重要程度(0.0-1.0)

Sitemap 的价值:

  • 帮助搜索引擎发现新页面(尤其是深层页面)

  • 告诉搜索引擎哪些页面最重要

  • 加速页面被收录的速度

  • 对大型网站(数千页面)尤其重要


两者的关系

Plain
搜索引擎爬虫访问网站


  读取 robots.txt ──→ 确定能爬哪些目录/页面


  根据 Sitemap ──→ 知道有哪些重要页面需要爬取


  开始抓取页面内容并建立索引

简单类比:

  • robots.txt = 门卫,告诉你"哪些房间能进,哪些不能进"

  • Sitemap = 建筑平面图,告诉你"这栋楼有哪些房间,分别在哪里"

两者配合使用,是技术 SEO 的基础配置。

offset,client,scroll

Image

Image

HTTP 协议基础详解

一、HTTP 的定义

HTTP(HyperText Transfer Protocol,**超文本传输协议**) 是一种用于分布式、协作式和超媒体信息系统的应用层协议。它基于请求-响应模型

Plain
客户端 (Client)  ----  HTTP 请求 (Request)  ---->  服务器 (Server)
客户端 (Client)  <---  HTTP 响应 (Response) ----  服务器 (Server)
  • 客户端:通常是浏览器,也可以是任何发送 HTTP 请求的程序(如 curl、Postman、移动端 App)

  • 服务器:接收请求并返回响应的 Web 服务器(如 Nginx、Apache、Node.js 服务)

一次完整的 HTTP 交互由请求报文响应报文组成:

HTTP
-- 请求报文示例 --
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html

-- 响应报文示例 --
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1234

<html>...</html>

二、HTTP 是无状态协议

无状态(Stateless) 意味着:服务器不会保存任何关于客户端的历史请求信息。每一次请求都是独立的,服务器不知道两次请求是否来自同一个客户端。

为什么这样设计?

  • 简化服务器设计,不需要维护会话状态

  • 提高可扩展性,请求可以被任意服务器处理

  • 降低服务器内存消耗

如何实现状态跟踪?

正因为无状态带来了很多业务场景的不便(如登录态),所以发展出了多种"状态管理"手段:

技术原理存储位置
Cookie服务器通过 Set-Cookie 响应头在客户端存储数据,后续请求自动携带客户端(浏览器)
Session服务器端存储会话数据,通过 Cookie 中的 Session ID 关联服务器端
Token(JWT等)服务端签发令牌,客户端每次请求在 Header 中携带客户端
URL 参数将状态信息放在 URL 查询参数中URL 中

Token(令牌) 是一个 广义概念 ,泛指用于身份验证和授权的凭证字符串。

JWT 是 Token 的一种具体实现形式。所有的 JWT 都是 Token,但不是所有的 Token 都是 JWT。

双token机制:

Access Token(JWT) → 短有效期(15分钟-2小时),用于身份验证

Refresh Token(随机字符串) → 长有效期(7天-30天),用于刷新 Access Token

  • Access Token 用 JWT :无状态验证,性能好,适合频繁请求

  • Refresh Token 用随机字符串 :需要存储,可以随时吊销(如用户退出登录、密码修改后强制下线)

  • JWT 的实现过程可以概括为: 用户登录成功后,服务端将用户标识信息(如用户ID、角色等)放入 Payload,连同固定的 Header(声明算法类型)一起进行 Base64Url 编码,再使用服务端密钥(secret)通过指定算法(如 HS256)对编码后的 Header 和 Payload 进行签名,最终将编码后的 Header、Payload、Signature 三段用 . 拼接生成 JWT 字符串返回给客户端;客户端收到后将其存储(通常在 localStorage 或 Cookie 中),并在后续每次请求时通过 Authorization: Bearer <token> 请求头携带发送给服务端;服务端收到请求后,先按 . 拆分三段,对 Header 和 Payload 重新计算签名并与传入的 Signature 对比,验证通过则说明令牌未被篡改,再检查 Payload 中的过期时间(exp)是否有效,验证全部通过后直接从 Payload 中提取用户信息用于业务处理,整个过程无需查询数据库,实现无状态认证。

Plain
Cookie 工作流程:

客户端                          服务器
  |---- POST /login ----------->|  登录成功
  |<--- Set-Cookie: sid=abc123--|  下发 Cookie
  |                             |
  |---- GET /user  ------------>|  携带 Cookie
  |     Cookie: sid=abc123      |  识别用户
  |<--- 200 OK (用户信息) ------|

三、HTTP 协议的演进

HTTP/1.0(1996 年)

核心特点:

  • 每次请求都需要建立一个新的 TCP 连接,请求完成后立即断开

  • 引入了 Content-Type 头部,支持传输非 HTML 内容

  • 引入了状态码体系

  • 支持 GETPOSTHEAD 方法

Plain
客户端                服务器
  |---- TCP 连接 ---->|  建立连接
  |---- GET /a ------>|  请求资源
  |<--- 200 OK ------|  响应
  |---- TCP 断开 ---->|  关闭连接

  |---- TCP 连接 ---->|  重新建立连接
  |---- GET /b ------>|  请求资源
  |<--- 200 OK ------|  响应
  |---- TCP 断开 ---->|  关闭连接

问题:每个资源都需单独建立 TCP 连接,开销大、效率低。


HTTP/1.1(1997 年,使用最广泛的版本)

核心改进:

  1. 持久连接(Keep-Alive)默认开启

    • 一个 TCP 连接可以发送多个请求和响应,不用每次都建立新连接
    Plain
    客户端                服务器
      |---- TCP 连接 ---->|  建立连接(一次)
      |---- GET /a ------>|
      |<--- 200 OK ------|
      |---- GET /b ------>|  同一连接复用
      |<--- 200 OK ------|
      |---- GET /c ------>|
      |<--- 200 OK ------|
      |---- TCP 断开 ---->|  最终关闭
  2. 管线化(Pipelining)

    • 客户端可以在不等待响应的情况下连续发送多个请求

    • 但服务器必须按序响应(队头阻塞问题)

    Plain
    客户端发出:  req1 -> req2 -> req3
    服务器返回:  res1 -> res2 -> res3 (必须按顺序)

    实际上由于实现复杂、队头阻塞等问题,很多浏览器默认不启用管线化。

  3. Host 头部(虚拟主机)

    • 同一 IP 的服务器可以托管多个域名

    • Host: www.example.com 成为必传头

  4. 新增请求方法PUTDELETEOPTIONSTRACECONNECT

  5. 分块传输编码(Chunked Transfer Encoding)

    • 服务器可以分块发送响应,不需要提前知道 Content-Length
  6. 更丰富的缓存机制

    • 引入 Cache-ControlETagIf-None-Match 等头部

HTTP/2(2015 年)

核心改进:

  1. 二进制分帧层(Binary Framing)

    • HTTP/1.x 是纯文本协议,HTTP/2 将数据分割为更小的二进制帧

    • 更易于解析,更高效

    Plain
    HTTP/1.1: "GET /index.html HTTP/1.1\r\nHost: ..."  (纯文本)
    
    HTTP/2:  [帧头][帧数据] [帧头][帧数据] [帧头][帧数据]  (二进制帧)
  2. 多路复用(Multiplexing)

    • 最关键改进:一个 TCP 连接上可以并发传输多个请求和响应

    • 彻底解决了 HTTP/1.1 的队头阻塞问题(应用层)

    • 每个请求/响应通过 Stream ID 标识,互不干扰

    Plain
    一个 TCP 连接:
    Stream 1: |--req--|----------|--res--|
    Stream 2: |---req---|-----|----res----|
    Stream 3: |--req--|----res----|
    帧交错发送,不需要等待前一个完成
  3. 头部压缩(HPACK)

    • 使用 HPACK 算法压缩 HTTP 头部

    • 维护一个头部字段索引表,避免重复传输相同头部

    • 大幅减少头部开销(尤其是 Cookie 等重复信息)

  4. 服务端推送(Server Push)

    • 服务器可以主动将客户端需要的资源"推送"过去

    • 例如:客户端请求 index.html,服务器主动推送 style.cssscript.js

    Plain
    客户端: GET /index.html
    服务器: PUSH_PROMISE /style.css    (主动推送)
    服务器: PUSH_PROMISE /script.js    (主动推送)
    服务器: 200 /index.html
    服务器: 200 /style.css
    服务器: 200 /script.js
  5. 流优先级(Stream Priority)

    • 客户端可以为不同的流设置优先级和依赖关系

注意:HTTP/2 在应用层解决了队头阻塞,但 TCP 层面的队头阻塞依然存在(一个 TCP 包丢失会阻塞所有流)。这正是 HTTP/3 要解决的问题。

Image


HTTP/3(基于 QUIC,2022 年标准化)

核心改进:

  1. 底层传输协议从 TCP 改为 QUIC(基于 UDP)

    Plain
    HTTP/1.1 & HTTP/2:  HTTP → TCP → IP
    HTTP/3:             HTTP → QUIC (UDP) → IP
  2. 彻底解决队头阻塞

    • QUIC 在传输层也支持多路复用

    • 单个流的数据包丢失不会影响其他流

  3. 更快的连接建立

    • TCP + TLS 需要 2-3 个 RTT(往返时间)

    • QUIC 将传输层握手和 TLS 握手合并,首次连接仅需 1 个 RTT

    • 0-RTT 恢复:已连接过的客户端可以 0 延迟恢复连接

  4. 连接迁移

    • TCP 连接基于四元组(源IP、源端口、目标IP、目标端口)

    • 网络切换(如 WiFi → 4G)会导致连接断开

    • QUIC 使用连接 ID 标识连接,网络切换时连接不中断

  5. 内置 TLS 1.3 加密

    • 加密不再是可选项,所有 QUIC 连接都默认加密

版本对比总结

特性HTTP/1.0HTTP/1.1HTTP/2HTTP/3
连接方式短连接持久连接多路复用多路复用
传输格式文本文本二进制帧二进制帧
队头阻塞存在存在(严重)TCP层仍存在彻底解决
头部压缩HPACKQPACK
服务端推送不支持不支持支持支持
底层协议TCPTCPTCPQUIC(UDP)
连接建立0 RTT0 RTT1-2 RTT(含TLS)0-1 RTT
连接迁移不支持不支持不支持支持

四、常见 HTTP 状态码

状态码是服务器对请求结果的反馈,由三位数字组成,按首位分类:

1xx —— 信息性状态码

状态码含义
100 Continue服务器已收到请求头,客户端应继续发送请求体
101 Switching Protocols服务器同意切换协议(如升级到 WebSocket)
103 Early Hints预加载资源提示

2xx —— 成功

状态码含义使用场景
200 OK请求成功最常见的成功状态
201 Created资源已创建POST 创建资源后返回
204 No Content成功但无响应体DELETE 删除成功后返回
206 Partial Content部分内容(范围请求)断点续传、视频分段加载

3xx —— 重定向

状态码含义使用场景
301 Moved Permanently永久重定向永久更换域名/URL,搜索引擎更新索引
302 Found临时重定向临时跳转(语义上应该用 GET)
303 See Other用 GET 请求另一个 URLPOST 后重定向到结果页
304 Not Modified资源未修改,使用缓存缓存验证成功
307 Temporary Redirect临时重定向(保持原请求方法)POST 临时重定向,不会降级为 GET
308 Permanent Redirect永久重定向(保持原请求方法)永久重定向且保持方法不变

301 vs 302:301 是永久性重定向,浏览器会缓存,下次直接请求新地址;302 是临时的,每次都要先请求原地址。

302 vs 307:302 允许浏览器将 POST 降级为 GET,307 严格保持原方法不变。

4xx —— 客户端错误

状态码含义使用场景
400 Bad Request请求格式错误参数校验失败、JSON 格式错误
401 Unauthorized未认证(需要登录)缺少或无效的认证凭证(Token/Cookie)
403 Forbidden已认证但无权限已登录但没有访问该资源的权限
404 Not Found资源不存在URL 错误或资源已被删除
405 Method Not Allowed请求方法不支持对只读资源发送 DELETE 请求
408 Request Timeout请求超时客户端发送请求太慢
409 Conflict资源冲突创建已存在的资源、版本冲突
413 Payload Too Large请求体过大上传文件超过限制
429 Too Many Requests请求频率超限触发限流/反爬

401 vs 403:401 表示"你是谁?我不知道",应该先登录;403 表示"我知道你是谁,但你没权限"。

5xx —— 服务器错误

状态码含义使用场景
500 Internal Server Error服务器内部错误代码 bug、未捕获的异常
502 Bad Gateway网关/代理收到上游无效响应Nginx 反代后端服务挂掉
503 Service Unavailable服务暂不可用服务器过载或维护中
504 Gateway Timeout网关超时上游服务器响应太慢

500 vs 502 vs 503

  • 500:代码本身出错了(NPE、数据库连接失败等)
  • 502:Nginx/Apache 等代理服务器无法连接后端应用服务器
  • 503:服务临时不可用(过载、维护),通常可重试恢复

五、HTTP 请求方法

方法语义幂等性有请求体说明
GET获取资源✅ 幂等通常无获取数据,可缓存、可收藏;请求内容没有通用语义
POST按目标资源语义处理内容不保证幂等通常有常用于提交或创建;满足显式条件时也可缓存
PUT全量更新资源✅ 幂等✅ 有完整替换目标资源
PATCH应用部分修改取决于补丁语义✅ 有幂等性由补丁格式和接口设计决定
DELETE删除资源✅ 幂等可选删除指定资源
HEAD获取响应头✅ 幂等❌ 无同 GET 但不返回响应体,用于检测资源是否存在
OPTIONS获取支持的方法✅ 幂等❌ 无CORS 预检请求会用到
TRACE回显请求✅ 幂等❌ 无用于诊断,生产中通常禁用
CONNECT建立隧道连接--用于 HTTPS 代理

幂等性:同一个请求执行一次和执行多次的效果相同。GET 幂等(多次读同一数据结果一致),POST 不幂等(多次提交可能创建多个资源)。

GET vs POST 的关键区别

特性GETPOST
参数位置URL 查询参数 ?key=value请求体 (Body)
参数长度URL 上限由客户端、服务器与中间设施决定,没有通用 2KB/8KB 标准请求体同样受各层配置限制,不是无限
可缓存规范允许满足显式缓存条件时允许,但实践中较少
可收藏为书签
浏览器历史参数保留在 URL 中参数不保留在 URL 中
安全性URL 更易进入历史、日志与 Referer请求体不等于加密;两者都应使用 HTTPS
幂等性规范语义幂等规范语义不保证幂等
内容类型查询串按 URL 规则编码;请求内容没有通用语义可使用多种媒体类型

六、追问详解:HTTP 与 HTTPS 的区别

核心区别

Plain
HTTP:   客户端 ----[明文数据]----> 服务器     (不安全)
HTTPS:  客户端 ----[加密数据]----> 服务器     (安全)

HTTPS = HTTP + SSL/TLS,在 HTTP 的基础上加了一层安全层:

特性HTTPHTTPS
默认端口80443
数据传输明文加密
证书不需要需要 CA 颁发的 SSL 证书
安全性防窃听、防篡改、防冒充
性能有额外加密开销(现在已可忽略)
SEO不利搜索引擎优先收录 HTTPS

HTTPS 提供的三大安全保障

  1. 机密性(Confidentiality) —— 加密传输

    • 使用对称加密算法(如 AES)加密数据

    • 即使被截获也无法读取内容

  2. 完整性(Integrity) —— 防篡改

    • 使用 MAC(消息认证码)确保数据未被修改
  3. 身份认证(Authentication) —— 防冒充

    • 通过 CA 证书验证服务器身份

    • 确保连接的是真正的目标服务器

TLS 握手过程(简化)

Plain
客户端                                    服务器
  |                                        |
  |---- ClientHello (支持的TLS版本、加密套件) -->|
  |<--- ServerHello (选定的版本、加密套件) ------|
  |<--- Server Certificate (证书) -------------|
  |<--- ServerHelloDone -----------------------|
  |                                        |
  |  (客户端验证证书、生成预主密钥)              |
  |---- ClientKeyExchange ------------------->|
  |---- ChangeCipherSpec -------------------->|
  |---- Finished (加密) --------------------->|
  |                                        |
  |<--- ChangeCipherSpec --------------------|
  |<--- Finished (加密) ----------------------|
  |                                        |
  |=== 加密通信开始 ==========================|

七、追问详解:HTTP/2 的改进

HTTP/2 相比 HTTP/1.1 的核心改进总结为以下几点:

1. 二进制分帧

  • HTTP/1.1 是纯文本协议,解析复杂(需处理换行符、空格等)

  • HTTP/2 将数据封装为二进制帧(Frame),每帧有固定的格式

  • 帧类型:HEADERS帧、DATA帧、SETTINGS帧、PRIORITY帧等

  • 多个帧组成一个消息(Message),一个或多个消息组成一个流(Stream)

2. 多路复用

  • 这是最重要的改进,彻底解决了 HTTP/1.1 的应用层队头阻塞

  • 同一个 TCP 连接中,多个请求和响应可以并行交错传输

  • 每个请求通过唯一的 Stream ID 标识,接收端根据 ID 重新组装

3. 头部压缩(HPACK)

  • HTTP 头部往往很大(尤其含 Cookie 时),且每次请求高度重复

  • HPACK 算法:

    • 维护一份静态表(常用头部字段的预定义索引)

    • 维护一份动态表(之前传输过的头部字段)

    • 霍夫曼编码压缩头部值

    • 只需发送索引号或增量更新

4. 服务端推送

  • 服务器预测客户端需要的资源,主动推送

  • 减少了请求往返次数

  • 实际应用中因控制粒度不够灵活,使用率不算高(Chrome 后来甚至移除了支持)

5. 流量控制

  • HTTP/2 实现了流级别的流量控制(基于窗口)

  • 防止一个流占用过多带宽影响其他流

6. 请求优先级

  • 客户端可以为每个流分配权重和依赖关系

  • 例如:HTML 文档优先级最高,CSS 次之,图片和脚本较低


八、补充知识:HTTP 常见请求头和响应头

请求头

头部说明
Host目标主机名(HTTP/1.1 必传)
User-Agent客户端标识
Accept客户端可接受的响应内容类型
Accept-Encoding可接受的压缩编码(gzip, br 等)
Cookie客户端存储的 Cookie
Authorization认证凭证(Bearer Token 等)
Content-Type请求体的数据类型
If-None-Match缓存验证(配合 ETag)
If-Modified-Since缓存验证(配合 Last-Modified)

响应头

头部说明
Content-Type响应体的数据类型(如 text/html, application/json
Content-Length响应体大小(字节)
Set-Cookie设置 Cookie
Cache-Control缓存策略
ETag资源的唯一标识(用于缓存验证)
Location重定向目标 URL(配合 3xx 状态码)
Access-Control-Allow-OriginCORS 跨域允许的源
Transfer-Encoding传输编码(如 chunked)

浏览器缓存机制详解

一、定义

浏览器缓存是浏览器将请求过的资源(如HTML、CSS、JS、图片等)保存在本地,当再次请求相同资源时,直接使用缓存而不向服务器发起请求的策略,目的是减少网络传输、提升页面加载速度、降低服务器压力

二、缓存存储位置优先级

Plain
Service Worker → 内存缓存(Memory Cache) → 磁盘缓存(Disk Cache) → 推送缓存(Push Cache)
存储位置特点
Service Worker可编程控制,需HTTPS,容量大
Memory Cache读取快,生命周期短(关闭标签页即失效)
Disk Cache持久化存储,容量较大,读取较慢
Push CacheHTTP/2专属,会话级别,只在本次连接有效

三、强缓存(无需与服务器通信)

强缓存命中时,浏览器直接从本地读取资源,不会发送请求到服务器,状态码显示 200 (from cache)200 (from disk cache)

1. Expires(HTTP/1.0)

Plain
Expires: Thu, 01 Jun 2025 16:00:00 GMT
  • 原理:服务器返回资源的绝对过期时间,浏览器在该时间之前直接使用缓存

  • 缺点:依赖客户端本地时间,如果用户修改了本地时间,缓存可能失效或不失效

2. Cache-Control(HTTP/1.1,优先级更高)

Plain
Cache-Control: max-age=3600

常用指令:

指令含义
max-age=秒数资源在多少秒后过期
s-maxage=秒数同max-age,但仅对CDN等共享缓存生效
public允许任何缓存(浏览器、CDN等)存储
private只允许浏览器缓存,不允许CDN等缓存
no-cache不是不缓存,而是每次使用前必须向服务器验证(走协商缓存)
no-store真正的不缓存,每次都从服务器重新下载
immutable资源永不变,在有效期内即使用户刷新也不验证

两者同时存在时Cache-Control 优先级高于 Expires


四、协商缓存(需与服务器通信验证)

强缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器判断资源是否更新:

  • 未更新:返回 304 Not Modified,浏览器继续使用本地缓存

  • 已更新:返回 200 和新资源

1. Last-Modified / If-Modified-Since

Plain
# 首次响应
Last-Modified: Wed, 10 May 2025 08:00:00 GMT

# 后续请求携带
If-Modified-Since: Wed, 10 May 2025 08:00:00 GMT
  • 原理:服务器返回资源最后修改时间,下次请求时浏览器将该时间发送给服务器验证

  • 缺点

    • 精度只到秒级,1秒内的多次修改无法识别

    • 资源内容没变但修改时间变了(如重新部署),也会导致缓存失效

2. ETag / If-None-Match(优先级更高)

Plain
# 首次响应
ETag: "abc123def456"

# 后续请求携带
If-None-Match: "abc123def456"
  • 原理:服务器返回资源的唯一标识(通常是内容的哈希值),内容变化则标识变化

  • 优点:比 Last-Modified 更精确,能感知内容变化

  • 缺点:需要服务器计算哈希,有一定性能开销

两者同时存在时ETag 优先级高于 Last-Modified


五、完整缓存决策流程

Plain
请求资源

检查 Cache-Control/Expires(强缓存)

命中? → YES → 直接使用本地缓存(200 from cache)
    ↓ NO
携带 If-None-Match/If-Modified-Since 发起请求(协商缓存)

服务器验证资源是否更新

未更新? → YES → 返回 304,使用本地缓存
    ↓ NO
返回 200 + 新资源

六、GET vs POST 的缓存行为

方法默认行为原因
GET默认可缓存幂等、无副作用,适合缓存
POST默认不缓存非幂等、有副作用,每次请求可能产生不同结果

POST 也可以通过设置 Cache-ControlContent-Location 响应头实现缓存,但实践中很少使用。


七、常见追问解答

1. no-cache 和 no-store 的区别

属性no-cacheno-store
含义可以缓存,但使用前必须向服务器验证禁止任何缓存,每次都从服务器下载
是否存储存储到本地缓存不存储到本地缓存
网络请求每次都发请求(协商缓存)每次都发请求(完整下载)
使用场景需要最新版本但允许缓存验证的资源敏感数据(密码、银行卡信息)
Bash
# no-cache:缓存但每次都验证
Cache-Control: no-cache

# no-store:完全不缓存
Cache-Control: no-store

2. 如何控制缓存策略?

JavaScript
// 频繁变动的资源(如HTML页面)
Cache-Control: no-cache  // 每次验证,确保获取最新

// 不常变动的资源(如版本化的JS/CSS)
Cache-Control: max-age=31536000  // 缓存1年

// 敏感数据
Cache-Control: no-store  // 完全不缓存

// 配合文件名哈希实现长期缓存 + 即时更新
// app.a1b2c3d4.js → max-age=31536000
// 内容变化后文件名变化,自动获取新资源

3. 用户行为对缓存的影响

用户操作缓存行为
地址栏输入URL正常缓存流程
F5刷新跳过强缓存,走协商缓存(加 Cache-Control: max-age=0
Ctrl+F5强制刷新跳过所有缓存(加 Cache-Control: no-cachePragma: no-cache
前进/后退正常缓存流程(部分浏览器可能用 Memory Cache)

八、最佳实践总结

Plain
1. HTML文件:Cache-Control: no-cache(每次验证,确保入口文件最新)
2. 带哈希的静态资源:Cache-Control: max-age=31536000, immutable(长期缓存)
3. API接口:Cache-Control: no-store(通常不缓存)
4. 图片等不常变资源:Cache-Control: max-age=86400(缓存1天)

一、AI 相关

1. AI 工具使用熟悉度

是的,我在日常开发中高频使用 AI 工具,主要包括:

  • 编码辅助:使用 Cursor、Trae 等 AI IDE 进行代码补全、Bug 修复、重构建议

  • 代码审查:让 AI 检查代码中的潜在问题、安全漏洞

  • 文档生成:自动生成 JSDoc 注释、API 文档

  • 技术调研:快速了解不熟悉的技术方案、对比不同方案的优劣

  • 调试辅助:将报错信息提供给 AI,快速定位问题

整体来说,AI 已经成为我工作流中不可或缺的一部分,更像是一个「智能编程伙伴」。


2. SSE 流式输出 + 不完整 Markdown 的渲染处理

SSE(Server-Sent Events)是什么:

SSE 是一种服务端向客户端单向推送数据的技术,基于 HTTP 协议。与 WebSocket 的双向通信不同,SSE 是服务端到客户端的单向流。它的特点是:

  • 基于普通 HTTP,不需要特殊协议升级

  • 自动重连机制

  • 数据格式是纯文本,以 data: 前缀分隔事件

  • 天然适合 AI 大模型的流式输出场景

前端通过 **EventSource** 或 **fetch** + **ReadableStream** 来消费:

JavaScript
const response = await fetch('/api/chat', { method: 'POST', body: JSON.stringify(data) })
const reader = response.body.getReader()
const decoder = new TextDecoder()

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  const chunk = decoder.decode(value)
  // 处理 SSE 数据块
  accumulatedText += parseSSEChunk(chunk)
  renderMarkdown(accumulatedText)
}

不完整 Markdown 的渲染问题:

这是 SSE 流式渲染中最核心的难点。例如 AI 正在输出:

Plain
这是**加粗文

此时只有 ** 的左标记,没有右标记,直接渲染会出错。

解决方案有几种策略:

策略一:容忍不完整语法的 Markdown 解析器

使用专门的「流式友好」Markdown 解析器(如 markdown-it 配合自定义处理),它能容忍不完整的语法:

  • 遇到未闭合的 **,暂时当作普通文本渲染

  • 遇到未闭合的代码块 ```,保持代码块样式但不执行语法高亮

策略二:分段渲染 + 延迟解析

JavaScript
function renderMarkdownSafely(text) {
  // 1. 找到最后一个可能不完整的行
  const lines = text.split('\n')
  const lastLine = lines[lines.length - 1]

  // 2. 判断最后一行是否有未闭合的 Markdown 语法
  const isComplete = checkMarkdownComplete(lastLine)

  // 3. 完整部分正常渲染,不完整的最后一行降级为纯文本
  if (isComplete) {
    return marked(text)
  } else {
    const completePart = lines.slice(0, -1).join('\n')
    return marked(completePart) + escapeHtml(lastLine)
  }
}

策略三:CSS 层面兜底

对流式渲染容器加 CSS 保护,防止不完整标签导致布局异常:

CSS
.streaming-content {
  overflow-wrap: break-word;
  word-break: break-word;
}
.streaming-content strong:empty,
.streaming-content em:empty {
  display: none;
}

实际项目中我倾向的方案是: 策略一 + 策略三 结合。使用一个支持容错的 Markdown 渲染库,同时用 CSS 做兜底保护,每个 chunk 到达时增量渲染,并对未闭合的语法块做特殊处理(等待下一个 chunk 补全后再渲染样式)。


3. 正常工作中的 AI 工作流 & 上下文定义

典型的 AI 辅助开发工作流:

Plain
明确需求 → 构建上下文 → AI 生成代码 → 人工审查 → 测试验证 → 提交

上下文定义是核心,关键在于让 AI 准确理解「我在什么环境里,要做什么事」:

示例:我要给项目加一个「文章收藏」功能

我会这样构建 Prompt 上下文:

Plain
【项目背景】
这是一个 Vue 3 + TypeScript + Pinia 的内容管理系统

【技术约束】
- 使用 Element Plus 作为 UI 库
- 请求封装在 src/utils/request.ts 中,基于 Axios
- 状态管理使用 Pinia,store 文件在 src/stores/ 目录
- 组件采用 `<script setup>` + Composition API 风格
- 代码规范:ESLint Airbnb + Prettier,tab 缩进

【现有代码参考】
以下是现有的「点赞」功能实现,收藏功能要参考它的模式:
(附上点赞组件和 store 的关键代码)

【需求描述】
请实现文章收藏功能,包括:
1. 收藏按钮组件
2. 收藏状态管理 store
3. 收藏 API 接口调用

【输出要求】
- 遵循上述代码风格和目录结构
- TypeScript 类型要完整
- 不要添加注释

上下文的关键要素:

  1. 技术栈声明 — 让 AI 知道用什么框架、什么版本

  2. 约束声明 — 告诉它项目规范、代码风格、禁止事项

  3. 示例代码 — 给它参考样本,比纯文字描述高效 10 倍

  4. 明确的输出期望 — 说清楚要什么,不要什么


4. AI 生成代码与项目规范冲突时的约束

多层次约束策略:

第一层:Project Rules / Rules 文件

在项目中创建 .cursorrules.github/copilot-instructions.mdCLAUDE.md 等规则文件,写明:

Markdown
## 项目规范
- 使用 Vue 3 Composition API + `<script setup>`
- TypeScript 严格模式
- 组件命名使用 PascalCase
- CSS 使用 scoped + BEM 命名
- 禁止使用 any 类型
- API 请求必须通过封装的 request 实例
- 错误处理必须统一使用 errorHandler

第二层:System Prompt 约束

在与 AI 对话时,一开始就在 System Prompt 中声明项目规范,作为全局约束。

第三层:Few-shot 示例

给 AI 展示「正确的代码长什么样」和「错误的代码长什么样」:

Plain
✅ 正确示例:
const useUserStore = defineStore('user', () => {
  const userInfo = ref<UserInfo | null>(null)
  // ...
})

❌ 错误示例(不要这样写):
export default {
  data() {
    return { userInfo: null }
  }
}

第四层:验证拦截

  • ESLint + Prettier 自动格式化,AI 生成的代码提交前必须过 lint

  • Code Review 环节人工把关

  • 写单元测试验证行为正确性


5. 大项目中如何控制 Token 消耗

核心原则:只给 AI 它需要的上下文,而不是全部代码

方法一:按模块/功能切分上下文

不要把整个项目扔给 AI,而是只给相关模块:

Plain
我正在处理用户模块,以下是相关的文件:
- src/stores/user.ts(用户状态管理)
- src/api/user.ts(用户 API)
- src/views/user/Profile.vue(个人资料页面)
请在这几个文件的基础上帮我实现...

方法二:代码摘要代替完整代码

对于大的文件,只给关键部分:

Plain
以下是 router/index.ts 的路由结构概要:
- / → HomeView
- /login → LoginView
- /user/:id → UserDetailView(需要鉴权)
- /admin → AdminView(需要管理员权限)

方法三:分层对话策略

层次内容Token 消耗
第 1 轮技术栈 + 目录结构 + 规范
第 2 轮当前模块的关键文件
第 3 轮具体需求 + 参考代码片段

先让 AI 理解项目结构,再逐步深入具体问题。

方法四:善用 **.cursorignore** 等工具

配置 AI IDE 的忽略文件,排除不需要索引的目录:

Plain
node_modules/
dist/
public/
*.test.ts
*.spec.ts

方法五:RAG(检索增强生成)

对于超大项目,可以:

  • 将项目文档、API 定义、类型定义等结构化存入知识库

  • AI 按需检索相关上下文,而不是全量加载

  • 这在企业级项目中越来越常见


二、八股文

1. new 操作符做了什么

new 操作符执行时会经历以下 4 个步骤:

JavaScript
function Person(name) {
  this.name = name
}
const p = new Person('Tom')

等价于:

JavaScript
function myNew(Constructor, ...args) {
  // 1. 创建一个空对象,指向构造函数的原型
  const obj = Object.create(Constructor.prototype)

  // 2. 执行构造函数,this 指向这个新对象
  const result = Constructor.apply(obj, args)

  // 3. 如果构造函数返回了一个对象,则返回该对象;否则返回新创建的对象
  return result instanceof Object ? result : obj
}

四个步骤总结:

  1. 创建空对象let obj = {}

  2. 链接原型obj.__proto__ = Constructor.prototype

  3. 绑定 this 并执行Constructor.call(obj, ...args)

  4. 判断返回值:构造函数返回的是对象就用返回的,否则用新创建的 obj


2. CommonJS 和 ES6 Module 的区别

对比维度CommonJSES6 Module
语法require() / module.exportsimport / export
加载时机运行时加载(动态)编译时静态分析(静态)
值的拷贝深拷贝(原始值复制,对象引用复制)活绑定(实时引用,读取的是最新值)
Tree Shaking❌ 不支持✅ 支持(静态分析可确定哪些模块没用到)
循环依赖可能输出已经执行的部分结果通过活绑定可以正确处理
环境Node.js 原生支持浏览器原生支持,Node.js 需配置
异步/同步同步加载支持异步加载(import() 动态导入)

关键区别举例 — 活绑定 vs 值拷贝:

JavaScript
// CommonJS
// counter.js
let count = 0
module.exports = { count, increment: () => count++ }

// main.js
const { count, increment } = require('./counter')
increment()
console.log(count) // 0 ❌ 拿的是导出时的快照

// ES Module
// counter.js
export let count = 0
export function increment() { count++ }

// main.js
import { count, increment } from './counter.js'
increment()
console.log(count) // 1 ✅ 活绑定,读取最新值

3. 0.1 + 0.2 !== 0.3 的原因

根本原因:JavaScript 使用 IEEE 754 双精度浮点数(64位)表示数字,而 0.1 和 0.2 在二进制中是无限循环小数,无法精确表示。

详细过程:

Plain
0.1 → 二进制: 0.000110011001100110011...(无限循环)
0.2 → 二进制: 0.00110011001100110011...(无限循环)

由于 64 位浮点数的尾数只有 52 位,超出部分会被截断,产生精度丢失。

Plain
0.1 + 0.2 = 0.30000000000000004 ≠ 0.3

解决方案:

JavaScript
// 方法一:toFixed(简单场景)
(0.1 + 0.2).toFixed(10) // "0.3000000000"

// 方法二:转为整数运算
(0.1 * 10 + 0.2 * 10) / 10 // 0.3

// 方法三:Math.round 精度容差
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // true

// 方法四:使用 toPrecision
parseFloat((0.1 + 0.2).toPrecision(12)) // 0.3

扩展:为什么 0.1 + 0.2 是 0.30000000000000004 而不是 0.30000000000000003?

因为两个近似值相加后的截断/舍入结果刚好超过了 0.3 一点点。


4. 浏览器的安全策略

① 同源策略(Same-Origin Policy)

  • 协议、域名、端口三者都相同才算同源

  • 限制:不同源的脚本不能读取对方的 DOM、Cookie、LocalStorage、IndexDB

  • AJAX 请求也受同源策略限制

  • 目的:防止恶意网站窃取用户数据

② CORS(跨源资源共享)

  • 服务端通过 Access-Control-Allow-Origin 等响应头来放行跨域请求

  • 简单请求直接发送,复杂请求会先发 OPTIONS 预检请求

③ CSP(Content Security Policy)

  • 通过 HTTP 响应头 Content-Security-Policy 配置

  • 限制页面可以加载的资源来源,防止 XSS 攻击

  • 例如:Content-Security-Policy: script-src 'self' https://cdn.example.com

④ XSS 防护

  • 输入过滤和输出编码

  • 使用 HttpOnly Cookie 防止 JS 读取

  • CSP 限制内联脚本

⑤ CSRF 防护

  • SameSite Cookie 属性(Strict / Lax

  • CSRF Token

  • 验证 Referer / Origin 头

⑥ 其他安全机制

  • X-Frame-Options:防止点击劫持(Clickjacking),限制页面是否可以被 iframe 嵌入

  • X-Content-Type-Options:防止 MIME 嗅探,设置 nosniff

  • HSTS(HTTP Strict Transport Security):强制使用 HTTPS

  • Sandbox:iframe 的沙箱属性,限制嵌入内容的能力


5. 浏览器的渲染机制

完整渲染流程(从 URL 到像素):

Plain
URL → 网络请求 → 解析HTML → 构建DOM树

                    解析CSS → 构建CSSOM树

                  DOM + CSSOM → 渲染树(Render Tree)

                    布局(Layout/Reflow)→ 计算每个节点的位置和大小

                    绘制(Paint)→ 生成绘制指令

                    合成(Composite)→ GPU 合成图层并显示

详细步骤:

  1. 解析 HTML → DOM 树

    • 逐行解析 HTML,遇到 <script> 会阻塞解析(除非 async/defer)

    • 生成树形 DOM 结构

  2. 解析 CSS → CSSOM 树

    • 解析所有 CSS(包括外部样式表、内联样式、继承样式)

    • CSS 不会阻塞 DOM 解析,但会阻塞渲染

  3. 构建渲染树(Render Tree)

    • 只包含可见节点display: none 的节点不包含)

    • visibility: hidden 会包含在渲染树中(占据空间)

    • 为每个节点计算最终的样式值(层叠、继承、优先级)

  4. 布局(Layout / Reflow)

    • 计算每个节点的几何信息(位置、宽高)

    • 相对单位转换为绝对像素

  5. 绘制(Paint)

    • 将布局信息转换为绘制指令(像素)

    • 按层绘制背景、文字、图片、边框等

  6. 合成(Composite)

    • 将多个图层合并

    • GPU 加速处理(transform、opacity 等属性触发合成层提升)

    • 最终输出到屏幕


6. 怎么避免重排(Reflow)和重绘(Repaint)

先区分概念:

  • 重排(Reflow):元素的几何属性(位置、大小)改变,需要重新计算布局。代价大。

  • 重绘(Repaint):元素外观改变(颜色、背景),但不影响布局。代价相对小。

  • 重排一定会引起重绘,重绘不一定引起重排。

触发重排的操作:

  • 增删可见 DOM 元素

  • 元素尺寸改变(宽高、边距、边框、内边距)

  • 内容变化(文字、图片尺寸)

  • 浏览器窗口 resize

  • 读取布局属性(offsetWidthscrollTopgetComputedStylegetBoundingClientRect

避免重排重绘的策略:

① 批量修改 DOM

JavaScript
// ❌ 每次修改都触发重排
el.style.width = '100px'
el.style.height = '100px'
el.style.margin = '10px'

// ✅ 合并为一次修改
el.style.cssText = 'width:100px; height:100px; margin:10px;'
// 或使用 class
el.classList.add('new-style')

② 离线操作 DOM

JavaScript
// 使用 DocumentFragment
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li')
  li.textContent = `Item ${i}`
  fragment.appendChild(li)
}
document.getElementById('list').appendChild(fragment) // 只触发一次重排

// 或使用 display:none 隐藏元素后再操作
const el = document.getElementById('list')
el.style.display = 'none'
// ... 大量修改
el.style.display = 'block' // 只触发两次重排

③ 避免频繁读取布局属性

JavaScript
// ❌ 读写交替导致强制同步布局(Forced Synchronous Layout)
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = container.offsetWidth + 'px' // 每次循环都触发重排
}

// ✅ 缓存布局值
const width = container.offsetWidth
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = width + 'px'
}

④ 使用 CSS transform 代替 top/left

JavaScript
// ❌ 触发重排
el.style.top = '100px'

// ✅ 只触发合成,不重排不重绘
el.style.transform = 'translateY(100px)'

⑤ 使用 **will-change** 提示浏览器

CSS
.animated-element {
  will-change: transform, opacity;
}

⑥ 使用 **requestAnimationFrame** 批量处理动画

JavaScript
requestAnimationFrame(() => {
  el.style.transform = `translateX(${x}px)`
})

7. 进程通信方式及使用场景

浏览器中常见的进程通信方式:

① 管道(Pipe)

  • 原理:半双工通信,数据只能单向流动

  • 使用场景:父子进程间简单的数据传递

  • 例如:shell 命令 cat file.txt | grep "keyword"

② 消息队列(Message Queue)

  • 原理:进程将消息写入队列,另一个进程从队列中读取

  • 使用场景:异步通信,不需要立即响应的场景

  • 例如:浏览器中的 postMessage

③ 共享内存(Shared Memory)

  • 原理:多个进程可以访问同一块物理内存

  • 使用场景:大量数据传输,性能要求高的场景

  • 例如:SharedArrayBuffer(Web Worker 间共享数据)

④ 信号量(Semaphore)

  • 原理:用于进程间的同步和互斥

  • 使用场景:控制对共享资源的并发访问

⑤ Socket

  • 原理:可以在不同机器间通信

  • 使用场景:网络通信、不同主机间的进程通信

  • 例如:WebSocket

浏览器架构中的进程通信:

通信方式使用场景
IPC(浏览器内部)主进程与渲染进程通信
postMessage页面与 iframe、Web Worker 通信
Broadcast Channel同源页面间通信
Service Worker拦截网络请求、离线缓存、推送通知
SharedArrayBuffer + AtomicsWorker 间高性能数据共享

8. TCP 是怎么实现可靠传输的

TCP 通过以下机制实现可靠传输:

① 三次握手建立连接

Plain
客户端 → SYN=1, seq=x          → 服务端
客户端 ← SYN=1, ACK=1, seq=y, ack=x+1 ← 服务端
客户端 → ACK=1, seq=x+1, ack=y+1  → 服务端
  • 确保双方的发送和接收能力都正常

  • 同步初始序列号

② 序列号与确认应答(Sequence Number & ACK)

  • 每个字节的数据都有一个序列号

  • 接收方收到数据后发送 ACK,告知「我已收到哪些数据」

  • 发送方收到 ACK 后确认数据已被成功接收

③ 超时重传(Retransmission)

  • 发送方启动定时器,如果在规定时间内没收到 ACK,就重传数据

  • 超时时间(RTO)动态计算,基于 RTT(往返时间)的估算

④ 滑动窗口(Sliding Window)

  • 发送方维护一个「发送窗口」,窗口内的数据可以连续发送而不必等待每个 ACK

  • 窗口大小由接收方告知(流量控制)

  • 提高了传输效率(不用逐个等待 ACK)

⑤ 流量控制(Flow Control)

  • 接收方通过 TCP 头部的窗口大小字段告知发送方「我还能接收多少数据」

  • 防止发送方发送过快导致接收方缓冲区溢出

⑥ 拥塞控制(Congestion Control)

  • 4 种算法:
    • 慢启动:初始窗口小,指数增长

    • 拥塞避免:窗口达到阈值后线性增长

    • 快重传:收到 3 个重复 ACK 立即重传

    • 快恢复:快重传后不回到慢启动,而是将阈值减半,从新阈值开始拥塞避免

⑦ 校验和(Checksum)

  • 每个 TCP 段都有校验和,接收方验证数据是否损坏

  • 如果校验失败,丢弃该段,等待超时重传

⑧ 四次挥手断开连接

  • 确保双方数据都已发送完毕

  • 有 TIME_WAIT 状态等待 2MSL,防止最后的 ACK 丢失


9. HTTP/3.0 为什么要基于 UDP

核心原因:TCP 的队头阻塞问题在 HTTP/2 中无法彻底解决。

TCP 的队头阻塞问题:

Plain
HTTP/2 虽然在应用层实现了多路复用(多个请求共享一个 TCP 连接),
但在传输层,TCP 保证的是字节流的顺序交付。

如果第 1 个 TCP 段丢失,即使第 2、3、4 个段已到达,
TCP 也必须等待第 1 个段重传成功后才能向上交付数据。
→ 所有 HTTP/2 流都被这一个丢包阻塞了。

HTTP/3 基于 QUIC 协议(运行在 UDP 之上)的解决方案:

① 彻底解决队头阻塞

  • QUIC 在传输层就实现了多路复用

  • 每个 HTTP 流有独立的可靠性保证

  • 一个流的丢包不会影响其他流

② 更快的连接建立

  • TCP + TLS 1.2 需要 3 个 RTT(TCP 握手 + TLS 握手)

  • TCP + TLS 1.3 需要 2 个 RTT

  • QUIC 只需要 1 个 RTT(首次连接),甚至 0 RTT(后续连接)

③ 连接迁移

  • TCP 连接基于四元组(源IP、源端口、目标IP、目标端口)

  • 网络切换(WiFi → 4G)会导致 IP 变化,TCP 连接断开

  • QUIC 使用 Connection ID 标识连接,网络切换后连接不中断

④ UDP 本身的灵活性

  • UDP 是无连接、不可靠的「裸」传输层协议

  • QUIC 在 UDP 之上自己实现了可靠传输、流量控制、拥塞控制

  • 不受操作系统内核更新限制,可以更快迭代

为什么不在 TCP 之上做?

  • TCP 是操作系统内核实现的,修改 TCP 协议需要更新全世界的操作系统

  • UDP 足够简单和灵活,可以在用户态自由实现上层协议

  • 这就是「在 UDP 之上重建 TCP 的优点,同时避免 TCP 的缺点」


10. Vue Router 是怎么实现的

Vue Router 本质上是一个前端路由管理器,核心原理是URL 变化与视图映射的对应关系

两种路由模式:

① Hash 模式

JavaScript
// 原理:监听 hashchange 事件
window.addEventListener('hashchange', () => {
  const hash = window.location.hash // #/about
  // 根据 hash 匹配路由,渲染对应组件
})

// URL 形式:http://example.com/#/about
  • 不会向服务器发送请求(hash 变化不会触发页面刷新)

  • 兼容性好,支持 IE

  • #/ 部分不会发送给服务器

② History 模式

JavaScript
// 原理:使用 HTML5 History API
// pushState 和 replaceState 改变 URL 而不刷新页面
history.pushState(state, '', '/about')

// 监听 popstate 事件(浏览器前进后退)
window.addEventListener('popstate', () => {
  // 根据 location.pathname 匹配路由
})

// 也可以拦截 <a> 标签的 click 事件,用 pushState 替代默认跳转
  • URL 更美观:http://example.com/about

  • 需要服务器配合:所有路由都指向 index.html,否则刷新会 404

Vue Router 3.x 的实现核心:

JavaScript
// 简化版实现思路
class VueRouter {
  constructor(options) {
    this.routes = options.routes
    this.current = '/'

    // 根据 mode 选择监听方式
    if (options.mode === 'hash') {
      window.addEventListener('hashchange', this.onHashChange.bind(this))
    } else {
      window.addEventListener('popstate', this.onPopState.bind(this))
    }
  }

  onHashChange() {
    this.current = window.location.hash.slice(1) || '/'
    this.matchRoute()
  }

  matchRoute() {
    const matched = this.routes.find(r => r.path === this.current)
    // 触发视图更新(Vue 响应式系统)
    this.matchedComponent = matched?.component
  }

  push(path) {
    if (this.mode === 'hash') {
      window.location.hash = path
    } else {
      history.pushState(null, '', path)
      this.current = path
      this.matchRoute()
    }
  }
}

Vue Router 4.x(Vue 3)的变化:

  • 内部使用 createRouter + createWebHistory / createWebHashHistory

  • 路由匹配使用 path-to-regexp 进行路径解析

  • 支持组合式 API(useRouteruseRoute

  • 导航守卫改为基于 Promise 的异步流程

整体流程:

Plain
URL 变化 → 路由匹配 → 触发导航守卫(beforeEach → beforeEnter → beforeRouteEnter)
→ 组件解析(异步组件加载)→ 更新 <router-view> 渲染目标组件 → afterEach

12. 正常开发项目时性能优化思路

我会按照 「度量 → 分析 → 优化 → 验证」 的闭环流程来做:

第一步:度量 — 用数据说话

  • Web Vitals:关注 LCP(最大内容绘制)、FID(首次输入延迟)、CLS(累计布局偏移)

  • Chrome DevTools:Performance 面板录制、Lighthouse 审计

  • Network 面板:查看资源加载瀑布图、文件大小、加载耗时

第二步:分类优化

网络层面:

  • 代码分割(Code Splitting)+ 路由懒加载

  • 资源压缩(gzip/brotli)

  • 图片优化(WebP 格式、响应式图片、懒加载)

  • CDN 加速静态资源

  • HTTP 缓存策略(强缓存 + 协商缓存)

  • HTTP/2 多路复用

渲染层面:

  • 避免重排重绘(前面已详细回答)

  • 使用虚拟列表处理长列表(只渲染可视区域)

  • CSS 动画使用 transform/opacity(触发 GPU 合成)

  • 关键 CSS 内联,非关键 CSS 异步加载

JavaScript 层面:

  • 减少主线程阻塞(长任务拆分)

  • Web Worker 处理计算密集型任务

  • 避免内存泄漏(及时清理定时器、事件监听器)

  • 使用 requestIdleCallback / requestAnimationFrame 调度任务

Vue 项目专项:

  • 合理使用 v-if vs v-show

  • v-for 必须加 key

  • 组件级缓存(<keep-alive>

  • 大组件使用异步组件(defineAsyncComponent

  • computed 缓存计算结果,避免模板中做复杂计算

  • shallowRef / shallowReactive 减少深层响应式开销

  • 长列表使用虚拟滚动组件

第三步:验证

  • 优化前后用同一工具测量,对比数据

  • 确保优化有效且没有引入新问题

核心理念:性能优化不是一锤子买卖,而是持续的过程。先找到瓶颈(通常是网络请求和渲染),再有针对性地优化。不要过早优化,也不要盲目优化。


1. SSE (Server-Sent Events)

SSE 的原理:

  • SSE 基于 HTTP 协议,客户端通过 EventSource API 建立一个持久的 HTTP 连接

  • 服务端响应头设置 Content-Type: text/event-stream,表示这是一个事件流

  • 服务端通过这个持久连接,以文本格式一段一段地发送数据,每段数据格式如下:

    Plain
    data: {"message": "hello"}\n\n

    每条消息以 \n\n(两个换行符)分隔

  • 客户端通过 onmessage 事件监听器接收每条消息

关于原生 EventSource 不支持 POST 的问题:

  • 对,原生 EventSource 只支持 GET 请求,不支持自定义请求头

  • 所以在需要 POST 请求或自定义请求头的场景下(如 AI 对话携带 Token),需要用 polyfill 库(如 eventsource-parsermicrosoft/fetch-event-source)来实现

SSE 重连原理:

  • 原生 EventSource 有内置的自动重连机制:当连接断开时,浏览器会自动重新发起请求

  • 服务端可以在响应中通过 id: 字段设置每条消息的 ID(即 Last-Event-ID

  • 重连时,浏览器会在请求头中携带 Last-Event-ID,告诉服务端从哪条消息开始续传

  • 但正如 Speaker 1 所说,AI 对话场景下这个重连机制往往不太适用,因为上下文状态可能已经丢失


2. SSE vs WebSocket

对话中的总结基本正确,补充一下:

特性SSEWebSocket
方向单向(服务端→客户端)全双工(双向)
协议基于 HTTP独立的 ws:// 协议
数据格式仅文本文本 + 二进制
重连机制内置自动重连需要手动实现
适用场景消息通知、AI 流式对话即时通讯、实时游戏

3. 登录流程与 Token 存储

Speaker 1 的回答基本正确,补充完善:

  1. 客户端发送账号密码(HTTPS POST,密码在 body 中)

  2. 服务端验证后返回一个 JWT Token

  3. 将 Token 存储在 Cookie 中,并设置安全属性

  4. 后续请求自动携带 Cookie 中的 Token

关于密码加密:

  • 前端可以用 RSA 公钥加密密码再传输(后端用私钥解密)

  • 或者前端做一次 哈希(如 SHA-256) 再传输,但注意这不能替代 HTTPS


Speaker 1 提到了 HttpOnlySameSite,但还有几个重要属性:

属性作用
HttpOnly禁止 JavaScript 直接读取 Cookie,降低凭据被窃取风险;不能阻止 XSS 以用户身份发请求
SameSite=Strict/Lax/None控制跨站请求是否携带 Cookie,可降低部分 CSRF 风险但不能替代完整防护
Secure只在 HTTPS 连接下传输 Cookie
Domain指定 Cookie 所属域名
Path指定 Cookie 的有效路径
Max-Age / ExpiresCookie 的过期时间

CSRF 攻击原理(Speaker 1 解释基本正确):

  • 攻击者诱导用户访问恶意网站

  • 恶意网站向目标网站发起请求(如 <img src="目标网站/api/transfer?to=attacker&amount=1000">

  • 浏览器会自动携带目标网站的 Cookie,导致请求被当作合法请求处理


5. XSS 攻击与防御

Speaker 1 提到了三种类型,补充一下具体的防御手段:

防御措施:

  1. 输入过滤/输出转义:将 < 转义为 &lt;> 转义为 &gt;" 转义为 &quot;

  2. 使用 **textContent** 而非 **v-html**(Vue 中)或 innerText 而非 innerHTML(原生 JS)

  3. CSP(Content Security Policy):通过 HTTP 头 Content-Security-Policy 限制可执行脚本的来源白名单

  4. 对用户输入进行严格校验和过滤

关于转义的具体做法:

  • 不是简单删除 <script> 标签(这样容易被绕过,如 <img onerror="alert(1)">

  • 而是将所有 HTML 特殊字符进行实体编码,这样浏览器会将其当作纯文本显示


6. 二叉树遍历

Speaker 1 的回答基本正确。层序遍历(BFS)的伪代码:

JavaScript
function levelOrder(root) {
    if (!root) return [];
    const queue = [root];
    const result = [];

    while (queue.length) {
        const levelSize = queue.length;
        const level = [];

        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift();
            level.push(node.val);
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
        result.push(level);
    }
    return result; // 二维数组
}

7. HTTP 缓存策略

Speaker 1 的回答中有一个重要错误需要纠正:

强缓存:

  • Cache-Control相对时间(如 max-age=3600,表示 3600 秒后过期)

  • Expires绝对时间(如 Expires: Thu, 01 Jan 2026 00:00:00 GMT

  • Speaker 1 说反了:Cache-Control 是相对时间,Expires 是绝对时间

  • 优先级:**Cache-Control** > **Expires**

协商缓存:

  • Last-Modified / If-Modified-Since:基于文件修改时间,精度为秒级

  • ETag / If-None-Match:基于文件内容的哈希值,更精确

  • 优先级:**ETag** > **Last-Modified**

为什么会有两套?

  • Cache-Control 是 HTTP/1.1 引入的,功能更强大(可设置 no-cacheno-storeprivate 等),是为了替代 Expires

  • ETagLast-Modified 更精确,因为文件可能在 1 秒内多次修改,或者内容没变但时间变了(如 touch 命令)

缓存流程:

Plain
1. 强缓存检查 → 命中则直接使用缓存(200 from cache)
2. 强缓存未命中 → 发起协商缓存请求
3. 协商缓存命中 → 返回 304 Not Modified,使用缓存
4. 协商缓存未命中 → 返回 200 + 新资源

1. Last-Modified vs ETag 的区别

Speaker 1 的回答基本正确,补充一下完整对比:

特性Last-Modified / If-Modified-SinceETag / If-None-Match
精度秒级,1秒内多次修改无法区分内容级,只要内容变化就会生成新的标识
原理基于文件的最后修改时间基于文件内容生成的哈希值(如 MD5)
性能无需计算哈希,性能更好需要计算哈希,有一定开销
优先级较低较高(两者同时存在时优先使用 ETag)

为什么会有两套?

  • Last-Modified 是 HTTP/1.0 就有的,简单但不够精确

  • ETag 是 HTTP/1.1 引入的,更精确但有一定性能开销

  • 实际项目中,很多服务器会同时返回两个,让浏览器根据优先级自行判断


2. 强缓存 vs 协商缓存在实际项目中的使用

Speaker 1 说的基本正确,实际项目中的典型策略:

强缓存(**Cache-Control: max-age=31536000**):

  • 适合带哈希值的静态资源,如打包后的 CSS、JS 文件

    • 例:app.a1b2c3d4.jsstyle.e5f6g7h8.css

    • 文件名中的哈希值在内容变化时会改变,所以可以放心设置很长的缓存时间

  • 字体文件、图片等不常变化的资源

协商缓存(**Cache-Control: no-cache**):

  • 适合HTML 入口文件,因为 HTML 是页面的入口,需要每次都去服务端验证是否有更新

  • 适合需要实时性但变化不频繁的 API 数据

不缓存(**Cache-Control: no-store**):

  • 适合敏感数据,如用户信息、支付接口等

3. 强缓存下如何更新资源?

这是 Speaker 1 没回答上来的问题,答案是前端工程化中非常核心的一个概念——文件名哈希(Content Hash)

原理:

Plain
打包前:app.js
打包后:app.a1b2c3d4.js(文件内容的哈希值作为文件名的一部分)

流程:

  1. 构建工具(Webpack/Vite)在打包时,会根据文件内容生成一个哈希值,拼接到文件名中

  2. HTML 文件引用的是带哈希的文件名:<script src="/js/app.a1b2c3d4.js"></script>

  3. 当 CSS/JS 内容更新后,重新打包会生成新的哈希:app.x9y8z7w6.js

  4. HTML 文件也会更新引用:<script src="/js/app.x9y8z7w6.js"></script>

  5. 因为 HTML 走的是协商缓存,浏览器会发现 HTML 变了,进而加载新的 JS 文件

  6. 旧的 JS 文件(app.a1b2c3d4.js)因为没人引用了,浏览器会自然淘汰

这就是为什么:

  • HTML 用协商缓存(需要及时发现更新)

  • 带哈希的静态资源用强缓存(哈希变了就是新文件,哈希没变就是同一个文件,可以放心缓存)

总结一句话: 不是去"刷新"用户的缓存,而是通过文件名的变化来"绕过"缓存,让浏览器主动请求新资源。

问题1:Vue的虚拟DOM工作原理及diff算法优化

虚拟DOM的工作原理

虚拟DOM(Virtual DOM)是Vue渲染系统的核心,其本质是一个轻量级的JavaScript对象,是对真实DOM的抽象表示。

核心流程(渲染管线):

  1. 编译阶段:Vue模板被编译为渲染函数(render function),该函数返回虚拟DOM树

  2. 挂载阶段:运行时渲染器调用渲染函数,遍历返回的虚拟DOM树,创建对应的真实DOM节点。此过程作为响应式副作用执行,会追踪所有依赖

  3. 补丁阶段(Patch):当依赖的数据变化时,副作用重新运行,生成新的虚拟DOM树,渲染器对比新旧两棵树,计算差异并应用到真实DOM

JavaScript
// 虚拟节点的基本结构
const vnode = {
  type: 'div',
  props: { id: 'hello', class: 'container' },
  children: [
    { type: 'span', props: {}, children: 'Hello World' }
  ]
}

Vue的diff算法优化

Vue 2的双端比较算法

Vue 2采用双端指针法进行子节点比较:

  1. 头-头比较:旧头节点与新头节点对比

  2. 尾-尾比较:旧尾节点与新尾节点对比

  3. 头-尾比较:旧头节点与新尾节点对比

  4. 尾-头比较:旧尾节点与新头节点对比

如果以上四种都不匹配,则通过key在旧节点中查找可复用节点。

Vue 3的Block Tree架构优化

Vue 3引入了多项革命性优化:

1. 静态提升(Static Hoisting)

JavaScript
// 编译前
<div>
  <h1>静态标题</h1>
  <p>{{ message }}</p>
</div>

// 编译后 - 静态节点被提升,只创建一次
const _hoisted_1 = createVNode("h1", null, "静态标题")
function render() {
  return (openBlock(), createBlock("div", null, [
    _hoisted_1,  // 直接复用
    createVNode("p", null, ctx.message)
  ]))
}

2. Patch Flags(补丁标志)

JavaScript
// 编译器标记节点的动态部分
createVNode("div", {
  class: _normalizeClass({ active: isActive })
}, null, 2 /* CLASS */)  // 只需检查class变化
标志值含义优化效果
1TEXT只检查文本
2CLASS只检查class
4STYLE只检查style
8PROPS只检查特定props
16FULL_PROPS完整对比

3. 树结构打平(Tree Flattening)

将模板中的动态节点收集到扁平数组中,遍历时跳过静态节点,减少95%的无效比较。

4. 最长递增子序列优化

在处理节点移动时,找出最长的不需要移动的节点序列,最小化DOM操作次数。


问题2:Vuex和Pinia的区别及选择场景

核心架构对比

特性VuexPinia
API设计State + Mutations + Actions + GettersState + Actions + Getters(无Mutations)
状态更新必须通过commit触发Mutation直接修改state或在Actions中修改
异步处理Actions中调用MutationsActions直接修改状态
TypeScript需手动配置类型声明原生支持,自动类型推导
模块化通过modules嵌套定义,需namespaced多个独立store文件,天然模块化
体积约10-12KB约1KB
Vue版本Vue 2/3主要为Vue 3设计

代码对比

Vuex写法:

JavaScript
// store/index.js
const store = createStore({
  state: { count: 0 },
  mutations: {
    increment(state) { state.count++ }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => commit('increment'), 1000)
    }
  }
})
// 组件中使用
this.$store.commit('increment')
this.$store.dispatch('incrementAsync')

Pinia写法:

JavaScript
// stores/counter.js
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() { this.count++ },
    async incrementAsync() {
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.count++
    }
  }
})
// 组件中使用
const counter = useCounterStore()
counter.increment()

选择场景

选择Pinia的场景:

  • Vue 3新项目

  • 需要良好的TypeScript支持

  • 追求简洁API和开发效率

  • 中小型项目

选择Vuex的场景:

  • 维护现有Vuex项目

  • 需要严格的变更追踪(mutations提供明确记录)

  • 大型团队需要严格代码规范

  • Vue 2项目


问题3:Axios请求/响应拦截器设计

二次封装设计

JavaScript
// utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router'

const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 15000
})

let isRefreshing = false
let requestQueue = []

const executeQueue = (token) => {
  requestQueue.forEach(cb => cb(token))
  requestQueue = []
}

// 请求拦截器
service.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }
    return config
  },
  error => Promise.reject(error)
)

// 响应拦截器
service.interceptors.response.use(
  response => {
    const res = response.data
    if (res.code !== 200) {
      ElMessage.error(res.message || '请求失败')
      return Promise.reject(new Error(res.message))
    }
    return res
  },
  async error => {
    const originalRequest = error.config

    // Token过期处理(401)
    if (error.response?.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        return new Promise((resolve) => {
          requestQueue.push((token) => {
            originalRequest.headers['Authorization'] = `Bearer ${token}`
            resolve(service(originalRequest))
          })
        })
      }

      originalRequest._retry = true
      isRefreshing = true

      try {
        const refreshToken = localStorage.getItem('refreshToken')
        const { data } = await axios.post('/auth/refresh', { refreshToken })
        localStorage.setItem('token', data.accessToken)
        executeQueue(data.accessToken)
        originalRequest.headers['Authorization'] = `Bearer ${data.accessToken}`
        return service(originalRequest)
      } catch (refreshError) {
        localStorage.removeItem('token')
        localStorage.removeItem('refreshToken')
        router.push('/login')
        return Promise.reject(refreshError)
      } finally {
        isRefreshing = false
      }
    }

    // 网络错误处理
    if (!error.response) {
      ElMessage.error('网络连接异常,请检查网络')
      return Promise.reject(error)
    }

    // 其他HTTP错误
    const errorMessages = {
      403: '没有权限访问',
      404: '请求资源不存在',
      500: '服务器内部错误'
    }
    ElMessage.error(errorMessages[error.response.status] || '请求失败')
    return Promise.reject(error)
  }
)

export default service

关键设计点

  1. Token自动注入:请求拦截器统一添加Authorization头

  2. Token无感刷新:使用并发锁+请求队列,避免多次刷新请求

  3. 统一错误处理:响应拦截器统一处理业务错误和HTTP错误

  4. 请求重试机制:可添加指数退避重试策略


问题7:微信小程序生命周期

应用生命周期(App级)

app.js中定义,管控整个小程序的启动与销毁:

JavaScript
App({
  onLaunch(options) {
    // 小程序首次启动触发,全局仅执行1次
    // 适合:初始化全局数据、获取用户授权、请求全局配置
  },
  onShow(options) {
    // 小程序启动/从后台切到前台触发,可多次执行
    // 适合:刷新全局状态、重启定时器
  },
  onHide() {
    // 小程序从前台切到后台触发
    // 适合:暂停定时器、保存临时数据
  },
  onError(msg) {
    // 小程序发生脚本错误时触发
    // 适合:捕获全局错误、上报错误日志
  },
  onPageNotFound() {
    // 访问不存在的页面时触发
    // 适合:页面跳转兜底
  }
})

页面生命周期(Page级)

在页面的.js文件中定义,控制单个页面的加载与卸载:

JavaScript
Page({
  onLoad(options) {
    // 页面首次加载触发,仅执行1次
    // 适合:接收页面参数、初始化页面数据、请求核心接口
  },
  onShow() {
    // 页面显示/从后台切回前台触发,可多次执行
    // 适合:刷新页面数据、启动页面定时器
  },
  onReady() {
    // 页面初次渲染完成触发,仅执行1次
    // 适合:操作DOM、初始化第三方组件
  },
  onHide() {
    // 页面被隐藏触发(跳转其他页面或切后台)
    // 适合:暂停定时器、保存临时表单数据
  },
  onUnload() {
    // 页面被卸载触发(redirectTo、navigateBack)
    // 适合:清除定时器、取消未完成的请求
  },
  onPullDownRefresh() {
    // 监听用户下拉刷新
  },
  onReachBottom() {
    // 页面上拉触底触发
    // 适合:加载下一页数据
  },
  onShareAppMessage() {
    // 用户点击分享
  }
})

核心区别

维度应用生命周期页面生命周期
作用范围整个小程序单个页面
定义位置app.js页面.js文件
执行次数onLaunch仅1次onLoad仅1次,onShow多次
执行顺序onLaunch → onShowonLoad → onShow → onReady
数据传递通过globalData通过options参数

问题8:微信支付完整流程

完整支付流程

Plain
用户选择商品 → 前端请求下单 → 后端调用微信统一下单API
→ 获取prepay_id → 前端调起支付 → 用户输入密码
→ 微信异步通知后端 → 后端验签更新订单 → 前端查询订单状态

具体实现

后端下单接口(Java Spring Boot):

Java
@PostMapping("/api/pay/create")
public Result createOrder(@RequestBody OrderDTO orderDTO) {
    // 1. 创建统一下单请求
    WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
    request.setBody(orderDTO.getBody());
    request.setOutTradeNo(orderDTO.getOrderNo());
    request.setTotalFee(orderDTO.getTotalFee()); // 单位:分
    request.setNotifyUrl("https://your.domain/api/pay/notify");
    request.setTradeType("JSAPI");
    request.setOpenid(orderDTO.getOpenid());

    // 2. 调用微信统一下单接口
    WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request);

    // 3. 生成前端支付参数
    Map<String, String> payInfo = new HashMap<>();
    payInfo.put("appId", appId);
    payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
    payInfo.put("nonceStr", WxPayUtil.generateNonceStr());
    payInfo.put("package", "prepay_id=" + result.getPrepayId());
    payInfo.put("signType", "RSA");
    payInfo.put("paySign", WxPayUtil.generateSignature(payInfo, privateKey));

    return Result.success(payInfo);
}

支付回调处理:

Java
@PostMapping("/api/pay/notify")
public String payNotify(HttpServletRequest request) {
    // 1. 验证签名
    // 2. 解密回调数据
    // 3. 更新订单状态(幂等处理)
    // 4. 返回成功响应
    return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
}

小程序前端调起支付:

JavaScript
// pages/pay/pay.js
Page({
  handlePay() {
    wx.request({
      url: 'https://your.domain/api/pay/create',
      method: 'POST',
      data: { orderNo: this.data.orderNo, totalFee: this.data.totalFee },
      success: (res) => {
        if (res.data.code === 0) {
          const payParams = res.data.data
          wx.requestPayment({
            timeStamp: payParams.timeStamp,
            nonceStr: payParams.nonceStr,
            package: payParams.package,
            signType: payParams.signType,
            paySign: payParams.paySign,
            success: () => {
              // 支付成功,查询订单状态确认
              this.queryOrderStatus()
            },
            fail: (err) => {
              if (err.errMsg.includes('cancel')) {
                wx.showToast({ title: '已取消支付', icon: 'none' })
              } else {
                wx.showToast({ title: '支付失败', icon: 'none' })
              }
            }
          })
        }
      }
    })
  },

  queryOrderStatus() {
    // 调用后端查询订单接口确认支付状态
    wx.request({
      url: `https://your.domain/api/pay/query?orderNo=${this.data.orderNo}`,
      success: (res) => {
        if (res.data.data.status === 'PAID') {
          wx.showToast({ title: '支付成功' })
          wx.redirectTo({ url: '/pages/order/detail?orderNo=' + this.data.orderNo })
        }
      }
    })
  }
})

异常处理要点

  1. 签名验证:必须验证回调签名,确保来源真实

  2. 幂等处理:使用商户订单号做幂等键,防止重复处理

  3. 超时处理:设置合理的支付超时时间

  4. 并发处理:防止重复支付,对按钮做防抖

  5. 补单机制:定时查询未确认订单的状态


问题9:性能优化具体案例

案例:电商列表页性能优化

问题发现:

  • 使用Lighthouse检测,FCP(首次内容渲染)达到4.5秒

  • 使用Webpack Bundle Analyzer发现打包体积3.2MB

  • 长列表滚动卡顿,用户反馈体验差

优化策略:

1. 路由懒加载(体积减少40%)

JavaScript
const routes = [
  {
    path: '/product-list',
    component: () => import(/* webpackChunkName: "product" */ '@/views/ProductList.vue')
  }
]

2. 第三方库按需引入

JavaScript
// 优化前:全量引入
import ElementPlus from 'element-plus'

// 优化后:按需引入
import { ElButton, ElCard, ElPagination } from 'element-plus'

3. 虚拟滚动优化长列表

Plain
<template>
  <RecycleScroller
    :items="productList"
    :item-size="120"
    key-field="id"
    v-slot="{ item }"
  >
    <ProductCard :product="item" />
  </RecycleScroller>
</template>

4. 图片懒加载+WebP格式

Plain
<template>
  <img v-lazy="product.imageWebp" loading="lazy" />
</template>

5. CDN加速+Gzip压缩

Nginx
# nginx配置
gzip on;
gzip_types text/plain application/javascript application/x-javascript text/css;

优化效果:

指标优化前优化后提升
JS总体积3.2MB680KB↓78%
FCP时间4.5s0.8s↑462%
Lighthouse评分58分92分↑58%

问题10:Canvas图片压缩的质量与压缩率平衡

实现方案

JavaScript
function compressImage(file, maxWidth = 1920, quality = 0.8) {
  return new Promise((resolve) => {
    const reader = new FileReader()
    reader.onload = (e) => {
      const img = new Image()
      img.onload = () => {
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')

        // 计算压缩后的尺寸
        let { width, height } = img
        if (width > maxWidth) {
          height = (maxWidth / width) * height
          width = maxWidth
        }

        canvas.width = width
        canvas.height = height

        // 绘制图片
        ctx.drawImage(img, 0, 0, width, height)

        // 导出压缩后的图片
        canvas.toBlob((blob) => {
          resolve(new File([blob], file.name, { type: 'image/jpeg' }))
        }, 'image/jpeg', quality)
      }
      img.src = e.target.result
    }
    reader.readAsDataURL(file)
  })
}

质量与压缩率平衡策略

  1. 根据图片尺寸动态调整质量

    • 大图(>2MB):quality = 0.6

    • 中图(1-2MB):quality = 0.7-0.8

    • 小图(<1MB):quality = 0.8-0.9

  2. 确保OCR清晰度

    • 保持最小分辨率(如1000px宽度)

    • 使用image/png格式保留文字边缘清晰度

    • 对于文档类图片,quality不低于0.85

  3. 渐进式压缩

JavaScript
async function smartCompress(file) {
  const size = file.size / 1024 / 1024 // MB

  if (size > 5) {
    return compressImage(file, 1600, 0.6)
  } else if (size > 2) {
    return compressImage(file, 1920, 0.75)
  } else {
    return compressImage(file, 2560, 0.85)
  }
}

问题11:基于知识库的AI问答功能实现

技术架构

Plain
用户提问 → 向量化查询 → 知识库检索 → 上下文拼接 → LLM生成回答

实现方案

1. 知识库向量化存储

JavaScript
// 使用Embedding API将文档向量化
async function vectorizeDocument(text) {
  const response = await openai.embeddings.create({
    model: "text-embedding-ada-002",
    input: text
  })
  return response.data[0].embedding
}

// 存储到向量数据库(如Pinecone、Milvus)
async function storeVector(id, embedding, metadata) {
  await vectorDB.upsert({
    id,
    values: embedding,
    metadata
  })
}

2. 语义检索

JavaScript
async function searchKnowledgeBase(query, topK = 5) {
  const queryEmbedding = await vectorizeDocument(query)

  const results = await vectorDB.query({
    vector: queryEmbedding,
    topK,
    includeMetadata: true
  })

  return results.matches.map(match => ({
    content: match.metadata.content,
    score: match.score
  }))
}

3. RAG问答实现

JavaScript
async function answerQuestion(question) {
  // 1. 检索相关文档
  const relevantDocs = await searchKnowledgeBase(question)

  // 2. 构建上下文
  const context = relevantDocs
    .map(doc => doc.content)
    .join('\n\n')

  // 3. 调用LLM生成回答
  const response = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [
      {
        role: "system",
        content: `你是一个基于知识库的问答助手。请根据以下参考资料回答用户问题,如果资料中没有相关信息,请说明无法找到相关内容。

参考资料:
${context}`
      },
      {
        role: "user",
        content: question
      }
    ]
  })

  return response.choices[0].message.content
}

使用的技术/工具

  • Embedding模型:OpenAI text-embedding-ada-002、本地模型如BGE

  • 向量数据库:Pinecone、Milvus、Chroma、Weaviate

  • LLM:OpenAI GPT-4、Claude、本地部署的开源模型

  • 文档处理:LangChain、LlamaIndex


问题12:AI工具提高开发效率的经验

实际应用场景

1. 代码生成与补全

  • 使用AI IDE(如Trae)的智能补全功能,快速生成样板代码

  • 通过自然语言描述需求,AI生成完整函数实现

2. 代码审查与优化

  • 让AI分析代码,发现潜在的性能问题和安全漏洞

  • 获取重构建议,提升代码质量

3. 文档与注释生成

  • 自动生成API文档、代码注释

  • 快速编写README、变更日志

4. 问题排查

  • 将错误信息提供给AI,快速定位问题原因

  • 获取解决方案和修复代码

5. 学习新技术

  • 通过对话方式学习新框架、新API

  • 获取最佳实践和设计模式建议

效率提升案例

  • 组件开发:通过描述需求,AI生成组件框架代码,开发时间从2小时缩短到30分钟

  • Bug修复:将错误堆栈提供给AI,平均定位时间从30分钟缩短到5分钟

  • 单元测试:AI根据函数签名自动生成测试用例,覆盖率从40%提升到80%


问题13:多端电商应用技术架构设计

技术选型

Plain
┌─────────────────────────────────────────────┐
│                    表现层                      │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐      │
│  │  Web    │  │  H5     │  │ 小程序   │      │
│  │  Vue3   │  │  Vue3   │  │ uni-app │      │
│  └─────────┘  └─────────┘  └─────────┘      │
├─────────────────────────────────────────────┤
│                 跨端框架层                     │
│            uni-app / Taro                     │
├─────────────────────────────────────────────┤
│                 状态管理                       │
│               Pinia                          │
├─────────────────────────────────────────────┤
│                 网络请求                       │
│           Axios / uni.request                 │
├─────────────────────────────────────────────┤
│                 后端服务                       │
│        Node.js / Java / Go                   │
└─────────────────────────────────────────────┘

处理不同端差异的策略

1. 条件编译

JavaScript
// #ifdef H5
console.log('H5端特有逻辑')
// #endif

// #ifdef MP-WEIXIN
console.log('微信小程序特有逻辑')
// #endif

// #ifdef APP-PLUS
console.log('App端特有逻辑')
// #endif

2. 平台适配层

JavaScript
// utils/platform.js
export const platform = {
  isH5: process.env.UNI_PLATFORM === 'h5',
  isMP: process.env.UNI_PLATFORM === 'mp-weixin',
  isApp: process.env.UNI_PLATFORM === 'app-plus',

  // 统一API封装
  async login() {
    if (this.isMP) {
      return uni.login({ provider: 'weixin' })
    } else if (this.isH5) {
      return h5Login()
    }
  },

  // 支付适配
  async pay(orderInfo) {
    if (this.isMP) {
      return wxPay(orderInfo)
    } else if (this.isH5) {
      return h5Pay(orderInfo)
    } else if (this.isApp) {
      return appPay(orderInfo)
    }
  }
}

3. 组件差异化

Plain
<template>
  <!-- 通用组件 -->
  <view class="product-card">
    <image :src="product.image" mode="aspectFill" />
    <text>{{ product.name }}</text>

    <!-- 小程序端显示分享按钮 -->
    <!-- #ifdef MP-WEIXIN -->
    <button open-type="share">分享</button>
    <!-- #endif -->
  </view>
</template>

4. 样式适配

SCSS
// 使用rpx实现响应式布局
.product-card {
  width: 750rpx;
  padding: 20rpx;

  // H5端特殊样式
  /* #ifdef H5 */
  max-width: 750px;
  margin: 0 auto;
  /* #endif */
}