性能优化是一门大学问
老生常谈之————从输入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渲染(重排重绘)
 
重绘和回流

上图就是像素管道,通常我们会使用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秒钟还打不开网页,用户会感到失望,会放弃他们想做的事,以后他们或许都不会再回来。
 
浏览器本地存储
浏览器的本地存储主要分为Cookie、WebStorage和IndexedDB, 其中WebStorage又可以分为localStorage和sessionStorage。
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⽂件。对于静态⻚⾯⾮常⼤ - 存在的问题,样式和⼀些其他需要发请求的内容,他还是需要到前端来
后,才能拿到资源。 
 - 根据你的路由配置,提前把React、vue的组件动态⽣成的dom,提前⽣成
 
Node.js同构、redux代码注⽔