Network
HTTP
HTTP(HyperText Transfer Protocol):基于 Client/Server 模型,是无状态协议(应用层):服务器不会保存客户端的请求上下文,每次请求都是独立的
HTTP/1.1
1.0 局限性: HTTP/1.0 是短连接,每次 HTTP 请求都需要建立 TCP 连接,传输数据和断开 TCP 连接
1.1 新特性:
- HTTP/1.1 新增持久连接,特点是一个 TCP 连接上可以发送多次 HTTP 请求,只要浏览器或服务器没有明确断开连接,该 TCP 连接就会一直保活
Connection: Keep-Alive;HTTP/1.1 中持久连接默认开启,如果不想使用持久连接,可以在 HTTP 请求头中设置Connection: Close字段 - 使用 CDN 内容分发网络实现域名分片
- 支持虚拟主机:HTTP/1.0 中,一个域名绑定一个唯一的 IP 地址,一个服务器只能绑定一个域名;随着虚拟主机技术的发展,一个物理主机可以虚拟化为多个虚拟主机,每个虚拟主机有单独的域名,这些虚拟主机(域名)共用同一个 IP 地址;HTTP/1.1 的请求头中增加了 Host 字段,表示域名 URL 地址,服务器可以根据不同的 Host 字段,进行不同的处理(HTTPS 使用 SNI 来实现多域名支持)
- 支持动态大小的响应数据:HTTP/1.0 中,需要在响应头中指定传输数据的大小,例如 Content-Length:1024,这样浏览器可以根据指定的传输数据大小接收数据;HTTP/1.1 通过引入 Chunk Transfer 分块传输机制解决该问题,服务器将传输数据分割为若干个任意大小的数据块,每个数据块发送时,附加上一个数据块的长度,最后使用一个 0 长度的数据块作为数据发送结束的标志,提供对动态大小的响应数据的支持
- HTTP/1.1 还引入了客户端 cookie
HTTP/2.0
1.1 局限性:
- TCP 的慢启动:TCP 建立连接后发送数据先使用较慢的发送速率,再逐渐增加发送速率,以探测网络带宽调整至合适的发送速率,直到稳态,导致页面首次渲染时间增加
- 同时建立多条 TCP 连接时,这些连接会竞争带宽,影响关键资源的加载速度
- HTTP/1.1 队头阻塞问题:HTTP/1.1 使用持久连接,虽然多个 HTTP 请求可以共用一个 TCP 管道,但是同一时刻只能处理一个请求,当前请求完成前,后续请求只能阻塞
- 协议开销大:header 携带的内容过多,且不能压缩,增加了传输成本
2.0 新特性:
- 一个域名只使用一个 TCP 长连接传输数据,整个页面资源的加载只需要一次 TCP 慢启动,同时避免了多个 TCP 连接竞争带宽的问题
- HTTP 多路复用技术,引入二进制分帧层,并行处理请求,转换为若干个带有请求 ID 编号的帧,通过 TCP/IP 协议栈发送给服务器,服务器收到请求帧后,将所有 ID 相同的帧合并为一个完整的请求,并处理该请求;类似的,服务器的二进制分帧层将响应数据转换为若干个带有响应 ID 编号的帧,通过 TCP/IP 协议栈发送给浏览器,浏览器收到响应帧后,将所有 ID 相同的帧合并为一个完整的响应
- 请求优先级:HTTP/2.0 支持请求优先级,发送请求时,标记该请求的优先级,服务器收到请求后,优先处理优先级高的请求
- 服务器推送:HTTP/2.0 服务器推送(Server Push)允许客户端请求某个资源(例如 index.html)时,服务器推送其他资源(例如
style.css,main.js),避免发送额外的请求 - 头部压缩:HTTP/2.0 对请求头和响应头进行(gzip)压缩
- 可重置:HTTP/2.0 可以在不中断 TCP 连接的前提下,取消当前的请求或响应
HTTP/3.0
2.0 局限性:
- 随着丢包率的增加,HTTP/2.0 的传输效率降低,2% 丢包率时,HTTP/2.0 的传输效率可能低于 HTTP/1.1
- 只使用一个 TCP 长连接,仍然受到队头阻塞影响(1.1 多条 TCP 连接可以分散风险),TCP 三次握手,TLS 一次握手,浪费 3 到 4 个 RTT
3.0 新特性:
- HTTP/3.0(QUIC,Quick UDP Internet Connection)基于 UDP,实现类似 TCP 的多路数据流,可靠传输等特性,同时避免了 TCP 的慢启动,队头阻塞等问题
- 引入了 0-RTT 连接建立机制,对于之前连接过的服务器,HTTP/3.0 可以在 0 RTT 内建立连接并发送数据;对于第一次连接的服务器,HTTP/3.0 需要 1 RTT 建立连接并发送数据
- HTTP/3.0 还引入了连接迁移机制,允许在网络环境变化时(例如:从 Wi-Fi 切换到 5G),保持连接不中断,继续传输数据
HTTP 报文
HTTP 报文分为请求报文和响应报文
- 请求报文:请求行,请求头,请求体
- 响应报文:响应行(状态行),响应头,响应体
请求报文
- 请求行:HTTP 请求报文的第一行,包含请求方法(GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH,CONNECT,TRACE),请求 URL 和 HTTP 版本
- 请求头部的字段:
Accept客户端支持的媒体类型,例如application/json,text/plain,text/html等Accept-Encoding客户端支持的编码,例如 gzip 等Accept-Language客户端的偏好语言Expect客户端询问服务器是否接受请求体If-Modified-Since字段值时间戳;询问服务器指定时间戳后,资源是否有修改If-None-Match字段值是 etag 版本号,询问服务器 etag 版本号是否有更新,即资源是否有修改Authorization字段值是 tokenCookieHost请求的主机名和端口号Range请求实体的字节范围,用于范围请求(分块传输,断点续传)Referrer请求的源页面的 URLUser-Agent用户代理,即使用的浏览器和操作系统Origin预检请求或实际请求的源主机Access-Control-Request-Method用于预检请求,告诉浏览器实际请求使用的请求方法Access-Control-Request-Headers用于预检请求,告诉浏览器实际请求的请求头字段Connection当前会话结束后,是否关闭 HTTP 连接,默认Connection: Keep-AliveCache-Control缓存控制Cache-Control: max-age=10, stale-while-revalidate=60 表示资源在客户端的缓存有效期为 10 秒,过期后,在 60 秒内,浏览器可以直接使用过期的资源,同时在后台发送请求更新资源(swr 策略)
Content-Length请求体的长度Content-Type请求体的媒体类型Via代理服务器设置的请求头/响应头字段,适用于正向/反向代理,记录中间节点
响应报文
Access-Control-Allow-Credentials服务器是否允许跨域请求携带凭据,凭据包括 cookie,TLS 客户端证书等,默认不允许跨域请求携带凭据,以防止跨站请求伪造攻击Access-Control-Expose-Headers可以通过xhr.getResponseHeader()获取响应头字段,默认跨域响应仅暴露 CORS 白名单中的响应头字段,可以在跨域响应的Access-Control-Expose-Headers响应头字段中,指定暴露的其他响应头字段Access-Control-Allow-Methods用于响应预检请求,指定实际请求允许使用的请求方法Access-Control-Allow-Origin指定允许(跨域)资源共享的源站Access-Control-Allow-Headers用于响应预检请求,指定实际请求允许使用的请求头字段Access-Control-Max-Age指定缓存预检请求的响应头字段Access-Control-Allow-Methods和Access-Control-Allow-Headers的有效期,单位是秒;有效期内,浏览器可以直接发送复杂请求的跨域请求,不需要先发送预检请求Age对象在代理缓存中停留的时间Allow服务器响应状态码为 405 Method Not Allowed 时,必须携带Allow响应头字段,表示服务器允许哪些请求方法Content-Disposition指定响应体以网页,或以网页的一部分,或以附件的形式下载到本地Content-Encoding响应体的编码Content-Language响应体的偏好语言Content-Length响应体的长度Content-Location响应体对应资源的 URLLocation3xx 重定向的 URL,或 201 Created 新创建的资源的 URLContent-Range响应体在整个资源中的字节范围Content-Type响应体的媒体类型Accept-Ranges表示服务器支持范围请求(分块传输,断点续传)Vary使用内容协商时,创建缓存键Set-Cookie用于服务器将 cookie 发送到 User-Agent 用户代理,用户代理在后续的请求中,可以将 cookie 发送回服务器,可以在一个响应中,设置多个 Set-Cookie 字段以发送多个 cookieWWW-Authentication定义 HTTP 身份验证方法:质询,用于获取资源的访问权限ETag资源的版本号,资源更新时,必须生成新的 ETag 值Expires资源的过期时间,无效的日期(例如 0)也表示资源已过期Last-Modified资源的上一次修改时间Date消息创建的日期,时间
HTTP 状态码
1XX Informational 信息响应
- 100 Continue 客户端应该继续请求,如果请求已完成则忽略
- 101 Switching Protocols
2XX Success 成功响应
- 200 OK 请求成功
- 201 Created 请求成功并创建了新的资源
- 204 No Content 请求成功,响应体为空
- 206 Partial Content 范围请求成功(分块传输,断点续传)
3XX Redirection 重定向响应
- 301 Moved Permanently 永久重定向,请求的资源永久移动到 Location 头部指定的 URL,会将 POST 请求重定向为 GET 请求
- 302 Found 临时重定向,请求的资源临时移动到 Location 头部指定的 URL,会将 POST 请求重定向为 GET 请求
- 303 See Other 指定请求重定向的页面时,必须使用 GET 方法
- 304 Not Modified 协商缓存
- 请求强缓存的资源,不会请求服务器
- 请求协商缓存的资源,仍会请求服务器
- 307 Temporary Redirect 临时重定向,请求的资源临时移动到 Location 头部指定的 URL,不会将 POST 请求重定向为 GET 请求
- 308 Permanent Redirect 永久重定向,请求的资源永久移动到 Location 头部指定的 URL,不会将 POST 请求重定向为 GET 请求
4XX Client Error 客户端错误响应
- 400 Bad Request 客户端错误
- 401 Unauthorized 客户端没有身份验证凭证,无权访问资源
- 403 Forbidden 客户端(可能)有身份验证凭证,但服务器拒绝客户端访问资源
- 404 Not Found 请求的资源不存在(可能临时丢失或永久丢失)
- 405 Method Not Allowed 客户端使用的请求方法不被允许
- 408 Request Timeout 服务器决定关闭空闲连接,而不是继续等待新请求
- 410 Gone 请求的资源已永久丢失
- 429 Too Many Requests 客户端发送了过多的请求,服务器暂时拒绝处理请求
5XX Server Error 服务器端错误响应
- 500 Internal Server Error 泛指服务器端错误
- 502 Bad Gateway 作为网关或代理的服务器,从上游服务器接收到无效的响应
- 503 Service Unavailable 服务器暂时无法处理请求,可能是停机维护或过载
- 504 Gateway Timeout 作为网关或代理的服务器,从上游服务器接收的响应超时
DNS 域名系统
DNS 域名系统是一个分布式数据库(应用层),存储域名到 IP 地址的映射,使用 UDP,端口号 53
- 递归查询:直接返回域名解析结果
- 迭代查询:返回下一级 DNS 服务器地址
DNS 解析过程
- 检查 DNS 缓存顺序:
- 浏览器 DNS
- 操作系统 DNS
- 本机
/etc/hosts文件
- 如果上述都没有命中,开始执行递归查询:客户端请求本地 DNS 服务器(例如:家庭路由器,企业 DNS 服务器,运营商提供的 DNS 服务器),如果命中,则返回;如果未命中,则本地服务器执行迭代查询:
- 本地 DNS 服务器 -> 根 DNS 服务器(例:.)
- 本地 DNS 服务器 -> 顶级 DNS(TLD)服务器(例:.com)
- 本地 DNS 服务器 -> 权威域名服务器(例:阿里云解析)
- 本地 DNS 服务器缓存结果,并返回结果给客户端
- 如果配置了 CDN,DNS 会将最终的域名解析权交给 CNAME 指向的 CDN 专用 DNS 服务器(就近节点)
TCP/UDP
传输层协议
TCP 特点:
- 数据分段:数据在发送端分段,在接收端重组
- 到达确认:接收端收到分段后,向发送端返回一个 ACK 确认包,确认号等于分段序号 +1
- 流量控制,拥塞控制
- 失序处理:TCP 对收到的分段排序
- 重复处理:TCP 丢弃重复的分段
- 数据校验:TCP 使用首部校验和,丢弃错误的分段
三次握手
- seq(sequence number)序列号,随机生成
- ack(acknowledgement number)确认号,ack = seq + 1
- ACK(acknowledgement),ACK = 1 确认
- SYN(synchronous)SYN 默认 0,SYN = 1 表示请求同步连接
- FIN(finish)FIN 默认 0,FIN = 1 表示请求终止连接
# SYN=1 seq=x
client ----- handshake1 ----> server
====> SYN1 = 1 ====> # 客户端向服务器请求同步
====> seq1 ====>
# SYN=1 ACK=1 seq=y ack=x+1
client <---- handshake2 <------- server
<==== ack1 = seq1+1 <==== # 确认收到 seq1
<==== ACK1 = 1 <==== # 确认 SYN1, 客户端到服务器同步
<==== SYN2 = 1 <==== # 服务器向客户端请求同步
<==== seq2 <====
# ACK=1 ack=y+1 seq=x+1
# 客户端向服务器握手两次,防止已失效的连接请求发送到服务器, 导致服务器资源的浪费
client ----- handshake3 -------> server
====> ack2 = seq2+1 ====> # 确认收到 seq2
====> ACK2 = 1 ====> # 确认 SYN2, 服务器到客户端同步四次挥手
# 双方都可以主动发起
# FIN=1 seq=x1
client ----- waving1 -------> server
====> FIN1 = 1 ====> # 客户端向服务器请求终止
====> seq1 ====>
FIN_WAIT_1 # 客户端等待服务器第 1 次确认 FIN1
# ACK=1 ack=x1+1 seq=y1
client <---- waving2 <---------- server
<==== ack1 = seq1+1 <==== # 确认收到 seq1
<==== ACK1 = 1 <==== # 服务器第 1 次确认 FIN1
FIN_WAIT_2 # 服务器发送剩余数据,客户端等待服务器第 2 次确认 FIN1
# 和服务器向客户端请求终止的 FIN2
# ACK=1 FIN=1 ack=x1+1 seq=y2 (服务器剩余分段序号 y1-y2)
client <---- waving3 <---------- server
<==== ack1 = seq1+1 <==== # 确认收到 seq1
<==== ACK1 = 1 <==== # 服务器第 2 次确认 FIN1, 客户端到服务器的单向连接关闭
<==== FIN2 = 1 <==== # 服务器向客户端请求终止
<==== seq2 <====
# ACK=1 ack=y2+1 seq=x1+1
client ----- waving4 ----------> server
====> ACK2 = 1 ====> # 确认 FIN2,服务器到客户端单向连接关闭, 服务器关闭 CLOSED
====> ack2 = seq2+1 ====> # 确认收到 seq2
TIME_WAIT # 客户端等待 2MSL 确保服务端收到第四次挥手 ACK 后, 客户端关闭 CLOSED
# MSL(Maximum Segment Lifetime)最长分段寿命,大约 1-4 分钟TCP 与 UDP 的区别:
| TCP | UDP |
|---|---|
| 面向连接 | 无连接 |
| 点对点 | 一对一,一对多,多对一,多对多 |
| 字节流 | 数据报 |
| 有序 | 无序 |
| 流量控制,拥塞控制 | 无 |
| 可靠 | 不可靠 |
| 慢 | 快 |
输入 URL 到页面加载完成
- 判断地址栏内容是搜索关键字,还是请求 URL
- 如果是搜索关键字,则组合为携带搜索关键字的新 URL
- 如果是请求 URL,则按需加上
https://协议字段,组合为新 URL
- beforeunload 事件:用户回车后,会触发 beforeunload 事件,beforeunload 事件允许页面卸载前,执行数据清理等操作;也可以询问用户是否离开当前页面,用户可以通过 beforeunload 事件取消导航(页面跳转)
- 渲染进程通过进程间通信(IPC)将请求 URL 发送给网络进程
- 网络进程先检查本地缓存是否缓存了请求资源,如果有缓存,则直接返回请求资源给渲染进程(强制缓存);如果没有缓存,则发送网络请求
- DNS 解析:对 URL 进行 DNS 解析,以获取服务器 IP 地址和端口号;HTTP 的默认端口号是 80,HTTPS 默认端口号是 443,如果是 HTTPS 协议,还需要建立 TLS 或 SSL 连接
- 建立 TCP 连接:进入 TCP 队列,通过三次握手与服务器建立连接(在 HTTP/1.1 协议下,chrome 限制一个域名最多同时建立 6 个 TCP 连接)
- 浏览器发送 HTTP 请求:浏览器生成请求行(get,post,... 请求方法,URL,协议),请求头,请求体等,并将 cookie 等数据附加到请求头中,发送 HTTP 请求给服务器
- RESTful:get,post,put,delete,patch,...
- 应用层:加 HTTP 头部,包括请求方法,URL,协议等
- 传输层:加 TCP 头部,包括源端口号,目标端口号等
- 网络层:加 IP 头部,包括源 IP 地址,目标 IP 地址等
- 服务器收到 HTTP 请求:服务器生成响应行,响应头,响应体等,发送 HTTP 响应给浏览器网络进程
- 服务器网络层解析出 IP 头部,将数据包向上交付给传输层
- 服务器传输层解析出 TCP 头部,将数据包向上交付给应用层
- 服务器应用层解析出请求头和请求体
- 如果不需要重定向,服务器根据请求头中的
If Not Match字段值判断请求资源是否被更新(协商缓存),如果没有更新,则返回 304 状态码,不返回请求资源;如果有更新,则同时返回 200 状态码和请求资源 - 如果希望使用强缓存,则设置响应头字段
Cache-Control: max-age=2000,例如 Nginx 配置文件add_header Cache-Control "public,immutable";对应的响应头字段Cache-Control: public,immutable - 如果需要重定向,则服务器直接返回 301 或 302 状态码,在响应头的
Location字段中指定重定向地址,浏览器根据状态码和Location字段进行重定向操作
- 如果不需要重定向,服务器根据请求头中的
- 关于是否断开连接:数据传输完成,TCP 四次挥手断开连接,如果浏览器或服务器在 HTTP 头部设置
Connection: Keep-Alive字段,则会建立持久的 TCP 连接,节省下一次 HTTP 请求时建立连接的时间,提高资源加载速度 - 关于重定向:浏览器收到服务器返回的响应头后,网络进程解析响应头,如果状态码是 301 或 302,则网络进程获取响应头的
Location字段值(重定向的地址),发送新的 HTTP/HTTPS 请求 - 关于响应体的数据类型:浏览器根据 HTTP 响应头的
Content-Type字段值判断响应数据类型,并根据响应数据类型决定如何处理响应体。如果Content-Type字段值是二进制数据流类型:Content-Type: application/octet-stream,则提交给浏览器的下载管理器,同时该 URL 请求的导航(页面跳转)结束;如果Content-Type字段值是 HTML 类型:Content-Type: text/html; charset=utf-8,则网络进程通知浏览器进程分配一个渲染进程进行页面渲染
- 分配渲染进程:浏览器进程检查新 URL 和已打开 URL 的域名是否相同,如果相同则复用已有的渲染进程,如果不同则创建新的渲染进程
- 渲染文档:渲染进程解析文档;将 HTML 解析为 DOM 树,将 CSS 解析为 CSSOM 树,将 DOM 树和 CSSOM 树合并为渲染树;回流,重绘
预检请求
简单请求
满足以下所有的是简单请求
- 请求方法是 GET/POST/HEAD(HTTP/1.0 提供的 3 种请求方法)
- Content-Type 字段值是
application/x-www-form-urlencoded(键值对表单),multipart/form-data(多部分表单数据/文件)或text/plain(纯文本) - 请求头中没有自定义字段
复杂请求
浏览器每次发送复杂请求前,都会先发送 OPTIONS 预检请求,询问服务器允许哪些 HTTP 请求方法和请求头字段,是否允许跨域请求等,OPTIONS 预检请求的目的是确保实际请求对服务器是安全的,OPTIONS 预检请求包含以下请求头字段
Origin发送请求的域名Access-Control-Request-Method实际请求将使用的 HTTP 请求方法Access-Control-Request-Headers实际请求将携带的请求头字段
服务器通过请求头告诉浏览器:允许发送跨域请求的域名,允许哪些 HTTP 请求方法和请求头字段等
Access-Control-Allow-Origin允许发送跨域请求的域名Access-Control-Allow-Methods允许哪些 HTTP 请求方法Access-Control-Allow-Headers允许哪些 HTTP 请求头字段
服务器设置
Access-Control-Max-Age指定缓存预检请求的响应头字段Access-Control-Allow-Methods和Access-Control-Allow-Headers的有效期,单位是秒;有效期内,浏览器可以直接发送复杂请求的跨域请求,不需要先发送 OPTIONS 预检请求
浏览器缓存
HTTP 缓存是保存资源副本的技术,提高页面性能,减少网络流量,降低服务器压力;浏览器或服务器判断请求的资源已被缓存时,直接返回;HTTP 缓存分为私有缓存和共享缓存
- 私有缓存:浏览器缓存
- 共享缓存:CDN 缓存,网关缓存,代理缓存
浏览器缓存,也称为客户端缓存;浏览器缓存分为强缓存和协商缓存,强缓存的优先级高于协商缓存
- 强缓存优先级高于协商缓存
- 强缓存中,
Cache-Control优先级高于Expires - 协商缓存中,
ETag优先级高于Last-Modified
强缓存
- 请求强缓存的资源,不会发送请求到服务器,直接从客户端缓存中获取资源,浏览器直接返回
200 From Memory Cache/From Disk Cache - 服务器可以使用响应头中的
Cache-Control或Expires字段设置强缓存,Cache-Control的优先级高于Expires,表示资源在客户端的缓存有效期
Cache-Control: max-age=30000000指定存活时间Expires: Mon, 01 Jan 2025 00:00:00 GMT指定过期时间
协商缓存
- 请求协商缓存的资源,仍会请求服务器,服务器根据请求头的
Last-Modified/If-Modified-Since和ETag/If-None-Match两对字段判断协商缓存是否命中;如果命中,服务器返回304 Not Modified,响应体为空;如果未命中,服务器返回200 OK,响应体中携带更新的资源 - 服务器可以使用响应头中的
ETag或Last-Modified字段设置协商缓存,客户端请求时自动携带If-None-Match(对应ETag)或If-Modified-Since(对应Last-Modified)请求头,ETag的优先级高于Last-Modified
Etag: "1.0.0"哈希值或自定义字符Last-Modified: Mon, 01 Jan 2025 00:00:00 GMT最后修改时间
- index.html 使用协商缓存
- *.css,*.js,图片,字体等使用强缓存,并在文件名后加 hash 值
浏览器渲染
渲染进程
chrome 为每一个页面创建一个渲染进程,渲染进程是多线程的,主要包含
- GUI 渲染线程:负责渲染页面,解析 HTML,CSS;构建 DOM 树,CSSOM 树;将 DOM 树和 CSSOM 树合并为渲染树(Render Tree);布局和绘制,回流和重绘等
- JS 引擎线程(主线程):一个页面(一个渲染进程)中只有一个 JS 引擎线程,负责将同步任务加入同步任务栈(函数调用栈),执行所有同步代码,宏任务和微任务
- 事件触发线程:将异步任务加入异步任务队列(宏任务加入宏任务队列,微任务加入微任务队列)
- 定时器触发线程:执行 setTimeout,setInterval 的线程
- 网络线程:执行 XMLHttpRequest,fetch,postMessage 的线程
- I/O 线程:负责文件 I/O,IPC 进程间通信等
GUI 渲染线程和 JS 引擎线程是互斥执行的:GUI 渲染线程执行时,JS 引擎线程会被挂起;JS 引擎线程执行时,GUI 渲染线程会被挂起
浏览器渲染过程
HTML 文档 → HTML 解析器 → DOM 树
↓
CSS 文件 → CSS 解析器 → CSSOM 树 → 渲染树 → 布局 → 绘制 → 显示- 解析 HTML,深度优先遍历以构建 DOM 树
遇到
<style>标签时,会同时构建 CSSOM 树 - 将 DOM 树和 CSSOM 树合并为渲染树
- 布局和绘制
- 回流和重绘:回流 reflow,有关宽高等,性能开销大;重绘 repaint,有关颜色等,性能开销小
浏览器一帧中做了什么
- 处理用户交互事件(click,input,scroll 等)
- 执行同步代码
- 清空微任务队列(
Promise.then,MutationObserver 等) - 执行 requestAnimationFrame 下一帧回流重绘前的回调函数(动画等帧率敏感的操作)
- 布局和绘制(回流和重绘)
- 执行宏任务队列中的一个任务(
setTimeout,setInterval,I/O等) - 如果有空闲时间,则执行 requestIdleCallback 回调函数(如懒加载 js 脚本,日志上报等)
CSS 的阻塞
CSS 不会阻塞 DOM 树的构建,会阻塞 DOM 树的渲染和后续 JS 脚本的执行
- 构建 CSSOM 树,不会阻塞 DOM 树的构建
- 等待 CSSOM 树构建完成后,才能将 DOM 树和 CSSOM 树合并为渲染树 (Render Tree)
- 等待 CSSOM 树构建完成后,才能执行后续的 JS 脚本
JS 的阻塞
浏览器解析 HTML 时,遇到未使用 async 或 defer 或 type="module" 标记的 <script> 标签时,会阻塞 DOM 树的构建,并等待 CSSOM 树构建完成后,转而执行后续的 JS 脚本(DOM 是流式解析,增量构建的;而 CSSOM 是层叠、继承、覆盖,全量构建,JS 必须等待 CSSOM 避免读取脏数据)
- async 是异步加载,JS 脚本可用时立即执行,执行 JS 脚本时可能阻塞 DOM 树的构建
- defer 是延迟执行,延迟到 DOM 树构建完成后执行 JS 脚本
- 对于 type="module" 标记的
<script>标签,默认是 defer 延迟执行,如果添加 async,则会覆盖默认的 defer<script type="module" src="/src/main.js" async></script>
浏览器安全
跨域
同源策略(仅在浏览器发生,是浏览器规则):http 交互默认情况下只能在同协议同域名(IP)同端口的两台终端进行
跨域:当 A 源浏览器网页向 B 源服务器地址(不满足同源策略)请求对应信息,就会产生跨域,跨域默认情况下会被浏览器拦截,除非对应的浏览器出具标记允许 A 源的访问(服务器有响应,但被浏览器拦截)
- DOM 层面:不同源则不允许相互操作 DOM,但是引入了跨文档消息机制,允许一个窗口使用另一个窗口的引用,
targetWindow.postMessage,和不同源的 DOM 进行通信 - 数据层面:不同源则不允许相互访问 cookie,sessionStorage,localStorage,IndexedDB 等,但是页面中可以嵌入第三方页面(仍然有 CSP 内容安全策略限制)
- 网络层面:不同源则不允许使用 fetch,XMLHttpRequest 发送数据给不同源的主机,但是引入了 CORS 跨域资源共享
const iframe = document.createElement("iframe");
iframe.src = "http://127.0.0.1:5501/toMessage.html";
document.body.appendChild(iframe);
function sendMessage() {
console.log("[5500] 正在发送消息到iframe...");
const targetWindow = iframe.contentWindow;
targetWindow.postMessage(
{ type: "greet", content: "你好,iframe!" },
"http://127.0.0.1:5501",
);
console.log("[5500] 消息已发送");
}
iframe.onload = () => {
console.log("[5500] iframe 加载完成");
sendMessage();
};
window.addEventListener("message", (e) => {
if (e.origin !== "http://127.0.0.1:5501") {
return;
}
console.log("[5500] 收到 iframe 的回复消息:", e.data);
});window.addEventListener("message", (e) => {
if (e.origin !== "http://127.0.0.1:5500") {
return;
}
console.log("[5501] 收到主页面消息:", e.data);
console.log("[5501] 正在回复消息...");
e.source.postMessage({ type: "reply", content: "你好,主页面!" }, e.origin);
console.log("[5501] 回复消息已发送");
});postMessage 只能在父页面和嵌入的 iframe 之间通信,单独打开的
toMessage.html是独立的页面,无法与父页面通信
解决跨域:
- 前后端协商 jsonp:通过
<script>标签加载外部 js 文件不受同源策略的限制,可以发送跨域请求,但只能发送 GET 请求 - 前端解决:使用代理(vite/webpack),只在开发环境中使用
- 后端解决:设置请求头
Access-Control-Allow-Credentials,Access-Control-Expose-Headers,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers - 使用 Nginx 代理
/**
* 前端定义回调函数用于接收后端返回的数据
* 后端接收到请求后拼接一段 js 代码如:`${callback}('hello')`
* 前端加载并执行代码,实现跨域数据接收
*/
const jsonp = (name) => {
let script = document.createElement("script");
script.src = "http://localhost:3000/api/jsonp?callback=" + name;
document.body.appendChild(script);
return new Promise((resolve) => {
/* 挂载回调函数到 window */
window[name] = (data) => {
resolve(data);
delete window[name];
document.body.removeChild(script);
};
});
};
/* 定义唯一回调函数名,避免多个 jsonp 请求的回调函数名冲突 */
jsonp(`callback${new Date().getTime()}`).then((res) => {
console.log(res);
});// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});function cors(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
);
// res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
res.header("Content-type", "application/json;charset=utf-8");
// 预检 (pre-flight) 请求
if (req.method.toUpperCase() === "OPTIONS") {
return res.sendStatus(204);
}
next();
}location /api {
proxy_pass http://172.X.X.X:3000;
}HTTPS
HTTP 明文传输不安全,HTTPS 引入安全层:IP(网络层)-> TCP(传输层)-> SSL/TLS(安全层)-> HTTP(应用层)
TLS 握手
- 客户端问候:客户端请求服务器,发送 Client Hello 消息,该消息包括客户端支持的 TLS 版本,支持的密码套件,和客户端随机数
- 服务器问候:服务器响应客户端,发送 Server Hello 消息,该消息包括服务器选择的 TLS 版本,选择的密码套件,和服务器随机数,以及服务器的数字证书(包含服务器的公钥,数字签名等)
- 身份验证:客户端使用证书颁发机构的公钥验证服务器的 TLS 证书,确保服务器的身份合法性
- 预主密钥:客户端生成一个随机的预主密钥,使用服务器公钥加密预主密钥,并发送给服务器
- 私钥解密:服务器使用服务器私钥解密预主密钥
- 会话密钥:客户端和服务器使用客户端随机数,服务器随机数和预主密钥共同生成一个会话密钥,用于后续的对称加密
- 客户端就绪:客户端发送一个 Client Finished 消息,该消息使用会话密钥加密,表示客户端已经准备好对称加密通信
- 服务器就绪:服务器也发送一个 Server Finished 消息,该消息也使用会话密钥加密,表示服务器也已经准备好对称加密通信
- 安全通信:握手完成后,客户端和服务器使用会话密钥进行安全的对称加密通信
数字证书
- 非对称加密中,服务器需要将公钥发送给客户端,公钥发送过程中,可能被中间人拦截并替换,中间人就可以取代服务器与客户端通信,即中间人攻击
- 解决方法是,服务器不是将公钥直接发送给客户端,而是将公钥写入证书认证机构(Certificate Authority,CA)颁发的数字证书中,服务器将数字证书(包含服务器的公钥)发送给客户端
- 通过数字证书,服务器可以向浏览器证明身份合法性
数字签名
数字签名(Digital Signature)是一种用于验证数字文档完整性和来源的技术。它使用非对称加密技术,通常包括以下步骤:
- 哈希处理:发送者对原始数据进行哈希处理,生成一个固定长度的哈希值(摘要 A)
- 私钥加密:使用发送者的私钥对这个哈希值进行加密,生成数字签名
- 发送:发送者将原始数据和数字签名一起发送给接收者
- 验证:接收者使用发送者的公钥解密数字签名,得到原始哈希值(摘要 A),然后对接收的数据进行哈希处理,生成新的哈希值(摘要 B)
- 比较:如果解密得到的哈希值(摘要 A)与新生成的哈希值(摘要 B)相同,那么数据未被篡改,且来源可信
数字签名确保了数据的完整性和不可否认性,常用于电子邮件、数字合同、数字证书等
浏览器攻击
- 跨站脚本攻击(XSS,Cross-Site Scripting)
XSS 是指攻击者在网页中注入恶意脚本代码,当用户浏览该网页时,恶意脚本被执行,攻击者可以窃取用户的敏感信息(如 cookie,localStorage)
- 跨站请求伪造(CSRF,Cross-Site Request Forgery)
CSRF 是指攻击者诱导用户在已认证的情况下,向受信任的网站发送恶意请求,攻击者可以利用用户的身份进行未授权的操作(本质是利用 cookie 会在同源请求中携带发送给服务器的特点)
- 中间人攻击(MITM,Man-in-the-Middle)
MITM 是指攻击者在用户和服务器之间拦截和篡改通信数据,攻击者可以窃取敏感信息,篡改数据,甚至冒充服务器与用户通信
XSS 跨站脚本攻击
- 反射型 XSS:非持久型 XSS,反射型 XSS 的恶意代码在地址栏上
http://127.0.0.1:5500/index.html?a=<script>alert(1)</script> - 存储型 XSS:持久型 XSS,存储型 XSS 的恶意代码存储在数据库中,最严重
- DOM 型 XSS:例如
document.write(),eval(),innerHTML,location,v-html,dangerouslySetInnerHTML 等
预防 XSS
- 处理用户输入时,对输入进行过滤;输出到页面时,对输出进行转义
- 禁用
document.write(),eval(),innerHTML,location,v-html,dangerouslySetInnerHTML 等 - 设置响应头的 CSP 内容安全策略
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;也可以通过设置
<meta>标签定义内容安全策略<meta http-equiv="content-security-policy" content="default-src 'self'; script-src 'self' https://trusted.cdn.com;">
性能优化
web 性能指标
- 加载速度
- FCP(First Contentful Paint):首次内容绘制,指浏览器首次将任何文本、图片、非空白 canvas 或 SVG 等内容渲染到屏幕上的时间点
- LCP(Largest Contentful Paint):最大内容绘制,指浏览器首次将页面上最大的文本块或图像渲染到屏幕上的时间点(<= 2.5s)
- SI(Speed Index):速度指数,指页面内容在加载过程中可见部分的平均绘制时间,衡量页面加载过程中内容的可见程度
- TBT(Total Blocking Time):总阻塞时间,指 FCP 和 TTI(Time to Interactive)之间所有长任务的时间总和,衡量页面加载过程中主线程被阻塞的程度
- 视觉稳定性
- CLS(Cumulative Layout Shift):累计布局偏移,指页面在加载过程中发生的所有意外布局偏移的总和,衡量页面视觉稳定性的指标
- 交互响应性
- INP(Interaction to Next Paint):交互到下一次绘制,指用户与页面交互(例如点击按钮)后,浏览器完成下一次绘制的时间点,替代了 FID(First Input Delay)作为交互性能的指标(<= 200ms)
优化方法
网络与加载(Network & Loading)
- 减少请求数:HTTP 缓存(强缓存/协商缓存)、Service Worker 离线拦截、资源合并(雪碧图)、内联关键资源
- 减小传输体积:压缩(Gzip/Brotli)、Tree-Shaking 剔除无用代码、图片/视频格式优化(AVIF/WebP)、分包策略
- 优化传输过程:CDN 加速、DNS 预解析、TCP 复用、HTTP/3 启用
- 智能加载策略:
- 懒加载:图片、组件、路由按需加载
- 预加载/预连接(Preload/Prefetch/Preconnect):提前获取关键资源
- SWR 策略:利用缓存旧数据提升首屏速度
渲染与解析(Rendering & Parsing)
关键渲染路径优化:减少阻塞渲染的资源(将非关键 CSS 内联,异步加载非关键 JS)
减少重排重绘:避免一次性修改多个 DOM 样式(使用类名批量修改,dom 离线修改,使用 DocumentFragment),使用 content-visibility 优化长列表,减少布局计算
CSS 优化:降低选择器复杂度,提升合成层性能(Will-change)
JavaScript 解析:优化长任务拆分、避免脚本阻塞解析
.long-list-item {
content-visibility: auto;
/* 提前预渲染视口外 500px 的内容,避免滚动时闪烁 */
contain-intrinsic-size: 500px;
}
.box {
will-change:
transform, opacity; /* 告诉浏览器即将修改这些属性,避免动画临时计算,减少卡顿 */
transform: translateZ(0); /* 强制创建合成层(老浏览器兼容) */
}运行时与交互(Runtime & Interaction)
- 主线程优化:将长任务拆分成小任务(RequestIdleCallback、Scheduler API),避免阻塞主线程导致输入延迟
- 动画性能:使用 requestAnimationFrame 做 DOM 动画,避免强制同步布局(Forced Synchronous Layout)
- 内存管理:避免内存泄漏(未清除的定时器、游离的 DOM 引用),减少垃圾回收的停顿时间
- 算法与逻辑:前端代码逻辑效率,如虚拟列表处理大数据渲染(@tanstack/vue-virtual)
用户感知(Perception)
- 骨架屏:白屏期间显示占位结构,提升首屏感知速度
- 进度指示器:清晰的加载进度
- 渐进式展示:先渲染文字内容,再渲染图片/复杂组件
- 错误兜底:SWR 策略、离线页面的友好提示