# 前端性能优化

# 基础

  • 浏览器的渲染过程
  • DNS 详解
  • TCP 三次握手与四次挥手
  • HTTP 缓存机制
  • CDN 与集群

# HTTP 协议

# 处理流程

1,用户输入网址 2,浏览器得到网址,并通过防火墙 3,在 internet 里找到 DNS Server,DNS 把网址解析为 ip_address 4,浏览器带着 ip_address 去找到 Hosting Server[托管服务器]- 浏览器发送 Http 请求 5,托管服务器分配一个可用资源返回给浏览器,浏览器携带内容回程 - 服务器处理请求并返回资源文件 6,浏览器处理资源文件-用户在浏览器上看到反馈

# 网络请求阶段关键节点

PerformanceTiming -> 提供了在加载和使用当前页面期间发生的各种事件的性能计时信息,返回毫秒级

提示卸载
  <- performance.timing.navigationStart
  <- performance.timing.redirectStart
重定向
  <- performance.timing.redirectEnd
  <- performance.timing.fetchStart
应用缓存
  <- performance.timing.domainLookupStart
DNS
  <- performance.timing.domainLookupEnd
  <- performance.timing.connectStart
TCP <- performance.timing.secureConnectionStart
  <- performance.timing.connectEnd
  <- performance.timing.requestStart
请求
  <- performance.timing.responseStart
响应
  [卸载]  <- performance.timing.unloadEventStart
         <- performance.timing.unloadEventEnd
  <- performance.timing.domLoading
处理
  <- performance.timing.domInteractive
  [DOMContentLoaded]  <- performance.timing.domContentLoadedEventStart
                      <- performance.timing.domContentLoadedEventEnd
  <- performance.timing.domComplete
  <- performance.timing.loadEventStart
加载
  <- performance.timing.loadEventEnd
  • 较有用的页面性能数据大概包括
window.onload = function () {
  setTimeout(function () {
    let t = performance.timing,a = [];
    a.push({'DNS查询耗时': (t.domainLookupEnd - t.domainLookupStart).toFixed(0)})
    a.push({'TCP链接耗时': (t.connectEnd - t.connectStart).toFixed(0)})
    a.push({'request请求耗时': (t.responseEnd - t.responseStart).toFixed(0)})
    a.push({'解析dom树耗时': (t.domComplete - t.domInteractive).toFixed(0)})
    a.push({'白屏时间': (t.responseStart - t.navigationStart).toFixed(0)})
    a.push({'domready时间': (t.domContentLoadedEventEnd - t.navigationStart).toFixed(0)})
    a.push({'onload时间||总下载时间': (t.loadEventEnd - t.navigationStart).toFixed(0)})

    if (t = performance.memory) {
      a.push({'js内存使用占比 :': (t.usedJSHeapSize / t.totalJSHeapSize * 100).toFixed(2) + '%'})
    }
    console.log(a)
  })
}

navigation.timing 提供可用于衡量一个网站性能的数据,提供的是端到端延迟数据 resource timing 提供让用户查看一个资源从输入 url 到下载下来经历各个过程所消耗的时间

# DNS 详解
  • DNS(Domain Namee System), 域名系统,用于将域名转换为 IP
  • 顶级域名
  • 域名资源记录
  • 域名服务器
  • 域名解析
                          -> [com] Root Server
www.google.com -> server  -> [google.com] TLD Server
                          -> [www.google.com] Name Server
# 域名资源记录
  1. SOA (StartOf Authority,起始授权记录) 一个区域解析库有且只能有一个 SOA 记录,并且必须放在第一条
  2. A 记录 (主机记录) 用于名称解析的重要记录,将特定的主机名映射到对应主机的 IP 地址上
  3. CNAME 记录 (别名记录) 用于返回一个域名,即当前查询的域名是另一个域名的跳转,主要用于域名的内部跳转,为服务器配置提供灵活性
  4. NS 记录 (域名服务器记录) 用于返回保存下一级域名信息的服务器地址。该记录只能设置为域名,不能设置为 IP 地址。
  5. MX (邮件记录) 用于返回接收电子邮件的服务器地址
  6. IPv6 主机记录 (AAAA 记录) 与 A 记录对应,用于将特定的主机映射到一个主机的 IPv6 地址
# TCP 三次握手与四次挥手
  • OSI 模型
