前端性能优化

图像优化

  1. 图像优化的思想:压缩
  2. 压缩的两个思路:
    (1)减少对某种颜色的编码数来减小文件的大小
    (2)损失部分源文件信息 以达到近似的效果 使得压缩后的文件尺寸更小
  3. 能不用图像就不用图像 若一定要用图像:
    (1)如果在不同的页面或不同的交互状态下 图像需要呈现不同的的效果(边角的裁切、渐变、阴影等)可以用CSS来将同一张图片处理成所需的不同效果
    (2)如果图像上要显示字 用代码的形式把字加到图片上面 而不是传递一张带字的图像 因为带字的图像更大 带字的图片给用户带来的体验不好(字不能被搜索到、不能被选择等) 在高分辨率设备上显示效果不好

矢量图和位图

  1. 图像文件分为矢量图和位图
  2. 矢量图中的图形元素是一个对象 适用于文本、控件图标、二维码等简单的图形 缩放后依旧清晰 iconfont就是矢量图
  3. 位图是通过对一个矩阵中的栅格(像素块)进行编码来表示图像。每个栅格(像素块)只能编码表示一个特定的颜色。组成图像的栅格(像素块)像素点越多且每个像素点所能表示的颜色范围越广 则位图图像越逼真 适用于复杂的图片
    (1)每个栅格(像素块)存储的是图像局部的RGBA信息 浏览器会为每个颜色通道分配1个字节的存储空间 一个像素点有4个通道就是4个字节 图像的大小和包含的像素数成正比 包含的像素数越多 效果越好 图片越大
    (2)图像的尺寸在长宽两个维度上同时增大 像素数成倍增加
    在这里插入图片描述

分辨率

我们为图像设置的长height和宽width在不同设备屏幕上显示的效果会有区别 这是因为屏幕分辨率和图像分辨率

  1. 图像分辨率表示该图像包含的真实像素值 如200像素*200像素代表图像长宽各200个像素点
  2. 设备分辨率表示显示器屏幕能显示的最大像素值 设备分辨率越大 显示的图像越清晰
  3. 高设备分辨率更适合矢量图 因为他不会因放大而失真 高图像分辨率的位图能更好的利用高设备分辨率
  4. img标签有一个srcset属性 可以为不同设备提供不同分辨率的图像
<img src="photo.jpg" srcset="photo@2x.jpg 2x, photo@3x.jpg 3x, photo@4x.jpg 4x" alt="photo"> 
  1. picture标签可以在多图像文件选择时 进行更多的控制
<picture>
	<source media="(min-width: 800px)" srcset="photo.jpg, photo-2x.jpg 2x">
	<source media="(min-width: 450px)" srcset="photo-s.jpg, photo-s-2x.jpg 2x">
	<img src="photo.jpg">
</picture>

压缩的有损和无损

首先确定业务需要展示的图像的颜色阶数、图像显示的分辨率及清晰程度 若获取的图像源文件的相应参数指标过高 可适当进行有损压缩 通过降低源文件图像质量来降低图像文件大小

图像格式

GIF包含的颜色阶数少 但能呈现动画
JPEG不支持透明度 但图像文件的压缩比高
PNG文件尺寸较大 但支持透明且色彩表现力强

位图

JPEG
  1. 有损压缩 通过去除相关冗余图像和色彩数据等方式来实现
  2. 适用于背景图 轮播图 商品banner图 照片等
  3. 不支持透明度
压缩模式
  1. 基线模式:当网络连接慢或不稳定时 自上而下加载图像
  2. 渐进式模式:将图像文件分为多次扫描 首先展示一个低质量模糊的图像 随着扫描到的图像信息不断增多 图像清晰度不断提升

优点:在网络连接较慢时 可以快速加载出图像质量比较模糊的预览版本 用户可以先了解图像的大致内容 然后再决定是否花时间等待完整图像的加载
缺点:渐进式的解码速度比基线慢 因为它增加了重复的检索开销

  1. 具体使用哪种模式要具体情况具体分析 小图像就不适合用渐进式
  2. 可以通过gulp将JPEG图像转为渐进式JPEG
  3. 其他压缩模式(新出来的 很不错)
    (1)MozJPEG:引入对逐行扫描的优化及栅格量化的功能
    (2)Guetzli:找到人眼感知上无法区分的最小体积的JPEG
  4. 使用建议
    (1)使用外部工具找到图像的最佳表现质量后 再用MozJPEG压缩
    (2)Guetzli可以获得更高质量的图像但压缩速度相对较慢
  5. 进行JPEG图像优化时 主要关注业务可接受的最低图像质量
GIF
  1. 仅支持256个颜色 不适合用来展示照片 适合展示图标或logo
  2. 目前只有需要用到动画时才会有GIF
  3. 单帧GIF转为PNG:可以使用npm引入ImageMagick工具来检查GIF图像是单帧还是多帧 若时单帧会返回一个GIF字符串 若是多帧会返回多帧信息 如果是单帧 使用ImageMagick将GIF转化为更适合展示图形的PNG格式(PNG-8)
const im = require('imagemagick')
// 检查是否为动画
im.identfy(['-format', '%m', 'my.gif'], (err, output) => {
	if(err) throw err;
	// 通过output处理判断流程
})
// 将GIF转为PNG
im.convert(['my.gif', 'my.png'], (err, stdout) => {
	if(err) throw err;
	console.log('转化完成', stdout);
})
  1. 由于动画包含很多静态帧 并且每个静态帧图像上的内容在相邻的不同帧上通常没有太大差异 所以可以移除动画里连续帧中重复的像素信息 使用gifsicle实现:
    在这里插入图片描述
  2. 较长的GIF转为视频可以压缩文件大小:可以使用ffmpeg转化 相比视频 GIF解码过程也很耗时
