欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

网站性能优化

程序员文章站 2022-05-14 13:57:25
...

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

  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管理多个容器