vue 前端性能優(yōu)化總結(jié)

思維導(dǎo)圖

1438361582-5cdb82bdb6ce8.png

一.npm run build 縮小打包的體積

首先來(lái)看我未作任何處理的一個(gè)打包


微信截圖_20210308113609.png
微信截圖_20210308113634.png

這個(gè)包很大硝桩,里面有很多文件走越。你會(huì)發(fā)現(xiàn)里面有很多.map結(jié)尾的文件,占據(jù)了非常大的空間坷随,下面我們一步一步來(lái):

1.productionSourceMap

Type: boolean
Default: true
用途:

設(shè)置生產(chǎn)環(huán)境的 source map 開(kāi)啟與關(guān)閉饱岸。

用法
module.exports = {
  publicPath: './', // 基本路徑
  outputDir: 'dist', // 輸出文件目錄
  assetsDir: './assets',
  indexPath: 'index.html',
  filenameHashing: true, // 生成的靜態(tài)資源在它們的文件名中包含了 hash 以便更好的控制緩存
  lintOnSave: false, // eslint-loader 是否在保存的時(shí)候檢查
  productionSourceMap: true, // 生產(chǎn)環(huán)境是否生成 sourceMap 文件
}

source map 直譯過(guò)來(lái)就是資源地圖。所以载慈,source map的作用就是定位惭等。source map定位的時(shí)瀏覽器控制臺(tái)輸出語(yǔ)句在項(xiàng)目文件的位置。


微信截圖_20210308114320.png
微信截圖_20210308114329.png

好的办铡,加下來(lái)我們把productionSourceMap改為false辞做,看看效果:


微信截圖_20210308142644.png

發(fā)現(xiàn).map文件都沒(méi)有了,進(jìn)入下一步:

webpack-bundle-analyzer

這是一款用戶(hù)分析打包大小的插件寡具,使用如下:

cnpm install webpack-bundle-analyzer --s-d

在vue-cli3.0項(xiàng)目中,新建vue.config.js中

module.exports = {
  
  productionSourceMap: false,
  chainWebpack:config=>{
    config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
  }
}

在vue-cli2.x項(xiàng)目中凭豪,在build/webpack.prod.config.js中的module.export - webpackConfig前面加上:

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

執(zhí)行:

npm run build --report

就可以看到:


微信截圖_20210308145325.png
微信截圖_20210308160420.png

fast 3G的情況下加載了17s,可以看到vendor.js這個(gè)文件有1.84m晒杈,里面echarts和elementui這兩個(gè)庫(kù)占了大頭嫂伞。address.json這個(gè)是地理位置的json,最好是可以改成后臺(tái)獲取拯钻,以減小打包文件體積帖努,這里暫時(shí)不表。先來(lái)看看怎么處理那兩個(gè)大頭粪般。
有兩個(gè)方向:

1.按需加載

本來(lái)直接在Main.js中:

import echarts from 'echarts'
Vue.prototype.$echarts = echarts

現(xiàn)在改為在echarts組件里引入

var echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/line') // 按需導(dǎo)入折線(xiàn)組件
require('echarts/lib/component/tooltip') // 提示組件
require('echarts/lib/component/legend') // 圖例組件
2.使用cdn托管