PNG
  1. 无损压缩 高保真
  2. 支持透明度 对线条的处理更细腻 增强了色彩的表现力
  3. 常使用的PNG子类型:PNG-8、PNG-24、PNG-32等
    (1)PNG-8除了不支持动画 其他都和GIF相同 同时包含透明通道
    (2)PNG-24不包括透明通道 PNG-32包括
  4. 缺点:文件体积太大
  5. 优点:便于扩展 他将图像信息保存在“块”中 开发中可以通过添加自定义的“块”来增加额外的功能 浏览器可以使用pngcrush将多余的“块”删除压缩
在这里插入代码片
WebP
  1. WebP:以较高的视觉体验为前提 尽可能地降低有损压缩和无损压缩后的文件尺寸 支持透明度和动画 存在兼容性问题
  2. 由于WebP的有损压缩使用VP8视频关键帧编码 对较高质量(80-99)的图像编码来说 会比JPEG占用更多的计算资源 但在较低质量(0-50)时 仍有很大优势
  3. 使用
在这里插入代码片
  1. 兼容性处理
    (1)前端处理浏览器兼容性判断 通过window.navigator.user-Agent获取版本信息 根据兼容支持情况决定是否请求WebP图像 也可以使用picture标签来选择现实的图像格式 在picture标签中添加多个source标签 以及包含旧图像格式的img标签(写在WebP后面) 若浏览器不支持WebP格式而未能检测获取 最后也能通过img标签显示出旧图像格式
    在这里插入图片描述
    (2)服务器根据HTTP请求头的Accept字段来决定返回图像的文件格式 如果请求头中包含image/webp 则返回WebP格式图片 否则返回旧图像格式 这样维护性更强

矢量图

SVG
  1. 优点:由于文本文件的高压缩比 最后得到的图像文件体积会更小
  2. 缺点:由于显示器的本质时元素点构成位图 所以在渲染绘制矢量图时 会比位图的显示多一步光栅化 所以SVG渲染成本较高 并且学习成本较高
  3. 优化:
    (1)SVG尽量简洁 去除冗余信息 如注释 隐藏图层 元数据等
    (2)为了加快渲染 建议使用预定义的SVG形状代替自定义路径 这样会减少最终生成图像所包含标记的数量 预定义形状:circle标签 rect标签 line标签 polygon标签等
    (3)少用曲线
    (4)不要在SVG中嵌入位图
    (5)使用svgo优化SVG 它可以通过降低定义中的数字精度来缩小文件的尺寸
在这里插入代码片

(6)优化后使用gzip或brotli压缩

Base64

  1. Base64一种用于传输8为字节码的编码方式
  2. 一般展示图像的方法都是通过将图像文件的URL值传给img标签的src属性 当HTML解析到img标签时就会发起对该图像URL的网络请求 而Base64通过将代表图像的编码直接写入HTML或CSS中来实现图像的展示 浏览器会自动解析编码并展示图像 无须发起请求
    在这里插入图片描述
  3. 经 Base64编码的图像会膨胀3/4
  4. 适用于小图像
  5. 使用建议:图像文件尺寸是否很小、是否真的无法以雪碧图的形式引入、更新频率是否很低 避免使用 Base64时增加维护成本

文件格式选择策略

在这里插入图片描述

  1. 矢量图(SVG)缩放不失真且表示图标是文件尺寸小 所以适用于图标
  2. WebP首选 不兼容WebP时 动画用GIF 要求图片有较高分辨率来展示细节且需要透明度时用PNG 追求更高图像压缩比用JPEG 针对响应式场景 建议提供多张不同尺寸的图像供浏览器调用

使用建议

雪碧图/精灵图

  1. 雪碧图/精灵图:将多张小图标拼接成一张大图 有效减少HTTP请求数量以加速显示内容
  2. 应用场景:
    (1)图标时网站的静态图标 不随用户信息变化而变化
    (2)图标体积尽量小 不建议将大体积的图标拼接成大图 因为拼接后的大图体积更大 网络带宽的增加与请求完成所耗费时间的延长会完全淹没通过坚守HTTP请求次数所带来的性能提升
  3. 使用:通过background-image引入雪碧图的URL后 使用background-position(负值)来定位所需要的单个图标在雪碧图上的起始位置 配合height width来锁定图标的尺寸
  4. 在HTTP1环境下 雪碧图可以减少HTTP请求来提升性能 但是如果小图标变更 缓存的大图(雪碧图)就会失效 在HTTP2中 由于可以在一个HTTP连接上发起多次请求 所以加载单张图像文件(小图标)更好

Web字体

  1. 每个字型都是矢量图标 所以可以将字体和矢量图标打包在一起 以节省对图标资源的HTTP请求次数
  2. 通过@font-face声明使用的字体系列
  3. 子集:从字体文件中删除未使用的字符。未使用的字符通常是不使用的语言的字符,或者是网站不需要的特殊字符,但通常会嵌入字体文件中。通过子集可以减小Web字体的大小,将它们编码为base64,并将存储在本地。
  4. 通过@font-face和unicode-range属性可以定义使用的字体子集 unicode-range属性用来指定所需字体在@font-face中的子集范围 属性值有三个种形式:U+233(单一取值)、U+233-2ff(范围取值)、U+2??(通配符范围取值) 取值的含义是@font-face中的代码索引点 unicode-range属性存在兼容性问题
    在这里插入图片描述
  5. 字体文件预加载:默认情况下 构建渲染树签会阻塞字体文件的亲贵 导致部分文本渲染延迟 对此 我们可以使用<link rel="proload">预加载字体资源

