網站效能優化

文叔叔發表於2018-09-04

網站效能優化是必須的技能,而且需要長期積累,以下是我自己總結的一些效能優化的策略,主要分為幾個方面:

  1. 網路請求優化
  2. 頁面渲染優化
  3. JS阻塞效能與記憶體洩漏
  4. 負載均衡

1 網路請求優化

1.1 瀏覽器快取

瀏覽器在向伺服器發起請求前,會先查詢本地是否有相同的檔案,如果有,就會直接拉取本地快取,這和我們在後臺部署的Redis、Memcache類似,都是起到了中間緩衝的作用,我們先看看瀏覽器處理快取的策略:

網站效能優化

瀏覽器預設的快取是放在記憶體內的,記憶體裡的快取會因為程式的結束或者說瀏覽器的關閉而被清除,而存在硬碟裡的快取才能夠被長期保留下去。很多時候,我們在network皮膚中各請求的size項裡,會看到兩種不同的狀態:from memory cache 和 from disk cache,前者指快取來自記憶體,後者指快取來自硬碟。而控制快取存放位置的,不是別人,就是我們在伺服器上設定的Etag欄位。在瀏覽器接收到伺服器響應後,會檢測響應頭部(Header),如果有Etag欄位,那麼瀏覽器就會將本次快取寫入硬碟中。

以Nginx為例,設定Etag

etag on;   //開啟etag驗證
expires 14d;    //設定快取過期時間為14天
複製程式碼

開啟我們的網站,在chrome devtools的network皮膚中觀察我們的請求資源,如果在響應頭部看見Etag和Expires欄位,就說明我們的快取配置成功了。

網站效能優化

在我們配置快取時一定要切記,瀏覽器在處理使用者請求時,如果命中強快取,瀏覽器會直接拉取本地快取,不會與伺服器發生任何通訊,也就是說,如果我們在伺服器端更新了檔案,並不會被瀏覽器得知,就無法替換失效的快取。所以我們在構建階段,需要為我們的靜態資源新增md5 hash字尾,避免資源更新而引起的前後端檔案無法同步的問題。

1.2 資源打包壓縮

我們之前所作的瀏覽器快取工作,只有在使用者第二次訪問我們的頁面才能起到效果,如果要在使用者首次開啟頁面就實現優良的效能,必須對資源進行優化。我們常將網路效能優化措施歸結為三大方面:減少請求數、減小請求資源體積、提升網路傳輸速率。現在,讓我們逐個擊破:

以Webpack為例

  • 壓縮JS
new webpack.optimize.UglifyJsPlugin()
複製程式碼
  • 壓縮Html
new HtmlWebpackPlugin({
            template: __dirname + '/views/index.html', // new 一個這個外掛的例項,並傳入相關的引數
            filename: '../index.html',
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true,
            },
            chunksSortMode: 'dependency'
        })
複製程式碼

我們在使用html-webpack-plugin 自動化注入JS、CSS打包HTML檔案時,很少會為其新增配置項,這裡我給出樣例,大家直接複製就行。

PS:這裡有一個技巧,在我們書寫HTML元素的src 或 href 屬性時,可以省略協議部分,這樣也能簡單起到節省資源的目的。

  • 壓縮CSS

在使用webpack的過程中,我們通常會以模組的形式引入css檔案(webpack的思想不就是萬物皆模組嘛),但是在上線的時候,我們還需要將這些css提取出來,並且壓縮,這些看似複雜的過程只需要簡單的幾行配置就行

const ExtractTextPlugin = require('extract-text-webpack-plugin')
module: {
        rules: [..., {
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                fallback: 'style-loader',
                use: {
                    loader: 'css-loader',
                    options: {
                        minimize: true
                    }
                }
            })
        }]
    }
複製程式碼
  • 使用webpack3的新特性:ModuleConcatenationPlugin
new webpack.optimize.ModuleConcatenationPlugin()
複製程式碼
  • 把prod環境的shouldUseSourceMap設定為false,去掉build生成的map檔案
devtool: shouldUseSourceMap ? 'source-map' : false,
複製程式碼

最後,我們還應該在伺服器上開啟Gzip傳輸壓縮,它能將我們的文字類檔案體積壓縮至原先的四分之一,效果立竿見影,還是切換到我們的nginx配置文件,新增如下兩項配置專案:

gzip on;
gzip_types text/plain application/javascriptapplication/x-javascripttext/css application/xml text/javascriptapplication/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
複製程式碼

如果你在網站請求的響應頭裡看到這樣的欄位,那麼就說明我們們的Gzip壓縮配置成功啦:

網站效能優化