大型Web應(yīng)用對(duì)速度的追求并沒(méi)有止步于僅僅利用瀏覽器緩存拼余,因?yàn)闉g覽器緩存始終只是為了提升二次訪(fǎng)問(wèn)的速度,對(duì)于首次訪(fǎng)問(wèn)的加速亩歹,我們需要從網(wǎng)絡(luò)層面進(jìn)行優(yōu)化匙监,最常見(jiàn)的手段就是CDN(Content Delivery Network凡橱,內(nèi)容分發(fā)網(wǎng)絡(luò))加速。通過(guò)將靜態(tài)資源緩存到離用戶(hù)很近的相同網(wǎng)絡(luò)運(yùn)營(yíng)商的CDN節(jié)點(diǎn)上亭姥,不但能提升用戶(hù)的訪(fǎng)問(wèn)速度稼钩,還能節(jié)省服務(wù)器的帶寬消耗,降低負(fù)載达罗。不同地區(qū)的用戶(hù)會(huì)訪(fǎng)問(wèn)到離自己最近的相同網(wǎng)絡(luò)線(xiàn)路上的CDN節(jié)點(diǎn)坝撑,當(dāng)請(qǐng)求達(dá)到CDN節(jié)點(diǎn)后,節(jié)點(diǎn)會(huì)判斷自己的內(nèi)容緩存是否有效粮揉,如果有效巡李,則立即響應(yīng)緩存內(nèi)容給用戶(hù),從而加快響應(yīng)速度扶认。如果CDN節(jié)點(diǎn)的緩存失效侨拦,它會(huì)根據(jù)服務(wù)配置去我們的內(nèi)容源服務(wù)器獲取最新的資源響應(yīng)給用戶(hù),并將內(nèi)容緩存下來(lái)以便響應(yīng)給后續(xù)訪(fǎng)問(wèn)的用戶(hù)辐宾。因此阳谍,一個(gè)地區(qū)內(nèi)只要有一個(gè)用戶(hù)先加載資源,在CDN中建立了緩存螃概,該地區(qū)的其他后續(xù)用戶(hù)都能因此而受益。

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
    <meta http-equiv="pragram" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache, must-revalidate">
    <meta http-equiv="expires" content="0">
    <title>chitic-sems-app</title>
      <link rel="stylesheet" ></head>
      <% for (var i in htmlWebpackPlugin.options.css) { %>
        <link href="<%= htmlWebpackPlugin.options.css[i] %>" rel="stylesheet">
        <% } %>
       
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <% for (var i in htmlWebpackPlugin.options.js) { %>
      <script src="<%= htmlWebpackPlugin.options.js[i] %>"></script>
      <% } %>
  </body>
</html>

這里有個(gè)坑:
1.不要把任何頁(yè)面內(nèi)容寫(xiě)到 body鸽疾、head 外面。
2.app.js 插入到了你寫(xiě)的 script 的前面,獲取不到 Vue尺栖。

JS部分
vue-cli2.X項(xiàng)目

首先在config/index.js文件中:
在dev和build內(nèi)部加入:

cdn: {
      css:[],
      js: [
        'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
        'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
        'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
        'https://cdn.bootcss.com/element-ui/2.13.2/index.js',
        'https://cdn.bootcss.com/echarts/4.2.1/echarts.simple.min.js'
      ]
    }

dev用于開(kāi)發(fā)環(huán)境碳胳,build用于開(kāi)發(fā)環(huán)境的資源請(qǐng)求。
然后在webpack.base.conf.js的module.export{}內(nèi)部加入

externals: {
    'vue': 'Vue',
    'vuex': 'Vuex',
    'vue-router': 'VueRouter',
    'element-ui':'ELEMENT',
    'echarts':'echarts'
  }

externals的目的就是將不怎么需要更新的第三方庫(kù)脫離webpack打包豺鼻,不被打入bundle中综液,從而減少打包時(shí)間,但又不影響運(yùn)用第三方庫(kù)的方式
下一步在webpack.dev.conf.js中利用HtmlWebpackPlugin注入資源

new HtmlWebpackPlugin(Object.assign(
      {
        filename: 'index.html',
        template: 'index.html',
        inject: true
      },
      config.dev.cdn
      )),

此時(shí)你開(kāi)發(fā)環(huán)境下已經(jīng)引入了這些資源了儒飒。
下一步在webpack.prod.conf.js中利用HtmlWebpackPlugin注入資源

new HtmlWebpackPlugin(Object.assign({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    },
      config.build.cdn)),

大功告成谬莹,讓我們運(yùn)行一下npm run build --report
當(dāng)把a(bǔ)ddress.json也改成接口之后,我們?cè)賮?lái)看看打包的大小


微信截圖_20210308171058.png
vue-cli3.0項(xiàng)目