display: none的使用

  1. 对于不展示的图像 避免在首屏时进行资源请求加载
    (1)在这里插入图片描述
    img1不显示 但是浏览器还是会去请求这张图片
    (2)在这里插入图片描述
    img2不显示 浏览器也不会去请求这张图片 因为CSS解析后发现父级使用display: none 再去计算子级的样式没什么意义 所以不会去下载子级div的背景图像
  2. 建议使用picture标签 或img标签的srcset属性进行响应式显示

加载优化

图像延迟加载

  1. 延迟加载:对于首屏之外的内容 尤其是图片和视频 一方面由于资源文件很大 若是全部加载完 耗时费力 容易阻塞渲染引起卡顿 另一方面就算加载完 用户也不一定会滚动屏幕浏览全部页面内容 如果首屏内容没有吸引住用户 那么很可能整个页面都会被关闭 所以在首次打开网站时 应尽量只加载首屏内容或包含的资源 而首屏之外设计的图片或视频 可以等用户滚动视窗浏览时再去加载
  2. src属性代表了CDN上的图片资源 当img标签的src属性有URL后 他就会立刻向该URL发起资源请求 所以IMG标签代表的时图片的占位符
  3. 所有未展示在页面视窗中的商品 其src属性值均使用了Base64的值 当页面发生滚动时 之前未出现在视窗中的商品出现在视窗中后 图片的真实URL会被替换到img标签的src属性上 进而发起资源请求

实现图片延迟加载

传统方式
  1. 通过监听scroll事件与resize事件 在事件的回调函数中 判断需要进行延迟加载的图片是否进入视窗区域

  2. HTML代码:在这里插入图片描述
    src:加载图片前的占位符图片 可以用Base64图片或低分辨率图片占位
    data-src:自定义属性 保存图片真实的URL

  3. JS实现逻辑:
    在这里插入图片描述
    (1)在文档的DOMContentLoadwd事件中 添加延迟加载处理逻辑 首先获取class属性名为“lazy”的所有img标签 将这些标签暂存在一个名为lazyImages的数组中 表示需要进行延迟加载但还未加载的图片集合 当一个图片被加载后 便将其从lazyImages数组中移除 直到lazyImages数组为空 表示所有待延迟加载的图片均已加载完毕 此时便可移除页面滚动事件
    (2)使用getBoundingClientRect()函数获取元素的相对位置 判断图片是否出现在视窗中 getBoundingClientRect()函数会返回图片元素的宽width和高height 及其与视窗的相对位置
    在这里插入图片描述
    window.innerHeight表示整个视窗的高度
    (3)对于只可上下滚动的页面 判断一个图片元素是否出现在屏幕视窗中:元素上边缘距屏幕视窗顶部的top值小于整个视窗的高度window.innerHeight
    (4)为了防止用户随心所欲的滚动页面 造成scroll事件频繁被触发 所以将延迟加载放在定时器中

  4. 缺陷:代码繁琐 性能差 虽然用定时器进行了触发限制 但当文档滚动或窗口大小调整 不管图片是否出现在视窗中 每隔200ms都会进行一次检查 并跟踪未加载的图片数量 加载完成后取消滚动事件等操作都导致性能差

  5. 优点:兼容性好

Intersection Observer
  1. 每当页面滚动或尺寸发生变化 使得目标元素与设备视窗或其他指定元素产生交集时 便会触发通过Intersection Observer API配置的回调函数 在该回调函数中进行延迟加载
    在这里插入图片描述
  2. 存在兼容性问题
CSS类名方式
  1. CSS为background-image属性定义了带.visible的类名和不带.visible的类名的两种规则 带.visible的background-image属性值为真实图片的URL 不带的时低分辨率图片或Base64图片
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 通过JS判断元素是否出现在视窗中 若在 则为元素的class属性添加visible类名
    在这里插入图片描述
原生的延迟加载支持
  1. img标签和iframe标签的loading属性支持延迟加载 其取值:
    (1)lazy:延迟加载
    (2)eager:立即加载
    (3)auto:浏览器自己决定是否延迟加载 默认值
  2. 兼容性问题解决:前端判断浏览器是否支持
    在这里插入图片描述

视频加载

不要自动播放

  1. Chrome等浏览器之前的版本中 会对视频进行预加载 即在HTML完成加载和解析时触发DOMContentLoaded事件开始请求视频资源 当请求完成后触发window.onload事件开始页面渲染
  2. video标签的proload默认值为auto 表示所有视频文件自动被下载 可将其改为none来阻止视频的自动预加载
  3. proload为none时 poster属性为视频提供占位符图片 当视频未加载出来时 显示占位符图片 不至于让页面一片空白 建议搭配使用
  4. 后来Chrome等浏览器video标签的proload默认值变为metadata 表示仅加载视频的元数据 对于不自动播放视频的场景 用户在播放视频前 若能提前知道视频的播放市场 播放列表等元数据 能给用户更好的体验 并且不会因为提前加载了过多资源而阻塞页面渲染
  5. 若你的网站中包含同一域名下的多个视频资源 最好将proload设为metadata或none 这样能更好地避免HTTP地最大连接数 因为HTTP1.1规定同一域名下地最大连接数为6 如果同时有超过此数量的资源请求连接 那么多余的连接会被挂起 影响性能

