性能优化启示录

为什么要进行性能优化

  • 57%的⽤户更在乎⽹⻚在3秒内是否完成加载

  • 52%的在线⽤户认为⽹⻚打开速度影响到他们对⽹站的忠实度

  • 每慢1秒造成⻚⾯ PV 降低11%,⽤户满意度也随之降低降低16%

  • 近半数移动⽤户因为在10秒内仍未打开⻚⾯从⽽放弃。

性能优化学徒工

雅⻁军规践⾏

  • html数量控制
    能尽量用CSS解决的就用CSS解决。(阴影,渐变)

  • 压缩,合并,MD5,CDN
    接下来请大家思考一个问题,为什么CDN对于前端这么重要?

image.png
  • 还有一个很重要的点就是离线缓存
    打开谷歌控制台的application


    application

localStorage存储本地数据

//需求是什么?
//假设我们需要请求a.xx3322.js
//在本地存储localstorage存储key为a.js,对应的value是a.xx3322.js
//key为a.xx3322.js,对应的value就是我们的目标js代码
//所以就不需要


上面主要就是实现一个小球运动的效果,当我们已经在rendering里面选中了paint flashing的时候,我们可以看到小球运动起来的效果是外面包裹着一层绿色,说明这是要进行重绘的区域

image.png

使用Chrome DevTools的performance面板可以记录和分析页面在运行时的所有活动。
https://www.cnblogs.com/xiaohuochai/p/9182710.html
image.png

loading:加载时间
scripting:脚本执行时间
rendering:重排时间
painting:重绘时间
idle:空闲时间,网站性能越好,空闲时间越长

网站的渲染流程


将上面的图从summary切换到event log

image.png

event log按照时间先后来排序
可以看到网站的渲染流程是这样的:

  1. 获取DOM进行分层
  2. 对每个图层节点进行样式的计算 Recalculate Style
  3. 为每个对应的节点图形和位置 重排Layout
  4. 对每个节点进行绘制并添加到图层位图中 Paint
    (并不是每个图层都会GPU进行参与)
    只有Composite Layers才会让GPU参与
  5. 将这个位图上传至GPU 旋转、缩放、偏移、修改透明

所以渲染过程总的来说是这样的:Layout -》 Paint -》 Composite Layers

我们说DOM会进行分层,那么什么元素会独立成层呢?


根元素、position、transfrom、半透明元素、CSS滤镜、Video 、Overflow

我们说GPU跑起来会比CPU快,那么哪些元素属性会让GPU参与进来呢?