新建vue.config.js

// 代碼壓縮
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 是否為生產(chǎn)環(huán)境
const isProduction = process.env.NODE_ENV !== 'development'

// 本地環(huán)境是否需要使用cdn
const devNeedCdn = true

// cdn鏈接
const cdn = {
    // cdn:模塊名稱(chēng)和模塊作用域命名(對(duì)應(yīng)window里面掛載的變量名稱(chēng))
    externals: {
        vue: 'Vue',
        vuex: 'Vuex',
        'vue-router': 'VueRouter',
        'element-ui':'ELEMENT'
    },
    // cdn的css鏈接
    css: [],
    // cdn的js鏈接
    js: [
        'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
        'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
        'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
        'https://cdn.bootcss.com/element-ui/2.13.2/index.js'
    ]
}
let path = require('path')
module.exports = {
  productionSourceMap: false, //優(yōu)化打包體積
  publicPath: './',
  lintOnSave: false,
  chainWebpack:config =>{
    // ============注入cdn start============
      config.plugin('html').tap(args => {
        // 生產(chǎn)環(huán)境或本地需要cdn時(shí)桩了,才注入cdn
        if (isProduction || devNeedCdn) args[0].cdn = cdn
        return args
      })
      config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
      
    // ============注入cdn start============
  },
  configureWebpack: config => {
    // 用cdn方式引入附帽,則構(gòu)建時(shí)要忽略相關(guān)資源
    if (isProduction || devNeedCdn) config.externals = cdn.externals
    // 生產(chǎn)環(huán)境相關(guān)配置
    if (isProduction) {
      // 代碼壓縮
      config.plugins.push(
          new UglifyJsPlugin({
              uglifyOptions: {
                  //生產(chǎn)環(huán)境自動(dòng)刪除console
                  compress: {
                      // warnings: false, // 若打包錯(cuò)誤,則注釋這行
                      drop_debugger: true,
                      drop_console: true,
                      pure_funcs: ['console.log']
                  }
              },
              sourceMap: false,
              parallel: true
          })
      )
    }
        // 生產(chǎn)環(huán)境相關(guān)配置

  }
}

來(lái) 運(yùn)行打包
可以看到原來(lái)1.84m的文件井誉,現(xiàn)在只剩下了250k,讓我們情理緩存之后再來(lái)看一下加載速度:


微信截圖_20210308173416.png

可以看到從17s到了9s 縮短了一半蕉扮,接下來(lái)還有什么辦法呢,我們繼續(xù)下一步

Gzip壓縮

gzip壓縮可以提高2-3倍的速度颗圣,非常棒喳钟。讓我們來(lái)操作一下
選擇的項(xiàng)目比較老屁使,來(lái)講一下

vue-cli2.X的

下載compression-webpack-plugin這個(gè)plugin
在你的webpack.prod.conf.js文件里添加以下代碼:(與webpackConfig平級(jí))

//開(kāi)啟gzip壓縮
if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      filename: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

再將config文件夾下的index.js中的productionGzip改為true即可,如果報(bào)錯(cuò)
說(shuō)明你的plugin版本太高了奔则,降低版本即可
npm install --save-dev compression-webpack-plugin@1.1.12

vue-cli3.0的

在vue.config.js內(nèi)加入

 // gzip壓縮
      const productionGzipExtensions = ['html', 'js', 'css']
      config.plugins.push(
          new CompressionWebpackPlugin({
              filename: '[path].gz[query]',
              algorithm: 'gzip',
              test: new RegExp(
                  '\\.(' + productionGzipExtensions.join('|') + ')$'
              ),
              threshold: 10240, // 只有大小大于該值的資源會(huì)被處理 10240
              minRatio: 0.8, // 只有壓縮率小于這個(gè)值的資源才會(huì)被處理
              deleteOriginalAssets: false // 刪除原文件
          })
      )
    }

此時(shí)我們來(lái)執(zhí)行打包之后就可以看到


微信截圖_20210309111151.png