视频代替尺寸过大的GIF动画

  1. GIF相对于视频有几个特性:没有音轨 连续循环播放 加载完自动播放
    在这里插入图片描述
  2. 对视频进行延迟加载
    在这里插入图片描述
    playsinline用于在IOS中指定自动播放 将视频真正的URL放在data-src自定义属性中 使用Intersection Observer进行延迟加载 对所有source元素进行解析 将data-src上的真实URL传给src 在视频的延迟加载中 需要调用元素的load方法来触发加载 然后视频才会根据autoplay属性自动播放
    在这里插入图片描述

加载注意事项

首屏加载

  1. 首屏内的内容正常加载 首屏外的延迟加载
  2. 前面讲到当目标图像经页面滚动出现在屏幕视窗中时 触发对图像资源的请求 但这样做并不完美 因为当图像标签出现在屏幕视窗时还只是占位符图像 如果网络延迟或图像过大 那么图像的请求加载过程是可以被用户感知到的 所以我们应该在图像即将滚动出现在屏幕视窗之前一段距离时(把这段距离叫做缓冲区)就开始请求加载图像或iframe中的内容 这样能更好地缩短用户的等待加载时长 使用Intersection Observer进行延迟加载时 可以通过配置options对象的rootMargin属性来建立缓冲区:
    在这里插入图片描述
    上述代码在屏幕视窗下设置了一个宽度为256px的缓冲区 当媒体元素到视窗下边界的距离小于256px时 回调函数就会执行资源的请求加载
  3. 对于传统的延迟加载方式 可以通过getBoundingClientRect来设置

资源占位

  1. 当延迟加载的媒体资源未渲染出来之前 应当在页面中使用相同尺寸的展位图像 如果不使用的话 图像延迟显示出来后 有了图像的尺寸可能导致页面布局移位 给用户不好的体验 甚至可能触发回流增加系统资源开销造成卡顿 所以最好使用一个与目标媒体资源长宽相同的纯色占位符 或Base64图片 也可以采用LQIP或SQIP等方法
  2. LQIP:低质量图片占位符 即使用原图的较低分辨率版本来占位
  3. SQIP:基于SVG的 LQIP技术
  4. 对于img标记的图像 一开始应将src指向占位图像 当需要加载所需图像时 将src指向所需图像的URL 发起资源请求 对于video标记的视频 应将占位图像放在poster属性中 此外应给img video设置widtn height使占位图像和真实图像大小一致

资源加载失败

具体情况具体分析 当请求的资源加载失败时 可以将占位图像变为按钮 让用户单机以重新加载 或者在占位图像区域显示加载失败的提示信息

图像解码延迟

渐进式JPEG会先呈现出一个低像素的图像版本 随后会慢慢呈现出原图的样貌 因为图像从被浏览器请求获取 再到最终完整呈现在屏幕上 需要经历解码的过程 图像尺寸越大 所需要的解码事件越长 如果在JS中请求加载较大的图像文件 并把它直接放入DOM结构中 那么可能占用浏览器主进程 进而导致解码期间用户界面出现短暂的无响应 为了减少此类卡顿现象 可以采用decode方法进行异步图像解码后 再将其插入DOM结构中

JS是否可用

资源优先级

优先级

  1. 资源优先级:lowest low high highest
  2. 在head标签中 CSS具有最高的优先级highest 其次是script标签 但若script标签带有defer或async 优先级会将为low
  3. 当发现资源默认被分配的优先级不是我们想要的时 该如何更改优先级:当资源很重要又被分配了过低的优先级时 就可以尝试让其进行预加载或预连接 如果需要浏览器处理完一些任务后再去提取某些资源 可尝试使用预提取
预加载
  1. <link ref="proload">强制浏览器进行预加载
  2. 如果预加载指定的资源在3S内未被当前页面使用 浏览器会在控制台警告提示 必须处理该警告
  3. 应用
    (1)通常字体文件位于页面加载的若干CSS文件的末尾 为了减少用户等待文本内容的加载使劲啊 避免系统字体与偏好字体发生冲突 应该提前获取字体
    (2)首次渲染之前必须加载的资源 如CSS JS等 以前会将这些资源内联到HTML中 但这样做会浪费带宽 所以我们可以对单个文件进行预加载 不仅可以很快地请求资源还能尽量使用缓存 缺点是可能会在浏览器和服务器之间发生额外的往返请求 因为浏览器需要加载解析HTML后 才知道后续的资源请求情况 可以使用HTML2的推送来解决这个问题 但是HTML2的推送控制了带宽使用量 留给浏览器自我决策的空间很小 可能不会检索已经缓存了的资源文件
预连接
  1. <link ref="preconnect">告知浏览器当前页面需要和某站点建立连接 尽快启动该过程 这样做成本低 但会消耗CPU时间 因为当HTTPS连接建立好后 10S内若没有使用该连接 浏览器会关闭该链接 之前为建立连接所消耗的资源相当于浪费了
  2. <link ref="dns-prefetch">DNS预解析 仅处理DNS查询
预提取
  1. 根据用户已发生的行为来判断其接下来的行为 告知浏览器稍后可能需要的某些资源 在当前页面加载完成后 在带宽可用情况下 这些资源将以lowest优先级进行提取
  2. 应用:用户在搜索框中查询某商品 可预提取查询结果列表中的某个商品详情页 或在用户使用搜索查询时 预提取查询结果的下一页
  3. 预提取只会提取下一页内容 下一页内容的资源不会提前下载
  4. 预提取不会降低现有资源的优先级
  5. 在这里插入图片描述文件会被提取两次 第二次可能使用缓存

渲染优化

流畅的使用体验

