网站性能优化

in #web6 years ago (edited)

性能优化是必须的技能,而且需要长期积累,以下是我自己总结的一些性能优化的策略,主要分为几个方面:

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({
        })

我们在使用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();
                }
            }
}

3.2 内存泄漏

  • 闭包内存泄漏Pattern
  • 在某个页面(SPA)WillMount的时候,记得关闭一些资源,例如WebSocket的断开,eChart对象置空等。

4. 负载均衡

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