web下的效能優化1(網路方向)

appleguardu發表於2020-05-02

效能優化(網路方向)

web應用無非是兩臺主機之間互相傳輸資料包的一個過程; 如何減少傳輸過程的耗時就是網路方向優化的重點, 優化出發點從第一篇文章中說起

DNS解析過程的優化

當瀏覽器從第三方服務跨域請求資源的時候,在瀏覽器發起請求之前,這個第三方的跨域域名需要被解析為一個IP地址,這個過程就是DNS解析;
DNS快取可以用來減少這個過程的耗時,DNS解析可能會增加請求的延遲,對於那些需要請求許多第三方的資源的網站而言,DNS解析的耗時延遲可能會大大降低網頁載入效能。

  • dns-prefetch

當站點引用跨域域上的資源時,都應在<head>元素中放置dns-prefetch提示,但是要記住一些注意事項。首先,dns-prefetch僅對跨域域上的DNS查詢有效,因此請避免將其用於您當前訪問的站點

<link rel="dns-prefetch" href="https://fonts.googleapis.com/">
  • preconnect

由於dns-prefetch僅執行DNS查詢,但preconnect會建立與伺服器的連線。如果站點是通過HTTPS服務的,則此過程包括DNS解析,建立TCP連線以及執行TLS握手。將兩者結合起來可提供機會,進一步減少跨源請求的感知延遲

<!-- 注意順序, precontent和dns-prefetch的相容性 -->
<link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin>
<link rel="dns-prefetch" href="https://fonts.googleapis.com/">

TCP傳輸階段優化

這個前端方面好像能做的有限, 我們都知道 http協議 是基於 tcp的;
升級http協議版本可以考慮下, 比如把 http/1.0 -> http/1.1 -> http/2;
這個需要我們在應用伺服器上配置(nginx, Apache等), 不做概述了, 另外還需要客戶端和伺服器都支援哦, 目前還沒開發出穩定版本,好多隻支援https,不過也不遠了...

  • http2 的優勢
#  1.多路複用: 同一個tcp連線傳輸多個資源
這樣可以突破統一域名下只允許有限個tcp同時連線, 
這樣http1.1所做的減少請求數優化就沒有太大必要了
如多張小圖合成一張大圖(雪碧圖),合併js和css檔案

# 2.報文頭壓縮和二進位制編碼: 減少傳輸體積
http1 中第一次請求有完整的http報文頭部,第二次請求的也是;
http2 中第一次請求有完整的http報文頭部,第二次請求只會攜帶 path 欄位;
這樣就大大減少了傳送的量。這個的實現要求客戶端和服務同時維護一個報文頭表。

# 3.Server Push
http2可以讓服務先把其它很可能客戶端會請求的資源(比如圖片)先push發給你,
不用等到請求的時候再傳送,這樣可以提高頁面整體的載入速度
但目前支援性不太好...emm...

總的來說, 在 c 端業務下不會太普及, 畢竟需要軟體支援才行...

http 請求響應階段優化

為了讓資料包傳輸的更快, 我們可以從兩個方面入手: 請求的資料包大小(伺服器), 請求資料包的頻率(客戶端)

減少請求檔案的大小

請求檔案對應的是我們專案完成後,打包所指的靜態資原始檔(會被部署到伺服器), 檔案越小, 傳輸的資料包也會相對較小, 講道理也會更快到達客戶端

how to reduce a package size?

目前我們都會使用打包工具了(比如webpack, rollup, glup 等), 如何使用工具來減小包的體積呢? 這邊建議您去官網文件呢...當然這裡列舉一下常用的手段(webpack 的), 但是注意要外掛版本更新哦

  • JS檔案壓縮
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
 plugins: [
   new UglifyJsPlugin({
     // 允許併發
     parallel: true,
     // 開啟快取
     cache: true,
     compress: {
       // 刪除所有的console語句    
       drop_console: true,
       // 把使用多次的靜態值自動定義為變數
       reduce_vars: true,
     },
     output: {
       // 不保留註釋
       comment: false,
       // 使輸出的程式碼儘可能緊湊
       beautify: false
     }
   })
 ]
}
  • CSS 檔案壓縮
// optimize-css-assets-webpack-plugin
plugins: [
  new OptimizeCSSAssetsPlugin({
    assetNameRegExp: /\.css$/g,
    cssProcessor: require('cssnano'),
  }),
];
  • html 檔案壓縮
// html-webpack-plugin
plugins: [
  new HtmlWebpackPlugin({
    template: path.join(__dirname, 'src/index.html'),
    filename: 'index.html',
    chunks: ['index'],
    inject: true,
    minify: {
      html5: true,
      collapseWhitespace: true,
      preserveLineBreaks: false,
      minifyCSS: true,
      minifyJS: true,
      removeComments: false,
    },
  }),
];
  • source map 檔案關閉
  • tree shaking