目前大部分设备的屏幕分辨率都在60fps左右 也就是说每秒屏幕会刷新60次 所以为了让用户体验更好 就需要浏览器在渲染页面动画或相应用户操作时 每一帧生成速率尽量接近屏幕的刷新率 若按照60fps来算 留给每一帧画面的时间不到17ms 再除去浏览器对资源的一些整理工作 一帧画面的渲染尽量在10ms内完成 若达不到这个要求会导致帧率下降 屏幕上的内容会抖动或卡顿

渲染过程

JS执行优化

实现动画效果

  1. 实现动画的方法:
    (1)CSS:transition和animation
    (2)HTML:canvas
    (3)JS:使用setTimeout或setInterval来实现 通过设置一个间隔时间来不断改变目标图像的位置达到视觉变化的效果
  2. 定时器实现的动画在低端机器上会出现抖动或卡顿 因为浏览器无法确定定时器的回调函数的执行时机 定时器会出现实际执行时间比设定的延迟时间晚一些的情况 其次屏幕分辨率和尺寸也会影响刷新频率 不同设备的屏幕绘制频率可能有所不同 而定时器只能设置某个固定的时间间隔 这个时间间隔不一定与所有屏幕的刷新时间同步 可能导致动画丢帧
  3. 为了避免动画丢帧造成的卡顿现象 推荐使用window中的requestAnimationFrame方法实现动画 他将回调函数的执行时机交由系统决定 即若屏幕刷新频率是60HZ 则回调函数大约没16.7ms(1/60)执行一次 如果屏幕的刷新频率是75HZ 那么回调函数大约每13.3ms(1/75)执行一次 也就是说requestAnimationFrame方法的执行时机与系统刷新频率同步 这就能保证回调函数在屏幕每次刷新间隔中只被执行了一次 从而避免丢帧
    在这里插入图片描述
    requestAnimationFrame方法接收一个回调函数作为参数 即下次重绘前更新动画帧所调用的函数 返回值是一个long类型的证书 作为回调任务队列中的唯一标识 可将该值传给window.cancelAnimationFrame来取消回调
  4. requestAnimationFrame方法还可以通过节流不必要的函数执行 来帮助CPU节能 当浏览器页面最小化或被隐藏起来时 动画对用户来说是不可见的 没必要执行动画 如果使用定时器实现动画效果 当页面最小化或被隐藏起来时 需要调用clearInterval销毁定时器 否则后台动画会不断执行 而requestAnimationFrame方法在页面未激活时 屏幕刷新任务会被系统暂停 只有当页面被激活时 动画任务才会被激活并从上次暂停的地方继续执行
  5. 在页面的一些高频事件中 比如页面滚动的scroll、页面尺寸更改的resize 需要防止在一个刷新时间间隔内执行多次函数 这就是所谓的函数节流 对60HZ的显示器来说 差不多16.7ms刷新一次 多次绘制不会在屏幕上提现出来

恰当使用Web Worker

事件节流与事件防抖

防抖可以有效避免在规定时间间隔内频繁触发事件回调函数 但是如果用户操作过于频繁 每次在防抖定时器计时结束前就进行下一次操作 那么同一事件所要触发的回调函数会被无限延迟 频繁延迟会让用户操作迟迟得不到相应 这样的优化弄巧成拙 所以我们可以为事件防抖设置一个延迟等待时间的底线 即在延迟事件内可以重新生成定时器 但只要延迟时间到了就必须对用户之前的操作做出响应 这样便可以结合节流的思想提供一个升级版的防抖实现方式
在这里插入图片描述
在这里插入图片描述

计算样式优化

减少要计算样式的元素数量

  1. CSS引擎在查找样式表时 对每条规则的匹配顺序时从右向左的
    在这里插入图片描述
    CSS引擎需要首先遍历页面上的所有li标签元素 然后确定每个li标签有包含类名为product-list的父元素才是目标元素 所以为了提高页面的渲染性能 计算样式应尽量减少参与样式计算的元素数量
  2. 使用类选择器代替标签选择器 给li添加个class 直接使用class操作li
  3. 避免使用通配符选择器 否则计算样式时 浏览器会遍历页面中的每一个元素 性能开销大

使用BEM规范

  1. BEM:Block(块)、Element(元素)、Modifier(修饰符)
    (1)中划线-:仅作为连接字符使用 某个块或子元素的多个单词之间的连接符
    (2)单下划线_:描述块或其子元素的状态
    (3)双下划线__:连接块与块的子元素
  2. 块:独立的页面元素 HTML文档会用一个唯一的类名来表示这个块 类名命名规则:(1)只能使用类选择器(2)每个块应定义一个前缀用来表示命名空间(3)每条样式规则必须属于一个块
.mylist{}
  1. 元素:块中的子元素 子元素也被视作块的直接子元素 其类名需要使用块的名称作为前缀
.mylist__item{}
  1. 修饰符:块或元素的某个特定的状态 比如按钮的中、大、小状态
.mylist__item_big{}

重排重绘优化

触发重排重绘的操作
  1. 对DOM元素几何属性的修改(width、height、padding、margin、left、top等)某元素的几何属性发生变化会波及与他相关的所有节点元素进行几何属性的重新计算
  2. 更改DOM树的结构 如对DOM树节点的增、删、移动等 会影响当前节点后的所有节点元素 而不会再次影响前面已经遍历过的元素
  3. 获取某些特定的属性值操作 如页面可见区域宽高offsetWidth、offsetHeight、页面视窗中元素与视窗边界的距离offsetTop offsetLeft、类似的属性还有scrollTop scrollLeft scrollWidth scrollHeight clientTop clientWidth clientHeight window.getComputedStyle 这些属性都需要浏览器进行页面布局计算
