<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊一聊前端性能與體驗的優(yōu)化

          共 23309字,需瀏覽 47分鐘

           ·

          2021-04-29 09:34


          前言

          性能優(yōu)化 ,每個工程師跑不掉的一個話題。這里是本人總結的一些優(yōu)化手法,希望對大家有所幫助,后續(xù)也會繼續(xù)更新。演示源碼和 PPT 無條件分享。

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

          橫屏觀看更佳:http://118.25.49.69:8086

          前端性能的影響

          前端性能的一個重要指標是頁面加載時間,不僅事關用戶體驗,也是搜索引擎排名考慮的一個因素。

          • 來自 Google 的數(shù)據(jù)表明,一個有 10 條數(shù)據(jù) 0.4 秒能加載完的頁面,變成 30 條數(shù)據(jù) 0.9 秒加載完之后,流量和廣告收入下降90%
          • Google Map 首頁文件大小從100KB減小到70-80KB后,流量在第一周漲了10%,接下來的三周漲了25%
          • 亞馬遜的數(shù)據(jù)表明:加載時間增加100 毫秒,銷量就下降 1%

          所以:重鑄性能之光,我輩義不容辭??

          一、調試工具

          磨刀不誤砍柴工,讀完大學再打工!

          1.1 Network

          這里可以看到資源加載詳情,初步評估影響頁面性能的因素。鼠標右鍵可以自定義選項卡,頁面底部是當前加載資源的一個概覽。DOMContentLoaded DOM 渲染完成的時間,Load:當前頁面所有資源加載完成的時間

          思考:如何判斷哪些資源對當前頁面加載無用,做對應優(yōu)化?

          shift + cmd + P  調出控制臺的擴展工具,添加規(guī)則

          擴展工具 更多使用姿勢

          瀑布流 waterfall

          • Queueing 瀏覽器將資源放入隊列時間
          • Stalled 因放入隊列時間而發(fā)生的停滯時間
          • DNS Lookup DNS 解析時間
          • Initial connection  建立 HTTP 連接的時間
          • SSL 瀏覽器與服務器建立安全性連接的時間
          • TTFB 等待服務端返回數(shù)據(jù)的時間
          • Content Download  瀏覽器下載資源的時間

          1.2 Lighthouse

          • First Contentful Paint  首屏渲染時間,1s 以內綠色
          • Speed Index  速度指數(shù),4s 以內綠色
          • Time to Interactive 到頁面可交換的時間

          根據(jù) chrome 的一些策略自動對網(wǎng)站做一個質量評估,并且會給出一些優(yōu)化的建議。

          1.3 Peformance

          對網(wǎng)站最專業(yè)的分析~后面會多次講到

          1.4 webPageTest

          可以模擬不同場景下訪問的情況,比如模擬不同瀏覽器、不同國家等等,在線測試地址:webPageTest (https://www.webpagetest.org/)

          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 監(jiān)聽視窗激活狀態(tài)

          大學都刷過慕課吧?只要離開窗口視頻就會暫停~

          或者一些考試網(wǎng)站,提醒你不能離開當前窗口

          再或者,這種效果~

          // 窗口激活狀態(tài)監(jiān)聽
          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 監(jiān)聽網(wǎng)絡變化

          網(wǎng)絡變化時給用戶反饋網(wǎng)絡問題,有時候看直播的時候自己的網(wǎng)絡卡頓,直播平臺也會提醒你或者自動給你切換清晰度

          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 更多計算規(guī)則

          DNS 解析耗時: domainLookupEnd - domainLookupStart
          TCP 連接耗時: connectEnd - connectStart
          SSL 安全連接耗時: connectEnd - secureConnectionStart
          網(wǎng)絡請求耗時 (TTFB): responseStart - requestStart
          數(shù)據(jù)傳輸耗時: responseEnd - responseStart
          DOM 解析耗時: domInteractive - responseEnd
          資源加載耗時: loadEventStart - domContentLoadedEventEnd
          First Byte 時間: responseStart - domainLookupStart
          白屏時間: responseEnd - fetchStart
          首次可交互時間: domInteractive - fetchStart
          DOM Ready 時間: domContentLoadEventEnd - fetchStart
          頁面完全加載時間: loadEventStart - fetchStart
          http 頭部大小:transferSize - encodedBodySize
          重定向次數(shù):performance.navigation.redirectCount
          重定向耗時: redirectEnd - redirectStart

          三、老生常談,雅虎軍規(guī)

          磨好刀了,就該想想往哪里捅比較好了~ ??????

          關于雅虎軍規(guī),你知道的有多少條,平時寫用到的又有哪些?針對以下規(guī)則,我們可以做很多優(yōu)化工作

          3.1 減少 cookie 傳輸

          cookie 傳輸會造成帶寬浪費,可以:

          • 減少 cookie 中存儲的東西
          • 靜態(tài)資源不需要 cookie,可以采用其他的域名,不會主動帶上 cookie。

          3.2 避免過多的回流與重繪

          連續(xù)觸發(fā)頁面回流操作

            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進行優(yōu)化,將對 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 (http://wilsonpage.github.io/fastdom/examples/animation.html)

          關于任務拆分與組合的思想,react fiber架構做的很牛逼,有興趣的可以去了解一下調度算法在 fiber 中的實踐

          四、壓縮

          嗯哼哼、確定一下沒有走錯場子,繼續(xù)繼續(xù)!

          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 具體優(yōu)缺點可參考:webpack 常用的三種 JS 壓縮插件。壓縮原理簡單的講就是去除一些空格、換行、注釋,借助 es6 模塊化的功能,做了一些tree-shaking的優(yōu)化。同時做了一些代碼混淆,一方面是為了更小的體積,另一方面也是為了源碼的安全性。

          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 的特點

          • 二進制分幀
          • 首部壓縮
          • 流量控制
          • 多路復用
          • 請求優(yōu)先級
          • 服務器推送http2_push: 'xxx.jpg'具體升級方式也很簡單,修改一下 nginx 配置,方法請自行Google

          五、Webpack 優(yōu)化

          上文中也提到了部分 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

          關于實現(xiàn)骨架屏還有很多種方案,用Puppeteer服務端渲染的挺多的

          使用 css 偽類:只要 css 就能實現(xiàn)的骨架屏方案

          等等

          七、窗口化

          原理:只加載當前窗口能顯示的 DOM 元素,當視圖變化時,刪除隱藏的,添加要顯示的 DOM 就可以保證頁面上存在的 dom 元素數(shù)量永遠不多,頁面就不會卡頓

          圖中使用的插件: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;
          # 連接數(shù),達到 100 斷開
          keepalive_requests 100;

          Cache-Control / Expires / Max-Age

          設置資源是否緩存,以及緩存時間

          Etag / If-None-Match

          資源唯一標識作對比,如果有變化,從服務器拉取資源。如果沒變化則取緩存資源,狀態(tài)碼 304,也就是協(xié)商緩存

          Last-Modified / If-Modified-Since

          通過對比時間的差異來覺得要不要從服務器獲取資源

          更多 HTTP 緩存參數(shù)可參考:使用 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

          場景:首頁不需要這樣的字體文件,下個頁面需要:首頁會以最低優(yōu)先級 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"/>

          路由懶加載

          通過函數(shù) + import 實現(xiàn)

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

          十、SSR && react-snap

          • 服務端渲染 SSR,vue 使用 nuxt.js,react 使用 next.js
          • react-snap 可以借助 Puppeteer 實現(xiàn)先渲染單頁,然后保留 DOM,發(fā)送到客戶端

          十一、體驗優(yōu)化

          白屏 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>

          參考文章

          • 前端性能優(yōu)化之雅虎 35 條軍規(guī) (https://juejin.cn/post/6844903657318645767#heading-1)
          • webpack 實踐——webpack-bundle-analyzer 的使用 (https://segmentfault.com/a/1190000012220132)
          • nginx 開啟 gzip](https://juejin.cn/post/6844903605187641357)
          • gzip 壓縮文件&webPack 配置 Compression-webpack-plugin (https://segmentfault.com/a/1190000020976930)
          • webpack 常用的三種 JS 壓縮插件 (https://blog.csdn.net/qq_24147051/article/details/103557728)
          • 只要 css 就能實現(xiàn)的骨架屏方案 (https://segmentfault.com/a/1190000020437426)
          • 使用 HTTP 緩存:Etag, Last-Modified 與 Cache-Control (https://harttle.land/2017/04/04/using-http-cache.html)
          • webpackPrefetch 和 webpackPreload (https://www.cnblogs.com/skychx/p/webpack-webpackChunkName-webpackPreload-webpackPreload.html)


          最后



          如果你覺得這篇內容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          1. 點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)

          2. 歡迎加我微信「 sherlocked_93 」拉你進技術群,長期交流學習...

          3. 關注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。


          點個在看支持我吧,轉發(fā)就更好了


          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲成人蜜芽在线 | 午夜视频色 | 麻豆久久久久久久久91 | 欧美成人网在线观看 | 无码群交东京热 |