超過(guò)10k的文件都出現(xiàn)了.gz結(jié)尾的壓縮包蛮寂,體積只有原來(lái)的1/3-1/2。如果資源請(qǐng)求這些壓縮包的話(huà)应狱,加載速度還會(huì)提升一個(gè)檔次共郭。當(dāng)然光是前端這樣做還不夠,還需要配置nginx疾呻。這里還有一個(gè)小技巧除嘹,在webpack.prod.conf.js的UglifyJsPlugin中添加一下一句話(huà),可以去掉生產(chǎn)環(huán)境里的console.log

new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false,
          // =====以下是新增的=====
          drop_console: true, // 刪除頁(yè)面中的 console.log
          pure_funcs: ['console.log']
          // =====以上是新增的=====
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),

下面我們來(lái)講講nginx的配置
在nginx.conf內(nèi)添加如下代碼:

        gzip on;
        gzip_min_length 1k;
        gzip_comp_level 9;
        gzip_types text/plain application/javascript application/x-javascript text/css application/xml 
text/javascript application/x-httpd-php image/jpeg image/gif image/png;
        gzip_vary on;
        gzip_disable "MSIE [1-6]\.";   

整體配置如下

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
    upstream njsolar  {
        server localhost:9000; #Apache
    }
    
    server {
        listen       8083;
        server_name  localhost;
        gzip on;
        gzip_min_length 1k;
        gzip_comp_level 9;
        gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
        gzip_vary on;
        gzip_disable "MSIE [1-6]\.";    
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location / {
            root   /usr/local/deploy/njsolar-ui/dist;
            index  index.html index.htm;
        }
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}
    }
}

此時(shí)我們啟動(dòng)nginx:./nginx.exe,我們會(huì)發(fā)現(xiàn):


微信截圖_20210309143405.png

我們成功請(qǐng)求到了gzip文件岸蜗,vender文件從最初的1.8M縮減到了現(xiàn)在的75kb尉咕,請(qǐng)求時(shí)間也從17s縮減到了7s,如果從fast3G切換成online璃岳,基本上加載時(shí)間就在一秒左右年缎,取得巨大進(jìn)步。
如果還要再進(jìn)一步的話(huà)铃慷,那就可以用SSR和骨架屏的方案单芜,比較復(fù)雜,這里不加贅述犁柜。vue可以用nuxt.js框架開(kāi)發(fā)洲鸠。
首屏加載與打包的優(yōu)化講完了,接下來(lái)我們來(lái)講一講

Vue代碼優(yōu)化

1.路由懶加載

未用懶加載馋缅,vue中路由代碼如下

 import Vue from 'vue'
                import Router from 'vue-router'
                import HelloWorld from '@/components/HelloWorld'

                Vue.use(Router)

                export default new Router({
                  routes: [
                    {
                      path: '/',
                      name: 'HelloWorld',
                      component:HelloWorld
                    }
                  ]
 })

vue異步組件實(shí)現(xiàn)懶加載

方法如下:component:resolve=>(require(['需要加載的路由的地址'])扒腕,resolve)

import Vue from 'vue'
import Router from 'vue-router'
  /* 此處省去之前導(dǎo)入的HelloWorld模塊 */
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: resolve=>(require(["@/components/HelloWorld"],resolve))
    }
  ]
})

ES 提出的import方法,(------最常用------)

方法如下:const HelloWorld = ()=>import('需要加載的模塊地址')(不加 { } 萤悴,表示直接return)

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const HelloWorld = ()=>import("@/components/HelloWorld")
export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component:HelloWorld
    }
  ]
})
2.組件懶加載

原來(lái)組件中寫(xiě)法

<template>
  <div class="hello">
  <One-com></One-com>
  1111
  </div>
</template>