避免对样式的频繁改动
  1. 浏览器进行页面布局时的计算顺序从上到下 从左到右
  2. 渲染顺序:执行JS代码 样式计算 页面布局 绘制与合成。如果在JS运行阶段设计上述三种操作 浏览器会强制提前页面布局的执行 为了尽量降低页面布局计算带来的性能损耗 应当避免使用JS对样式进行频繁修改 可以通过以下方式降低触发重排 重绘的频率
    (1)使用类名对样式逐条修改:将多行样式修改合并到一个类名中
    在这里插入图片描述
    然后在JS中通过给指定元素添加类的方式一次性完成样式修改
    在这里插入图片描述
    (2)缓存对敏感属性值的计算
    敏感属性值:offsetWidth、offsetHeight offsetTop offsetLeft scrollTop scrollLeft scrollWidth scrollHeight clientTop clientWidth clientHeight window.getComputedStyle
    在这里插入图片描述
    以上代码优化:
    在这里插入图片描述
    在这里插入图片描述
    (3)使用requestAnimationFrame方法控制渲染帧
    前面讲JS动画时 提到了requestAnimationFrame方法可以控制回调在两个渲染帧之间仅触发一次 如果在其回调函数中一开始就取值到即时敏感属性 其实获取的时上一帧旧布局的值 并不会触发页面布局的重新计算
    在这里插入图片描述
    如果在请求此元素高度之前更改其样式 浏览器就无法直接使用上一帧的旧属性值 而需要先应用更改的样式 再运行页面布局计算后 才能返回所需的正确高度值 这样多余的开销是没有必要的 所以我们可以在requestAnimationFrame方法的回调函数中 始终优先样式的读取 再执行相应的写操作
降低绘制复杂度
  1. 位图的阴影效果可以使用PS处理而非全交给CSS样式处理
  2. 不需要重新绘制的区域尽量避免重绘 如页面的顶部有个固定区域header头标 若他与页面其他位置的某个区域位于同一图层 当后者发生重绘时 就可能触发包括header区域在内的整个页面重绘 对于固定不变不期望发生重绘的区域 可将其提升为独立的绘图曾 避免被其他区域的重绘连带着触发重绘 虽然创建新图层能够减少绘制区域 但也不能创建太多的图层 因为每个图层都需要浏览器为其分配内存及管理开销

合成处理

合成处理是将已绘制的不同图层放在一起 最终在屏幕上渲染出来的过程 在这个环节中 有两个因素可能会影响页面性能:(1)所需合成的图层数量(2)实现动画的相关属性

新增图层
  1. 使用CSS的will-change属性创建 该方法存在兼容性问题
.new-layer{
	will-change: transform;
}
  1. 若浏览器不支持will-change属性 则使用3D变换来强制创建
.new-layer{
	transform: translate(0);
}
仅与合成相关的动画属性
  1. 如果一个动画的实现不经过页面布局和重绘阶段 仅在合成处理阶段就能完成 能节约大量性能开销 满足这一要求的动画属性:透明度opacity、图层变换transform
  2. 使用opacity和transform实现相应的动画效果时 动画元素应位于独立的绘图层上 防止影响其他绘制区域

数据存储

数据存储分类

  1. 在进行数据存储与取用时 根据该操作是否阻塞当前活动线程的执行 可以将存储方式划分为同步或异步 文件系统 WebSQL IndexedDB都是异步方式 localstorage sessionstorage是同步
  2. 数据模型:每个数据项或数据单元的存储形式 如数据库表字段中的结构化方式、非关系型数据库中的键值对方法、文件系统中按字节流的存储方式
  3. 事务:作为单个逻辑工作单元执行的一系列操作 事务若想正确处理执行需要满足四个基本要素:
    (1)原子性:事务中所有操作要么全都完成要么全都不完成 不会停留在中间的某个操作中
    (2)一致性:事务提交后 数据库状态能够满足原有约束
    (3)隔离性:事务与事务之间不会发生干扰
    (4)持久性:事务对数据的修改是确定的
  4. 持久化:数据留存的时效性 分为会话级 设备级 全局级
    (1)会话级的持久化指仅在当前浏览器标签处于活动状态时 网页中所保存的数据有效 当关闭浏览器页签后 数据随之消失 sessionstorage的持久化就是会话级
    (2)设备级的持久化允许跨浏览器标签页进行数据存取 大部分存储方式都属于设备级
    (3)全局级要求能够跨设备与跨会话存储数据 即能够将数据存储在云端

cookie

  1. cookie是服务器创建后发送到用户浏览器并保存在本地的一小块数据 在该浏览器下次向同一服务器发起请求时 它将被携带并发送到服务器上 他的作用通常时告诉服务器 先后两次请求来自同一浏览器 这样便可用来保存用户的登录状态 使基于无状态的HTTP协议能够记录状态信息
  2. cookie是会话级的 即浏览器关闭后会被自动删除 仅在页面会话期间有效 但是如果你给cookie制定了过期时间或有效期 cookie就是持久的

Web SQL

Web SQL:使用SQL语句操作客户端数据库

