前端性能优化笔记

288 阅读11分钟

HTTP 请求过程中潜在的性能优化点

  • dns是否可通过缓存减小dns查询时间?
  • 网络请求的过程在最近的网络环境?
  • 相同的静态资源是否可缓存?
  • 能否减小请求http请求大小?
  • 减小http请求
  • 服务端渲染

深入理解http请求的过程是前端性能优化的核心

一 资源压缩与合并

即减小HTTP请求数量与大小 google首页案例学习

  • HTML压缩: 去除空格/制表符/换行符等
  • CSS压缩: 无效代码去除 / 语义合并
  • JS压缩与混乱: 无效字符删除 / 剔除注释 / 代码语义缩减和优化 / 代码保护
  • 文件合并
    文件合并存在的问题:首屏渲染问题 / 缓存大面积失效问题 解决问题:公共库合并 / 不同页面的合并 / 见机行事,随机应变
  • 开启gzip

二 图片相关

有损压缩

根据色彩丰富程度选择格式

  • png8 256色 + alpha
  • png24 2^24色
  • png32 2^24色 + alpha

业务场景

  • jpg有损压缩、压缩率高、不支持透明

  • png支持透明,浏览器兼容好

  • webp压缩程度更好,在ios webvier有兼容问题

  • svg适量图,代码内嵌,相对小,图片样式相对简单场景

  • jpg - 不需要透明场景

  • png - 需要透明场景

  • webp - 安卓

  • svg - 图片样式相对简单如icon-font logo

01 CSS雪碧图

减少网站HTTP请求数量(postCSS可自动化制作雪碧图)

02 Image inline

将图片内容内嵌到html中,减小HTTP请求数量

03 使用适量图

使用iconfont解决icon问题

04 安卓下使用webp

转化效果优秀,稳定统一

三 CSS和JS的装载与执行

HTML渲染过程特点

  • 顺序执行、并发加载(并发受域名限制)
  • 资源是否会阻塞
  • 依赖关系
  • 引入方式

词法分析是从上到下的,顺序执行

  • css head中阻塞页面的渲染

  • css不阻塞外部js的加载,但是会阻塞js的执行

  • 直接引入JS会阻塞页面渲染

  • JS不阻塞资源的加载

  • JS顺序执行,阻塞后续JS逻辑的执行

四 懒加载与预加载

01 预加载

资源在使用时能从缓存中加载,提升用户体验 例1:在网络闲置时偷偷加载后面的资源 异步import 例2:播放视频音乐时边下边播

02 懒加载

减少无效资源加载 且 并发时会阻塞JS加载 ==> 需要时在加载

例1:动态import异步加载资源

例2:图片进入可视区域时在动态把src地址加入src

<img src="" lazyload="true" data-original="http://img.com/001.jpg" width="100" height="100"/>
<img src="" lazyload="true" data-original="http://img.com/002.jpg" width="100" height="100"/>

图片的src是空的,真实的url在data-original中

var viewHeight = document.documentElement.clientHeight // 可视区域高度
function lazyload(){
    var eles = document.querySelectorAll('img[data-original][lazyload]')
    Array.prototype.forEach.call(eles, function(item, index){
        var rect
        if(item.dataset.original === ''){
            return 
        }
        rect = item.getBoundingClientRect()  
        if(rect.bottom >= 0 && rect.top < viewHeight){
            !function(){
                var img = new Image()
                img.src = item.dataset.url
                img.onload = function(){
                    item.src = img.src
                }
                item.removeAttribute('data-original')
                item.removeAttribute('lazyload')
            }()
        }
    })
}
lazyload()
document.addEventListener('scroll', lazyload)

zepto的zepto.lazyload也实现了lazyload

五 重绘与回流

CSS性能让JS变慢? 一个线程JS解析 一个线程UI渲染 频繁触发重绘与回流,会导致UI频繁渲染,最终导致JS变慢

01 回流

  • 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)
  • 当页面布局和几何属性改变时就需要回流

适当减少CSS触发回流

02 重绘

当render tree中的一些元素需要更新属性,而这些属性只影响元素外观,风格,而不影响布局的,比如background-color。则称为重绘

回流必将引起重绘,而重绘不一定引起回流

03 避免重绘回流的两种方法