<script>
import One from './one'
export default {
  components:{
    "One-com":One
  },
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

組件懶加載寫(xiě)法:

<template>
  <div class="hello">
  <One-com></One-com>
  1111
  </div>
</template>

<script>
export default {
  components:{
    "One-com":resolve=>(['./one'],resolve)
  },
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

3.http連接優(yōu)化

HTTP協(xié)議定義Web客戶(hù)端如何從服務(wù)器請(qǐng)求web頁(yè)面瘾腰,以及服務(wù)器如何把web頁(yè)面?zhèn)魉徒o客戶(hù)端,瀏覽器請(qǐng)求獲取數(shù)據(jù)通過(guò)http連接完成覆履,因此蹋盆,優(yōu)化http連接優(yōu)化是web項(xiàng)目?jī)?yōu)化的重要方向之一。Web項(xiàng)目的由瀏覽器呈現(xiàn)硝全,下面我們來(lái)看看瀏覽器對(duì)請(qǐng)求的處理過(guò)程


微信截圖_20210309155209.png

4248308543-5c4873c796513.png

附上幾份緩存相關(guān)的教程:
https://www.cnblogs.com/fangsmile/p/13072940.html
https://blog.csdn.net/lianxin19900610/article/details/82417233
https://mp.weixin.qq.com/s/FVNlCZhOpJk5gUCnhsmSzQ

3.1 減少HTTP請(qǐng)求

3.1.1 合并CSS怪嫌、js、圖片柳沙。將css岩灭、js、圖片合并赂鲤,可以有效減少http請(qǐng)求數(shù)噪径;
3.1.2 合理規(guī)劃API柱恤,將可合并的接口請(qǐng)求合并。瀏覽器對(duì)并發(fā)的http請(qǐng)求數(shù)量有限制找爱,即使API響應(yīng)很快梗顺,在多個(gè)接口并發(fā)請(qǐng)求時(shí),仍會(huì)在瀏覽器中造成不同程度的卡頓车摄;

3.2合理使用緩存

合理設(shè)置HTTP緩存寺谤。從上圖瀏覽器請(qǐng)求處理流程圖中可以看出,恰當(dāng)?shù)木彺嬖O(shè)置可以大大減少HTTP請(qǐng)求吮播,節(jié)省帶寬变屁。如很少變化的資源(html、css意狠、js粟关、圖片等)通過(guò) HTTP Header中的cache-control和Expires可設(shè)定瀏覽器緩存,變化不頻繁又可能變的資源使用Last-Modifed來(lái)做請(qǐng)求驗(yàn)證环戈。

3.3使用字體圖標(biāo)闷板,少用圖片

使用字體圖標(biāo)的優(yōu)點(diǎn):

3.3.1輕量:一個(gè)圖標(biāo)字體比一系列的圖像(特別是在Retina屏中使用雙倍圖像)要小。一旦圖標(biāo)字體加載了院塞,圖標(biāo)就會(huì)馬上渲染出來(lái)遮晚,不需要下載一個(gè)圖像±怪梗可以減少HTTP請(qǐng)求县遣,還可以配合HTML5離線(xiàn)存儲(chǔ)做性能優(yōu)化;
3.3.2靈活性:圖標(biāo)字體可以用過(guò)font-size屬性設(shè)置其任何大小创泄,還可以加各種文字效果,包括顏色括蝠、Hover狀態(tài)鞠抑、透明度、陰影和翻轉(zhuǎn)等效果忌警「樽荆可以在任何背景下顯示。
3.3.3兼容性:網(wǎng)頁(yè)字體支持所有現(xiàn)代瀏覽器法绵,包括IE低版本箕速。

3.4圖片懶加載

在實(shí)際的項(xiàng)目開(kāi)發(fā)中,我們通常會(huì)遇見(jiàn)這樣的場(chǎng)景:一個(gè)頁(yè)面有很多圖片朋譬,而首屏出現(xiàn)的圖片大概就一兩張盐茎,那么我們還要一次性把所有圖片都加載出來(lái)嗎?顯然這是愚蠢的徙赢,不僅影響頁(yè)面渲染速度字柠,還浪費(fèi)帶寬探越。這也就是們通常所說(shuō)的首屏加載,技術(shù)上現(xiàn)實(shí)其中要用的技術(shù)就是圖片懶加載--到可視區(qū)域再加載窑业。

