客户端从服务器获取到需要渲染的 HTML 页面后,会开启一个 GUI 渲染线程,自上而下的解析代码,最后绘制出页面。
自上而下解析代码的过程是同步的,但是加载代码里的资源有些是异步的:
- 加载 css 资源
- 遇到 <style></style> 内嵌样式,
同步
交给 GUI 渲染线程解析。 - 遇到 外链样式,
异步
开辟一个 http 网络请求线程请求资源,GUI 渲染线程不会等待请求的结果,直接继续向下解析渲染,GUI 渲染线程等待所有的同步操作都完成后,再来解析请求回来的资源。 - 遇到
@import
导入的样式,同步
开辟一个 http 网路请求线程,但是在请求结果返回之前,GUI 渲染线程会被阻塞
- 加载 js 资源
script
标签请求 js 资源,默认都是同步
的,GUI 渲染会阻塞等待请求回来的资源- async 属性,GUI 渲染线程不会阻塞,而是继续向下解析渲染,等资源请求回来后,
马上
解析请求回来的资源 - defer 属性,GUI 渲染线程不会阻塞,而是继续向下解析渲染,等资源请求回来后,GUI 渲染线程不会马上解析请求回来的资源,而是等待所有的同步操作都完成后,再解析请求回来的资源
总结:async 有可能会改变资源解析的顺序(先请求的资源后返回结果)
- 加载 img 和音视频资源
异步
GUI 渲染线程会等待资源请求回来再向下渲染,所以是阻塞的
DOM 树 -> CSS 树 -> attachment -> layout -> layers -> paint
- DOM tree: 解析 HTML 生成 DOM 树(document)
- CSSOM tree: 解析 CSS 生成 CSS 树(document.stylesheets)
- Render tree: 将 DOM 树 和 CSS 树合并生成 Render 树(既包含结构也包含样式,display: none 的元素不包含)
- Layout: 计算每个元素渲染到页面的具体位置
- Layers: 通过布局树进行分层,生成图层树
- Painting: 根据每个图层的绘制步骤,绘制页面
- Display
JS 触发视觉变化 -> 样式计算 -> 布局(重排/回流) -> 重绘 -> 合成
- 重排一定会导致重绘,重绘不一定会重排
- 重排/回流:新增删除元素,修改元素的大小,改变元素的位置,获取元素的位置相关的信息,视口大小的变化
- 重绘:元素样式的改变不影响元素在文档流中的位置(color, background-color, opacity, visibility, transform)
- 读写分离,按需缓存布局信息,避免频繁读取
- 脱离文档流
- 合理利用特殊样式属性(如 transform: translateZ(0) 或者 will-change),将渲染层提升为合成层,开启 GPU 加速,提高页面性能
- 尽量使用 css3 动画
- 渲染时给图片增加固定宽高
- 判断是关键词搜索还是站点的访问
- URL 解析
- 缓存检查
- DNS 解析
- TCP 三次握手建立连接通过,如果是 https 还要经历 ssl 协商
- 数据传输
- TCP 四次挥手
- 渲染页面
- 利用缓存(强缓存和协商缓存),数据采用 localStorage 缓存
现代的项目都是经 webpack 打包的,文件名都是带有 hash 的,可以使用强缓存,因为当文件内容变化后,打包出来的文件名也会变成新的,所以加载新的文件,自然旧的缓存不会产生新的影响
- CDN 服务器分地域分布
css、图片等不经常改变的资源可以放在 CDN 上,客户端用户根据位置从就近的 CDN 上获取
- DNS 优化
使用 dns-prefetch 预解析域名
- 减少数据传输的大小
- 压缩资源(webpack 等)
- 服务器开启 gzip(请求 gzip 资源)
- 大数据量分批次处理(下拉刷新、分页等)
- 减少 http 请求次数
- 资源合并(雪碧图等)
- 小图片 base64 编码
- 字体图标
- 改变资源加载的优先级
- 懒加载
- prefetch
- preload
- 静态资源的优化
- 图片选择合适的格式
- HTML 语义化,尽量减少 html 的嵌套层级,减少使用 iframe
- css 选择器减少层级,使用外链 css 可以利用缓存,减少
@import
的使用,阻塞渲染 - 使用 async defer 异步加载 js
- 使用 HTTP2.0
- 图片优化
图片格式: - jpg
适合色彩丰富的照片、banner 图;不适合图形文字、图标(纹理边缘有锯齿感),不支持透明度 - png
适合纯色、透明、图标,支持半透明;不适合色彩丰富图片,因为无损存储会导致体积大 - gif
适合动画,可以动的图标;不支持半透明,不适合存储彩色图片 - webp
适合半透明图片,可以保证图片质量和较小体积;兼容性不好(谷歌推出的) - svg
比 jpg 体积更小,渲染成本过高,适合小且色彩单一的图标
图片优化: - 避免空 src 的图片 - 减小图片尺寸,节约用户流量 - img 标签设置 alt 属性,提升图片加载失败时的用户体验 - 原生的 loading: lazy 图片懒加载 - 不同环境下,加载不同尺寸和像素的图片 - 对于较大的图片可以考虑采用渐进式图片 - 采用 base64 减少图片请求 - 采用雪碧图
- HTML 优化
- 语义化 HTML,代码简洁清晰,利于搜索引擎,便于团队开发
- 提前声明字符编码,让浏览器快速确定如何渲染网页内容
- 减少 HTML 嵌套层级,减少 DOM 节点数量
- 删除多余的空格、空行、注释及无用的属性等
- HTML 减少 iframe 使用
- 避免使用 table 布局
- CSS 优化
- 减少伪类选择器、减少样式层数、减少使用通配符
- 避免使用 css 表达式
- 删除空行、注释、减少无意义的单温
- 使用外链 css,可以对 css 进行缓存
- 添加媒体字段,只加载有效的 css 文件
- 减少 @import 的使用
- JS 优化
- 通过 async、defer 异步加载文件
- 减少 DOM 操作,缓存访问过的元素
- 使用 webworker 解决程序阻塞问题
- IntersectionObserver
- 虚拟列表
- requestAnimationFrame、requestIdleCallback
- 尽量避免使用 eval,消耗时间久
- 使用事件委托,减少事件绑定个数(document.body 上做事件委托,需要使用
passive:true
选项来避免额外的问题) - 尽量使用 canvas 动画,css 动画
- 字体优化
- FOUT(Flash Of Unstyled Text)等待一段时间,如果没加载完成,先显示默认。加载完成后在进行切换
- FOIT(Flash Of Invisible Text)字体加载完毕后显示,加载超时降级系统字体(白屏)
浏览器在渲染的时候,会先解析 html,等待 head 里的 css 加载回来,然后去解析 css,然后渲染;如果 css 样式放在 body 底部,在样式之前的 dom 会渲染一次,然后等 body 底部的 css 样式加载完成后再来一次渲染(重绘)
js 代码会阻塞 html 解析和页面渲染(浏览器会等待 js 加载回来后,在开始继续渲染),但是 js 会等待它上面的 css 文件都加载完成再执行,以保证可以正确的操作元素的样式
- 进程
- 进程是 CPU 分配资源的最小单位
- 在同一个时间内,单个 CPU 只能执行一个任务,只能运行一个进程
- 如果有一个进程正在进行,其他进程就的暂停
- CPU 使用了时间片轮转的算法来实现多进程的调度
- 线程
- 线程是 CPU 调度的最小单位
- 一个进程可以包括多个线程,这些线程共享该进程的资源
- Browser 进程:主进程,负责浏览器界面显示,页面管理,其他子进程的创建和销毁等
- Render 进程:渲染进程,用于控制和处理选项卡周玲的网站内容显示(每个 tab 页都有一个渲染进程)
- GPU 进程:用于完成图像处理任务,同时还支持分解成多个进程进行处理(当 GPU 开启硬件加速时才会开启)
- NPAPI 插件进程:管理浏览器中的各个插件
- 网络进程:处理网络请求、文件访问等操作
- Storage 进程
- Audio 进程
Network、Storage、Audio 进程都是现代浏览器独立出来的
浏览器可以打开多个页签,如果浏览器是单进程的,某个页签的页面崩溃了,那么其他页面也就无法访问了
多进程的目的:
- 职责分离,故障范围小(比如一个页面的崩溃不会影响到其他的页面)
- 隔离性(每个页面之间是独立的进程,相互之间不受影响)
- 性能
浏览器进程的线程如下:
- UI 线程:用于绘制浏览器的按钮和输入字段
现代浏览器已经将 Network、Storage 独立出新的进程了
渲染进程的线程如下:
- GUI 渲染线程:渲染、布局和绘制页面
- JS 引擎线程:解析执行 JS 脚本
- 事件触发线程:当事件满足触发条件时,把事件放入到 JS 引擎任务队列中执行
- 定时器触发线程:定时任务并不是由 JS 引擎线程计时的,而是由定时器触发线程来计时的,计时完毕后会通知事件触发线程
GUI 渲染线程和 JS 引擎线程是互斥的,同一时间两个线程最多只会有一个线程在工作
输入 URL,到浏览器渲染页面这个过程中,浏览器进程和渲染进程会进行很多的交互逻辑,最终页面才得以将页面内容渲染到页面上
gzip 压缩原理:
客户端请求头 Accept-Encoding 声明浏览器支持的压缩方式,服务端配置启用压缩,压缩的文件类型,压缩方式。当客户端请求到服务端的时候,服务器解析请求头,如果客户端支持 gzip 压缩,响应时对请求的资源进行压缩并返回给客户端,浏览器按照自己的方式解析,在 http 响应头,我们可以看到 Content-Encoding: gzip,这是指服务端使用了 gzip 压缩。
前端代码经 webpack 打包时,使用 compression-webpack-plugin 进行 gzip 压缩
哪些文件适合 gzip 压缩呢来优化性能呢?
html, css, js。简单来说,gzip 压缩是在一个文本文件中找出类似的字符串,并临时替换它们,是整个文件变小
gzip 压缩的缺点:
jpeg 这类文件用 gzip 压缩的不够好。客户端解析也占据了一些时间,但是随着硬件性能不断的提高,这些问题正在不断的弱化。