IndexedDB

  1. IndexedDB:事务性数据库系统 其事务型类似基于SQL的关系型数据库管理系统(RDBMS) 但其并不像RDBMS使用固定列表 二十一种基于JS的面向对象的数据库 更接近于NOSQL
  2. 特点:
    (1)存储空间大 不少于250M 无上限
    (2)支持事务
    (3)支持多种数据模型 所有类型的数据都可以以键值对的形式进行存储
    (4)同源约束 每个页面只能访问其自身域名下的数据库 不能跨域访问
    (5)异步 IndexedDB的数据操作不会阻塞浏览器主线程 这让其可以在读写大量数据时不拖慢网页
  3. 当浏览的网站页面被加载出来时 首先需要向服务器请求相应的数据来构造页面显示的状态信息 然后利用这些信息完成页面渲染 如果能将首次请求的信息保存在IndexedDB中 则能有效减少一些频繁访问页面的加载时间
  4. 要保存图片或视频等较大文件时 可以使用文件或Blob对象的数据格式进行存储 但是IOS系统的Safari暂不支持将Blob对象存储到IndexedDB中 为此 我们可以考虑使用二进制数据容器ArrayBuffer类型来对在不支持Blob对象的平台上进行替代使用(不懂
  5. 在进行数据存储时 会因为很多原因造成数据的读写失败 并且数据存储在某些情况下时不可控的 比如写入IndexedDB失败的原因可能是用户使用的设备磁盘空间快满了 也可能时用户当前处在无痕模式下(一些浏览器目前暂不允许在无痕模式下进行IndexedDB的写操作) 由于IndexedDB的主要操作方法都集中在IDBDatabase IDBTransaction IDBRequest接口对象上 所以可以为他们提娜佳error监听事件 然后根据报错信息进行恰当的错误处理
  6. 通常我们所熟悉的服务器端数据库 能够帮助开发者对未经授权的请求进行限制 将有效避免因误操作造成对数据库中数据的不当修改和删除 但客户端浏览器的本地数据库却缺少这类限制 数据库可以被浏览器扩展插件及chrome开发者工具直接访问和操作 甚至清空所有数据 因此我们需要为应用添加相应处理 以确保这类情况不会引发错误 即使用户本身没有直接更改数据 但数据可能因为代码版本的升级而出现一些国企版本的错误 为此我们需要在版本升级时 针对历史版本进行相应的升级处理 可以使用IDBOpenDBRequest.onupgradeneeded()方法来捕获IndexedDB版本升级事件
  7. IndexedDB在存储数据对象时 首先创建该数据对象的一个机构化副本 而这个复制过程是在主线程中进行的 所以数据对象嵌套的越复杂 规模越大 对主线程的阻塞时间就越长 因此在操作IndexedDB时 读写数据的大小不应该大于待访问的数据大小 对规模较大的状态树进行频繁改动 也会给主线程的执行带来很大压力 即便使用了节流和防抖 也可能阻塞主线程的执行 增加数据写入出错的风险 因此建议将原本整个状态对象的存储方式分解为粒度更小的单个状态记录进行存储 这样可在进行状态更新时 仅更新实际发生修改的状态记录 而不会引起整个应用的状态对象进行大规模的数据存储

Cache Storage

  1. Cache Storage:为缓存网络请求与响应而设计的数据存储机制 这些请求与相应可以是在应用程序运行过程中常规创建的 也可以时专门为了在缓存中存储一些数据而创建的

缓存

HTTP缓存

强制缓存

  1. 与强制缓存相关的两个字段是expires和cache-control expires在HTTP1.0协议中用来控制缓存失效日期时间戳 他由服务器指定后通过响应头告知浏览器 浏览器在接收到带有该字段的响应体后进行缓存 若之后浏览器再次发起相同的资源请求 便会对比expires和本地当前的时间戳 如果当前请求的本地时间戳小于expires的值 则说明浏览器缓存的响应还未过期 可以直接使用 无需向服务器端再次发起请求 只有当本地时间戳大于expires值 缓存过期时 才允许重新向服务器发起请求 显然 expires对本地时间戳过分依赖 如果客户端本地的时间与服务器端的时间不同步 或者客户端时间进行主动修改 那么对于缓存过期的判断可能与预期不符
  2. 为了解决expires的局限性 HTTP1.1协议开始新增了cache-control cache-control中的max-age也可以用来控制响应资源的有效期 以秒为单位 表示该资源在被请求到后的多少秒内有效
  3. cache-control的属性值:
    (1)no-store和no-cache
    no-cache:不管强制缓存是否过期 强制进行协商缓存
    no=store:禁止使用任何缓存策略 客户端的每次请求都需要服务器端给予全新的响应
    (2)private和public
    public:响应资源可以被浏览器 代理服务器缓存
    private:响应资源只能被服务器缓存 默认值
    (3)max-age和s-maxage
    max-age:服务器告诉浏览器响应资源的过期时长
    s-maxage:缓存在代理服务器中的过期时长 只有设置了public他才有效

协商缓存

  1. last-modified缺陷:
    (1)他根据资源最后的修改时间戳进行判断 虽然请求的文件资源进行了编辑 但内容没有发生任何变化 时间戳也会更新 从而导致协商缓存时关于有效性的判断验证会失效 需要重新进行完整的资源请求
    (2)标识资源修改的时间戳单位为秒 五福哦文件修改的速度非常快 假设在几百毫秒内完成 那么上述通过时间戳的方式来验证缓存的有效性 是无法识别出该次文件资源的更新的
  2. Etag时服务器为不同资源进行哈希运算生成的一个字符串 该字符串类似于文件指纹 只要文件内容编码存在差异 对应的Etag标签值就会不同
  3. Etag比last-modified优先级更高
  4. Etag的缺陷:
    (1)服务器对于生成文件资源的Etag需要付出额外的计算开销 如果资源的尺寸较大 数量较多且修改比较频繁 那么生成Etag会影响服务器性能
    (2)Etag字段值得生成分为强验证和弱验证 强验证根据资源内容进行生成 能够保证每个字节都相同 弱验证则根据资源得部分属性值来生成 生成速度快但无法确保每个字节都相同 并且在服务器集群场景下 也会因为不够准确而降低协商缓存有效性验证的成功率

缓存决策

在这里插入图片描述
(1)根据资源内容的属性判断是否需要使用缓存 如果不希望对该资源开启缓存(比如涉及用户的一些敏感信息) 则可直接设置cache-control的属性值为no-store来禁止任何缓存策略 这样请求和响应信息都不会被存储在对方及中间代理的磁盘系统上
(2)如果希望使用缓存 那么接下来需要确定是否使用协商缓存 如果需要则为cache-control添加no-cache表示强制使用协商缓存
(3)接下来考虑是否允许中间代理服务器缓存该资源 可为cache-control添加private或public来控制
(4)如果之前没有设置no-cache启用协商缓存 那么接下来可以设置强制缓存的过期时间 即为cache-control配置max-age 如果设置了协商缓存 那么可以进一步设置请求资源的last-modified和Etag等参数

缓存示例

在这里插入图片描述
若要展示出该HTML中的内容就需要加载出他包含的所有外链文件
(1)HTML在这里属于包含其他文件的主文件 为保证当其内容发生修改时能及时更新 应当将其设置为协商缓存 即为cache-control添加no-cache
(2)因为网站对图片的修改基本都是更换修改 同时考虑到图片文件的数量及大小可能占用很多缓存空间 所以可以采用强制缓存且过期时间不宜过长 即为cache-control配置max-age
(3)CSS文件属于文本文件 内容可能不定期会被修改 又想使用强缓存来提高重用效率 故可以考虑在CSS文件的命名中增加文件指纹或版本号(比如添加文件指纹后 原来的CSS文件名由style.css变为style.51ad84f7.css) 这样当发生文件修改后 不同的文件便会有不同的文件指纹 即需要请求的文件URL不同了 因此必然会对资源进行重新请求
(4)考虑到网络中浏览器与CDN等中间代理的缓存 其过期时间可适当延长到一年
(5)JS脚本文件类似于CSS样式文件的设置 设置文件指纹和较长的过期时间 若JS中包含了用户私人信息而不像被中间代理缓存 则可为cache-control添加private

缓存注意事项

  1. 拆分源码 分包下载:在代码构建过程中 按照模块拆分将代码打包成多个单独的文件 这样在每次修改后的更新提取时 仅需拉取发生修改的模块代码包 从而大大降低需要下载的内容的大小
  2. 预估资源的缓存时效
  3. 控制中间代理的缓存:设计用户隐私信息的尽量避免中间代理缓存 如果对用户响应相同的资源 则可以考虑让中间代理也进行缓存
  4. 避免网址冗余:不要将相同的资源设置为不同的URL
  5. 制定合理的缓存策略

Service Worker缓存

  1. Service Worker是浏览器后台独立于主线程之外的工作线程 能够是实现消息推送 后台加载 离线应用 移动端添加到主屏等功能 具备小程序“无需安装,用完即走”的特点 存在兼容性问题

Push缓存

  1. HTTP2可以进行服务器端推送 服务器可以对客户端浏览器的一个请求发送多个响应
  2. 客户端若想将应用中所包含的多种资源渲染到浏览器上 需要逐个资源进行请求 但其实一个HTML文件中所包含的JS CSS文件 图片等文件资源 是服务器可以在收到该HTML请求后预判出稍后会到来的请求 那么就可以利用服务器端推送节省这些多余的资源请求 提升页面加载速度
  3. 浏览器缓存(优先级由高到低):内存中的缓存 Service Worker缓存 HTTP缓存 HTTP2的push缓存
    在这里插入图片描述
  4. 内存中的缓存:浏览器中响应速度最快且命中优先级最高 但他的驻留周期很短 通常依赖渲染进程 一旦页面页签关闭进程结束 内存中的缓存数据就会被回收 通常体积不大的JS文件和CSS文件有一定概率会被纳入内存中进行缓存 而对于体积较大的文件或图片 很大概率会被直接放在磁盘上存储
  5. 只要高优先级的缓存命中成功 即便设置了低优先级的缓存 也不会对其进行询问
  6. push缓存适用于资源推送到页面 页面提取间隔时长较短的场景
  7. 每个HTTP2连接都有自己独立的push缓存 对使用了同一个连接的多个页面来说 他们可以共享该push缓存 反过来看 将资源推送给客户端时 资源并非只被同一页面提取 资源还可以被正在安装的Service Worker提取

Push缓存与预加载

  1. 他们的优化原理都是利用客户端的空闲带宽来获取资源文件 这种方式可以很好的将资源的执行与获取分离 当浏览器实际需要某个资源文件时 该资源文件其实已经存在于缓存中了 这样便省去了发起请求后的等待时间
  2. 二者的不同点:
    (1)push缓存是由服务器决定何时向客户端预先推送资源 而预加载时当客户端浏览器收到HTML文件后 经过解析其中带有preload的标签 才开启预加载
    (2)push缓存只能向同源或具有推送权的源进行资源推送 而预加载可以从任何源加载资源
    (3)预加载使用的时内存中的缓存 而推送使用的是push缓存
    (4)预加载的资源仅能被发起请求的页面使用 而服务器端push缓存的资源却能在浏览器的不同标签页面中公用
    (5)预加载使用的link标签上可以设置onload和onerror进行响应事件的监听 而push缓存则在服务器端进行监听相对更透明
    (6)预加载可以根据不同的头信息 使用内容协商来确定发送的资源是否正确 push缓存不可以
  3. 使用场景
    (1)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值