1.程式碼不會被執行,不可到達,比如 if(false){// 這裡邊的程式碼}
2.程式碼執行的結果不會被用到
3.程式碼只會影響死變數(只寫不讀)
4.方法不能有副作用

// 原理相關: 以後在研究
利用 ES6 模組的特點: 
  只能作為模組頂層的語句出現
  import 的模組名只能是字串常量
  import binding 是 immutable 的
程式碼擦除: uglify 階段刪除無用程式碼
  • scope hoisting(作用域提升)

分析出模組之間的依賴關係,儘可能的把打散的模組合併到一個函式中去,但前提是不能造成程式碼冗餘

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
module.exports = {
  resolve: {
    // 針對 Npm 中的第三方模組優先採用 jsnext:main 中指向的 ES6 模組化語法的檔案
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 開啟 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};
  • 專案中使用按需載入,懶載入(路由,元件級)
const router = new VueRouter({
  routes: [
    { path: '/foo', component: () => import(/* webpackChunkName: "foo" */ './Foo.vue') }
    { path: '/bar', component: () => import(/* webpackChunkName: "bar" */ './Bar.vue') }
  ]
})
  • 開啟 gizp 壓縮
有時候啟用也會消耗伺服器效能, 看情況使用吧

暫時先提這麼些吧...後續想到了再加

減少請求頻率

因為同一域名下 tcp 連線數的限制導致過多的請求會排隊阻塞, 所以我們需要儘量控制請求的數量和頻率

常見措施
  • 將靜態資源的內聯到HTML中

這樣這些資源無需從伺服器獲取, 但可能影響到渲染程式...

<!-- 1.小圖片內聯 base64 (url-loader) -->
<!-- 2.css內聯 -->
<!-- 3.js內聯 -->
<script>
  ${require('raw-loader!babel-loader!./node_modules/lib-flexible/flexible.js')}
</script>
  • 利用各級快取(下一篇儲存方面介紹)

通常都是在服務端做相關配置, 但你要知道

我們可以利用http快取(瀏覽器端)來減少和攔截二次請求, 當然一般都是在服務端設定的;
伺服器端也可以設定快取(redis等), 減少資料查詢的時間同樣可以縮短整個請求時間
  • 利用本地儲存
我們可以將常用不變的資訊存在本地(cookie,storage API 等);
判斷存在就不去請求相關的介面, 或者定期去請求也是可以的
  • 花錢買 CDN 加速

CDN 又叫內容分發網路,通過把資源部署到世界各地,使用者在訪問時按照就近原則從離使用者最近的伺服器獲取資源,從而加速資源的獲取速度。 CDN 其實是通過優化物理鏈路層傳輸過程中的網速有限、丟包等問題來提升網速的...

購買 cdn 伺服器;
然後把網頁的靜態資源上傳到 CDN 服務上去,
在請求這些靜態資源的時候需要通過 CDN 服務提供的 URL 地址去訪問;

# 注意, cdn 快取導致的新版本釋出後不生效的問題
所以打包的時候常在檔案後面加上 hash 值
然後在 HTML 檔案中的資源引入地址也需要換成 CDN 服務提供的地址
/alicdn/xx12dsa311.js

# 利用不同域名的 cdn 去存放資源, (tcp連線限制)
  • webpack 構建時新增 cdn
// 靜態資源的匯入 URL 需要變成指向 CDN 服務的絕對路徑的 URL 而不是相對於 HTML 檔案的 URL。
// 靜態資源的檔名稱需要帶上有檔案內容算出來的 Hash 值,以防止被快取。
// 不同型別的資源放到不同域名的 CDN 服務上去,以防止資源的並行載入被阻塞。
module.exports = {
  // 省略 entry 配置...
  output: {
    // 給輸出的 JavaScript 檔名稱加上 Hash 值
    filename: '[name]_[chunkhash:8].js',
    path: path.resolve(__dirname, './dist'),
    // 指定存放 JavaScript 檔案的 CDN 目錄 URL
    publicPath: '//js.cdn.com/id/',
  },
  module: {
    rules: [
      {
        // 增加對 CSS 檔案的支援
        test: /\.css$/,
        // 提取出 Chunk 中的 CSS 程式碼到單獨的檔案中
        use: ExtractTextPlugin.extract({
          // 壓縮 CSS 程式碼
          use: ['css-loader?minimize'],
          // 指定存放 CSS 中匯入的資源(例如圖片)的 CDN 目錄 URL
          publicPath: '//img.cdn.com/id/'
        }),
      },
      {
        // 增加對 PNG 檔案的支援
        test: /\.png$/,
        // 給輸出的 PNG 檔名稱加上 Hash 值
        use: ['file-loader?name=[name]_[hash:8].[ext]'],
      },
      // 省略其它 Loader 配置...
    ]
  },
  plugins: [
    // 使用 WebPlugin 自動生成 HTML
    new WebPlugin({
      // HTML 模版檔案所在的檔案路徑
      template: './template.html',
      // 輸出的 HTML 的檔名稱
      filename: 'index.html',
      // 指定存放 CSS 檔案的 CDN 目錄 URL
      stylePublicPath: '//css.cdn.com/id/',
    }),
    new ExtractTextPlugin({
      // 給輸出的 CSS 檔名稱加上 Hash 值
      filename: `[name]_[contenthash:8].css`,
    }),
    // 省略程式碼壓縮外掛配置...
  ],
};
/*
以上程式碼中最核心的部分是通過 publicPath 引數設定存放靜態資源的 CDN 目錄 URL,
為了讓不同型別的資源輸出到不同的 CDN,需要分別在:

output.publicPath 中設定 JavaScript 的地址。
css-loader.publicPath 中設定被 CSS 匯入的資源的的地址。
WebPlugin.stylePublicPath 中設定 CSS 檔案的地址。
設定好 publicPath 後,WebPlugin 在生成 HTML 檔案和 css-loader 轉換 CSS 程式碼時,會考慮到配置中的 publicPath,用對應的線上地址替換原來的相對地址。
*/

參考

DNS MDN]
webpack 文件
深入淺出 Webpack
Scope Hoisting

相關文章