问题:
- 有没有经常被产品或者qa说,你这个页面打开真慢?
- 有没有碰到过手机打开一个页面,发现卡顿,不流畅?
......
如果你遇到了上述问题,说明你对页面的性能优化没有做好。这篇文章会从宏观的角度来看待前端性能优化,保证大家有一个整体的思路来解决这个问题。
先从一个到经典的面试题开始
问题:用户在浏览器输入网址,看到整个页面,这中间发生了什么?
这个问题包含了整个互联网的运行过程,只有你理解了这个过程,才明白作为一个前端工程师,我们能做什么。
输入网址到底发生了什么?
- 用户输入www.baidu.com
- 浏览器通过DNS,把url解析成IP
- 和IP地址建立TCP链接,发送http请求 (三次握手,四次挥手)
- 服务器接受请求,查库,读写文件,拼接好返回http响应
- 浏览器收到html, 开始逐行渲染,生成dom tree。
- 再将css渲染成 cssOM.
- dom tree 与 cssOM 合并变成 render tree 渲染树。
- 通过回流,重回,生成页面。
- 加载js,执行js。
上面的九个步骤,是访问网址所需要走的流程。有了这些流程,我们可以逐个分析,进行优化。不过在进行讲解前,先要补充另一个概念。
浏览器执行 js是单线程 + 事件队列(task queue)
很多人喜欢说成: 浏览器是单线程 + 事件队列,这是不对的。当我们打开浏览器的时候,其实是操作系统为我们启动一个进程,在浏览器打开的一个个tab,就相当于一个个线程。所以js 是运行在一个个线程里面的。
**
优化点
优化,基本上分两个方向:
- 少加载文件,很重要
- 文件打包压缩
- 图片格式和压缩
- 缓存
- cdn 缩减距离,提高加载速度和效率
- srr 首页文件执行顺序
- lazy-load 懒加载
- 少执行代码
- 浏览器足够好的,大部分优化策略都集中在vue和react内部
- 长列表(内存做优化)
1. 移动端一些机型,比如淘宝首页无限滚动,如果直接渲染
- dom过多会崩掉
- 内存存储数据过多也会崩掉
- 一个长度10000的商品列表,怎么显示
DNS 缓存
优化: prefetch 预获取,比如使用了cdn的域名。
<link rel="dns-prefetch" href="//domain.com">
TCP和UDP进行比较
网络协议就是让机器聊天
- 知道目标是谁?IP协议负责寻址。
- TCP协议基于IP协议,负责数据的完整和有序,像快递公司
a. 三次握手
b. 重发,滑动窗口,粘包,慢启动等机制
c. tcp协议操作系统自带的 - HTTP基于TCP负责应用层
a. header和body构成 - UDP也是基于IP的,直管发和收,不管数据丢不丢
a. udp适合什么场景?
b. 性能好
c. 适合性能要求高,但是不在乎偶尔丢帧的场景,包足够小,不用分包
ⅰ. 游戏
ⅱ. 语音视频聊天
ⅲ. DNS(包足够小)
粘包是什么?
TCP有一个数据缓冲的策略,如果能把挨着很近的包,粘成一个包发。
使用浏览器的缓存机制
上图是浏览器缓存的一个简介,我们可以根据浏览器的缓存策略进行配置。具体的配置可以参考:什么是浏览器缓存机制?
cdn 的内部原理
cdn 就是把我们的图片,部署到各大机房(北京,上海,杭州,.....)
用户访问的时候,返回里你最近的图片地址。
图片优化
图片通常是最占用流量的,pc端加载的平均图片大小600k,简直比js打包后的文件还要大,所以针对图片的优化,也是不错的收益
-
jpg
-
有损压缩
-
体积小 不支持透明 3. 用于背景图,轮播图
-
png
-
png
-
无损压缩,质量高,支持透明
-
色彩线条更丰富,小图,比如logo,商品icon
-
svg
-
文本,体积小 矢量图
-
渲染成本,学习成本
图片打包雪碧图 减少http请求次数 webpack-spritesmith
图片懒加载
图片懒加载的意思用户试图内的图片进行加载。不出现在试图内的图片不进行加载。如何进行图片懒加载
gzip
accept-encoding: gzip 开启gzi
上面都是策略性和配置项对前端进行优化,接下来讲一下如何对我们所写的代码进行优化。
**
代码性能杀手: 回流(重排)与重绘
**
回流(重排):
如添加或者删除可见的dom,元素的位置发生变化,元素的尺寸发生变化,内容发生变化。(比如文本发生变化或者图片被另一个不同的尺寸的图片所代替);页面一开始渲染的时候,这个是无法避免的。因为回流是根据视口的大小来计算元素的位置和大小的,所以窗口尺寸变化一定会引发回流。
重绘:
当页面元素样式改变不影响元素在文档流中的位置时(如background-color,border-color,visibility),浏览器只会将新样式赋予元素并进行重新绘制操作。
什么时候会触发回流
当你要用到像这样的属性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 时,你就要注意了!“像这样”的属性,到底是像什么样?——这些值有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流。
注意: 回流一定会重绘,而重绘不一定会回流。
如果代码不停的出发回流与重绘,那么页面的性能肯定会下降很多。
减少重绘与回流的几种方法
1.分离读写操作
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
这样就仅仅发生1次重排了.
div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);
触发4次
2. 样式几种改变
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
虽然现代浏览器有渲染队列的优化机制
但是古董浏览器效率仍然底下,触发了4次重排
即便这样,我们仍然可以做出优化
我们需要cssText属性合并所有样式改变
div.style.cssText = 'left:10px;top:10px;width:20px;height:20px;';
这样只需要修改DOM一次一并处理
仅仅触发了1次重排
而且只用了一行代码,看起来相对干净一些
不过有一点要注意,cssText会覆盖已有的行间样式
如果想保留原有行间样式,这样做
div.style.cssText += ';left:10px;';
除了cssText以外,我们还可以通过修改class类名来进行样式修改
div.className = 'new-class';
这种办法可维护性好,还可以帮助我们免除显示性代码
(有一点点性能影响,改变class需要检查级联样式,不过瑕不掩瑜)
3. 缓存布局信息
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
这种读操作完就执行写操作造成了2次重排
缓存可以进行优化
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
这也相当于是分离读写操作了
优化为1次重排
4. 元素批量修改
var ul = document.getElementById('demo');
var frg = document.createDocumentFragment();
for(var i = 0; i < 5; i++){
var li = document.createElement('li');
var text = document.createTextNode(i);
li.appendChild(text);
frg.appendChild(li);
}
5. (脱离文档流)
动画效果应用到position属性为absolute 或者fixed 元素上
6. css3 硬件加速 (GPU加速)
比起考虑如何减少回流,重绘,我们更期望的是,根本不要回流和重绘:tansform\option\filters...这些属性会出发硬件加速,不会引发回流和重绘。
缺点:
可能会引发的坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等问题。
其他:
前端部署的最佳实战(缓存)
怎么最好的利用缓存
- html使用nocache
- 文件加指纹,并且修改文件名 xx.dd33.js
- 静态资源都在cdn专门的cdn域名
- cdn缩短用户和服务端的距离,提升效率
- 浏览器对一个域名的并发数有限制,所以用cdn域名专门加载静态资源。
- html和js css 要分开上线
- 先上拿一个
- 先上html,加载的js如果缓存使用 老的js 报错
- 先上js,如果加载js缓存失效,老的html加载了新的js报错
- js由于修改了内容生成新的文件
- 线上静态资源(缓存设置时间比较长,有变化,就是新文件)
- 在上html(不设置强缓存)
函数截流与防抖
// 截流
const throtter = (func,wait=100)=>{
let lastTime = 0;
return (...args)=>{
let now = new Date();
if(now-lastTime>wait){
lastTime = new;
func.apply(this,args)
}
}
}
const debounce = (func,wait=100)=>{
let timer = 0;
return (...args)=>{
if(timer){
clearTimeout(timer);
}
timer = setTimeout(()=>{
func.apply(this,args)
},wait)
}
}
vue-virtual-scroller
骨架屏幕
npm install vue-skeleton-webpack-plugin