4.其他小技巧

4.1盡量提取公共方法和公共組件钦幔。

4.1.1提取公共組件,盡量與業(yè)務(wù)隔離常柄,如篩選條件等鲤氢;
4.1.2提取公共方法,放在util.js中西潘,如表單校驗(yàn)封裝卷玉;
4.1.3提取在項(xiàng)目中出現(xiàn)較多的功能模塊,如彈窗詳情等秸架;

4.2 v-for和v-if

在vue中v-for和v-if不要放在同一個(gè)元素上使用揍庄。由于 v-for 和 v-if 放在同一個(gè)元素上使用會(huì)帶來(lái)一些性能上的影響,而且v-for時(shí)要加上key东抹,便于虛擬dom的遍歷與渲染,當(dāng)舊值和新值去對(duì)比的時(shí)候蚂子,可以更快的定位到diff

4.3 Keep-alive

一些不太需要刷新的頁(yè)面,內(nèi)容又比較復(fù)雜的缭黔,建議用Keep-alive食茎。Keep-alive會(huì)新增兩個(gè)生命鉤子函數(shù)。

4.4 created()和mouted()

一些異步請(qǐng)求盡量放在mouted里面馏谨,這樣不會(huì)導(dǎo)致因?yàn)檎?qǐng)求過(guò)久讓頁(yè)面長(zhǎng)時(shí)間白屏别渔。

4.5 treesharking

webpack會(huì)自動(dòng)在生產(chǎn)環(huán)境下(mode:production)進(jìn)行treesharking,前提是依賴(lài)必須通過(guò)es6的方式引入:import.

4.6 避免回流和重繪

回流(Reflow)

當(dāng) Render Tree 中部分或全部元素的尺寸、結(jié)構(gòu)惧互、或某些屬性發(fā)生改變時(shí)哎媚,瀏覽器重新渲染部分或全部文檔的過(guò)程稱(chēng)為回流。

會(huì)導(dǎo)致回流的事件:
  • 頁(yè)面首次渲染
  • 瀏覽器窗口大小發(fā)生改變
  • 元素尺寸或位置發(fā)生改變?cè)貎?nèi)容變化(文字?jǐn)?shù)量或圖片大小等等)
  • 元素字體大小變化
  • 添加或者刪除可見(jiàn)的 DOM 元素
  • 激活 CSS 偽類(lèi)(例如:hover)
  • 查詢(xún)某些屬性或調(diào)用某些方法
  • 一些常用且會(huì)導(dǎo)致回流的屬性和方法
    clientWidth喊儡、clientHeight拨与、clientTop、clientLeftoffsetWidth艾猜、offsetHeight买喧、offsetTop、offsetLeftscrollWidth匆赃、scrollHeight淤毛、scrollTop、scrollLeftscrollIntoView()算柳、scrollIntoViewIfNeeded()低淡、getComputedStyle()、
    getBoundingClientRect()、scrollTo()
重繪(Repaint)

當(dāng)頁(yè)面中元素樣式的改變并不影響它在文檔流中的位置時(shí)(例如:color查牌、background-color事期、visibility 等),瀏覽器會(huì)將新樣式賦予給元素并重新繪制它纸颜,這個(gè)過(guò)程稱(chēng)為重繪兽泣。

如何避免
CSS

1.避免使用 table 布局。
2.盡可能在 DOM 樹(shù)的最末端改變 class胁孙。
3.避免設(shè)置多層內(nèi)聯(lián)樣式唠倦。
4.將動(dòng)畫(huà)效果應(yīng)用到 position 屬性為 absolute 或 fixed 的元素上。
5.避免使用 CSS 表達(dá)式(例如:calc())涮较。

Javascript

1.避免頻繁操作樣式稠鼻,最好一次性重寫(xiě) style 屬性,或者將樣式列表定義為 class 并一次性更改 class 屬性狂票。