数据接收端   ⏫ 解封                DATA 数据发送端  ⏬ 封装
  应用层                       AH  DATA  应用层
  表示层                    PH AH  DATA  表示层
  会话层                SH  PH AH  DATA  会话层
  传输层            TH  SH  PH AH  DATA  传输层
  网络层        NH  TH  SH  PH AH  DATA  网络层
 数据链路层 DT  NH  TH  SH  PH AH  DATA 数据链路层
  物理层 <----------- 比特流  <---------- 物理层
OSI中的层       功能                                TCP/IP协议族
 物理层         以二进制数据形式在物理媒体上传输数据     ISO2110, IEEE802, IEEE802.2
 数据链路层      传输有地址的帧以及错误检测功能          SLIP, CSLIP, PPP, ARP, RARP, MTU
 网络层         为数据包选择路由                      IP, ICMP, RIP, OSPF, BGP. IGMP
 传输层         提供端到端的接口                      TCP, UDP
 会话层         解除或建立与别的节点的联系              没有协议
 表示层         数据格式化,代码转换,数据加密           没有协议
 应用层         文件传输,电子邮件,文件服务,虚拟终端    TFTP, HTTP, SNMP, FTP,SMTP,DNS,Telnet
  • TCP 在其协议中使用大量的标志位或 1 位(bit)布尔域来控制状态,常用的 3 个标志位
  • SYN - 创建一个连接
  • FIN - 终结一个连接
  • ACK - 确认接收到的数据
Client                                    Server
// 三次握手
                        SYN seq=x
SYN_SENT(connect())  ------------------>  LISTEN(listen())
                      SYN seq=y, ACK=x+1    | SYN_RCVD
ESTABLISHED          <------------------    |
        |       ACK=y+1
        |------------------------------->  ESTABLISHED established 建立连接
// 数据传输
                seq=x+1 ACK=y+1
(write())  ----------------------------->  (read())
                    ACK x+2                   |
           <-----------------------------     |
// 断链接四次挥手
                      FIN seq=x+2  ACK=y+1
FIN_WAIT_1(close()) --------------------->  CLOSE_WAIT
                          ACK x+3               |
FIN_WAIT_2          <---------------------      |
                        FIN seq=y+1             |
TIME_WAIT           <---------------------      | LAST_ACK (close())
        |             ACK=y+2
        |   ------------------------------>
# CDN

CDN 的全称是 Content Delivery Network, 即内容分发网络, CDN 是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥堵,提高用户访问响应速度和命中率,CDN 的关键技术主要有内容存储和分发。

# 缓存机制

缓存会根据请求保存内容的副本,当下一次请求到来时,如果相同的 url,并且缓存存在,并且有效,则用副本响应访问请求,而不是向源服务器在发请求访问

# 第一次请求

浏览器请求 -> 无缓存 -> 向 web 服务器请求 -> 请求响应,缓存协商 -> 呈现 缓存协商:

  1. 是否缓存 Expires、Cache-Control
  2. 缓存时间
  3. Etag
  4. Last-Modified
  5. ...
# 再次请求
                                  浏览器请求
                                    有缓存
                    缓存过期 ----------|------------------- 缓存没过期
              没有Etag-|--------------有Etag                     |
                      |                |                   从缓存中读取
                      |          向web服务器请求带 If-None-Match
                      |                  |
没有Last-Modified-|-有Last-Modified     服务器决定 200 / 304
       |                  |               |
       |                  |             200 服务端请求   304   缓存读取
       |                  |
       |                  |
  向web服务器请求   向web服务器请求带If-Modified-Since
       |                  |
请求响应,缓存协商   服务器决定 200 / 304
  • Etag: Web 服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)
  • If-None-Match: 当资源过期时(使用 Cache-Control 标识的 max-age),发现资源具有 Etage 声明,则再次向 web 服务器请求时带上头If-None-Match(Etag 的值)。web 服务器收到请求后发现头部有If-None-Match则被请求资源的相应校验串进行对比,决定 200 或 304
  • Last-Modified:标示这个响应资源的最后修改时间。web 服务器在响应请求时,告诉浏览器资源的最后修改时间。
  • If-Modified-Since: web 服务器发现请求头带有If-Modified-Since时,比对请求资源的修改时间与If-Modified-Since的值,从而判断资源文件是否过期

# 性能优化范畴(客户端)

浏览器重绘和回流 节流设计 资源加载和利用:懒加载、高效缓存 浏览器存储 DOM 树模型 CSS 解析器 构架渲染树

# 具体策略

# 离线缓存

basket.js