CSS3D、Video、Webgl(https://github.com/lgwebdream/gpu.js)、CSS滤镜、transfrom

CPU和GPU到底有什么区别呢?


https://www.zhihu.com/question/19903344

CPU即中央处理器,GPU即图形处理器。其次,要解释两者的区别,要先明白两者的相同之处:两者都有总线和外界联系,有自己的缓存体系,以及数字和逻辑运算单元。一句话,两者都为了完成计算任务而设计。

总结一下:
相同之处:总线和外界联系、缓存体系、数字和逻辑与预算单元、计算而生
不同之处:CPU主要负责和操作系统应用程序,GPU显示数据相关
http://www.sohu.com/a/200435336_463987
还要推一推gpu.js这个库
https://github.com/gpujs/gpu.js
GPU.js is a JavaScript Acceleration library for GPGPU (General purpose computing on GPUs) in JavaScript. GPU.js will automatically compile simple JavaScript functions into shader language and run them on the GPU. In case a GPU is not available, the functions will still run in regular JavaScript.
也就是说GPU.js会自动的将简单的js函数翻译成shader 语言并放在GPU上面运行他们。

像素管道

像素管道是网页性能优化的灵魂,让我们来看看什么是像素管道

image.png

上图就是像素管道,通常我们会使用JS修改一些样式,随后浏览器会进行样式计算,然后进行布局,绘制,最后将各个图层合并在一起完成整个渲染的流程,这期间的每一步都有可能导致页面卡顿。

注意,并不是所有的样式改动都需要经历这五个步骤。举例来说:如果在JS中修改了元素的几何属性(宽度、高度等),那么浏览器需要需要将这五个步骤都走一遍。但如果您只是修改了文字的颜色,则布局(Layout)是可以跳过去的。

除了最后的合成,前面四个步骤在不同的场景下都可以被跳过。例如:CSS动画就可以跳过JS运算,它不需要执行JS。

通过录制performance我们可以看到主线程的任务。
我们可以放大主线程从而精准的看到每一帧浏览器都执行了哪些任务以及每个任务耗费了多长时间。如下图所示:

image.png

我们如何改进上面的代码呢,减少重绘呢?

使用transform来代替top和left

我们可以看到https://csstriggers.com/transform上面对于transform的描述是
Changing transform does not trigger any geometry changes or painting, which is very good. This means that the operation can likely be carried out by the compositor thread with the help of the GPU.
也就是说改变transform并不会触发几何图形的更改或者重绘,transform的操作可以合成器线程在GPU的帮助下执行。

css-triggers给出了不同的CSS属性被更改后会触发像素管道的那些步骤。
简单来说,像素管道经历的步骤越多,渲染时间就越长,单个步骤内也可能因为某个原因而变得耗时很长。

将上面代码的keyframes部分更改成:

        0%{
            transform: translate(0)
        }
        25%{
            transform: translate(200px,0)
        }
        50%{
            transform: translate(200px,200px)
        }
        75%{
            transform: translate(0,200px)
        }

我们此时看到小球在运动,但是已经没有绿色了,已经没有重排了!!

image.png

神奇!!!
我们再来看看录制的summary
image.png

我的天呐!真神奇。换了transforms每次运动就不用重绘重排了,都是合成层和GPU在操作了,而且只要GPU开启,速度就会快很多。
https://csstriggers.com/这个网站拿好不送!!!

总结
CSS动画我们可以通过降低绘制区域并且使transform属性来完成动画,同时我们需要管理好图层,因为绘制和图层管理都需要成本,通常我们需要根据具体情况进行权衡并做出最好的选择。

重排

什么会引起重排?


  1. 添加或者删除元素的时候
  2. 元素的位置发生改变
  3. 元素的-webkit-box-sizing: border-box;不会让我们的盒子发生太多的变化
    如果用标准盒子模型的话,盒子越来越大
  4. 页面初始化
  5. 内容变化(没有撑开盒)
  6. js 读取一下几个值 offset、scroll、width、getComputerStyle
    为什么js读取的时候会引起重排?
    var ele = document.getElementById('myDiv');
    ele.style.borderLeft = '1px';
    ele.style.borderRight = '2px';
    ele.style.padding = '5px';

乍一想,元素的样式改变了三次,每次改变都会引起重排和重绘,所以总共有三次重排重绘过程,但是浏览器并不会这么笨,它会把三次修改“保存”起来(大多数浏览器通过队列化修改并批量执行来优化重排过程),一次完成!但是,有些时候你可能会(经常是不知不觉)强制刷新队列并要求计划任务立即执行。获取布局信息的操作会导致队列刷新,比如:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight

  • scrollTop, scrollLeft, scrollWidth, scrollHeight

  • clientTop, clientLeft, clientWidth, clientHeight

  • getComputedStyle() (currentStyle in IE)
    将上面的代码稍加修改,

    var ele = document.getElementById('myDiv');
    ele.style.borderLeft = '1px';
    ele.style.borderRight = '2px';
    
    // here use offsetHeight
    // ...
    ele.style.padding = '5px';
    

因为offsetHeight属性需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并触发重排以返回正确的值(即使队列中改变的样式属性和想要获取的属性值并没有什么关系),所以上面的代码,前两次的操作会缓存在渲染队列中待处理,但是一旦offsetHeight属性被请求了,队列就会立即执行,所以总共有两次重排与重绘。所以尽量不要在布局信息改变时做查询。

我们可以使用requestAnimationFrame,拆开来写,就给了浏览器优化的机会了。

  var ele = document.getElementById('myDiv');
  // here use offsetHeight
  // ...
  requestAnimationFrame(function(){
      ele.style.padding = '5px';
      ele.style.borderLeft = '1px';
      ele.style.borderRight = '2px';
  })

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
requestAnimationFrame告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

页面加载性能优化

必须知道的概念
TTFB(Time To First Byte ):⾸字节时间
FP(First Paint ):⾸次绘制,仅有⼀个div根节点。
FCP(First Contentful Paint): ⾸次有内容的绘制,包含⻚⾯的基本框架,但没有数据内容。
FMP(First Meaningful Paint):⾸次有意义的绘制,包含⻚⾯所有元素及数据
TTI(Time To Interactive):可交互时间
Long tasks:超过了 50ms 的任务
SSR&&CSR:服务端渲染和客户端渲染
Isomorphic JavaScript:同构化

image.png

image.png

FCP.png

FMP.png

类比于VUE
image.png

created 类比于 FP(首次绘制,只有一个app空节点)
mounted 类比于 FMP(页面基本框架绘制完成)

Performance — 前端性能监控利器
Performance是一个做前端性能监控离不开的API,最好在页面完全加载完成之后再使用,因为很多值必须在页面完全加载之后才能得到。最简单的办法是在window.onload事件中读取各种数据。
https://www.cnblogs.com/bldxh/p/6857324.html
https://cloud.tencent.com/developer/news/301840
从输入url到用户可以使用页面的全过程时间统计,会返回一个PerformanceTiming对象,单位均为毫秒。

image.png

每一个performance.timing属性都表示一个页面事件(例如页面发送了请求)或者页面加载(例如当DOM开始加载),测量以毫秒的形式从1970年1月1日的午夜开始。结果为0表示该事件未发生(例如redirectEnd或者redirectStart等)
image.png

其中有个方法叫getEntries()
获取所有资源请求的时间数据,这个函数返回一个按startTime排序的对象数组,数组成员除了会自动根据所请求资源的变化而改变以外,还可以用mark(),measure()方法自定义添加,该对象的属性中除了包含资源加载时间还有以下五个属性。
name:资源名称,是资源的绝对路径或调用mark方法自定义的名称
startTime:开始时间
duration:加载时间
entryType:资源类型,entryType类型不同数组中的对象结构也不同!具体见下
initiatorType:谁发起的请求,具体见下
话不多说,咱们来写写代码。

    

    const obsever = new PerformanceObserver((list)=>{
        for(const entry of list.getEntries()){
            console.log(entry.entryType);
            console.log(entry.startTime);
            console.log(entry.duration);
        }
    })

    obsever.observe({entryTypes:['paint']});

结果如下:


image.png

或者可以直接通过window.performance.getEntriesByType("paint")就可以取得FP和FCP的值

image.png

FMP主要用来给页面打点。

五分钟撸一个前端性能监控工具
http://web.jobbole.com/94938/

聊聊performance中的long task
什么是 long task?

image.png

https://www.itcodemonkey.com/article/10654.html

简单而言,任何在浏览器中执行超过 50 ms 的任务,都是 long task

那么 long task这个时间是怎么得来的?
因为浏览器是单线程,这意味着同一时间主线程只能处理一个任务,如果一个任务执行时间太长,浏览器就无法执行其他任务,用户就会感觉浏览器被卡死,因为他的输入得不到任何响应。

为了100ms内能给出相应,将空闲周期执行的任务限制在50ms意味着,即使用户的输入行为发生在任务刚执行时。浏览器仍有50ms来相应用户的输入。

long task 会长时间占据主线程资源,进而阻碍了其他关键任务的执行/响应,造成页面卡顿。

常见场景如:

不断计算 DOM 元素的大小、位置,并且根据结果对页面进行 relayout;

一次性生成十分庞大的 DOM 元素,如大型表单;

1000000次的循环计算;

long task的基本属性
Long Tasks API 定义了 PerformanceLongTaskTiming接口,用于描述 long task
一般而言,name + attribution 就可以基本定位出 long task 的来源:

name:告诉我们来源是

你可能感兴趣的:(性能优化启示录)