性能优化
一、优化DOM操作
二、优化CSS渲染效率(减少回流和重绘)
回流(Reflow)-- 浏览器的大工程 :DOM 元素布局变化(如尺寸、位置、显示 / 隐藏),会触发重新计算布局,成本高。
重绘(Repaint)-- 浏览器的小工程:元素样式变化(如颜色、背景),不影响布局,成本低于回流。
回流必然导致重绘
为什么回流比重绘更消耗性能?来看看浏览器渲染页面的完整过程:
- 解析HTML,构建DOM树
- 解析CSS,构建CSSOM树
- 将DOM树和CSSOM树结合,形成渲染树(RenderTree)
- 布局(Layout):计算每个节点在屏幕上的确切位置和大小
- 绘制(Paint):将计算好的节点绘制到屏幕上
回流会触发布局和绘制两个步骤,而重绘只触发绘制步骤。所以回流的代价更高!
触发回流的方式总结:
1.页面首次渲染
2.浏览器窗口的大小改变
3.元素尺寸或位置发生改变(避免频繁操作样式)
4.元素内容的变化
5.display: none与display: block之间的切换 (使用visibility而非display)
三、优化JavaScript执行(避免阻塞主线程)
JS 执行与渲染共用主线程,长任务(>50ms)会阻塞渲染,导致页面卡顿。(可参考浏览器架构)
浏览器架构
进程和线程
- 进程(process)是操作系统分配资源的基本单位(如内存、CPU 时间片),拥有独立的内存空间,进程间相互隔离(通过 IPC 通信)。
- 线程(thread)是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
主要以Chrome为例,介绍浏览器的多进程架构。在Chrome中,主要的进程有4个:
- 浏览器进程 (Browser Process):负责浏览器的TAB的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作,比如网络请求和文件访问。(总管)
- 渲染进程 (Renderer Process):负责一个Tab内的显示相关的工作,也称渲染引擎。(页面渲染)
- 插件进程 (Plugin Process):负责控制网页使用到的插件(按需启动)
- GPU进程 (GPU Process):负责处理整个应用程序的GPU任务(图形加速)
线程:Renderer 进程内通过多线程协作完成渲染与交互,核心是 GUI 渲染线程(渲染)、JS 引擎线程(执行脚本)、事件触发线程(调度异步任务),其中 GUI 与 JS 线程的互斥是 “JS 阻塞页面渲染” 的根源。
在 HTML 中,
<script>标签的async和defer属性都用于控制 JavaScript 脚本的加载与执行时机,但它们的行为模式和适用场景有本质区别
HTML 解析器是单线程,遇到<script>标签会暂停解析(默认行为),等待 JS 下载 + 执行完成后再继续(因 JS 可能通过document.write修改 DOM)。
若<script>带async(下载完成后立即执行,顺序无关)或defer(下载完成后按顺序执行,DOM 解析完后触发),则不阻塞 HTML 解析。
通过合理使用async和defer,可以显著优化页面加载性能,避免 JavaScript 阻塞关键渲染路径。
- 浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的 script 标签,就会暂停解析,先发送网络请求获取该 JS 脚本的代码内容,然后让 JS 引擎执行该代码,当代码执行完毕后恢复解析。整个过程如下图所示:
- 当浏览器遇到带有 async 属性的 script 时,请求该脚本的网络请求是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析,图示如下:
- 当然,如果在 JS 脚本请求回来之前,HTML 已经解析完毕了,那就啥事没有,立即执行 JS 代码,如下图所示:
- 当浏览器遇到带有 defer 属性的 script 时,获取该脚本的网络请求也是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器不会暂停解析并执行 JS 代码,而是等待 HTML 解析完毕再执行 JS 代码,图示如下:
未完待续...
参考:
juejin.cn/post/684490…
juejin.cn/post/752676…
juejin.cn/post/689462…