【!!!特別注意!!!】不要對圖片檔案進行Gzip壓縮!不要對圖片檔案進行Gzip壓縮!不要對圖片檔案進行Gzip壓縮!我只會告訴你效果適得其反,至於具體原因,還得考慮伺服器壓縮過程中的CPU佔用還有壓縮率等指標,對圖片進行壓縮不但會佔用後臺大量資源,壓縮效果其實並不可觀,可以說是“弊大於利”,所以請在gzip_types 把圖片的相關項去掉。針對圖片的相關處理,我們接下來會更加具體地介紹。

1.3 圖片資源優化

  • 不要在HTML裡縮放影像
  • 使用雪碧圖(CSS Sprite)
  • 使用字型圖示(iconfont)

1.4 使用CDN

使用CDN存放靜態資源,避免頻寬爆炸以及加快資源下載.

2 頁面渲染效能優化

網站效能優化

2.1 減少重繪和迴流

  • CSS屬性讀寫分離:瀏覽器每次對元素樣式進行讀操作時,都必須進行一次重新渲染(迴流 + 重繪),所以我們在使用JS對元素樣式進行讀寫操作時,最好將兩者分離開,先讀後寫,避免出現兩者交叉使用的情況。最最最客觀的解決方案,就是不用JS去操作元素樣式,這也是我最推薦的。
  • 通過切換class或者使用元素的style.csstext屬性去批量操作元素樣式。
  • DOM元素離線更新:當對DOM進行相關操作時,例、appendChild等都可以使用Document Fragment物件進行離線操作,帶元素“組裝”完成後再一次插入頁面,或者使用display:none 對元素隱藏,在元素“消失”後進行相關操作。
  • 將沒用的元素設為不可見:visibility: hidden,這樣可以減小重繪的壓力,必要的時候再將元素顯示。
  • 壓縮DOM的深度,一個渲染層內不要有過深的子元素,少用DOM完成頁面樣式,多使用偽元素或者box-shadow取代。
  • 圖片在渲染前指定大小:因為img元素是內聯元素,所以在載入圖片後會改變寬高,嚴重的情況會導致整個頁面重排,所以最好在渲染前就指定其大小,或者讓其脫離文件流。
  • 對頁面中可能發生大量重排重繪的元素單獨觸發渲染層,使用GPU分擔CPU壓力。(這項策略需要慎用,得著重考量以犧牲GPU佔用率為代價能否換來可期的效能優化,畢竟頁面中存在太多的渲染層對於GPU而言也是一種不必要的壓力,通常情況下,我們會對動畫元素採取硬體加速。)

2.2 減少頁面重新渲染以及Dom巢狀

  • 以React為例,如果會引起頁面State變化的,最好在shouldComponentUpdate進行處理,避免每次props或者state更新的時候都重新渲染,如果頁面主要用來顯示的話,可以使用PureComponent代替Component,注意PureComponent對於引用型別的變化,不會重新渲染。巧用Fragment代替Component,減少Dom巢狀。

3 JS阻塞效能與記憶體洩漏

3.1 巧用JS的防抖與節流

函式防抖:將幾次操作合併為一此操作進行。原理是維護一個計時器,規定在delay時間後觸發函式,但是在delay時間內再次觸發的話,就會取消之前的計時器而重新設定。這樣一來,只有最後一次操作能被觸發。

function debounce(fn, wait) {
    var timeout = null;
    return function() {
        if(timeout !== null) 
                clearTimeout(timeout);
        timeout = setTimeout(fn, wait);
    }
}
複製程式碼

函式節流:使得一定時間內只觸發一次函式。原理是通過判斷是否到達一定時間來觸發函式。

var throttle = function(func, delay) {
            var prev = Date.now();
            return function() {
                var context = this;
                var args = arguments;
                var now = Date.now();
                if (now - prev >= delay) {
                    func.apply(context, args);
                    prev = Date.now();
                }
            }
        }
複製程式碼

區別: 函式節流不管事件觸發有多頻繁,都會保證在規定時間內一定會執行一次真正的事件處理函式,而函式防抖只是在最後一次事件後才觸發一次函式。 比如在頁面的無限載入場景下,我們需要使用者在滾動頁面時,每隔一段時間發一次 Ajax 請求,而不是在使用者停下滾動頁面操作時才去請求資料。這樣的場景,就適合用節流技術來實現。

3.2 記憶體洩漏

  • 閉包記憶體洩漏Pattern
  • 在某個頁面(SPA)WillUnMount的時候,記得關閉一些資源,例如WebSocket的斷開,eChart物件置空等。

4 負載均衡

  1. 使用PM2管理多程式
  2. Nginx做反向代理
  3. Docker管理多個容器

相關文章