先说总结,前端优化核心逻辑就是:
减少请求数(或同时间请求数)与请求资源大小,减少重绘与回流
减少请求数(或者同一时间请求数)与请求资源大小
文件压缩与合并
html css js
文件压缩合并打包,比如gzip压缩,js混淆等(目前多通过webpack
构建处理),压缩打包后减少了资源大小与请求数量图片压缩
- 雪碧图(又称精灵图):多张小图片合成一张大图减少请求数
- 项目图片无损压缩后再使用, 这里推荐一个压缩工具https://tinypng.com/
- 小图片base64化:一些小图片(一般不超过5k),可以通过转成base64的方式直接打入资源包,这样就减少了请求数,目前流行的方案是通过
webpack
的url-loader
loader配置处理,不过图片转成base64会提高33%大小(原理是把图片每3个字节转成了4个字节),所以不能转太大的图片,会造成资源包过大。
-
加载优化(加载优化并不减少请求数,主要是利用一些技巧提升用户体验)
服务端ssr首屏渲染: 多用于优化移动端应用用户体验,提高首屏加载速度。一些应用不想首页显示后ajax获取相关数据造成的闪动或loading,通过服务端渲染ssr的方式解决首屏加载问题,缺点对服务端压力大(ssr能讲的特别多,不展开了,感兴趣可以自己了解)
预加载: 资源提前请求,需要是从缓存中加载。常见于H5活动页
和H5小游戏
,为了保证整体页面的流畅度,预判用户行为预先加载资源(例如guagame那样加载所有图片资源后开始游戏)
懒加载: 按需加载也称延迟加载,用于减少无效资源的加载。常见两种实现:- 代理加载:方法是图片加载时先使用默认loading图占位,实际图片加载后替换,这样就可以更快显示整体页面布局,提高用户体验
- 按需加载(目前主要有2种应用实现)
- 监听滚动条事件,当资源(主要是图片)即将进入视图区域时再加载显示,核心就是监听滚动高度
scrollTop
与元素高度clientHeight
的比较 - 监听用户交互事件(比如点击),一些封装组件,只有当用户使用相关功能的时候加载,核心逻辑就是响应事件后动态创建script标签加载资源(目前主流也是通过webpack配置
() => import(**/**.js)
这样的语法实现),最典型的场景就是路由跳转
- 监听滚动条事件,当资源(主要是图片)即将进入视图区域时再加载显示,核心就是监听滚动高度
-
缓存优化(核心还是尽量复用本地资源,减少请求数,减少请求资源大小)
-
使用CDN:CDN作为静态资源文件的分发网络(名词解释就是它能够能够实时地根据网络流量和各节点连接、负载状况以及到用户的距离和响应时间等综合信息将用户请求重新导向离用户最近的服务节点上,以达到用户就近取得所需的目的)能加快网站的资源加载速度,有效利用缓存的静态资源,下面说几个关于cdn的扩展问题:
- cdn缓存导致用户拿不到最新文件:通用解决办法还是通过
webpack
打包将文件名hash
处理,通过文件名变动让cdn缓存失效,每次版本更新,webpack
会分析变动,只改变文件有变动的文件名,最大化的利用缓存,还要提醒一点,作为主入口的index.html
是不缓存的,因为现代前端工程里面,index.html
都是模板文件非常小,不需要缓存,以防止主入口更新失效,而模板里面的引入的资源文件路径都是根据hash
动态生成,根据文件变动可以及时更新 - 域名发散:http1协议一个网络链接,后一个网络请求必须等前一个请求结束才能使用,而目前主流的游览器都对同一域名下的最大网络链接数有限制,比如chrome是6,如果页面里图片等加载资源过多,加载逻辑就变成了串行的加载,影响效率,所以将静态资源放置在多个子域名下面绕过这个限制,最大化利用游览器并行加载。(http2没有这个问题,因为http2协议使用一种信道复用的技术,允许同一个网络链接可以并行的发送请求,前面的请求不完成并不会堵塞后面的请求)
- cdn缓存导致用户拿不到最新文件:通用解决办法还是通过
-
cookie优化:因为cookie一直在游览器与服务器中间传递,所以cookie优化的核心就是减少cookie大小,
- 去除没必要的cookie
- 设置合适的cookie过期时间(通过max-age字段)
- 静态资源cdn的域名和主站的域名要分开,一些js,css,图片资源请求携带cookie是多余的,所以请求这些资源不需要发送cookie(可通过domain字段设置)
-
HTTP缓存(主要介绍关于资源缓存有效时间和资源文件识别相关的字段):
Pragma:可设置no-cache
禁用缓存,兼容性最好,优先级高于Cache-Control
,最高优先级
Expires: 设置过期时间,优先级低于Pragma
与Cache-Control
,此时间是服务器时间,如果和本地时间不统一会导致问题
Cache-Control: 解决Expires
服务器时间与本地时间统一问题,优先级高于Expires
,此字段请求和响应都可以设置(属性很多,说些关键的,感兴趣可以自行深入了解)- no-cache 不缓存
- max-age=delta-seconds 服务端(响应情况)设置最大有效时间
delta-seconds
(有效时间内游览器直接缓存拿数据,不请求) 客户端(请求情况)就是告知服务端希望接受一个存在时间不超过``delta-seconds`秒的资源 - s-maxage=delta-seconds 同max-age,区别是这个
delta-seconds
是设置的代理服务器时间 - only-if-cached 代理服务器如果有资源不需要去服务器请求
- must-revalidate 资源一定是从服务器获取,而非代理服务器缓存,如果失败返回504
- last-modified与if-modified-since: 服务器通过
last-modified
字段告知客户端资源修改最后时间,游览器储存后通过if-modified-since
字段发送储存时间,服务端收到请求后判断接收到的时间是否与当前一致,如果一致返回304,不返回资源,如果不一致,返回200,更新新的缓存时间并返回新的资源 - eTag :效果等同于last-modified,有时候文件时间修改了,但是文件内容没有变化,这就导致
last-modified
不准确,所以通过文件hash值确定文件变化,是一种更准备的缓存策略
-
// Cache-Control可以多段设置
Cache-Control: max-age=3600, must-revalidate // 表示一定从原服务器获取资源,获取后1小时内无需再次请求走缓存
/*
总结:
一个请求,可能有三种状态:
1. 本地有资源,也没有过期 这时候返回就是 200(from cache),直接走本地资源 最快(Cache-Control)
2. 本地有资源,但是过期了,去服务端请求,服务端判断文件没变化,返回304,不更新文件资源,还是走本地资源,一个简单的网络请求,也很快(last-modified与eTag字段)
3. 本地有资源(或者没资源),服务端判断文件有变化或者第一次请求,返回200,并发送文件资源,网络请求加资源下载,最慢
*/
减少重绘与回流
回流(reflow): 触发页面重布局的行为(消耗很大),比如盒模型相关属性改变,定位与浮动,节点内容改变等,当页面布局和几何属性变化就需要回流
重绘(repaint): 不改变文档流布局,只改变样式(消耗小)的行为,比如字体颜色与背景相关属性,visibility等,只影响外观风格不影响布局就是重绘
频繁触发重绘与回流,会导致ui频繁渲染,最终阻塞js线程,导致js变慢,而这一方面的优化,更多是代码习惯的优化,大概讲几个通用的吧
- 尽量减少会引起回流的操作,限制回流在一个图层中(图层, 可以类比ps的图层,简单说,就是脱离主文档流,比如一个元素设置了z-index或者transform: translateZ(0)属性它就是一个新的图层,如果一个dom元素频繁操作,应该独立成一个单独的图层,重绘回流操作就只限制在这一个图层,不要设置过多图层,图层合并计算是非常耗费资源的,所以只有当特别频繁的操作,例如动画,gif图才考虑独立图层出来)
- css资源放在head里面(css link引入方式会阻塞页面渲染,将css放在head里面,加载完成后再渲染,防止页面闪动)
- 使用translate替代top操作(top会触发回流,但是translate不会)
- 使用opacity替代visibility(只考虑可见性的话visibility会触发重绘,但opacity不会,要考虑需求差异,opacity和visibility对点击事件的结果不一样)
- 不要一条条改变dom样式,而是封装成预先定义的className整体替换(每次修改都会触发回流或重绘,整体替换只有一次)
- 复杂的操作先将dom结构display:none 然后操作后显示(先把dom设置none,然后修改几十次后显示,也只触发2次回流)
- 除非确实需要真实值,不然不要在循环中去获取dom的
offsetHeight offsetWidth
等属性,而是循环前就获取储存到变量中(因为这些属性的每一次读取都会触发一次回流) - 避免使用table布局(如果是div布局,修改dom,回流只会在这个dom后面的所有元素发生,但是table布局即使修改了最后一行一列也会导致整个table的重新布局)
- 使用GPU硬件加速(当一个元素设置css属性transform: translateZ(0)或translate3d(0,0,0)的时候,游览器就默认启动)
- 动画效果使用requestAnimationFrame代替setInterval操作(requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧随刷新频率,不因间隔时间过短造成过度绘制,也不会太长导致不流畅,且游览器有特别优化,更节省系统资源,提高性能)
其他优化
Web Worker: 本质就是可以创建一个新的线程,把非常耗费性能的操作放在worker线程里运行,不堵塞主线程,要考虑兼容问题