// 優(yōu)化前
const el = document.getElementById('test');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 優(yōu)化后,一次性修改樣式候齿,這樣可以將三次重排減少到一次重排
const el = document.getElementById('test');
el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'

2.避免頻繁操作 DOM,創(chuàng)建一個(gè) documentFragment闺属,在它上面應(yīng)用所有 DOM 操作慌盯,最后再把它添加到文檔中。
3.也可以先為元素設(shè)置 display: none掂器,操作結(jié)束后再把它顯示出來(lái)亚皂。因?yàn)樵?display 屬性為 none 的元素上進(jìn)行的 DOM 操作不會(huì)引發(fā)回流和重繪。
4.避免頻繁讀取會(huì)引發(fā)回流/重繪的屬性国瓮,如果確實(shí)需要多次使用灭必,就用一個(gè)變量緩存起來(lái)。
5.對(duì)具有復(fù)雜動(dòng)畫(huà)的元素使用絕對(duì)定位乃摹,使它脫離文檔流禁漓,否則會(huì)引起父元素及后續(xù)元素頻繁回流。

4.7 防抖與節(jié)流

防抖debounce

監(jiān)聽(tīng)一個(gè)input輸入框的keyup,但是不能一松開(kāi)鍵盤(pán)就觸發(fā)事件孵睬,因?yàn)榭赡軙?huì)頻繁調(diào)用接口播歼,應(yīng)該松開(kāi)若干毫秒后不再按下,才開(kāi)始調(diào)用接口

const input1 = document.getElementById('input')
var timer = null
input1.addEventListener('keyup',function(){
  //假如輸入123肪康,首先輸入1的時(shí)候荚恶,time是null撩穿,觸發(fā)定時(shí)器磷支,
  // 500毫秒之后打印,但是在500毫秒之內(nèi)再次輸入了2食寡,觸發(fā)keyup雾狈,此時(shí)time!=null
  // 就清空了原來(lái)應(yīng)該打印1的定時(shí)器抵皱,重新執(zhí)行一個(gè)新的定時(shí)器打印12善榛,關(guān)鍵在于清空原來(lái)的定時(shí)器
  if(timer){   
    clearTimeout(timer)
  }
  timer = setTimeout(() => {
    console.log(input1.value)
    timer = null
  }, 500);
})
// 封裝一個(gè)方法
function debounce(fn,delay = 500){
  // timer在閉包中
  let timer =null
  return function(){
    if(timer){   
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn()
      // fn.apply(this,arguments) 用這種更完美
      timer = null
    }, delay);
  }
}
input1.addEventListener('keyup',debounce(()=>{
  console.log(input1.value)
},1000))

節(jié)流throttle

拖拽一個(gè)元素時(shí)辩蛋,要隨時(shí)拿到該元素被拖拽的位置,直接用drag事件移盆,則會(huì)頻繁觸發(fā)悼院,很容易導(dǎo)致卡頓。 節(jié)流:無(wú)論拖拽速度多快咒循,都會(huì)每隔100ms觸發(fā)一次

const div1 = document.getElementById('div1')
var timer = null
div1.addEventListener('drag',function(e){
  //存在timer則說(shuō)明前一次還沒(méi)執(zhí)行完据途,必須前一次執(zhí)行完,才能執(zhí)行下一次操作叙甸,確保規(guī)定時(shí)間只執(zhí)行一次颖医,
  // 和防抖的區(qū)別在于,防抖是清空原來(lái)執(zhí)行新的裆蒸,節(jié)流是執(zhí)行原來(lái)的熔萧,正好相反
  if(timer){ 
    return
  }
  timer = setTimeout(() => {
    console.log(e.offsetX,e.offsetY)
    timer = null
  }, 500);
  
})