会触发页面重布局的属性

  • 盒模型相关属性
  • 定位属性及浮动
  • 改变节点内部文字结构
box 定位 文本
width display text-align
height top font-weight
padding bottom font-family
margin bottom line-height
border-width left vertival-align
border right white-space
min-height position font-size
overflow-y float
overflow clear

只会触发重绘的属性

text & box background outline
color background outline-color
visibility background-image outline
text-decoration background-image outline-style
border-style background-positon outline-width
border-radius background-repeat
box-shadow background-size

04 新建DOM的过程

  1. 获取DOM后分割为多个图层
  2. 对每个图层的节点计算样式结果(Recalculate style--样式重计算)
  3. 为每个节点生成图形和位置(Layout--回流和重布局)
  4. 将每个节点绘制填充到图层位图中(Paint Setup和Paint--重绘)
  5. 图层作为纹理上传转GPU
  6. 符合多个图层到页面上生成最终屏幕图像(Composite Laters -- 图层重组)

** 将频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素的重绘和回流的影响只会在这个图层中。 **

05 如何将DOM元素变成新的独立图层?

Chrome创建图层的条件

  1. 3D或透视变换CSS属性(perspective transform) (transform:translateZ(0) 将普通DOM变成图层)
  2. 使用加速视频解码的<video>节点
  3. 拥有3D(webGL)上下文或加速的2D上下文的<canvas>节点
  4. 混合插件 如flash
  5. 对自己的opacity做css动画或使用一个动画webkit变换的元素
  6. 拥有加速CSS过滤器的元素
  7. 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
  8. 元素有一个z-index较低且包含一个复合层的兄弟元素(该元素在复合层上渲染)

例:gif会频繁触发重绘,但又不在独立图层中。

06 结论

  1. 避免使用触发重绘、回流的CSS属性
  2. 将重绘,回流的影响范围限制在单独的图层之内

07 chrome工具

chrome > performance工具会详细展示从请求到渲染过程

chrome > Layers 查看渲染图层

chrome > Rendering > Paint flashing 会将重绘区域标示绿块

图层layers会非常消耗性能,不能“烂”用

08 最佳实践

  1. 用translate替代top改变 (top会触发layout)
  2. 用opacity替代visibility(visibility会触发重绘)
  3. 不要一条一条修改DOM的样式,预先定义好class,然后修改DOM的className
  4. 把DOM离线后修改(如:先把DOM给display:none(有一个reflow),然后修改1000次,最后显示出来)
  5. 不要把DOM结点的属性值放在一个循环里当成循环里的变量(offsetHeight / offsetWidth 获取此类属性即会触发回流)
  6. 不要使用table布局,可能很小一个改动会造成整个table重新布局
  7. 动画实现的速度的合理选择
  8. 对于动画新建图层
  9. 启用GPU硬件加速
  10. 为gif新建图层

六 浏览器存储

pwa / service worker

01 cookie

  • 因为HTTP请求无状态,所以需要cookie去维持客户端状态

  • 用于客户端与服务端的交互: http response header 中的 set-cookie,由服务端生成,客户端存储

  • 用于客户端自身数据存储: JS中可通过document.cookie读写cookie,需要设置过期时间expire

  • cookie作为客户端存储,数据限制大小4KB左右(已被localstorage替代)

  • httponly:禁用JS读写

cookie在相关域名下面-cdn的流量损耗 解决方法:cdn的域名和主站域名要分开

Chrome ==> Application ==> Cookies 查看

02 LocalStorage

  • HTML5设计出来专门用于浏览器存储的
  • 大小为5M左右
  • 仅在客户端使用,不和服务端进行通信
  • 接口封装较好
  • 浏览器本地缓存方案

Chrome ==> Application ==> Local Storage 查看

var testJsContent = localStorage.getItem('test')
if(testJsContent){
    eval(testJsContent)
}else{
    axios.get('/api/test.js').then((data)=>{
        localStorage.setItem('test', 'data')
    })
}

03 SessionStorage

  • 会话级别的浏览器存储(标签内存储,关闭标签后清空)
  • 大小为5M左右
  • 仅在客户端使用,不和服务端进行通信
  • 接口封装较好
  • 对于表单信息的维护

Chrome ==> Application ==> Session Storage 查看

