阅读 488

前端性能优化的最佳实践

问题:

  1. 有没有经常被产品或者qa说,你这个页面打开真慢?
  2. 有没有碰到过手机打开一个页面,发现卡顿,不流畅?

......

如果你遇到了上述问题,说明你对页面的性能优化没有做好。这篇文章会从宏观的角度来看待前端性能优化,保证大家有一个整体的思路来解决这个问题。

先从一个到经典的面试题开始

问题:用户在浏览器输入网址,看到整个页面,这中间发生了什么?
这个问题包含了整个互联网的运行过程,只有你理解了这个过程,才明白作为一个前端工程师,我们能做什么。

image.png


输入网址到底发生了什么?

  1. 用户输入www.baidu.com
  2. 浏览器通过DNS,把url解析成IP
  3. 和IP地址建立TCP链接,发送http请求 (三次握手,四次挥手
  4. 服务器接受请求,查库,读写文件,拼接好返回http响应
  5. 浏览器收到html, 开始逐行渲染,生成dom tree。
  6. 再将css渲染成 cssOM.
  7. dom tree 与 cssOM 合并变成 render tree 渲染树。
  8. 通过回流,重回,生成页面。
  9. 加载js,执行js。


上面的九个步骤,是访问网址所需要走的流程。有了这些流程,我们可以逐个分析,进行优化。不过在进行讲解前,先要补充另一个概念。

浏览器执行 js是单线程 + 事件队列(task queue)


很多人喜欢说成: 浏览器是单线程 + 事件队列,这是不对的。当我们打开浏览器的时候,其实是操作系统为我们启动一个进程,在浏览器打开的一个个tab,就相当于一个个线程。所以js 是运行在一个个线程里面的。

**

优化点

优化,基本上分两个方向:

  1. 少加载文件,很重要
  2. 文件打包压缩
  3. 图片格式和压缩
  4. 缓存
  5. cdn 缩减距离,提高加载速度和效率
  6. srr  首页文件执行顺序
  7. lazy-load 懒加载
  8. 少执行代码
  9. 浏览器足够好的,大部分优化策略都集中在vue和react内部
  10. 长列表(内存做优化)
  11. 移动端一些机型,比如淘宝首页无限滚动,如果直接渲染
  12. dom过多会崩掉
  13. 内存存储数据过多也会崩掉
  14. 一个长度10000的商品列表,怎么显示

下面我会列举出来,哪些是可以被优化的点。
**

DNS 缓存

优化: prefetch 预获取,比如使用了cdn的域名。

<link rel="dns-prefetch" href="//domain.com">
复制代码

image.png
这是淘宝进行的DNS 缓存。

TCP和UDP进行比较

网络协议就是让机器聊天

  1. 知道目标是谁?IP协议负责寻址。
  2. TCP协议基于IP协议,负责数据的完整和有序,像快递公司
    a. 三次握手
    b. 重发,滑动窗口,粘包,慢启动等机制
    c. tcp协议操作系统自带的
  3. HTTP基于TCP负责应用层
    a. header和body构成
  4. UDP也是基于IP的,直管发和收,不管数据丢不丢
    a. udp适合什么场景?
    b. 性能好
    c. 适合性能要求高,但是不在乎偶尔丢帧的场景,包足够小,不用分包
    ⅰ. 游戏
    ⅱ. 语音视频聊天
    ⅲ. DNS(包足够小)

粘包是什么?

TCP有一个数据缓冲的策略,如果能把挨着很近的包,粘成一个包发。

使用浏览器的缓存机制

image.png


上图是浏览器缓存的一个简介,我们可以根据浏览器的缓存策略进行配置。具体的配置可以参考:什么是浏览器缓存机制?

cdn 的内部原理

cdn 就是把我们的图片,部署到各大机房(北京,上海,杭州,.....)
用户访问的时候,返回里你最近的图片地址。

图片优化

图片通常是最占用流量的,pc端加载的平均图片大小600k,简直比js打包后的文件还要大,所以针对图片的优化,也是不错的收益

  1. jpg

  2. 有损压缩

  3. 体积小 不支持透明 3. 用于背景图,轮播图

  4. png

  5. png

  6. 无损压缩,质量高,支持透明

  7. 色彩线条更丰富,小图,比如logo,商品icon

  8. svg

  9. 文本,体积小 矢量图

  10. 渲染成本,学习成本

图片打包雪碧图 减少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...这些属性会出发硬件加速,不会引发回流和重绘。
缺点:
可能会引发的坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等问题。

其他:

前端部署的最佳实战(缓存)

怎么最好的利用缓存

  1. html使用nocache
  2. 文件加指纹,并且修改文件名 xx.dd33.js
  3. 静态资源都在cdn专门的cdn域名
  4. cdn缩短用户和服务端的距离,提升效率
  5. 浏览器对一个域名的并发数有限制,所以用cdn域名专门加载静态资源。
  6. html和js css 要分开上线
  7. 先上拿一个
  8. 先上html,加载的js如果缓存使用 老的js 报错
  9. 先上js,如果加载js缓存失效,老的html加载了新的js报错
  10. js由于修改了内容生成新的文件
  11. 线上静态资源(缓存设置时间比较长,有变化,就是新文件)
  12. 在上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