// 封裝一個(gè)方法
function throttle(fn,delay = 500){
  // timer在閉包中
  let timer =null
  return function(){
    if(timer){   
      return
    }
    timer = setTimeout(() => {
      console.log(this) //this是div1,箭頭函數(shù)承接上文,就是return的方法
      fn.apply(this,arguments) //只是為了綁定事件的參數(shù),fn.apply({},arguments)也可以起到效果
      timer = null
    }, delay);
  }
}
div1.addEventListener('drag',throttle((e)=>{
  console.log(this) //this是window 箭頭函數(shù)承接上文僚祷,就是window
  console.log(e.offsetX,e.offsetY)
},1000))
div1.addEventListener('drag',throttle(function(e){
  console.log(this) //this是div
  console.log(e.offsetX,e.offsetY)
},1000))

性能監(jiān)控

window.performance.timing

微信截圖_20210310144659.png
微信截圖_20210310144938.png

以下給出統(tǒng)計(jì)頁(yè)面性能指標(biāo)的方法佛致。

let times = {};
let t = window.performance.timing;

// 優(yōu)先使用 navigation v2  https://www.w3.org/TR/navigation-timing-2/
if (typeof win.PerformanceNavigationTiming === 'function') {
  try {
    var nt2Timing = performance.getEntriesByType('navigation')[0]
    if (nt2Timing) {
      t = nt2Timing
    }
  } catch (err) {
  }
}

//重定向時(shí)間
times.redirectTime = t.redirectEnd - t.redirectStart;

//dns 查詢(xún)耗時(shí)
times.dnsTime = t.domainLookupEnd - t.domainLookupStart;

//TTFB 讀取頁(yè)面第一個(gè)字節(jié)的時(shí)間
times.ttfbTime = t.responseStart - t.navigationStart;

//DNS 緩存時(shí)間
times.appcacheTime = t.domainLookupStart - t.fetchStart;

//卸載頁(yè)面的時(shí)間
times.unloadTime = t.unloadEventEnd - t.unloadEventStart;

//tcp 連接耗時(shí)
times.tcpTime = t.connectEnd - t.connectStart;

//request 請(qǐng)求耗時(shí)
times.reqTime = t.responseEnd - t.responseStart;

//解析 dom 樹(shù)耗時(shí)
times.analysisTime = t.domComplete - t.domInteractive;

//白屏?xí)r間 
times.blankTime = (t.domInteractive || t.domLoading) - t.fetchStart;

//domReadyTime
times.domReadyTime = t.domContentLoadedEventEnd - t.fetchStart;

當(dāng)然現(xiàn)在如果要做性能監(jiān)控的話(huà),一般都不用自己寫(xiě)久妆,git上有大把的開(kāi)源項(xiàng)目給你選擇晌杰。
這篇性能優(yōu)化的文章寫(xiě)的也很不錯(cuò)哦:https://segmentfault.com/a/1190000019185648

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市筷弦,隨后出現(xiàn)的幾起案子肋演,更是在濱河造成了極大的恐慌,老刑警劉巖烂琴,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爹殊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡奸绷,警方通過(guò)查閱死者的電腦和手機(jī)梗夸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)号醉,“玉大人反症,你說(shuō)我怎么就攤上這事∨吓桑” “怎么了铅碍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)线椰。 經(jīng)常有香客問(wèn)我胞谈,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任烦绳,我火速辦了婚禮卿捎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘径密。我一直安慰自己午阵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布享扔。 她就那樣靜靜地躺著趟庄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伪很。 梳的紋絲不亂的頭發(fā)上戚啥,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音锉试,去河邊找鬼猫十。 笑死,一個(gè)胖子當(dāng)著我的面吹牛呆盖,可吹牛的內(nèi)容都是我干的拖云。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼应又,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宙项!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起株扛,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尤筐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后洞就,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盆繁,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年旬蟋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了油昂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡倾贰,死狀恐怖冕碟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匆浙,我是刑警寧澤安寺,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站吞彤,受9級(jí)特大地震影響我衬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饰恕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一挠羔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧埋嵌,春花似錦破加、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至了罪,卻和暖如春锭环,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泊藕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工辅辩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娃圆。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓玫锋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親讼呢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撩鹿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容