前端面试复习笔记:浏览器原理核心知识点梳理
以往文章
📚 JS 基础笔记:11 个核心模块笔记帮你吃透(含考点陷阱)
📚 CSS基础:10 个基础模块笔记(Flex/Sticky/BFC 全拆解 + 陷阱提示)
📚 吃透前端项目优化系列(一):从构建提速开始,分节拆解工程化方案
📚 吃透前端项目优化系列(二):首屏渲染优化 +性能指标拆解
📚 吃透前端项目优化系列(三):Webpack 核心基础全解析
📚 吃透前端项目优化系列(四):Webpack 进阶
📚 一篇通关Vue(一):Vue 基础核心 5 大模块全解析
引言
作为一名前端开发者,每天都在和浏览器打交道,但你真的摸清它的 “脾气” 了吗?面试时被问到浏览器缓存策略、页面渲染机制就卡壳?别担心,这篇笔记或许能帮到你。最近在为前端面试做复习,系统梳理了浏览器原理相关的核心知识点,想着把这些整理好的内容分享出来,既能帮到同样在准备面试的小伙伴,也能通过交流查漏补缺。
前端开发的本质,是与浏览器进行 “对话”—— 理解浏览器的工作逻辑,才能写出更高效、更健壮的代码。
开始
📚 浏览器作为前端开发的 “主战场”,其底层原理是我们理解前端运行机制的关键。今天这篇笔记聚焦于浏览器原理这块核心内容,里面涵盖了以下 10 个关键模块:
序号 | 核心模块 | 学习价值 |
---|---|---|
1 | JavaScript 的三大组成部分 | 构建 JS 知识体系的基础框架 |
2 | BOM 浏览器对象模型包含哪些对象 | 掌握浏览器 API 的调用逻辑 |
3 | 进程与线程 | 理解浏览器多任务处理机制 |
4 | 浏览器页面渲染机制 | 解决页面卡顿、布局偏移问题 |
5 | 浏览器缓存 | 优化页面加载速度的核心手段 |
6 | 浏览器 “空闲时间”(Idle Time) | 提升代码执行效率的进阶技巧 |
7 | 浏览器 "一帧"(Frame) | 实现流畅动画与交互的关键 |
8 | script 标签中 “async” 和 “defer” 的区别 | 优化脚本加载顺序的实践方案 |
9 | DOM 事件流 | 处理复杂交互场景的底层逻辑 |
10 | 垃圾回收机制 GC | 避免内存泄漏的核心知识点 |
🔍 这些都是前端开发中与浏览器密切相关的基础且重要的知识点,正如业界常说:“理解了浏览器的工作原理,就掌握了前端优化的半壁江山”。无论是日常开发中解决疑难问题,还是面试时应对面试官的深度追问,这些内容都能派上大用场。接下来就和大家逐一梳理这些内容~
介绍
1:JavaScript的三大组成部分
JavaScript(浏览器环境)= ECMAScript(核心语法) + DOM(文档操作) + BOM(浏览器交互)
一、 ECMAScript(核心语法)
- 定义:由 ECMA 国际标准化组织制定的 JavaScript 语言核心规范,与具体运行环境无关。
- 包含内容:
- 语法规则(变量、函数、循环、条件判断等)。
- 数据类型(Number、String、Object、Symbol 等)。
- 内置对象(
Math
、Date
、Array
等)及方法。 - 关键字与保留字(
let
、class
、async
等)。
- 作用:规定 JavaScript 的基础语法和逻辑,是 JS 的 “编程语言本身”。
二、DOM(文档对象模型)
- 定义:W3C 制定的 处理 HTML/XML 文档的 API,将文档解析为 树形结构(DOM 树),每个节点对应文档中的元素、属性或文本。
- 包含内容:
- 节点操作(
getElementById
、appendChild
、removeChild
等)。 - 事件机制(
addEventListener
、click
、scroll
等)。 - 样式操作(
element.style
、getComputedStyle
等)。
- 节点操作(
- 作用:让 JavaScript 能够 动态操作网页内容和结构(如增删元素、修改样式)。
三、BOM(浏览器对象模型)
- 定义:浏览器厂商提供的 与浏览器交互的 API,无统一标准(但各浏览器实现大致一致)。
- 包含内容:
- 浏览器窗口控制(
window.open
、window.close
、resizeTo
等)。 - 导航与历史(
location.href
、history.back()
等)。 - 浏览器信息(
navigator.userAgent
、screen.width
等)。 - 全局对象(
window
是 BOM 的核心,所有 BOM 对象都是window
的属性)。
- 浏览器窗口控制(
- 作用:让 JavaScript 能够 控制浏览器行为(如跳转页面、获取屏幕尺寸、操作 Cookie 等)。
三者的关系与总结
- 包含关系:
- DOM 是 BOM 的一部分(
window.document
是 BOM 核心对象window
的属性)。 - ECMAScript 是独立于浏览器的核心语法,DOM 和 BOM 是浏览器环境下的扩展 API。
- DOM 是 BOM 的一部分(
- 完整公式:
JavaScript(浏览器环境)= ECMAScript(核心语法) + DOM(文档操作) + BOM(浏览器交互)
- 通俗理解:
- ECMAScript 是 “语法规则”(如怎么写变量、函数)。
- DOM 是 “操作网页内容的工具”(如修改文字、添加按钮)。
- BOM 是 “操作浏览器的工具”(如打开新窗口、获取浏览器版本)。
2:BOM浏览器对象模型包含哪些对象
BOM 以
window
为核心,包含document
、navigator
、location
、history
、screen
等对象,负责浏览器窗口控制、历史记录管理、屏幕信息获取等与网页内容无关的交互。其中,document
是连接 BOM 与 DOM 的桥梁,使 JavaScript 既能操作浏览器,又能控制网页内容。
一、BOM 核心概念与全局对象关系
1. 核心定义
- BOM(Browser Object Model):浏览器对象模型,是浏览器提供的 与网页内容无关的浏览器交互 API 集合,核心对象是
window
(所有 BOM 对象均为window
的属性)。 window
的双重角色:- 浏览器窗口的接口(控制窗口行为,如大小、跳转)。
- 全局对象(全局变量、函数自动成为
window
的属性 / 方法,可省略window.
直接调用)。
2. 与 Global 对象的关系
- ECMAScript 中的 Global 对象:抽象概念,代表全局作用域(如
isNaN
、parseInt
等全局方法的宿主)。 - 浏览器环境中:
window
是 Global 对象的 具体实现(即浏览器将window
作为 Global 对象的代理),因此:- 全局变量
a = 1
等价于window.a = 1
。 - 全局函数
fn() {}
等价于window.fn = function() {}
。
- 全局变量
二、BOM 包含的核心对象及功能
所有 BOM 对象均挂载在 window
上,可通过 window.xxx
访问(或直接省略 window.
):
对象名 | 核心功能 | 常用属性 / 方法 |
---|---|---|
window | BOM 顶级对象,控制浏览器窗口及全局作用域 | - 窗口控制:open() (打开新窗口)、close() (关闭窗口)、resizeTo() (调整大小)。 - 定时器:setTimeout() 、setInterval() 、clearTimeout() 。 - 全局属性:window.name (窗口名称)、window.innerWidth (窗口宽度)。 |
document | 文档对象模型(DOM 核心),控制网页内容(BOM 包含 DOM,document 是 window 的属性) | - 元素操作:getElementById() 、querySelector() 、createElement() 。 - 文档属性:document.title (页面标题)、document.URL (当前 URL)。 |
navigator | 提供浏览器自身信息 | - navigator.userAgent (浏览器标识字符串,用于判断浏览器类型)。 - navigator.platform (运行平台,如 "Win32")。 - navigator.language (浏览器语言)。 |
location | 控制当前页面 URL 及导航 | - 属性:href (完整 URL)、protocol (协议,如 "https:")、host (主机名 + 端口)。 - 方法:assign(url) (跳转 URL)、reload() (刷新页面)、replace(url) (替换当前历史记录)。 |
history | 管理浏览器历史记录(当前窗口访问过的 URL) | - history.length (历史记录条数)。 - 方法:back() (后退)、forward() (前进)、go(n) (跳转 n 步,如 go(-1) 等价于 back() )。 |
screen | 提供客户端屏幕信息 | - screen.width (屏幕宽度)、screen.height (屏幕高度)。 - screen.colorDepth (屏幕色深,如 24 位)。 |
frames | 框架集合(如 <iframe> ),返回窗口中所有框架的数组 | frames[0] (访问第一个框架窗口)、frames.length (框架数量)。 |
三、关键补充说明
- BOM 与 DOM 的关系:
- BOM 包含 DOM:
document
是window
的属性,因此 BOM 涵盖了 DOM 的操作能力。 - 区别:BOM 聚焦 浏览器窗口交互(如窗口控制、历史记录),DOM 聚焦 网页内容操作(如元素、样式)。
- BOM 包含 DOM:
- 全局变量与
window
的关系:- 全局作用域中声明的变量 / 函数,自动成为
window
的属性 / 方法:
- 全局作用域中声明的变量 / 函数,自动成为
var globalVar = 'hello';
console.log(window.globalVar); // 'hello'(等价)
function globalFn() {}
console.log(window.globalFn === globalFn); // true
- 注意:`let`/`const` 声明的全局变量不会成为 `window` 的属性(避免污染全局对象)。
- BOM 的无标准性:
- BOM 没有像 DOM 那样的 W3C 标准,不同浏览器对部分 API 的实现可能存在差异(如
userAgent
格式、history
方法的细节),开发时需注意兼容性。
- BOM 没有像 DOM 那样的 W3C 标准,不同浏览器对部分 API 的实现可能存在差异(如
四、总结
- BOM 以
window
为核心,包含document
、navigator
、location
、history
、screen
等对象,负责浏览器窗口控制、历史记录管理、屏幕信息获取等与网页内容无关的交互。其中,document
是连接 BOM 与 DOM 的桥梁,使 JavaScript 既能操作浏览器,又能控制网页内容。
3:进程与线程
进程:浏览器通过多进程隔离实现稳定与安全,核心是 Browser 进程(总管)、Renderer 进程(页面渲染)、GPU 进程(图形加速)、插件进程(按需启动)。
线程:Renderer 进程内通过多线程协作完成渲染与交互,核心是 GUI 渲染线程(渲染)、JS 引擎线程(执行脚本)、事件触发线程(调度异步任务),其中 GUI 与 JS 线程的互斥是 “JS 阻塞页面渲染” 的根源。
一、基础概念:进程与线程的区别
- 进程:操作系统分配资源的基本单位(如内存、CPU 时间片),拥有独立的内存空间,进程间相互隔离(通过 IPC 通信)。
- 线程:进程内的执行单元,共享进程的资源(内存、文件句柄等),一个进程可包含多个线程,线程间通过共享内存通信。
- 核心关系:一个进程可以包含多个线程(线程是进程的 “子任务执行者”),进程是线程的 “资源容器”。
二、浏览器的主要进程(4 类核心进程)
浏览器是 多进程架构,通过进程隔离提高安全性和稳定性(单个进程崩溃不影响其他进程),核心进程如下:
进程类型 | 作用与细节 | 典型场景 |
---|---|---|
1. Browser 进程(主进程) | - 浏览器的 “总管”,仅 1 个。 - 负责 用户交互(地址栏、前进 / 后退按钮、菜单)。 - 负责 进程管理(创建 / 销毁其他进程,如打开新标签页时创建 Renderer 进程)。 - 负责 全局资源管理(网络请求、下载管理、存储(Cookie、LocalStorage))。 | 点击浏览器图标启动浏览器 → 启动 Browser 进程;关闭浏览器 → 主进程销毁所有子进程。 |
2. Renderer 进程(渲染进程) | - 负责 页面渲染与脚本执行,每个标签页默认对应 1 个(特殊情况合并,见下文)。 - 运行在 “沙箱模式”(限制对系统资源的访问,提高安全性)。 - 内部包含多个线程(GUI 渲染线程、JS 引擎线程等,见下文)。 | 打开新标签页 → 创建 Renderer 进程;标签页崩溃 → 仅该进程重启,不影响其他标签。 |
3. GPU 进程 | - 负责 图形渲染加速,1 个(或多个,取决于 GPU 核心数)。 - 处理 3D 绘制、CSS 动画合成、页面滚动流畅度优化等。 - 将 Renderer 进程生成的 “虚拟像素信息(bitmap)” 绘制到屏幕。 | CSS 3D 变换、WebGL 渲染、高分辨率屏幕适配 → 依赖 GPU 加速。 |
4. 插件进程(Plugin 进程) | - 每种插件(如 Flash、PDF 插件)对应 1 个进程,按需创建(未使用插件时不启动)。 | 打开含 Flash 的页面 → 创建 Flash 插件进程;关闭页面 → 插件进程销毁。 |
特殊说明:Renderer 进程的合并策略
- 并非 “每个标签页必占 1 个进程”:
- 同一站点(相同协议 + 域名 + 端口)的标签页可能共享 1 个 Renderer 进程(节省资源)。
- 空白标签页或极简页面(如
about:blank
)会合并到已有进程。
三、Renderer 进程内的核心线程(5 类关键线程)
Renderer 进程是页面渲染的核心,内部通过 多线程协作 完成渲染、脚本执行等任务,核心线程如下:
线程类型 | 作用与细节 | 与其他线程的关系 |
---|---|---|
1. GUI 渲染线程 | - 负责 页面渲染:解析 HTML 构建 DOM 树、解析 CSS 构建 CSSOM 树、合并为 Render 树、布局(回流 reflow)、绘制(重绘 repaint)。 - 负责 样式与布局更新:当 DOM/CSS 变化时,重新计算并渲染。 | 与 JS 引擎线程互斥:JS 引擎执行时,GUI 线程会被 “挂起”(避免渲染不一致);JS 执行完毕后,GUI 线程恢复并重新渲染。 |
2. JS 引擎线程(如 V8 引擎) | - 负责 解析与执行 JavaScript 代码。 - 单线程(同一时间只能执行一个任务),依赖 “任务队列” 处理异步任务。 | 与 GUI 线程互斥:JS 执行时间过长(如长循环)会阻塞 GUI 线程,导致页面卡顿、无法响应交互。 |
3. 事件触发线程 | - 负责 管理异步任务队列:收集各类异步事件的结果(如定时器到期、HTTP 请求完成),并将对应的回调函数加入 “任务队列”。 - 不执行任务,仅负责 “调度”(将任务交给 JS 引擎线程执行)。 | 接收其他线程的 “事件通知”(如定时器线程的超时事件、异步 HTTP 线程的请求完成事件),再将回调加入队列。 |
4. 定时器线程(Timer 线程) | - 负责 管理 setTimeout/setInterval:独立计时(不依赖 JS 引擎,避免 JS 阻塞导致计时不准)。 - 计时结束后,将回调函数通过 “事件触发线程” 加入任务队列。 | 例:setTimeout(fn, 1000) → 定时器线程计时 1 秒 → 通知事件触发线程 → fn 进入任务队列 → JS 引擎空闲时执行 fn。 |
5. 异步 HTTP 请求线程 | - 负责 处理 XMLHttpRequest/fetch 请求:浏览器限制同域并发请求数(通常 6 个),超过则排队。 - 请求完成(或失败)后,将回调函数通过 “事件触发线程” 加入任务队列。 | 例:fetch(url).then(fn) → 异步 HTTP 线程发起请求 → 响应完成 → 通知事件触发线程 → fn 进入任务队列 → JS 引擎执行 fn。 |
四、关键协作流程示例(以 “点击按钮发送请求并更新 DOM” 为例)
- 用户点击按钮:Browser 进程捕获事件 → 通知对应标签页的 Renderer 进程。
- JS 引擎线程:执行按钮点击事件的回调函数(同步代码)。
- 异步 HTTP 线程:回调中若有
fetch
请求 → 异步 HTTP 线程发起网络请求,JS 引擎继续执行后续同步代码(不阻塞)。 - 事件触发线程:请求完成后,异步 HTTP 线程通知事件触发线程 → 将
then
回调加入任务队列。 - JS 引擎线程:同步代码执行完毕 → 从任务队列取出
then
回调执行(修改响应式数据)。 - GUI 渲染线程:JS 引擎空闲 → GUI 线程恢复,根据数据变化重新渲染 DOM(回流 / 重绘) → 更新页面。
五、总结
- 进程:浏览器通过多进程隔离实现稳定与安全,核心是 Browser 进程(总管)、Renderer 进程(页面渲染)、GPU 进程(图形加速)、插件进程(按需启动)。
- 线程:Renderer 进程内通过多线程协作完成渲染与交互,核心是 GUI 渲染线程(渲染)、JS 引擎线程(执行脚本)、事件触发线程(调度异步任务),其中 GUI 与 JS 线程的互斥是 “JS 阻塞页面渲染” 的根源。
4:浏览器页面渲染机制
浏览器页面渲染是Browser 进程(资源加载) 与Renderer 进程(渲染流水线) 协作的结果 核心流程为:
资源加载(缓存/网络)→ DOM解析 → CSSOM解析 → 渲染树构建 → 布局 → 绘制 → 合成 → 显示
。
一、完整渲染流程:从 URL 到屏幕显示
1. 资源加载阶段(Browser 进程主导)
用户输入 URL 后,Browser 进程(主进程)先完成资源获取,核心步骤:
- 缓存检查:按优先级检查缓存(
Memory Cache
→Disk Cache
→Service Worker Cache
→Network
),命中则直接将缓存内容传递给 Renderer 进程;未命中则进入网络请求。 - 网络请求:
- DNS 解析:将域名转为 IP(优先使用 DNS 缓存,未命中则向 DNS 服务器查询)。
- 建立连接:HTTPS 需先完成 TLS 握手(HTTP 直接 TCP 三次握手),建立 TCP 连接。
- 请求与响应:发送 HTTP 请求(携带缓存头如
If-None-Match
),服务器返回资源(HTML/CSS/JS/ 图片等),Browser 进程接收后通过IPC(进程间通信) 传递给 Renderer 进程。
2. 渲染阶段(Renderer 进程主导)
Renderer 进程接收 HTML 后,启动渲染流水线,核心步骤:
步骤 | 核心操作 | 关键细节 |
---|---|---|
解析 HTML,构建 DOM 树 | 词法分析(将 HTML 字符转为标签)→ 语法分析(构建节点关系)→ 生成 DOM 树(描述文档结构的树形对象)。 | - HTML 解析器是单线程,遇到<script> 标签会暂停解析(默认行为),等待 JS 下载 + 执行完成后再继续(因 JS 可能通过document.write 修改 DOM)。 - 若<script> 带async (下载完成后立即执行,顺序无关)或defer (下载完成后按顺序执行,DOM 解析完后触发),则不阻塞 HTML 解析。 |
解析 CSS,构建 CSSOM 树 | 解析 CSS 规则(选择器 + 样式)→ 生成 CSSOM 树(描述元素样式的树形对象,含继承 / 层叠规则)。 | - CSS 解析与 HTML 解析并行进行(单独线程处理),不阻塞 DOM 树构建。 - 若 JS 中操作 CSSOM(如getComputedStyle ),JS 会等待 CSSOM 构建完成后再执行(避免获取到不完整样式),间接阻塞 DOM 解析。 |
构建渲染树(Render Tree) | 结合 DOM 树与 CSSOM 树,筛选可见元素(display: none 的元素不包含,visibility: hidden 包含),为每个可见元素绑定样式。 | - 渲染树仅关注 “可见且需渲染的元素”,是布局和绘制的依据。 - 若 CSSOM 未构建完成,渲染树无法生成(因缺少样式信息),导致页面延迟渲染。 |
布局(Layout / 回流) | 计算渲染树中元素的几何信息(位置、大小、间距等),输出 “盒模型布局”。 | - 布局是递归过程(父元素布局依赖子元素,子元素变化可能影响父元素)。 - 触发条件:元素尺寸 / 位置变化、DOM 增删、窗口 resize 等(见后文 “回流触发场景”)。 |
绘制(Paint / 重绘) | 按布局结果,将元素的视觉属性(颜色、阴影、背景等)绘制到图层(每个图层对应一块内存区域)。 | - 绘制是像素级操作,可按 “绘制顺序” 分步骤(如先画背景,再画文本,最后画边框)。 - 触发条件:元素视觉属性变化(如颜色、背景),但几何属性不变(此时无需布局,仅需重绘)。 |
合成(Composite) | 将多个图层合并为最终屏幕图像,由 GPU 处理(避免 CPU 瓶颈)。 | - 图层优势:单个图层重绘 / 布局不影响其他图层(如动画元素单独为图层,仅重绘该图层)。 - 复合图层创建:通过transform /opacity (硬件加速)、<video> /<canvas> 等标签,或will-change: transform 提示浏览器提前创建。 |
3. 显示阶段
合成后的图像通过 GPU 传递给显示器,按屏幕刷新率(如 60Hz)逐帧显示,完成页面渲染。
二、关键机制与优化细节补充
1. 渲染阻塞逻辑(核心考点)
- ** CSS 阻塞渲染,但不阻塞 DOM 解析**: CSSOM 是渲染树的必要条件,未加载完成会阻塞渲染(页面空白),但 HTML 解析可并行进行(因 DOM 与 CSSOM 是独立结构)。
- JS 阻塞 DOM 解析,可能阻塞 CSSOM:
- 默认情况下,
<script>
会暂停 HTML 解析(等待 JS 下载 + 执行),因 JS 可能通过document.createElement
修改 DOM。 - 若 JS 中访问 CSSOM(如
window.getComputedStyle(el)
),JS 会等待 CSSOM 构建完成后再执行,间接阻塞 DOM 解析(形成 “CSS→JS→DOM” 的阻塞链)。
- 默认情况下,
2. 图层合成与硬件加速
- 普通图层 vs 复合图层:
- 普通图层:默认文档流中的元素,共享一个图层(布局 / 重绘影响整个图层)。
- 复合图层:独立内存区域,由 GPU 单独绘制,修改时不影响其他图层(如动画元素、
fixed
定位元素)。
- 合理创建复合图层:
- 优势:减少重绘范围,利用 GPU 并行计算加速渲染。
- 注意:避免创建过多复合图层(GPU 内存有限,过多会导致 “图层爆炸”,反而卡顿)。
3. 事件触发顺序
- DOMContentLoaded:DOM 解析完成后触发(无需等待 CSS、图片、字体加载)。
- 若存在阻塞的 JS(无
async
/defer
),会等待 JS 执行完成后触发。
- 若存在阻塞的 JS(无
- load:所有资源(DOM、CSS、JS、图片、字体)加载完成后触发。
- 顺序:
DOMContentLoaded
→load
(除非资源加载失败,load
可能不触发)。
4. 回流与重绘优化技巧
-
批量修改 DOM:
- 用
documentFragment
临时存储 DOM 操作,完成后一次性插入(仅触发 1 次回流)。 - 先设置
element.style.display = 'none'
(触发 1 次回流),修改完成后恢复(再触发 1 次回流),替代多次零散修改。
- 用
-
避免频繁读取布局属性:
- 如
offsetTop
、scrollHeight
、getBoundingClientRect
等属性会强制浏览器同步布局(触发回流),建议缓存后使用:
// 优化前(多次触发回流) for (let i = 0; i < 100; i++) { el.style.left = el.offsetLeft + 10 + 'px'; } // 优化后(1次回流) const left = el.offsetLeft; // 缓存布局属性 for (let i = 0; i < 100; i++) { el.style.left = left + 10 * i + 'px'; }
- 如
-
脱离文档流: 用
position: absolute/fixed
或float
让元素脱离普通文档流,减少对其他元素的布局影响。 -
现代框架优化: Vue/React 通过虚拟 DOM 批量对比 DOM 变化,React Fiber 采用时间切片避免长时间阻塞渲染,均减少不必要的回流 / 重绘。
三、总结
浏览器页面渲染是Browser 进程(资源加载) 与Renderer 进程(渲染流水线) 协作的结果,核心流程为: 资源加载(缓存/网络)→ DOM解析 → CSSOM解析 → 渲染树构建 → 布局 → 绘制 → 合成 → 显示
。
关键优化方向:
- 减少渲染阻塞(合理使用
async
/defer
、内联关键 CSS); - 控制回流 / 重绘范围(批量修改样式、合理创建复合图层);
- 利用现代浏览器特性(预加载
preload
、GPU 加速)。
理解这一机制,能更清晰地解释 “为什么把 CSS 放头部、JS 放尾部”“为什么动画用transform
更流畅” 等实际开发问题。
四、TCP的三次握手和TCP四次挥手
三次握手的目的: 除了防止 “已失效的连接请求报文段” 导致服务器资源浪费外,更本质的是让通信双方协商初始序列号(seq)并确认彼此的接收能力(确保双方都知道对方能正常发送和接收数据) 四次挥手的目的: TCP 是全双工通信(双方可同时发送数据),四次挥手的本质是分别关闭两个方向的数据流。服务器的
ACK
和FIN
分开发送,是因为服务器收到客户端的关闭请求后,可能还需要处理未发送完的数据,无法立即关闭自己的发送方向。
一、TCP 三次握手(建立连接
核心目的:除了防止 “已失效的连接请求报文段” 导致服务器资源浪费外,更本质的是让通信双方协商初始序列号(seq)并确认彼此的接收能力(确保双方都知道对方能正常发送和接收数据)。
1. 第一次握手
- 发送方:客户端(主动打开连接)
- 报文内容:将标志位
SYN
置为 1(表示请求建立连接),生成一个随机初始序列号seq = J
(范围是 0 到 2³²-1,而非 1-6,1-6 仅为示例)。 - 状态变化:客户端发送后进入
SYN_SENT
状态(等待服务器确认)。
2. 第二次握手
- 接收方:服务器(被动打开连接)
- 报文内容:
- 检测到
SYN=1
,知道客户端请求连接,因此将SYN
和ACK
标志位都置为 1(SYN
表示同意建立连接,ACK
表示确认收到客户端的请求)。 - 确认号
ack = J + 1
(表示已收到客户端序列号为 J 的报文,下一次期望接收 J+1 及之后的数据)。 - 服务器生成自己的随机初始序列号
seq = K
(同样为 0 到 2³²-1 的随机值)。
- 检测到
- 状态变化:服务器发送后进入
SYN_RCVD
状态(等待客户端最终确认)。
3. 第三次握手
- 接收方:客户端
- 报文内容:
- 检查服务器返回的
ack
是否为J + 1
,且ACK=1
,确认服务器已正确接收第一次握手。 - 发送
ACK=1
,确认号ack = K + 1
(表示已收到服务器序列号为 K 的报文,下一次期望接收 K+1 及之后的数据),序列号seq = J + 1
(因为第一次握手的序列号 J 已被消耗,后续序列号递增)。
- 检查服务器返回的
- 状态变化:
- 客户端发送后直接进入
ESTABLISHED
状态(连接已建立)。 - 服务器收到报文后,检查
ack
是否为K + 1
且ACK=1
,确认无误后也进入ESTABLISHED
状态。
- 客户端发送后直接进入
- 结果:双方进入数据传输状态,可开始发送应用层数据。
二、TCP 四次挥手(释放连接)
核心目的:TCP 是全双工通信(双方可同时发送数据),四次挥手的本质是分别关闭两个方向的数据流。服务器的ACK
和FIN
分开发送,是因为服务器收到客户端的关闭请求后,可能还需要处理未发送完的数据,无法立即关闭自己的发送方向。
1. 第一次挥手
- 发送方:客户端(主动关闭连接)
- 报文内容:发送
FIN=1
(表示关闭客户端到服务器的数据流),序列号seq = M
(当前客户端已发送数据的最后一个序列号 + 1)。 - 状态变化:客户端进入
FIN_WAIT_1
状态(等待服务器对 FIN 的确认)。
2. 第二次挥手
- 接收方:服务器
- 报文内容:收到
FIN
后,立即发送ACK=1
,确认号ack = M + 1
(表示已收到客户端的关闭请求),序列号seq = N
(服务器当前的序列号,由之前的数据传输延续而来)。 - 状态变化:服务器进入
CLOSE_WAIT
状态(此时客户端到服务器的数据流已关闭,但服务器仍可向客户端发送数据)。 - 客户端收到
ACK
后,进入FIN_WAIT_2
状态(等待服务器关闭其发送方向)。
3. 第三次挥手
- 发送方:服务器
- 报文内容:当服务器完成所有数据发送后,发送
FIN=1
(关闭服务器到客户端的数据流),序列号seq = L
(服务器最后的数据序列号 + 1),同时ACK=1
(可选,因已在第二次挥手确认过),确认号ack = M + 1
(与第二次挥手一致,保持不变)。 - 状态变化:服务器进入
LAST_ACK
状态(等待客户端对 FIN 的确认)。
4. 第四次挥手
- 接收方:客户端
- 报文内容:收到服务器的
FIN
后,发送ACK=1
,确认号ack = L + 1
(表示已收到服务器的关闭请求),序列号seq = M + 1
(客户端最后的序列号,由第一次挥手延续而来)。 - 状态变化:
- 客户端进入
TIME_WAIT
状态(等待 2 倍最大报文段寿命 MSL,确保服务器能收到最终的 ACK,避免服务器因未收到 ACK 而重发 FIN)。 - 服务器收到
ACK
后,立即进入CLOSED
状态(连接完全关闭)。 - 客户端等待
TIME_WAIT
超时后,也进入CLOSED
状态。
- 客户端进入
三、关键补充说明
- 序列号(seq)的作用:TCP 通过序列号确保数据有序传输、去重和重传,每次发送数据后序列号会随数据长度递增(而非固定 + 1,仅挥手 / 握手时因无实际数据,序列号 + 1)。
- TIME_WAIT 的意义:客户端在第四次挥手后等待 2MSL,是为了:
- 确保最后一个 ACK 能到达服务器(若服务器未收到,会重发 FIN,客户端可在 TIME_WAIT 内再次回复)。
- 避免旧连接的报文段干扰新连接(2MSL 足够让网络中残留的旧报文段失效)。
- 半关闭状态:四次挥手中,
CLOSE_WAIT
和FIN_WAIT_2
都是 “半关闭” 状态,即一方已关闭发送方向,但另一方仍可发送数据。
5:浏览器缓存
浏览器缓存是浏览器为了提升性能、减少网络请求,对资源(如 HTML、CSS、JS、图片等)进行的本地存储机制
一、HTTP 缓存(核心)
基于 HTTP 协议头(如
Cache-Control
、Expires
、ETag
等)实现的缓存,是浏览器缓存中最关键的部分,也是前端优化中最常涉及的内容。
前端日常优化中提到的 “浏览器缓存设计”,90% 以上都是指 HTTP 缓存的配置(如设置合理的
Cache-Control
策略)。
- HTTP1.0缓存机制总结
- 核心目标设计:通过简单的过期时间控制资源缓存,减少重复请求。
- 关键字段与规则
- 唯一核心字段:
Expires
- 作用:直接指定资源的绝对过期时间(GMT 格式,如
Expires: Thu, 01 Jan 2025 00:00:00 GMT
)。 - 逻辑:浏览器加载资源时,对比本地时间与
Expires
时间,若未过期则直接使用缓存,否则重新请求
- 作用:直接指定资源的绝对过期时间(GMT 格式,如
- 缓存逻辑
- 仅支持「强缓存」:无协商缓存机制,过期后直接重新请求完整资源(无 304 状态码)。
- 唯一核心字段:
- 局限与问题
- 时间同步依赖:
Expires
时间由服务器计算,若客户端(浏览器)本地时间不准确(如手动修改),会导致缓存提前失效或永久有效(核心 bug)。 - 功能单一:仅能控制过期时间,无法配置缓存范围(如私有 / 公有)、过期后策略(如是否允许临时使用)等复杂需求。
- 时间同步依赖:
- HTTP1.1缓存机制总结
-
- 核心目标设计:解决 HTTP 1.0 的缺陷,提供更灵活、精准、可控的缓存策略,支持复杂场景(如代理缓存、过期后协商等)。
- 关键改进与核心机制
-
- 用
Cache-Control
替代Expires
作为核心控制字段 -
- 形式:采用
key: k1=v1,k2=v2
结构(如Cache-Control: max-age=3600, public
),将所有缓存指令集中管理,替代 HTTP 1.0 分散的单字段设计。 - 核心指令(解决
Expires
问题) -
max-age=秒数
:用「相对时间」替代绝对时间,由浏览器计算过期时间(如max-age=3600
表示缓存 1 小时),彻底规避客户端时间不准的问题。- 优先级:若同时存在
Expires
和max-age
,max-age
覆盖Expires
。
- 形式:采用
- 新增【协商缓存】机制
-
- 解决问题:强缓存过期后,避免盲目重新下载未变更的资源。
- 核心字段:
-
- 服务器响应时返回资源标识:
Last-Modified
(最后修改时间)、ETag
(内容哈希) - 浏览器请求时携带标识验证:
If-Modified-Since
(对应Last-Modified
)、If-None-Match
(对应ETag
)。 - 逻辑:服务器验证标识,若资源未变返回 304(复用缓存),若变更返回 200(新资源)
- 服务器响应时返回资源标识:
- 扩展缓存控制维度
-
public
:允许代理服务器(如 CDN)缓存(公有缓存) -private
:仅允许客户端浏览器缓存(私有缓存,默认值)。- 代理专用策略:
s-maxage
:仅作用于代理服务器,覆盖max-age
(如浏览器缓存 10 分钟,代理缓存 1 小时)
- 过期后行为:
stale-while-revalidate
:协商新资源时,临时使用过期缓存(提升体验)must-revalidate
:过期后必须等待服务器验证,禁止临时使用过期缓存(保证数据新鲜)
- 禁用缓存相关:
no-cache
:允许缓存,但每次使用前必须协商(强缓存失效)no-store
:完全禁止缓存(不存储任何内容)
- 代理专用策略:
- 最佳实践补充:
- 静态资源(JS/CSS/ 图片):Cache-Control: public, max-age=31536000, immutable
- 配合版本化文件名(如
app.v2.js
),避免更新时的缓存问题。
- 配合版本化文件名(如
- HTML 入口文件:Cache-Control: no-cache
- 强制每次验证,确保用户获取最新的资源引用。
- 动态 API:Cache-Control: private, max-age=0, must-revalidate
- 针对用户特定数据,禁用强缓存,每次验证。
- 静态资源(JS/CSS/ 图片):Cache-Control: public, max-age=31536000, immutable
- 用
二、其他缓存机制
- Memory Cache(内存缓存):将资源临时存储在内存中,关闭浏览器后失效,速度最快(如刚加载的 JS/CSS 可能暂存于此)
- Disk Cache(磁盘缓存):将资源存储在硬盘中,关闭浏览器后仍可保留,容量更大但读取速度慢于内存缓存(HTTP 缓存的资源通常会存在这里)
- Service Worker Cache:由前端代码控制的缓存,可自定义缓存策略(如 PWA 离线功能依赖此机制)
- Push Cache(推送缓存):HTTP/2 中的一种临时缓存,优先级最低,生命周期短。
三、HTTP1.1中Cache-Control:no-cache和no-store的区别
简单来说:
no-cache
是 “缓存但必须验证”,no-store
是 “完全不缓存”。二者的核心差异在于是否允许缓存存在以及是否需要与服务器协商验证。
- Cache-Control: no-cache
- 字面含义:并非 “不缓存”,而是 “缓存前必须先验证有效性”。
- 实际行为:
-
- 允许浏览器或代理服务器缓存资源,但每次使用缓存前必须向服务器发送验证请求(通过
If-None-Match
或If-Modified-Since
协商缓存)。 - 服务器验证后,若资源未修改则返回
304 Not Modified
,浏览器直接使用缓存;若已修改则返回200 OK
并携带新资源。
- 允许浏览器或代理服务器缓存资源,但每次使用缓存前必须向服务器发送验证请求(通过
- 适用场景:需要缓存但资源可能频繁更新的内容(如动态页面、用户个性化数据),既利用缓存减少带宽,又确保使用最新版本。
- Cache-Control: no-store
- 字面含义:“完全不缓存”,禁止任何形式的缓存存储。
- 实际行为:
-
- 浏览器和代理服务器不得缓存该资源,每次请求都必须从服务器重新下载完整资源(响应体不会被保存到缓存中)。
- 不存在 “协商缓存” 过程,因为根本没有缓存可复用。
- 适用场景:涉及敏感信息的资源(如登录页面、支付数据、个人隐私信息),确保每次请求都是最新的且不留下缓存痕迹,避免信息泄露。
四、Http请求跨域的原理
跨域的核心是浏览器的同源策略,解决跨域的方案需根据场景选择:
前后端接口交互:优先使用CORS(标准、安全、灵活); 开发环境调试:优先使用代理服务器(简单高效); 窗口间跨域通信:使用window.postMessage; 极旧浏览器兼容:可考虑JSONP(不推荐)。
一、跨域的核心:浏览器的同源策略
跨域问题的本质是浏览器的同源策略(Same-Origin Policy) 导致的限制。这是浏览器为了保护用户数据安全而实施的核心安全机制,其核心逻辑是:仅允许同源的页面 / 脚本与资源进行交互,禁止跨域的恶意访问。
1. 同源的定义
“同源” 需满足三个完全一致:
- 协议:如 http、https(example.com和example.com不同源);
- 主机(域名 / IP):如example.com和api.example.com不同源(子域不同),localhost和 127.0.0.1 也不同源(虽然都指向本机,但主机标识不同);
- 端口:如example.com:80和example.com:8080不同源(默认端口 80 可省略,但显式端口必须一致)。
2. 同源策略限制的行为
同源策略并非限制所有跨域行为,而是针对性限制可能存在安全风险的交互,主要包括:
- 网络请求:XMLHttpRequest、Fetch 等 AJAX 请求无法直接访问跨域接口;
- DOM 访问:禁止通过脚本操作跨域页面的 DOM(如 iframe 的 contentDocument);
- 存储访问:禁止访问跨域页面的 Cookie、LocalStorage、IndexedDB 等存储数据;
- 其他:如禁止跨域的 WebSocket 握手(需特殊配置)、禁止跨域调用 window 对象方法等。
3. 跨域的场景
跨域不仅存在于 “前端页面与后端接口” 的交互中,还包括:
- 两个不同源的窗口(如不同标签页、不同域名的 iframe)之间的脚本交互;
- 页面加载跨域的资源(如图片、脚本、样式)时的权限限制(部分资源如 script、img 允许加载,但无法读取内容)。
注意:跨域是浏览器的限制,服务器之间的直接通信(如后端服务 A 调用后端服务 B 的接口)不受同源策略约束,因为没有浏览器参与。
二、解决跨域的常见方案
跨域的解决方案本质是 “绕开或解除浏览器的同源策略限制”,不同方案适用于不同场景,以下是主流方法的详细说明:
1. CORS(Cross-Origin Resource Sharing,跨域资源共享)
这是目前最推荐、最标准的跨域解决方案,由浏览器和服务器协同实现,核心是通过服务器返回的 HTTP 响应头告知浏览器 “允许该跨域请求”。
- 原理:
- 前端发起跨域请求时,浏览器会自动在请求头中添加
Origin
字段(标识当前源); - 服务器收到请求后,通过响应头
Access-Control-Allow-Origin
指定允许的源(如https://example.com
或*
表示允许所有),同时可附加其他限制(如允许的请求方法、是否允许携带 Cookie 等); - 浏览器检查响应头,如果当前源在允许列表中,则放行请求结果;否则拦截并报错。
- 前端发起跨域请求时,浏览器会自动在请求头中添加
- 关键配置(服务器端):
Access-Control-Allow-Origin
:指定允许的源(必须配置);Access-Control-Allow-Methods
:允许的 HTTP 方法(如 GET、POST、PUT);Access-Control-Allow-Credentials
:是否允许请求携带 Cookie(需前后端同时开启);Access-Control-Allow-Headers
:允许的自定义请求头(如 Token)。
- 优缺点:
- 优点:支持所有 HTTP 方法(GET/POST/PUT 等),可精确控制允许的源,安全性高,是现代 Web 开发的首选方案;
- 缺点:需要后端配合配置,老版本浏览器(如 IE8 以下)支持有限。
2. JSONP(JSON with Padding)
JSONP 是早期解决跨域的 “hack 方案”,利用了script
标签不受同源策略限制的特性(浏览器允许加载跨域的脚本资源)。
- 原理:
- 前端定义一个回调函数(如
handleData(data)
),用于接收跨域接口返回的数据; - 前端通过
script
标签向跨域接口发送请求,URL 中携带回调函数名(如https://api.example.com/data?callback=handleData
); - 后端接收到请求后,将数据包裹在回调函数中返回(如
handleData({name: "test"})
); - 前端的
script
标签加载并执行该脚本,触发回调函数,从而获取数据。
- 前端定义一个回调函数(如
- 优缺点:
- 优点:实现简单,兼容性极好(支持所有浏览器);
- 缺点:仅支持 GET 请求(因
script
标签只能发起 GET),存在 XSS 安全风险(后端返回的脚本可能被注入恶意代码),且需要前后端强耦合(需约定回调函数名)。 - 现状:目前已基本被 CORS 取代,仅在极旧的浏览器环境中可能用到。
3. 代理服务器(反向代理)
代理服务器是开发和生产环境中常用的跨域解决方案,核心思路是 “通过同源的代理服务器转发请求,规避浏览器的跨域检查”。
- 原理:
- 前端页面与代理服务器 “同源”(协议、主机、端口一致),因此前端向代理服务器发送请求时,浏览器不视为跨域;
- 代理服务器收到请求后,作为 “中间层” 向后端跨域接口转发请求(服务器之间通信无跨域限制);
- 后端接口将响应返回给代理服务器,代理服务器再将响应转发给前端,完成交互。
- 常见场景:
- 开发环境:如 Vue 项目使用
vue-cli
的devServer.proxy
配置代理,React 项目使用create-react-app
的proxy
配置; - 生产环境:通过 Nginx、Apache 等服务器配置反向代理(如 Nginx 的
proxy_pass
指令)。
- 开发环境:如 Vue 项目使用
- 额外说明:代理服务器可通过修改请求头(如
Origin
、Referer
)“伪装” 成后端允许的源,解决后端对源的限制(即你提到的 “欺骗属性”)。
4. 其他跨域方案(补充)
除上述三种主流方案外,还有一些针对特定场景的跨域方法:
- document.domain:适用于主域相同、子域不同的场景(如a.example.com和b.example.com),通过将两者的
document.domain
设置为相同的主域(如example.com
),实现 DOM 和 Cookie 共享; - window.postMessage:用于两个跨域窗口(如不同标签页、iframe)之间的通信,通过
window.postMessage
发送消息,接收方通过message
事件监听并处理,是窗口间跨域交互的标准方案; - WebSocket:WebSocket 协议本身不受同源策略限制,只要后端允许,前端可直接与跨域的 WebSocket 服务建立连接(握手阶段需通过
Origin
头验证); - CORS 预检请求:对于复杂请求(如带自定义头、PUT/DELETE 方法),浏览器会先发送 OPTIONS 预检请求,验证服务器是否允许跨域,通过后才发送实际请求(属于 CORS 的细节补充)。
总结
跨域的核心是浏览器的同源策略,解决跨域的方案需根据场景选择:
- 前后端接口交互:优先使用CORS(标准、安全、灵活);
- 开发环境调试:优先使用代理服务器(简单高效);
- 窗口间跨域通信:使用window.postMessage;
- 极旧浏览器兼容:可考虑JSONP(不推荐)。
理解跨域的本质后,可根据项目需求和环境选择最合适的方案。
6:浏览器“空闲时间”(Idle Time)
指浏览器当前没有紧急任务需要处理的状态,通常是在完成了关键渲染路径任务(如 HTML 解析、CSS 计算、布局、绘制、JavaScript 执行等)后,处于等待下一个任务(如用户输入、定时器触发、网络请求响应等)的间隙。
一、浏览器 “空闲时间”(Idle Time)的核心原理
浏览器的主线程(Main Thread)负责处理关键任务:HTML 解析、CSS 计算、布局(Layout)、绘制(Paint)、合成(Composite)以及 JavaScript 执行等。这些任务直接影响页面的渲染和交互响应速度,优先级极高。
“空闲时间” 指的是主线程完成当前高优先级任务后,在等待下一个任务(如用户输入、定时器触发、网络请求响应等)到来前的间隙状态。此时主线程资源暂时闲置,可用于处理低优先级任务。
- 本质:浏览器主线程的 “碎片化空闲窗口”,由浏览器根据任务队列动态分配。
- 特点:并非 “完全空闲”,而是 “无高优先级任务待处理”,若有紧急任务(如用户点击),空闲时间会被中断,优先处理紧急任务。
二、核心特点补充
-
时间长度动态变化
- 空闲时间的时长不固定,通常在 几毫秒到数百毫秒 之间(最长不超过浏览器设定的上限,如 50ms,避免影响下一个高优先级任务)。
- 页面状态直接影响空闲时间:
- 静态页面(如博客文章):交互少、任务少,空闲时间较长且频繁。
- 动态交互页面(如游戏、实时数据看板):任务密集(如频繁的 JS 计算、动画),空闲时间短且碎片化。
-
低优先级任务的 “安全执行窗口”
浏览器会利用空闲时间处理非紧急任务,避免这些任务阻塞主线程(导致页面卡顿、响应延迟),典型场景包括:
- 非关键资源加载(如预加载下一页图片、低优先级脚本)。
- 数据持久化(如将内存中的临时数据同步到
localStorage
或IndexedDB
)。 - 性能监控数据上报(如页面加载性能、用户行为统计,非实时性数据)。
- JavaScript 引擎的垃圾回收(部分浏览器会在空闲时触发,减少对主线程的阻塞)。
- 开发者通过
requestIdleCallback
注册的自定义低优先级任务(如日志整理、数据预处理)。
三、关键 API:requestIdleCallback
开发者可通过浏览器提供的 requestIdleCallback
API 主动利用空闲时间执行任务,其工作原理如下:
- 基本用法
// 注册空闲时执行的任务
const taskId = requestIdleCallback((deadline) => {
// deadline对象包含当前空闲时间信息
// deadline.timeRemaining():返回当前空闲时间的剩余毫秒数(动态减少)
// deadline.didTimeout:布尔值,标识任务是否因超时而执行(若设置了timeout参数)
// 在空闲时间内尽可能完成任务
while (deadline.timeRemaining() > 0 && 还有任务要执行) {
执行单个子任务;
}
// 若任务未完成,可再次注册回调,等待下一次空闲时间
if (还有任务未完成) {
requestIdleCallback(同样的回调函数);
}}, { timeout: 1000 }); // 可选参数:若1000ms内未触发空闲时间,强制执行任务(避免任务永远不执行)
// 取消已注册的任务(若不再需要)
cancelIdleCallback(taskId);
-
- 任务可中断:若空闲时间耗尽(
timeRemaining()
返回 0)或有紧急任务到来,回调函数会被中断,确保不影响主线程响应。 - 非实时性:任务执行时间不确定(依赖空闲时间是否出现),因此不能用于实时性要求高的场景(如用户输入反馈、动画帧更新)。
- 任务可中断:若空闲时间耗尽(
四、注意事项
- 避免滥用空闲时间
- 不要在空闲时间执行耗时任务(如大量循环计算),即使时间允许,也可能导致后续紧急任务响应延迟。
- 优先通过 Web Worker 处理 CPU 密集型任务,而非依赖
requestIdleCallback
(Web Worker 不阻塞主线程,更适合 heavy task)。
- 兼容性与降级方案
requestIdleCallback
兼容性较好(支持现代浏览器,IE 不支持),如需兼容旧环境,可通过setTimeout
模拟(但精度较低)。
- 与其他 “延迟执行” API 的区别
setTimeout/setInterval
:按时间触发,不考虑主线程是否繁忙,可能在高优先级任务执行时插入,导致阻塞。requestAnimationFrame
:与浏览器重绘周期同步(用于动画),优先级高于空闲时间任务。requestIdleCallback
:完全依赖主线程空闲状态,优先级最低,是 “被动触发” 的延迟执行。
五、总结
浏览器 “空闲时间” 是主线程在高优先级任务间隙的资源闲置状态,其核心价值是在不影响页面响应速度的前提下,安全处理低优先级任务。开发者可通过requestIdleCallback
主动利用这一机制,优化页面性能(如减少主线程阻塞、提升交互流畅度),尤其适合处理非实时性、低紧急度的任务。
7:浏览器"一帧"(Frame)
一帧的核心是 “在固定时间内完成从输入处理到屏幕渲染的全流程”,各步骤环环相扣,任何一步耗时过长都会导致卡顿。理解帧的执行逻辑,是前端性能优化(如减少重排重绘、合理使用 RAF/RIC、图层管理)的基础。
一、浏览器 “一帧(Frame)” 的定义
“一帧” 是浏览器完成一次完整渲染更新的周期,对应屏幕的一次刷新。其时间长度由屏幕刷新率决定:
- 主流屏幕刷新率为 60Hz(每秒刷新 60 次),因此每帧约 16.6ms(1000ms/60≈16.6ms);
- 高刷新率屏幕(如 120Hz)每帧约 8.3ms,需更高效的渲染流程才能保证流畅。
核心意义:一帧是浏览器维持页面流畅交互的最小时间单位。若一帧内的任务耗时超过帧长,会导致 “掉帧”,用户会感受到卡顿(如滚动不流畅、动画延迟)。
二、一帧内的核心任务与流程
每帧的任务需按固定顺序执行,确保渲染结果及时呈现。完整流程如下:
- 处理输入事件(Input Handling) 优先响应用户输入(如点击、触摸、滚动),浏览器会将输入事件排队,在帧开始时优先处理(避免输入延迟)。例如:滚动事件需要即时计算新的视图位置,否则会出现 “滚动滞后”。
- 执行 JavaScript 代码(JS Execution) 处理当前帧内的 JS 任务(如定时器回调、网络请求回调、事件处理函数等)。
- 若 JS 执行时间过长(超过 16.6ms),会阻塞后续渲染步骤,直接导致掉帧。
- 因此,需避免在主线程执行复杂计算(可通过 Web Worker 转移到后台线程)。
- 执行 RequestAnimationFrame(RAF)回调
requestAnimationFrame
是浏览器提供的 API,用于在下一帧渲染前执行 JS 代码(如动画更新)。- 它的回调会在 “样式计算” 前触发,确保 JS 修改的 DOM/CSS 能在当前帧内被渲染。
- 优势:避免了定时器(setTimeout)可能导致的帧对齐问题,保证动画与屏幕刷新同步。
- 计算 CSS 样式(Style Calculation) 浏览器根据 CSS 选择器计算每个 DOM 元素的最终样式(如
color
、font-size
)。- 若 DOM 结构复杂(如 thousands of elements),样式计算可能成为性能瓶颈。
- 页面布局(Layout,又称 “重排”) 根据元素样式计算其在页面中的几何位置和大小(如
width
、height
、left
)。- 布局是 “流式计算”:一个元素的位置变化可能触发其他元素的布局(如父元素尺寸改变影响子元素),因此布局成本通常较高。
- 注意:并非所有样式修改都会触发布局(如仅修改
color
不会影响布局)。
- 绘制(Paint,又称 “重绘”) 将元素的视觉效果(如颜色、阴影、背景)绘制到图层的像素点上。
- 绘制可针对部分区域(“局部重绘”),但大面积绘制(如全屏背景色修改)仍会消耗性能。
- 与布局的区别:布局关注 “位置大小”,绘制关注 “视觉样式”。
- 合成图层(Composite) 浏览器将页面分割为多个 “图层”(如视频、Canvas、固定定位元素),分别绘制后再合并成最终屏幕图像。
- 优势:图层间的变化互不影响(如滚动时仅移动内容图层,无需重绘整个页面),是现代浏览器性能优化的核心手段。
- 最终合成的图像会提交给 GPU 渲染,显示在屏幕上。
- 执行 RequestIdleCallback(RIC)回调(可选)
requestIdleCallback
的回调会在当前帧所有任务完成后,且存在剩余时间时执行(若帧内任务已耗尽 16.6ms,则不执行)。- 用于执行低优先级任务(如日志上报、本地存储同步),避免阻塞关键渲染流程。
- 限制:单次回调执行时间建议不超过 50ms(避免影响下一帧),且需处理 “超时” 逻辑(通过
timeout
参数设置)。
三、常见误区修正
- “重绘” 与 “回流” 的顺序 正确顺序是:样式计算 → 布局(回流) → 绘制(重绘),即先确定样式,再计算位置,最后绘制视觉效果。
- RIC 的执行时机 RIC 并非 “一帧内的固定步骤”,而是 “帧空闲时的可选任务”。若当前帧任务耗时超过 16.6ms(如 JS 执行过久),RIC 会被推迟到后续空闲帧,而非阻塞下一帧渲染。
- 图层合成的作用 合成是渲染的最后一步,依赖于 GPU 加速,能极大减少布局和绘制的频率(如将动画元素放入独立图层,避免频繁重排重绘)。
四、总结
一帧的核心是 “在固定时间内完成从输入处理到屏幕渲染的全流程”,各步骤环环相扣,任何一步耗时过长都会导致卡顿。理解帧的执行逻辑,是前端性能优化(如减少重排重绘、合理使用 RAF/RIC、图层管理)的基础。
8:script标签中“async”和 “defer”的区别
在 HTML 中,
<script>
标签的async
和defer
属性都用于控制 JavaScript 脚本的加载与执行时机,但它们的行为模式和适用场景有本质区别
通过合理使用
async
和defer
,可以显著优化页面加载性能,避免 JavaScript 阻塞关键渲染路径。
一、基本语法与默认行为:
- 默认情况(无
async
/defer
):浏览器解析 HTML 时遇到<script>
标签,会立即停止解析,优先下载并执行脚本,执行完成后才继续解析后续 HTML
<script src="script.js"></script> <!-- 阻塞HTML解析 -->
defer
属性:告诉浏览器异步下载脚本,但延迟执行(直到 HTML 解析完成)
<script defer src="script.js"></script>
async
属性:告诉浏览器异步下载脚本,并在下载完成后立即执行(可能中断 HTML 解析)
<script async src="script.js"></script>
二、执行流程对比:
- 无
async
/defer
-
- HTML解析 → 遇到script → 暂停解析 → 下载脚本 → 执行脚本 → 继续解析HTML
- 问题:若脚本体积大(如外部库),会导致页面白屏时间延长。
defer
- HTML解析 → 遇到script(异步下载) → 继续解析HTML → HTML解析完成 → 按顺序执行defer脚本 → 触发DOMContentLoaded
- 关键点:
- 脚本下载与 HTML 解析并行。
- 所有
defer
脚本在DOMContentLoaded
事件前按顺序执行。 - 适合需要操作完整 DOM 的脚本(如初始化代码)
async
- HTML解析 → 遇到script(异步下载) → 继续解析HTML → 脚本下载完成 → 立即执行(可能中断HTML解析) → 继续解析HTML
- 关键点:
-
-
- 脚本下载与 HTML 解析并行。
- 脚本执行时机不确定(谁先下载完谁先执行)。
- 不保证脚本执行顺序,可能影响依赖关系(如 A 依赖 B,但 B 可能后执行)。
- 适合与其他脚本无依赖关系的独立功能(如第三方广告、埋点统计)。
-
三、最佳实践场景:
- 使用
defer
的场景:- 外部脚本需要操作 DOM(如操作表单、绑定事件)。
- 多个脚本有依赖关系(如先加载 jQuery,再加载插件)。
- 使用
async
的场景:- 独立功能脚本(如 Google Analytics、广告代码)。
- 脚本执行顺序不重要,且不依赖 DOM 完整加载。
- 避免混用:
async
和defer
不要同时使用,浏览器可能忽略defer
,仅保留async
行为。
9:DOM事件流
在浏览器原理中,DOM 事件流(DOM Event Flow)描述了浏览器处理 DOM 事件时,事件在 DOM 树中传播的完整过程。它是浏览器实现事件机制的核心逻辑,决定了事件从触发到被处理的路径和顺序。
DOM 事件流的核心是 “先捕获、再目标、后冒泡” 的传播机制,它让事件能够在 DOM 树中有序传递,既保证了事件的精准触发,又通过冒泡阶段实现了灵活的事件委托
一、DOM 事件流的三个阶段
- 捕获阶段(Capture Phase)
- 定义:事件从最顶层的根节点(
document
)开始,沿着 DOM 树向下传播,依次经过父节点,直到到达目标元素的直接父节点。 - 作用:给上层节点提供提前捕获事件的机会(较少直接使用,通常用于事件委托的底层逻辑)。
- 示例:若点击一个
<button>
,捕获阶段的路径为:document → html → body → ... → <button>的父节点
- 定义:事件从最顶层的根节点(
- 目标阶段(Target Phase)
- 定义:事件到达实际触发事件的目标元素(如被点击的
<button>
),此时目标元素上的事件处理函数会被执行。 - 注意:目标阶段属于事件流的 “终点”,但事件处理函数的执行顺序可能受注册方式影响(先注册的先执行)。
- 定义:事件到达实际触发事件的目标元素(如被点击的
- 冒泡阶段(Bubbling Phase)
- 定义:事件从目标元素开始,沿着 DOM 树向上传播,依次经过父节点,直到回到最顶层的根节点(
document
)。 - 作用:允许上层节点 “感知” 到子节点的事件,是事件委托(Event Delegation)的核心原理。
- 示例:点击
<button>
后,冒泡阶段的路径为:<button> → 父节点 → ... → body → html → document
- 定义:事件从目标元素开始,沿着 DOM 树向上传播,依次经过父节点,直到回到最顶层的根节点(
二、事件流的传播路径
DOM 结构如下:
<div id="grandparent">
<div id="parent">
<button id="child">点击我</button>
</div>
</div>
当点击`<button id="child">`时,事件流的完整路径为:
**捕获阶段**:document → html → body → grandparent → parent
**目标阶段**:child(触发按钮自身的事件)
**冒泡阶段**:child → parent → grandparent → body → html → document
三、控制事件在各阶段的触发
通过addEventListener
的第三个参数(useCapture
)可以指定事件处理函数在哪个阶段执行:
addEventListener('click', handler, true)
:在捕获阶段触发处理函数。addEventListener('click', handler, false)
(默认):在冒泡阶段触发处理函数。
四、关键特性与应用
- 事件委托(Event Delegation)
- 利用冒泡阶段的特性,将子元素的事件处理逻辑委托给父元素,减少事件监听的数量,优化性能(尤其适合动态生成的元素)。
- 阻止事件冒泡
- 通过
event.stopPropagation()
可以阻止事件在冒泡阶段继续向上传播(但不会影响目标阶段的执行)。
- 通过
- 阻止默认行为
- 通过
event.preventDefault()
可以阻止事件的默认行为(如链接跳转、表单提交),但不会影响事件流的传播(与stopPropagation
功能不同)。
- 通过
五、常见误区
- 误区 1:所有事件都有冒泡阶段?
- 不是。部分事件没有冒泡阶段,如
focus
、blur
、load
、unload
等(可通过event.bubbles
属性判断事件是否支持冒泡)。
- 不是。部分事件没有冒泡阶段,如
- 误区 2:捕获阶段没用?
- 捕获阶段常用于特殊场景,例如:实现 “事件拦截”(在事件到达目标前提前处理),或修复某些冒泡机制不适用的场景(如
focusin
/focusout
的捕获行为)。
- 捕获阶段常用于特殊场景,例如:实现 “事件拦截”(在事件到达目标前提前处理),或修复某些冒泡机制不适用的场景(如
10:垃圾回收机制GC(Garbage Collection)
JavaScript 的 GC 机制通过 “可达性” 判断垃圾,核心算法经历了从 “引用计数” 到 “标记 - 清除 / 整理” 的演进
一、垃圾回收的核心概念
1. 什么是垃圾回收(GC)?
- 定义:GC 是 JavaScript 引擎自动管理内存的机制,负责识别并释放 “不再被使用的内存空间”(即 “垃圾”),避免内存泄漏和程序崩溃。
- 本质:JS 作为高级语言,开发者无需手动分配 / 释放内存JavaScript 的 GC 机制通过 “可达性” 判断垃圾,核心算法经历了从 “引用计数” 到 “标记 - 清除 / 整理” 的演进(如 C/C++ 的
malloc
/free
),由引擎自动完成内存生命周期管理。 - 必要性:程序运行会持续分配内存(如创建对象、函数、数组),若不及时释放无用内存,会导致内存占用飙升,最终引发页面卡顿甚至进程崩溃。
2. 什么是 “垃圾”?
- 垃圾的判定标准:不可达的对象—— 即无法通过任何 “可达路径” 访问的对象。
- 可达路径:从 “根对象”(Root)出发能访问到的对象。根对象包括:全局对象(如
window
)、当前执行上下文的变量(如函数局部变量)、DOM 节点(已挂载到 DOM 树的元素)等。 - 例:函数执行完毕后,其局部变量若未被外部引用,则成为垃圾;被删除的 DOM 节点若仍被 JS 变量引用,则不会被回收(常见内存泄漏场景)。
- 可达路径:从 “根对象”(Root)出发能访问到的对象。根对象包括:全局对象(如
二、垃圾回收的核心算法
1. 标记 - 清除算法(Mark-and-Sweep)
目前主流 JS 引擎(如 V8)的基础算法,分为 “标记” 和 “清除” 两个阶段:
- 标记阶段: 从根对象出发,递归遍历所有可达对象,为其打上 “活动对象” 标记(如通过二进制位标记)。未被标记的对象即为 “垃圾”。
- 清除阶段: 遍历内存空间,释放所有未标记对象(垃圾)的内存,并将释放的内存加入 “空闲列表”,供后续分配使用。
- 优点: 实现简单,能有效回收循环引用的对象(如 A 引用 B,B 引用 A,但两者均不可达根对象时,会被标记为垃圾)。
- 缺点:
- 内存碎片化:释放的内存块分散在内存中,导致后续分配大对象时,可能因找不到连续的足够空间而失败。
- 全停顿(Stop-The-World):标记和清除过程会阻塞 JS 执行(单线程特性),若内存对象过多,可能导致页面卡顿。
2. 标记 - 整理算法(Mark-and-Compact)
为解决标记 - 清除的 “内存碎片化” 问题而优化的算法,在清除前增加 “整理” 步骤:
- 整理阶段: 清除未标记对象前,将所有活动对象向内存一端移动,使它们紧凑排列,释放的内存形成连续的块。
- 适用场景: V8 引擎的 “老生代” 内存回收(对象存活时间长,内存碎片化影响更大)。
3. 引用计数算法(Reference Counting)
早期浏览器使用的算法,现已被淘汰,核心逻辑是 “通过引用次数判断对象是否为垃圾”:
- 原理:
- 每个对象维护一个 “引用计数器”,被引用时 + 1,引用失效时 - 1。
- 当计数器为 0 时,对象被视为垃圾,立即回收。
- 致命缺陷:
- 无法处理循环引用:若 A 和 B 相互引用(A.ref = B,B.ref = A),即使两者均不可达根对象,计数器仍为 1,永远不会被回收,导致内存泄漏。
- 额外的性能开销:每次引用变化都需更新计数器,影响执行效率。
三、V8 引擎的垃圾回收优化(分代回收机制)
V8 引擎(Chrome/Node.js 的 JS 引擎)针对 “对象存活时间差异大” 的特点,采用分代回收策略,将内存分为 “新生代” 和 “老生代”,分别使用不同算法优化效率。
- 新生代(Young Generation)
- 存储对象:存活时间短的对象(如函数局部变量、临时创建的对象)。
- 内存大小:容量小(通常 1-8MB),分为两个等大的空间:
From Space
(使用区)和To Space
(空闲区)。 - 回收算法:Scavenge 算法(基于复制的 Cheney 算法):
- 新对象优先分配到
From Space
。 - 当
From Space
快满时,触发 GC: -
- 标记
From Space
中的活动对象。 - 将活动对象复制到
To Space
(按顺序排列,避免碎片化)。 - 清空
From Space
,交换From
和To
的角色(To
变为新的使用区)。
- 标记
- 晋升机制:若对象在多次回收中存活(通常 2 次),或
To Space
中活动对象占比超过 25%,则将对象 “晋升” 到老生代(因长期存活的对象复制成本高)。
- 新对象优先分配到
- 优点:回收速度快(复制少量存活对象),适合短期对象。
- 老生代(Old Generation)
- 存储对象:存活时间长的对象(如全局变量、长期缓存的数据)。
- 内存大小:容量大(远超过新生代),无固定分区。
- 回收算法:标记 - 清除 + 标记 - 整理(结合增量标记、惰性清理优化):
- 标记阶段:从根对象出发,标记所有活动对象(采用 “三色标记法” 优化)。
- 清理阶段:释放未标记对象的内存(惰性清理:不一次性清理所有垃圾,按需逐步释放,减少阻塞)。
- 整理阶段: 移动活动对象,使内存连续(解决碎片化)。
- 关键优化:
-
- 增量标记(Incremental Marking):将标记过程拆分为多个小步骤,穿插 JS 执行(而非一次性完成),减少全停顿时间。
- 三色标记法:
-
- 白色:未标记对象(潜在垃圾)。
- 灰色:自身已标记,引用的子对象未标记。
- 黑色:自身及引用的子对象均已标记(活动对象)。
- 作用:支持增量标记的暂停与恢复(通过检查是否有灰色对象判断标记是否完成)。
- 写屏障(Write Barrier):当增量标记期间,黑色对象引用白色对象时,强制将白色对象改为灰色,避免漏标(保证 “强三色不变性”)。
四、V8 对 “全停顿” 的优化
全停顿(Stop-The-World):GC 执行时会阻塞 JS 主线程(单线程特性),导致页面卡顿。
优化手段:
- 并行回收:标记和清理阶段使用多线程(V8 的后台线程)并行处理,减少主线程阻塞时间。
- 增量标记:拆分标记步骤,穿插 JS 执行,将单次停顿控制在毫秒级(用户无感知)。
- 并发回收:标记阶段主线程与 GC 线程同时运行(需写屏障保证一致性),几乎无停顿。
五、常见内存泄漏场景(GC 无法自动回收的情况)
即使有 GC 机制,若代码不当仍会导致内存泄漏,常见场景:
- 意外的全局变量:未声明的变量(如
a = 1
)会挂载到window
,成为根对象引用,永远不被回收。 - 闭包未释放:闭包持有外部函数的变量,若闭包长期存在(如被全局变量引用),变量不会被回收。
- DOM 引用残留:删除 DOM 节点后,JS 变量仍引用该节点(如
let el = document.getElementById('id'); el.remove();
,el
仍引用节点)。 - 定时器 / 事件监听未清除:废弃的
setInterval
或addEventListener
未被clear
,其回调函数及引用的变量不会被回收。
六、总结
- JavaScript 的 GC 机制通过 “
可达性
” 判断垃圾,核心算法经历了从 “引用计数
” 到 “标记 - 清除 / 整理
” 的演进。V8 引擎通过 “分代回收
”(新生代 Scavenge、老生代标记 - 整理)、增量标记、三色标记等优化,在保证回收效率的同时,最大限度减少对用户体验的影响。理解 GC 原理有助于编写更高效的代码,避免内存泄漏,提升页面性能。
最后
OK 以上就是本期关于浏览器原理的笔记内容啦。需要说明的是,这并不是浏览器原理的全部知识点,但已经覆盖了日常开发和面试中的绝大多数核心场景。大家可以结合这些知识点,在实际开发中多思考、多实践,加深理解。
这篇笔记虽然是个人复习整理,但也花了不少时间梳理逻辑,如果对你有帮助,记得点赞收藏支持一下!大家反馈不错的话,后续会继续分享 JS 基础的内容,包括JS运行机制、事件循环、闭包等核心知识点,学前端的朋友记得关注,咱们下期见。
更多
💻 Vue3 多端统一开发框架:vue3-multi-platform
📊 HuggingFaceAI论文智能分析系统:ai-paper-analyzer