Web 性能优化

Web 性能优化

DNS

  • DNS => Domain Name System
  • 域名需要转化成 IP => 浏览器(缓存) -> 操作系统(hosts) -> 运营商

TCP 连接

  • TCP => Transmission Control Protocol => 传输控制协议
  • 三次握手 => 确保客户端和服务端都可以收发消息
TCP 三次握手和四次挥手

HTTP 请求

  • HTTP => Hypertext Transfer Protocol => 超文本传输协议

Request

动词 URL HTTP/1.1
Accept: text/html
Host: baidu.com
Connection: keep-alive
Content-Type: application/json
...

{"id": "1"}

Response

HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: session_id=xxx; Cache-Control: max-age=3600
Connection: keep-alive
...

{"info", "this is response"}

HTML 解析过程

  • 解析 HTML 会构建 DOM 树
  • 解析 CSS 会构建 CSS 树
  • JS 的下载和执行会阻塞 HTML 的解析 => 下载和执行 JS 文件会修改 DOM 树
    1. 执行 JS 可以会修改 DOM 树
    2. 解析是一行一行的,只有解析到 script 行,才能去下载
  • CSS 的下载和解析会阻塞 JS 的执行,JS 的执行需要等到 CSS 下载和解析结束 => JS 需要读取 CSS 结果
HTML 解析过程