1.obj= {
  a.js: "a.xxx.js",
}
2. active.js
3. a.js
4. active("a.js")
5.
// 没有localStorage
localStorage["a.js"] ->
 script src="a.js" -> localStorage["a.js"] = "a.xx.js"
 localStorage["a.xx.js"] = "xxx";
// 有localStorage
localStorage["a.js"] == obj["a.js"];
eval(localStorage["a.xx.js"])

# 前端 ORM 存储方案

localForage

# last-modified ETag expires cache-control

ETag -> 毫秒级的 expires -> 有时间间隔

# expires 问题
  1. 会过期
  2. 修改 js 得改服务器
  3. 有些浏览器不支持 expires cache-control 策略
  4. 浏览器会清除本地缓存
  5. 多用于三方库

# 重排 重绘

fps

# 1, 网页整体渲染过程

  • 1 获取 dom 分割层
  • 2 根据每层节点结算样式结果 Recalculate Style
  • 3 为每个节点生成图形和位置 Layout
  • 4 将每个节点回执填充到当前帧的位图中 Paint
  • 5 将图层上传到 GPU(显卡) GPU bitmap 专门处理图形
  • 6 显卡根据符合要求的多个图层合并成图像 给用户看 Composite Layers

# 2, 渲染阶段 Layout -> Paint -> Composite Layer

# 3, 什么情况下会分层

根元素 position transfrom 半透明 滤镜 canvas video overflow

  • GPU 直接参与 css3d video webglk transform 滤镜 硬件加速

# 4,重排一定会引起重绘,重绘不一定引起出重排

# 5,重排引起的条件比较多

  • 添加或删除元素
  • 元素位置变化
  • 元素尺寸变化(盒模型)
  • 页面的初始化
  • 读取一些属性的时候,会引起重排(offset, scrolltop, client, width)
//
var h1 = $("#h1").clientheight;
$("xx").css("height", h1);

获取重绘的时间: window.performance.getEntriesByType("paint")

# 一些概念

  • TTFB: Time To First Byte 首字节时间
  • SSR CSR 服务器渲染和客户端渲染
    • CSR: vue->index.html->div->fetch->router->model->view->methods
    • SSR TTI
  • FP: first-paint 首次绘制
  • FCP: first-contentful-paint 有实际的内容 包含页面的基本框架,但没有数据内容
  • FMP: First Meaningful Paint 首次有意义的绘制
  • Isomoraphic JavaScript 同构化
  • Long tasks 超过 50ms 的任务
  • TTI Time To Interactive 可交互时间
// FCP.js
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry);
  }
});
observer.observe({ entryTypes: ["paint"] });
const arr = [];
for (var i = 0; i < 1000000; i++) {
  arr.push(i);
}
console.log(arr);
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry);
  }
});
observer.observe({ entryTypes: ["longtask"] });
// 主线程->页面阻塞
// longtask太长  定位到
requestAnimationFrame();
// react 16.8 fiber 核心算法
requestIdleCallback();  // 空闲api 去调用算法
=>
1000 / 30 = 33.3 // 秒/帧
1000 / 60 = 16.7 // 秒/帧
16.7 system + 程序
  • 开发一款 mini 性能监控平台统计页面核心指标
  • 使用 X-Tag 完成直接刷新页面 SSR 切页 SPA
  • 使用 localForage 完成前端缓存的管理
SSR + CSR 实现逻辑
// layout.html
// 地址变了->
// 监听
//
if(不是ajax){
  // 直接输出页面  传统的
  res.render
} else {
  // 用ajax 返回一个对象
  res.body = {
    html: "",
    script: "",
    css: "",
  }
}
res.body = "xxx";

# 为什么出现白屏

    1. css & js 文件获取
    1. JS 文件解析
    1. DOM 生成
    1. CSSOM 生成

# 更快的代码

BOM/DOM 交互优化 常见算法介绍 常见写法优化,编译器优化

# React 性能优化

# Vue 性能优化

#

如何找出系统中的性能瓶颈 常见性能优化策略 如何设计缓存机制 在 Vue 和 React 内部如何做性能优化 如何做搜索引擎 SEO

前端工程化与性能优化的关系,在项目中性能优化的经验

静态资源放 CDN 的好处

缓存前端静态资源的常用方法

描述网络请求阶段的关键节点,开发一个性能统计平台的思路

降低首屏时间的方法

网站并发限制及请求很多请求还不能解决静态资源请求的过多的优化时段

优化 Node 服务的手段

雅虎军规

获取到用户的网络及机型的方法