性能浅尝

性能优化是一门大学问

老生常谈之————从输入URL到页面呈现发生了什么

  • 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  • 建立TCP连接(三次握手);
  • 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器
  • 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器
  • 浏览器将该 html 文本并显示内容
  • 释放 TCP连接(四次挥手)

参考:https://segmentfault.com/a/1190000018827395

浏览器解析数据,绘制渲染页面的过程

  • 先预解析(将需要发送请求的标签的请求发出去)
  • 从上到下解析html文件
  • 遇到HTML标签,调用html解析器将其解析DOM树
  • 遇到css标记,调用css解析器将其解析CSSOM树
  • link 阻塞 - 为了解决闪屏,所有解决闪屏的样式
  • style 非阻塞,与闪屏的样式不相关的
  • 将DOM树和CSSOM树结合在一起,形成render树
  • layout布局 render渲染
  • 遇到script标签,阻塞,调用js解析器解析js代码,可能会修改DOM树,也可能会修改CSSOM树
  • 将DOM树和CSSOM树结合在一起,形成render树
  • layout布局 render渲染(重排重绘)

重绘和回流

image.png

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

回流reflow

定义

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。

产生原因
  • 页面渲染初始化
  • DOM结构改变,比如删除了某个节点
  • render树变化,比如减少了padding
  • 窗口resize
  • 最复杂的一种:获取某些属性,引发回流,如window.getComputedStyle 方法

重绘Repaint

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

  • 在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

回流与重绘区别

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

requestAnimationFrame(请求动画帧)

  • window.requestAnimationFrame(): 该方法由浏览器调用,在浏览器下次重绘之前调用,更新下一帧动画。
  • 通常我们电脑为60Hz屏幕,即1000/60 = 16.7ms刷新一次。与人眼的视觉停留效应时间相同,所以在电脑以60Hz的频率不断更新屏幕上的图像时,人眼并不会感觉出来。例如王者荣耀,当fps(framePerSecond)低于60时,人眼会明显的察觉不流畅。
  • 该方法在每次刷新的间隔中会执行一次,因此不会丢帧,不会卡顿。

requestAnimationFrame的优势

优势

  • 由系统决定回调函数的执行时机:保证回调函数稳定的在每一帧最开始触发
  • CPU节能:当页面处于未激活或者销毁的状态时,页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。而通过setTimeout实现的动画不会
  • 函数节流:在高频率事件(resize,scroll等)中,函数只被执行一次

用法

var progress = 0;
    //回调函数
    function render() {
     progress += 1; //修改图像的位置
     if (progress < 100) {
     //在动画没有结束前,递归渲染
     window.requestAnimationFrame(render);
     }
    }
    //第一帧渲染
    window.requestAnimationFrame(render);</pre>

流畅网页标准 RAIL

Chrome团队提出了一个以用户为中心的性能模型被称为RAIL,它为工程师提供一个目标,只要达到目标的网页,用户就会觉得很流畅;它将用户体验拆解为一些关键操作,例如:点击,加载等;并给这些操作规定一个目标,例如:点击一个按钮后,多长时间给反馈用户会觉得流畅

  • response: 研究表明,100ms内对用户的输入操作进行响应,通常会被人类认为是立即响应。时间再长,操作与反应之间的连接就会中断,人们就会觉得它的操作有延迟。例如:当用户点击一个按钮,如果100ms内给出响应,那么用户就会觉得响应很及时,不会察觉到丝毫延迟感.
  • animation: 现如今大多数设备的屏幕刷新频率是60Hz,也就是每秒钟屏幕刷新60次;因此网页动画的运行速度只要达到60FPS,我们就会觉得动画很流畅。通常浏览器需要花费一些时间将每一帧的内容绘制到屏幕上(包括样式计算、布局、绘制、合成等工作),所以通常我们只有10毫秒来执行JS代码。
  • 为了更好的性能,通常我们会充分利用浏览器空闲周期Idle Period做一些低优先级的事情。例如:在空闲周期预请求一些接下来可能会用到的数据或上报分析数据等。
  • load: 如果不能在1秒钟内加载网页并让用户看到内容,用户的注意力就会分散。用户会觉得他要做的事情被打断,如果10秒钟还打不开网页,用户会感到失望,会放弃他们想做的事,以后他们或许都不会再回来。

浏览器本地存储

浏览器的本地存储主要分为CookieWebStorageIndexedDB, 其中WebStorage又可以分为localStoragesessionStorage

cookie

Cookie 本质上就是浏览器里面存储的一个很小的文本文件,内部以键值对的方式来存储(在chrome开发者面板的Application这一栏可以看到)。向同一个域名下发送请求,都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,便能拿到客户端的状态。总的来说cookie的作用就是做状态存储的。

cookie的缺陷
  • 容量小:Cookie 的体积上限只有4KB,只能用来存储少量的信息。
  • 性能缺陷:Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie,这样随着请求数的增多,其实会造成巨大的性能浪费的,因为请求携带了很多不必要的内容。
  • 安全缺陷:由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获