async/defer

  • 需要解析 a.com 和 b.com,并且 b.com 必须要等到 1.js 下载并执行完之后才能解析

    // 在 index.html 中的  里面写
    
    
    
    // 在 index.html 的响应头中写
    Link: ; rel=dns-prefetch
    

    TCP 连接

    连接复用

    • Connection: keep-alive => HTTP 的 keep-alive 实现 TCP 连接的复用
    • 请求头中添加 Connection: keep-alive,并且在响应头中也添加 Connection: keep-alive
    • Keep-Alive: timeout = 5, max = 100 => 两次请求的时间间隔小于多少认为可以复用同一个 TCP 连接 => 请求头和响应头都可设置,最终决定于响应头
    • HTTP/1.1 及以上 Connection: keep-alive 是默认添加的
    • 串行的

    并发连接

    • 连接复用是串行的,并发连接是并行的
    • 并行连接有最大数量 => 不同的浏览器上限不同 => 大概是 4 - 12 个,大部分是 6 个
    • case: 将请求拆分 => case:不要一个 css,要多个 css,可以并行下载
    • case: 发送多个 ajax => 并行发送

    HTTP 管道化 HTTP pipelining

    • HTTP/1.1 有 bug

    HTTP/2

    • HTTP/1.1 基于字符串 => HTTP/2 基于帧 Frame
    • HTTP/2 每一帧的组成
      1. 9 byte => Length + Type + Flags + StreamId => 用于标记
      2. 最大 16 M => Payload => 数据
    • 请求头和响应头会被发送方压缩后,分成几个连续的 Frame 传输,接收方拼合这些 Frame 后,解压缩即可得到真正地请求头和响应头
    • 引入流 Stream的概念,一个 Stream 由双向传输的连续且有序的 Frame 组成,一个 TCP连接可以同时包含多个 Stream,一个 Stream 只用于一次请求和一次相应。Stream 之间不会相互影响
    • 头部字段改为小写,不允许出现大写
    • 引入了伪头部字段的概念,出现在头部字段前面,必须以冒号开头
    • 服务端可以先发响应,客户端拿到响应结果后可以保存,之后就不需要在发对应的请求了
    // get => : 伪头 => 表明 HTTP/1.1 的第一部分
    //     => header 都是小写
    //  伪头 + header -> 可能是一个 Frame
    :method: GET
    :scheme: https
    :path: /zh-CN/docs/Web/CSS/Cascade
    accept: text/html
    cookie: xxx
    cache-control: no-cache
    
    // POST 有多个 Frame
    

    多路复用

    • 在一个 TCP 连接中可以同时进行多个请求与响应,每一个请求+响应都在同一个 Stream 上,由于有标记的 9 个字节,所以响应能够和请求对应上
    • 一个 Stream 只能对应一个请求 + 响应
    • 有了多路复用就不需要并行连接了

    资源合并

    • Icon Font => 将图标变成字体文件
    • SVG Symbols => 使用 SVG 文件代替图标,case:一个 svg 里面有很多图标,使用
      1. 支持渐变 => Icon Font 不支持渐变
      2. SVG 编辑较 Icon Font 简单

    资源内联

    • 资源内联可以减少一些 TCP 连接
    • 但是如果文件过大,传输时间过长,不如并行
    • 小图片 => data URL => date url 可以通过 webpack 的 url-loader 编译
    • 小 CSS 文件 => => 通过 webpack 插件可以实现
    • 小 JS 文件 => => 通过 webpack 插件可以实现

    资源压缩

    • 将响应压缩之后再传递给浏览器,浏览器先解压缩再使用 => gzip
    • NGINX => https://docs.nginx.com/nginx/admin-guide/web-server/compression

    代码精简

    • HTML => 删空格、删闭合
    • CSS => 删未用
    • JS => 改名、tree shaking
    • SVG => 删除无用标签、属性
    • 图片 => 减少体积(有损/无损)

    减少 Cookie 体积

    • 最大 4kb
    • 同一个域名每次请求都带着
    • 启用新域名 => 新域名不会带着其他域名的 Cookie => 实现了连接并发和清 Cookie => cookie-free

    CDN

    • CDN => 内容分发网络 => 负载均衡
    • 域名传输到 DNS,DNS 会给一个最近的 IP
    • 优点
      1. cookie free
      2. 并行请求/多路复用
      3. 下载速度快
    • 缺点
      1. 跨域 => 设置 CORS
      2. 可控性差 => CDN 如果挂了,依赖 CDN
      3. 部署相对复杂

    缓存 & 内容协商

    • 强缓存 & 弱缓存
    • Cache-Control: max-age=3600; => 缓存1小时,如果再次请求,则不会发送请求,而是直接返回缓存内容 => memory cache | disk cache
    • 服务器主动更新缓存 => 首页不会缓存,首页引入新的文件 => 生成新的 hash
    • 内容协商 => 缓存过期后能重用吗?=> 缓存过期后再次请求就是协商请求(带上 ETag) => 服务端会对比 ETag => If-None-Match:
      1. 304 + 空响应 => Not Modify => 使用之前的缓存
      2. 200 + 新文件 => 删除或覆盖之前的缓存
    缓存 内容协商
    HTTP/1.1 Cache-Control: max-age=3600; ETag: XXX 请求头 => If-None-Match: XXX
    响应 => 304 + 空 | 200 + 新内容
    HTTP/1.0 Expires: 时间点A; Last-Modified: 时间点B 请求头 => If-Modified-Since: 时间点B
    响应 => 304 + 空 | 200 + 新内容

    Cache-Control

    • public/private => 中间设备是否可以缓存
    • max-age => 缓存时间
    • must-revalidate => 必须重新校验

    禁用缓存

    • 没有 Cache-Control 的情况下,浏览器也会缓存 => case: GET + (200 | 301)
    • 服务端 => response header =>
      1. Cache-Control: max-age=0,must-revalidate === Cache-Control: no-cache => 不缓存,但是可以协商
      2. Cache-Control: no-store => 不缓存、不协商
    • 浏览器端 =>
      1. url 添加随机数 => get /user?_=随机数
      2. request header => Cache-Control: no-cache, no-store, max-age=0

    DNS 缓存

    1. 操作系统缓存 IP
    2. 浏览器缓存 IP

    代码优化

    代码位置

    • css 文件放在前面 => 有利于用户看到样式,否则可能白屏或闪烁(firefox + css 在 body 内) => 下载过慢也可导致白屏或闪烁(firefox + css 在 body 内)
      1. 不阻塞 HTML 解析,尽早下载
      2. 防止被外部 JS 阻塞
    • JS 文件放在后面
      1. 可直接访问 DOM,无需关注 DOM ready 事件
      2. 避免阻塞 HTML 解析

    代码拆分

    • 根据变动频率进行分层。case:webpack 最终打包成 main-xxx.js(1MB),之后更改了一个文案,重新打包 main-yyy.js(1MB) => 用户需要重新下载
    • JS 分层
      1. runtime-xxx.js => webpack 升级用到的
      2. vendor-xxx.js => 第三方库 Vue | React
      3. common-xxx.js => 公司级别的基础库
      4. -index-xxx.js => 每个页面 => 上述只会更改这一个文件
      // webpack.config.js
      module.exports = {
        // -index-xxx.js
        entry: {
          app: './src/page/app.js',
          main: './src/page/main.js',
          admin: './src/page/admin.js'
        },
        plugins: [
          new HtmlWebpackPlugin({
            filename: 'app.html',
            chunks: ['app']
          }),
          new HtmlWebpackPlugin({
            filename: 'main.html',
            chunks: ['main']
          }),
          new HtmlWebpackPlugin({
            filename: 'admin.html',
            chunks: ['admin']
          })
        ],
        optimization: {
          runtimeChunk: 'single', // runtime-xxx.js
          splitChunks: {
            cacheGroups: {
              vendor: { // vendor-xxx.js
                priority: 10,
                minSize: 0, // 如果不写 0,由于 React 文件尺寸太小,会直接跳过
                test: /[\\/]node_modules[\\/]/, // 为了匹配 /node_modules/ 或 \node_modules\
                name: 'vendors', // 文件名
                chunks: 'all' // all 表示同步加载和异步加载,async 表示异步加载,initial 表示同步加载
                // 这三行的整体意思就是把两种加载方式的来自 node_modules 目录的文件打包为 vendors.xxx.js
              },
              common: { // common-xxx.js
                priority: 5,
                minSize: 0,
                minChunks: 2,
                name: 'common',
                chunks: 'all'
              }
            }
          }
        }
      }
      
    • CSS 分层
      1. reset/normalize-xxx.css => 基础
      2. vendor-xxx.css => 第三方库 antd
      3. common-xxx.css => 公司级别
      4. -index-xxx.css => 每个页面

    JS 动态导入

    • 有些 JS 文件用到的时候在下载
    const array = [1, 2, 3];
    import("lodash").then(_ => {
      const clone = _.cloneDeep(array);
    });
    
    import React, {Suspense, lazy} from 'react';
    import {BrowserRouter as Router, Route, Switch} from 'react-route-dom';
    
    const Home = lazy(() => import('./pages/Home'));
    const About = lazy(() => import('./pages/About'));
    
    const App = () => {
      
        
          
            
            
          
        
      
    }
    

    图片懒加载(Lazy Loading) 和预加载

    • 对于多屏图片,第一次请求只请求第一屏的图片,当滚动到第二屏的时候再请求第二屏的图片
    • 懒加载太慢了,第一次请求第一屏和第二屏的图片,滚动到第二屏的时候,请求第三屏的图片
    
    
    // 修改为 => placeholder.png 很小
    
    
    // 监听滚动事件 => 对于每一个下一屏的图片
    new Image().src = img.dataset.src
    // 监听 new Image 的 onload 事件,之后将 img 的 src 替换
    img.src = img.dataset.src
    

    CSS 优化

    1. 删除无用 css
    2. 使用更高效的选择器
    3. 减少重排 => reflow => 将 .left 动画更改为 transform 动画
    4. 不要使用 @import url.css => 不能并行
    5. 启用 GPU 硬件加速 => transform: translate3d(0, 0, 0)
    6. 使用缩写 =>
      1. FFFFFF -> #FFF

      2. 0.1 => .1
      3. 0px => 0

    JS 优化

    1. 尽量不用全局变量 => 全局变量过多会使变量查找变慢
    2. 尽量少操作 DOM => 可以使用 Fragment 一次性插入多个 DOM 节点
    3. 尽量少触发重排 => 可以使用节流和防抖来降低i重排频率
    4. 尽量少用闭包,避免内存泄漏 => IE 浏览器的 Bug
    5. 1W个 list 如何显示 => 虚拟滚动列表

你可能感兴趣的:(Web 性能优化)