最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 聊一聊前端性能与体验的优化

    正文概述 掘金(微医大前端技术)   2021-01-26   431

    前言

    性能优化 ,每个工程师跑不掉的一个话题。这里是本人总结的一些优化手法,希望对大家有所帮助,后续也会继续更新。 演示源码和 PPT 无条件分享。

    演示 PPT (一定要看,超帅)

    横屏观看更佳:http://118.25.49.69:8086

    聊一聊前端性能与体验的优化

    前端性能的影响

    前端性能的一个重要指标是页面加载时间,不仅事关用户体验,也是搜索引擎排名考虑的一个因素。

    • 来自 Google 的数据表明,一个有 10 条数据 0.4 秒能加载完的页面,变成 30 条数据 0.9 秒加载完之后,流量和广告收入下降90%
    • Google Map 首页文件大小从100KB减小到70-80KB后,流量在第一周涨了10%,接下来的三周涨了25%
    • 亚马逊的数据表明:加载时间增加100 毫秒,销量就下降 1%

    所以:重铸性能之光,我辈义不容辞?

    一、调试工具

    磨刀不误砍柴工,读完大学再打工!

    聊一聊前端性能与体验的优化 聊一聊前端性能与体验的优化

    1.1 Network

    聊一聊前端性能与体验的优化

    这里可以看到资源加载详情,初步评估影响页面性能的因素。鼠标右键可以自定义选项卡,页面底部是当前加载资源的一个概览。DOMContentLoaded DOM 渲染完成的时间,Load:当前页面所有资源加载完成的时间

    思考:如何判断哪些资源对当前页面加载无用,做对应优化?

    shift + cmd + P 调出控制台的扩展工具,添加规则 聊一聊前端性能与体验的优化

    扩展工具 更多使用姿势

    聊一聊前端性能与体验的优化

    瀑布流 waterfall

    聊一聊前端性能与体验的优化

    • Queueing 浏览器将资源放入队列时间
    • Stalled 因放入队列时间而发生的停滞时间
    • DNS Lookup DNS 解析时间
    • Initial connection 建立 HTTP 连接的时间
    • SSL 浏览器与服务器建立安全性连接的时间
    • TTFB 等待服务端返回数据的时间
    • Content Download 浏览器下载资源的时间

    1.2 Lighthouse

    聊一聊前端性能与体验的优化

    • First Contentful Paint 首屏渲染时间,1s 以内绿色
    • Speed Index 速度指数,4s 以内绿色
    • Time to Interactive 到页面可交换的时间

    根据 chrome 的一些策略自动对网站做一个质量评估,并且会给出一些优化的建议。

    1.3 Peformance

    聊一聊前端性能与体验的优化

    对网站最专业的分析~后面会多次讲到

    1.4 webPageTest

    可以模拟不同场景下访问的情况,比如模拟不同浏览器、不同国家等等,在线测试地址:webPageTest

    聊一聊前端性能与体验的优化 聊一聊前端性能与体验的优化

    1.5 资源打包分析

    webpack-bundle-analyzer

    聊一聊前端性能与体验的优化

    npm install --save-dev webpack-bundle-analyzer
    // webpack.config.js 文件
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
    module.exports={
      plugins: [
        new BundleAnalyzerPlugin({
              analyzerMode: 'server',
              analyzerHost: '127.0.0.1',
              analyzerPort: 8889,
              reportFilename: 'report.html',
              defaultSizes: 'parsed',
              openAnalyzer: true,
              generateStatsFile: false,
              statsFilename: 'stats.json',
              statsOptions: null,
              logLevel: 'info'
            }),
      ]
    }
    
    // package.json
    "analyz": "NODE_ENV=production npm_config_report=true npm run build"
    
    

    开启 source-map

    webpack.config.js

    module.exports = {
        mode: 'production',
        devtool: 'hidden-source-map',
    }
    

    package.json

    "analyze": "source-map-explorer 'build/*.js'",
    

    npm run analyze 聊一聊前端性能与体验的优化

    二、WEB API

    工欲善其事,必先利其器。浏览器提供的一些分析 API至关重要

    2.1 监听视窗激活状态

    大学都刷过慕课吧?只要离开窗口视频就会暂停~

    或者一些考试网站,提醒你不能离开当前窗口

    再或者,这种效果~ 聊一聊前端性能与体验的优化

    // 窗口激活状态监听
    let vEvent = 'visibilitychange';
    if (document.webkitHidden != undefined) {
        vEvent = 'webkitvisibilitychange';
    }
    
    function visibilityChanged() {
        if (document.hidden || document.webkitHidden) {
            document.title = '客官,别走啊~'
            console.log("Web page is hidden.")
        } else {
            document.title = '客官,你又回来了呢~'
            console.log("Web page is visible.")
        }
    }
    
    document.addEventListener(vEvent, visibilityChanged, false);
    

    其实有很多隐藏的 api,这里大家有兴趣的可以去试试看:

    2.2 观察长任务(performance 中 Task)

    const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
            console.log(entry)
        }
    })
    
    observer.observe({entryTypes: ['longtask']})
    

    2.3 监听网络变化

    网络变化时给用户反馈网络问题,有时候看直播的时候自己的网络卡顿,直播平台也会提醒你或者自动给你切换清晰度

    var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
    var type = connection.effectiveType;
    
    function updateConnectionStatus() {
      console.log("Connection type changed from " + type + " to " + connection.effectiveType);
      type = connection.effectiveType;
    }
    
    connection.addEventListener('change', updateConnectionStatus);
    

    2.4 计算 DOMContentLoaded 时间

    window.addEventListener('DOMContentLoaded', (event) => {
        let timing = performance.getEntriesByType('navigation')[0];
        console.log(timing.domInteractive);
        console.log(timing.fetchStart);
        let diff = timing.domInteractive - timing.fetchStart;
        console.log("TTI: " + diff);
    })
    

    2.5 更多计算规则

    DNS 解析耗时: domainLookupEnd - domainLookupStart
    TCP 连接耗时: connectEnd - connectStart
    SSL 安全连接耗时: connectEnd - secureConnectionStart
    网络请求耗时 (TTFB): responseStart - requestStart
    数据传输耗时: responseEnd - responseStart
    DOM 解析耗时: domInteractive - responseEnd
    资源加载耗时: loadEventStart - domContentLoadedEventEnd
    First Byte 时间: responseStart - domainLookupStart
    白屏时间: responseEnd - fetchStart
    首次可交互时间: domInteractive - fetchStart
    DOM Ready 时间: domContentLoadEventEnd - fetchStart
    页面完全加载时间: loadEventStart - fetchStart
    http 头部大小: transferSize - encodedBodySize
    重定向次数:performance.navigation.redirectCount
    重定向耗时: redirectEnd - redirectStart
    

    三、老生常谈,雅虎军规

    磨好刀了,就该想想往哪里捅比较好了~ ???

    关于雅虎军规,你知道的有多少条,平时写用到的又有哪些?针对以下规则,我们可以做很多优化工作

    聊一聊前端性能与体验的优化

    3.1 减少 cookie 传输

    cookie 传输会造成带宽浪费,可以:

    • 减少 cookie 中存储的东西
    • 静态资源不需要 cookie,可以采用其他的域名,不会主动带上 cookie。

    3.2 避免过多的回流与重绘

    连续触发页面回流操作

      let cards = document.getElementsByClassName("MuiPaper-rounded");
      const update = (timestamp) => {
        for (let i = 0; i <cards.length; i++) {
          let top = cards[i].offsetTop;
          cards[i].style.width = ((Math.sin(cards[i].offsetTop + timestamp / 100 + 1) * 500) + 'px')
        }
        window.requestAnimationFrame(update)
      }
      update(1000);
    

    看下效果,很明显的卡顿 聊一聊前端性能与体验的优化

    performance分析结果,load事件之后存在大量的回流,并且chrome都给标记了红色 聊一聊前端性能与体验的优化

    使用fastDom进行优化,将对 dom 的读和写分离,合并

     let cards = document.getElementsByClassName("MuiPaper-rounded");
      const update = (timestamp) => {
        for (let i = 0; i < cards.length; i++) {
          fastdom.measure(() => {
            let top = cards[i].offsetTop;
            fastdom.mutate(() => {
              cards[i].style.width =
                Math.sin(top + timestamp / 100 + 1) * 500 + "px";
            });
          });
        }
        window.requestAnimationFrame(update)
      }
      update(1000);
    

    再看下效果,很流畅~ 聊一聊前端性能与体验的优化 performance分析结果,load 事件之后也没有了那么多的红色标记 聊一聊前端性能与体验的优化

    感兴趣的可以去了解一下 fastDom:github fastdom 在线预览:fastdom demo

    关于任务拆分与组合的思想,react fiber架构做的很牛逼,有兴趣的可以去了解一下调度算法在 fiber 中的实践

    四、压缩

    聊一聊前端性能与体验的优化

    嗯哼哼、确定一下没有走错场子,继续继续!

    4.1 Gzip

    开启方式可参考:nginx 开启 gzip

    聊一聊前端性能与体验的优化

    还有一种方式:打包的时候生成 gz 文件,上传到服务器端,这样就不需要 nginx 来压缩了,可以降低服务器压力。 可参考:gzip 压缩文件&webPack 配置 Compression-webpack-plugin

    4.2 服务端压缩

    server.js

    const express = require('express');
    const app = express();
    const fs = require('fs');
    const compression = require('compression');
    const path = require('path');
    
    
    app.use(compression());
    app.use(express.static('build'));
    
    app.get('*', (req,res) =>{
        res.sendFile(path.join(__dirname+'/build/index.html'));
    });
    
    const listener = app.listen(process.env.PORT || 3000, function () {
        console.log(`Listening on port ${listener.address().port}`);
    });
    

    package.json

    "start": "npm run build && node server.js",
    

    聊一聊前端性能与体验的优化

    4.3 JavaScript、Css、Html 压缩

    工程化项目中直接使用对应的插件即可,webpack 的主要有下面三个:

    • UglifyJS
    • webpack-parallel-uglify-plugin
    • terser-webpack-plugin

    具体优缺点可参考:webpack 常用的三种 JS 压缩插件。压缩原理简单的讲就是去除一些空格、换行、注释,借助 es6 模块化的功能,做了一些tree-shaking的优化。同时做了一些代码混淆,一方面是为了更小的体积,另一方面也是为了源码的安全性。

    css 压缩主要是 mini-css-extract-plugin,当然前面的 js 压缩插件也会给你做好 css 压缩。使用姿势:

    npm install --save-dev mini-css-extract-plugin
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    plugins:[
     new MiniCssExtractPlugin({
           filename: "[name].css",
           chunkFilename: "[id].css"
       })
    ]
    

    html 压缩可以用HtmlWebpackPlugin,单页项目就一个 index.html,性能提升微乎其微~

    4.4 http2 首部压缩

    http2 的特点

    • 二进制分帧
    • 首部压缩
    • 流量控制
    • 多路复用
    • 请求优先级
    • 服务器推送http2_push: 'xxx.jpg'

    具体升级方式也很简单,修改一下 nginx 配置,方法请自行Google

    五、webpack 优化

    上文中也提到了部分 webpack 插件,下面我再来看看还有哪些~

    5.1 DllPlugin 提升构建速度

    通过DllPlugin插件,将一些比较大的,基本很少升级的包拆分出来,生成xx.dll.js文件,通过manifest.json引用

    webpack.dll.config.js

    const path = require("path");
    const webpack = require("webpack");
    module.exports = {
        mode: "production",
        entry: {
            react: ["react", "react-dom"],
        },
        output: {
            filename: "[name].dll.js",
            path: path.resolve(__dirname, "dll"),
            library: "[name]"
        },
        plugins: [
            new webpack.DllPlugin({
                name: "[name]",
                path: path.resolve(__dirname, "dll/[name].manifest.json")
            })
        ]
    };
    

    package.json

    "scripts": {
        "dll-build": "NODE_ENV=production webpack --config webpack.dll.config.js",
      },
    

    5.2 splitChunks 拆包

    optimization: {
            splitChunks: {
                cacheGroups: {
                    vendor: {
                        name: 'vendor',
                        test: /[\\/]node_modules[\\/]/,
                        minSize: 0,
                        minChunks: 1,
                        priority: 10,
                        chunks: 'initial'
                    },
                    common: {
                        name: 'common',
                        test: /[\\/]src[\\/]/,
                        chunks: 'all',
                        minSize: 0,
                        minChunks: 2
                    }
                }
            }
        },
    

    六、骨架屏

    用 css 提前占好位置,当资源加载完成即可填充,减少页面的回流与重绘,同时还能给用户最直接的反馈。 图中使用插件:react-placeholder 聊一聊前端性能与体验的优化

    关于实现骨架屏还有很多种方案,用Puppeteer服务端渲染的挺多的

    使用 css 伪类:只要 css 就能实现的骨架屏方案

    等等

    七、窗口化

    原理:只加载当前窗口能显示的 DOM 元素,当视图变化时,删除隐藏的,添加要显示的 DOM 就可以保证页面上存在的 dom 元素数量永远不多,页面就不会卡顿

    图中使用的插件:react-window 聊一聊前端性能与体验的优化

    安装:npm i react-window

    引入:import { FixedSizeList as List } from 'react-window';

    使用:

    const Row = ({ index, style }) => (
      <div style={style}>Row {index}</div>
    );
     
    const Example = () => (
      <List
        height={150}
        itemCount={1000}
        itemSize={35}
        width={300}
      >
        {Row}
      </List>
    );
    

    八、缓存

    8.1 http 缓存

    keep-alive

    判断是否开启:看response headers中有没有Connection: keep-alive 。开启以后,看network的瀑布流中就没有 Initial connection耗时了

    nginx 设置 keep-alive(默认开启)

    # 0 为关闭
    #keepalive_timeout 0;
    # 65s 无连接 关闭
    keepalive_timeout 65;
    # 连接数,达到 100 断开
    keepalive_requests 100;
    

    Cache-Control / Expires / Max-Age

    设置资源是否缓存,以及缓存时间

    Etag / If-None-Match

    资源唯一标识作对比,如果有变化,从服务器拉取资源。如果没变化则取缓存资源,状态码 304,也就是协商缓存

    Last-Modified / If-Modified-Since

    通过对比时间的差异来觉得要不要从服务器获取资源

    更多 HTTP 缓存参数可参考:使用 HTTP 缓存:Etag, Last-Modified 与 Cache-Control

    8.2 Service Worker

    借助 webpack 插件WorkboxWebpackPluginManifestPlugin,加载 serviceWorker.js,通过serviceWorker.register()注册

    new WorkboxWebpackPlugin.GenerateSW({
        clientsClaim: true,
        exclude: [/\.map$/, /asset-manifest\.json$/],
        importWorkboxFrom: 'cdn',
        navigateFallback: paths.publicUrlOrPath + 'index.html',
        navigateFallbackBlacklist: [
            new RegExp('^/_'),
            new RegExp('/[^/?]+\\.[^/]+$'),
        ],
    }),
    
    new ManifestPlugin({
        fileName: 'asset-manifest.json',
        publicPath: paths.publicUrlOrPath,
        generate: (seed, files, entrypoints) => {
            const manifestFiles = files.reduce((manifest, file) => {
                manifest[file.name] = file.path;
                return manifest;
            }, seed);
            const entrypointFiles = entrypoints.app.filter(
                fileName => !fileName.endsWith('.map')
            );
    
            return {
                files: manifestFiles,
                entrypoints: entrypointFiles,
            };
        },
    }),
    

    聊一聊前端性能与体验的优化

    九、预加载 && 懒加载

    9.1 preload

    就拿 demo 中的字体举例,正常情况下的加载顺序是这样的: 聊一聊前端性能与体验的优化

    加入 preload:

    <link rel="preload" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.119.woff2" as="font" crossorigin="anonymous"/> 
    <link rel="preload" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.118.woff2" as="font" crossorigin="anonymous"/> 
    <link rel="preload" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.116.woff2" as="font" crossorigin="anonymous"/> 
    

    聊一聊前端性能与体验的优化

    9.2 prefetch

    场景:首页不需要这样的字体文件,下个页面需要:首页会以最低优先级 Lowest 来提前加载

    加入 prefetch:

    <link rel="prefetch" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.113.woff2" as="font"/> 
    <link rel="prefetch" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.118.woff2" as="font"/> 
    <link rel="prefetch" href="https://fonts.gstatic.com/s/longcang/v5/LYjAdGP8kkgoTec8zkRgqHAtXN-dRp6ohF_hzzTtOcBgYoCKmPpHHEBiM6LIGv3EnKLjtw.117.woff2" as="font"/> 
    

    需要的页面,从prefetch cache中取 聊一聊前端性能与体验的优化

    webpack 也是支持这两个属性的:webpackPrefetch 和 webpackPreload

    9.3 懒加载

    图片

    机械图片 聊一聊前端性能与体验的优化

    渐进式图片(类似高斯模糊) 需要 UI 小姐姐出稿的时候指定这种格式 聊一聊前端性能与体验的优化

    响应式图片

    原生模式:<img src="./img/index.jpg" sizes="100vw" srcset="./img/dog.jpg 800w, ./img/index.jpg 1200w"/> 聊一聊前端性能与体验的优化

    路由懒加载

    通过函数 + import 实现

    const Page404 = () => import(/* webpackChunkName: "error" */'@views/errorPage/404');

    十、ssr && react-snap

    • 服务端渲染 SSR,vue 使用 nuxt.js,react 使用 next.js
    • react-snap 可以借助 Puppeteer 实现先渲染单页,然后保留 DOM,发送到客户端

    十一、体验优化

    白屏 loading

    聊一聊前端性能与体验的优化 loading.html 需要自取哦,还有种方式,使用webpack插件HtmlWebpackPlugin将 loading 资源插入到页面中

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Loading</title>
        <style>
          body {
            margin: 0;
          }
          #loadding {
            position: fixed;
            top: 0;
            bottom: 0;
            display: flex;
            width: 100%;
            align-items: center;
            justify-content: center;
          }
          #loadding > span {
            display: inline-block;
            width: 8px;
            height: 100%;
            margin-right: 5px;
            border-radius: 4px;
            -webkit-animation: load 1.04s ease infinite;
            animation: load 1.04s ease infinite;
          }
          @keyframes load {
            0%,
            100% {
              height: 40px;
              background: #98beff;
            }
            50% {
              height: 60px;
              margin-top: -20px;
              background: #3e7fee;
            }
          }
        </style>
      </head>
    
      <body>
        <div id="loadding">
          <span></span>
          <span style="animation-delay: 0.13s"></span>
          <span style="animation-delay: 0.26s"></span>
          <span style="animation-delay: 0.39s"></span>
          <span style="animation-delay: 0.52s"></span>
        </div>
      </body>
      <script>
        window.addEventListener("DOMContentLoaded", () => {
          const $loadding = document.getElementById("loadding");
          if (!$loadding) {
            return;
          }
          $loadding.style.display = "none";
          $loadding.parentNode.removeChild($loadding);
        });
      </script>
    </html>
    

    参考文章:

    • 前端性能优化之雅虎 35 条军规
    • webpack 实践——webpack-bundle-analyzer 的使用
    • nginx 开启 gzip
    • gzip 压缩文件&webPack 配置 Compression-webpack-plugin
    • webpack 常用的三种 JS 压缩插件
    • 只要 css 就能实现的骨架屏方案
    • 使用 HTTP 缓存:Etag, Last-Modified 与 Cache-Control
    • webpackPrefetch 和 webpackPreload

    起源地下载网 » 聊一聊前端性能与体验的优化

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元