localStorage

生命周期是永久,用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

优点
  • localStorage 的容量上限为5M,相比于Cookie的 4K 大大增加。当然这个 5M 是针对一个域名的,因此对于一个域名是持久存储的。
  • 只存在客户端,默认不参与与服务端的通信。这样就很好地避免了 Cookie 带来的性能问题和安全问题。
  • 接口封装。通过localStorage暴露在全局,并通过它的 setItem 和 getItem等方法进行操作,非常方便。
使用方式 setItem/getItem
localStorage.setItem("name", "sanyuan"); 
let name = localStorage.getItem("name");
应用场景

利用localStorage的较大容量和持久特性,可以利用localStorage存储一些内容稳定的资源,比如官网的logo,存储Base64格式的图片资源

sessionStorage

类似localStorage,但不是永久储存

特点
  • 容量上限也为 5M。
  • 只存在客户端,默认不参与与服务端的通信。
与localStorage区别

sessionStorage和localStorage有一个本质的区别,那就是前者只是会话级别的存储,并不是持久化存储。会话结束,也就是页面关闭,这部分sessionStorage就不复存在了。

IndexedDB

IndexedDB是运行在浏览器中的非关系型数据库, 本质上是数据库,绝不是和刚才WebStorage的 5M 一个量级,理论上这个容量是没有上限的。

防抖与节流

防抖debounce

当事件持续触发时,不执行处理函数,再规定的时间内没有触发事件,才执行处理函数。

function debounce(fn, wait) {    
    var timeout = null;    
    return function() {        
        if(timeout !== null)   clearTimeout(timeout);        
        timeout = setTimeout(fn, wait);    
    }
}
// 处理函数
function handle() {    
    console.log(Math.random()); 
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
  • 当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次,也就是说在持续触发scroll事件的过程中,事件处理函数handle一直没有执行。

节流throttle

  • 节流的核心思想: 如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器任务。
function throttle(fn, interval) {
  let flag = true;
  return funtion(...args) {
    let context = this;
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(context, args);
      flag = true;
    }, interval);
  };
};

图片一定要用懒加载

clientHeight、scrollTop 和 offsetTop

首先给图片一个占位资源:

<img src="default.jpg" data-src="http://www.xxx.com/target.jpg" />

通过监听 scroll 事件来判断图片是否到达视口:

let img = document.getElementsByTagName("img");
let num = img.length;
let count = 0;//计数器,从第一张图片开始计

lazyload();//首次加载别忘了显示图片

window.addEventListener('scroll', lazyload);

function lazyload() {
  let viewHeight = document.documentElement.clientHeight;//视口高度
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;//滚动条卷去的高度
  for(let i = count; i <num; i++) {
    // 元素现在已经出现在视口中
    if(img[i].offsetTop < scrollHeight + viewHeight) {
      if(img[i].getAttribute("src") !== "default.jpg") continue;
      img[i].src = img[i].getAttribute("data-src");
      count ++;
    }
  }
}

getBoundingClientRect

用另外一种方式来判断图片是否出现在了当前视口, 即 DOM 元素的 getBoundingClientRect API。

function lazyload() {
  for(let i = count; i <num; i++) {
    // 元素现在已经出现在视口中
    if(img[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
      if(img[i].getAttribute("src") !== "default.jpg") continue;
      img[i].src = img[i].getAttribute("data-src");
      count ++;
    }
  }
}

CSR与SSR

CSR客户端渲染

  • 原理:客户端渲染简单理解就是浏览器发送页面请求,服务器返回的是一个模板页面,浏览器从上至下解析过程中需要发送ajax请求获取数据,最后再调用模板引擎渲染HTML结构,并把渲染后的结果添加到页面指定容器中。
  • 个人理解:前端向服务器发送get请求,服务器返回响应,先返回index.html⽂件,浏览器解析HTML标签,发现需要获取资源的标签(link,script)时,就再次发送请求。然后在生成cssonTree,domTree,合成renderTree,在布局、绘制,最终渲染到页面。
  • 单页面应用seo不友好:因为搜索引擎爬虫抓取时,指挥抓取HTML文件。单页面应用一般是返回一个HTML文件,然后根据诸多的link/script标签再次发送请求。

SSR服务端渲染

原理

浏览器发送请求后,服务器把客户端网页和数据在后台渲染解析,之后把渲染后的结果返回客户端。

SSR的几种方式

预渲染

  • webpack插件:prerender-spa-plugin
    • 根据你的路由配置,提前把React、vue的组件动态⽣成的dom,提前⽣成
      静态html⽂件。对于静态⻚⾯⾮常⼤
    • 存在的问题,样式和⼀些其他需要发请求的内容,他还是需要到前端来
      后,才能拿到资源。

Node.js同构、redux代码注⽔

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容