04 IndexedDB

  • 是一种低级API,用于客户端存储大量结构化数据。该API使用索引来实现对该数据的高性能搜索。虽然webStorage对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案。
  • 为应用创建离线版本

05 Service Workers

客户端JS为单线程,当我们需要大消耗计算时会障碍JS与UI渲染 解决:由service workers发送到后台计算,计算完成后返回客户端渲染

Service Worker 是一个脚本,浏览器独立于当前页面,将其在后台运行,为实现一些不依赖页面或用户交互的特性打开了一扇大门。在未来这些特性将包括投送消息,背景后台同步,geofencing(地理围栏定位),但它将推出的第一个首要特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。

应用点:

  1. 使用拦截和处理网络请求的能力,去实现一个离线应用
  2. 使用service worker在后台运行同时能和页面通信的能力,实现大规模后台数据的处理

生命周期

06 PWA

progressive web apps 是一种web app新模型,并不是具体指某一种前沿的技术或某一个单一的知识点,这是一个渐进式的web app,是通过一系列新的web特性,配合优秀的UI交互设计,逐步的增强web app 的用户体验。

  • 可靠:在无网络环境或弱网环境提供基本的页面访问,而不会出现"未连接到互联网"的页面
  • 快速:针对网页渲染及网络数据访问有较好优化
  • 融入Engaging:应用可被增加到手机桌面,并且和普通应用一样有全屏、投送等特性
  • chrome 插件 lighthouse 用于检测PWA指标

七 缓存

httpheader

Cache-Control

  • max-age : 缓存最大有效时间数,比expires过期时间优秀级更高
  • s-maxage : 指定public缓存时间
  • private : 客户端私有缓存
  • public : CDN缓存设备中的缓存
  • no-cache : 去服务端发送请求判断是否使用浏览器缓存
  • no-store : 完全不使用缓存

Expires

  • 缓存过期时间,用来指定资源到期时间,是服务端的具体的时间点。
  • 告诉浏览器在过期时间前浏览器可直接从浏览器缓存存取数据,而无需再次请求

问题是当服务端数据变更,客户端无法感知 解决:last-modified / if-modified-since

  • 基于客户端和服务端协商的缓存机制
  • last-modified - response header
  • if-modified-since - request header
  • 需要与cache-control共同使用

客户端发送请求 => 服务端返回数据带上last-modified => 客户端保存last-modified 客户端之后每次请求都会带上if-modified-since => 服务端确认modified是否过期 (未过期返回304 / 过期返回新的last-modified及200)

last-modified 缺点

  1. 某些服务端不能获取精确的修改时间
  2. 文件修改时间改了,但文件内容没有变

Etag/if-None-Match

  • 文件内容的hash值
  • etag - response header
  • if-none-match - request header
  • 需要与cache-control共同使用

无变动返回304 、 有变动返回200

分级缓存策略

八 前端性能在服务端的优化

VUE React 类框架 有下载框架代码 => 执行JS => 渲染页面的性能损耗

多层次的优化方案

  • 构建层模板编译
  • 数据无关的prerender的方式
  • SSR 服务端渲染

九 前端性能优化祖宗 - 雅虎军规

  1. 减少HTTP请求
  2. 减少DNS查询
  3. 避免跳转 301 302
  4. 可缓存的AJAX
  5. 推迟加载内容
  6. 预加载
  7. 减少DOM元素数量
  8. 根据域名划分页面内容
  9. 使iframe数量最小
  10. 不要出现404
  11. 使用内容分发网络
  12. 为文件头指定Expires或Cache-Control
  13. Gzip压缩文件内容
  14. 配置ETag
  15. 尽早刷新输出缓冲
  16. 使用GET来完厂AJAX请求
  17. 把样式表置于顶部
  18. 避免CSS表达式
  19. 使用外表JS与CSS
  20. 削减JS和CSS
  21. 用<link>代替@import
  22. 避免滤镜
  23. 把脚本置于页面底部
  24. 剔除重复脚本
  25. 减少DOM访问
  26. 开发智能事件处理程序
  27. 减小Cookie体积
  28. 对页面内容使用无cookie域名
  29. 优化图像
  30. 优化CSS Spirite
  31. 不要在HTML中缩放图像
  32. favicon.ico要小且可缓存
  33. 保持单个内容小于25k
  34. 打包组件成复合文本