前端性能

性能衡量指标

相关指标及计算方式:


维度:

  • 运营商
  • 网络
  • URL

性能监控

如何监控

性能优化

优化点:

  • 高频事件消抖、节流。使用_.debounce()_. throttle(),控制高频事件的操作,如:scrollonChange
    • _.debounce()的多次连续的调用,最终实际上只会调用一次;
    • _. throttle()将频繁调用的函数限定在一个给定的调用频率内。
  • JavaScritp很快,但是DOM很慢,减少修改DOM。
    • DOM的渲染需要计算DOM+CSSOM,每次DOM和CSSOM的每次修改都会触发重绘;
      • 渲染过程:JavaScript -> Style -> Layoout -> Paint -> Composite
    • 避免 position: fixed;布局,Z轴图层堆叠关系会改变,引起重绘;
    • 图层隔离:将那些会变动的元素提升至单独的图层,比如:动画、渐变;
    • 降低图层复杂度;
    • 避免线程阻塞;
    • 优化CSS;
    • 文件:引入方式、位置、文件合并、延迟加载;
    • 硬件加速:GPU加速渲染

基于各环节优化:
path

  • 减少http请求,合理设置 HTTP缓存
  • 使用HTTP/2
  • 持久连接,使用 keep-alive 或者 WebSocket
  • 使用浏览器缓存
  • 启用压缩
  • CSS Sprites
  • LazyLoad Images
  • 样式文件放在顶部,脚本文件放在底部
  • 减少 cookie 传输
  • CDN 加速
    • CDN(contentdistribute network,内容分发网络)的本质仍然是一个缓存,而且将数据缓存在离用户最近的地方,使用户以最快速度获取数据,即所谓网络访问第一跳。
  • 反向代理
    • 传统代理服务器位于浏览器一侧,代理浏览器将http请求发送到互联网上,而反向代理服务器位于网站机房一侧,代理网站web服务器接收http请求。
    • 论坛网站,把热门词条、帖子、博客缓存在反向代理服务器上加速用户访问速度,当这些动态内容有变化时,通过内部通知机制通知反向代理缓存失效,反向代理会重新加载最新的动态内容再次缓存起来。
    • 此外,反向代理也可以实现负载均衡的功能,而通过负载均衡构建的应用集群可以提高系统总体处理能力,进而改善网站高并发情况下的性能。
  • 面向未来,考虑 service worker

加载优化

加载过程是最为耗时的过程,可能会占到总耗时的80%时间,是优化重点

1、减少HTTP请求

  • 合并 CSS、JavaScript
  • 合并小图片,使用雪碧图

2、缓存

  • 缓存一切可缓存的资源
  • 使用长 Cache(使用时间戳更新Cache)
  • 使用外联式引用CSS、JavaScript

3、压缩HTML、CSS、JavaScript

减少资源大小可以加快网页显示速度,所以要对HTML、CSS、JavaScript等进行代码压缩,并在服务端设置GZip

  • 压缩(例如,多余的空格、换行符和缩进)
  • 启用Gzip

4、无阻塞

写在HTML头部的JavaScript(无异步),和写在HTML标签中的Style会阻塞页面的渲染,因此CSS放在页面头部并使用Link方式引入,避免在HTML标签中学Style,JavaScript放在页面尾部,或使用异步方式加载

5、使用首屏加载

首屏的快速显示,可以大大提升用户对⻚面速度的感知,因此应尽量针对首屏的快速显示做优化

6、按需加载

将不影响首屏的资源和当前屏幕资源不用的资源放到用户需要时才加载,可以大大提升重要资源的 显示速度和降低总体流量。

PS:按需加载会导致大量重绘,影响渲染性能

  • LazyLoad
  • 滚屏加载
  • 通过 Media Query 加载

7、预加载

大型重资源⻚面(如游戏)可使用增加Loading的方法,资源加载完成后再显示⻚面。但Loading时间过⻓,会造成用户流失
对用户行为分析,可以在当前⻚加载下一⻚资源,提升速度

  • 可感知Loading(如进入空间游戏的Loading)
  • 不可感知的Loading(如提前加载下一⻚)

8、压缩图片

图片是最占流量的资源,因此尽量避免使用他,使用时选择最合适的格式(实现需求的前提下,以 大小判断),合适的大小,然后使用智图压缩,同时在代码中用 srcset 来按需显示

PS:过度压缩图片大小影响图片显示效果

  • 使用其它方式代替图片(1. 使用CSS3 2. 使用SVG 3. 使用IconFont)
  • 使用 srcset
  • 选择合适的图片 (1. webP优于JPG 2. PNG8优于GIF)
  • 选择合适的大小 (1. 首次加载不大于1024KB 2. 不宽于640(基于手机屏幕一般宽度))

Cookie会影响加载速度,所以静态资源域名不使用Cookie。

10、避免重定向

重定向会影响加载速度,所以在服务器正确设置避免重定向。

11、异步加载第三方资源

第三方资源不可控会影响⻚面的加载和显示,因此要异步加载第三方资源。

12、CDN 预热

可以将静态资源提前预热处理,加速用户的访问速度。

脚本执行优化

脚本处理不当会阻塞⻚面加载、渲染,因此在使用时需当注意

1、CSS写在头部,JavaScript写在尾部或异步

2、避免图片和 iframe 等的空 src

空Src会重新加载当前⻚面,影响速度和效率。

3、尽量避免重设图片大小

重设图片大小是指在⻚面、CSS、JavaScript 等中多次重置图片大小,多次重设图片大小会引发图片的多次重绘,影响性能。

4、图片尽量避免使用DataURL

DataURL图片没有使用图片的压缩算法文件会变大,并且要解码后再渲染,加载慢耗时⻓。

三、CSS优化

1、尽量避免写在HTML标签中写Style属性

2、避免CSS表达式

CSS表达式的执行需跳出CSS树的渲染,因此请避免CSS表达式。

3、移除空的CSS规则

空的CSS规则增加了CSS文件的大小,且影响CSS树的执行,所以需移除空的CSS规则。

4、正确使用 Display 的属性

Display属性会影响⻚面的渲染,因此请合理使用

  • display:inline 后不应该再使用 widthheightmarginpadding 以及 float
  • display:inline-block 后不应该再使用 float
  • display:block 后不应该再使用 vertical-align
  • display:table-* 后不应该再使用 margin 或者 float

5、不滥用 Float

Float在渲染时计算量比较大,尽量减少使用。

6、不滥用Web字体

Web字体需要下载,解析,重绘当前⻚面,尽量减少使用。

7、不声明过多的 font-size

过多的 font-size 引发CSS树的效率。

8、值为 0 时不需要任何单位

为了浏览器的兼容性和性能,值为0时不要带单位。

9、标准化各种浏览器前缀

  • 无前缀应放在最后。
  • CSS动画只用 (-webkit- 无前缀)两种即可。
  • 其它前缀为 -webkit- -moz- -ms- 无前缀 四种,(-o-Opera浏览器改用blink内核,所以淘汰)

JavaScript执行优化

1、减少重绘和回流

  • 避免不必要的Dom操作
  • 尽量改变 class 而不是 style,使用 classList 代替 className
  • 避免使用 document.write
  • 减少drawImage

2、缓存Dom选择与计算

Webpack相关

1、分割配置文件 dev prod

开发环境:

  • 构建更快
  • 模块热替换
  • 能从chrome控制台报错信息对应的源码的错误处(source map)等

生产环境

  • chunk分离
  • 缓存
  • 安全
  • tree shaking

对于开发和生产环境的配置文件肯定是不完全相同的,所以我们将不同环境的配置文件分离开,同时将共用部分抽出来,最后使用 webpack-merge 插件整合。

2、代码分离 prod

代码分离能够将工程代码分离到各个文件中,然后按需加载或并行加载这些文件,也用于获取更小的 bundle,以及控制资源加载优先级
webpack有三种常用的代码分离方法:

  • 入口起点:配置多入口,输出多个chunk。
  • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk(webpack4 虽然废弃,但有代替方法)
  • 动态导入:常用于按需加载的模块分离
    • 路由组件动态导入
    • 普通组件的异步加载

Loader dev prod

为Loader指定作用范围可以加快我们的构建速度,提升开发体验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
test: /\.jsx?$/,
use: [
"babel-loader"
],
exclude: /node_modules/
}
//or
{
test: /\.jsx?$/,
use: [
"babel-loader"
],
include: /src/
}

webpack 解析(resolve) dev

resolve的部分配置过多项,会导致webpack搜索范围变大,效率下降,如没有特殊需求,可以保持默认配置保持开发体验。

1
2
3
4
5
6
7
8
//解析模块时应该搜索的目录
modules: ["node_modules"] //default

//自动解析确定的扩展,不宜过多
extensions: [".js", ".json"] //default

//解析目录时要使用的文件名,不宜过多
mainFiles: ["index"] //default

webpack 外部扩展(Externals) dev prod

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法,什么意思呢?顾名思义,externals可以排除掉我们打包结果中某些依赖:

1
2
3
4
5
6
7
8
//排除react 我们打包后的bundle中没有react了
externals: {
//将react指向一个全局变量 react
react: "react"
}

//这里的 "react" 会直接去全局变量中寻找
import react from "react"

Dlls 优化构建速度 dev

webpack在每次build时都会将整个项目重新构建一遍,不管这个文件是否发生了改变(当然若文件未更改hash值不变),但是其实我们所依赖的三方库更改并不频繁,若是将三方库抽离出来单独构建,将构建好的目标和构建生成的json对照文件引如我们的项目,这样不用每次都去打包这些代码。

使用 DllPlugin 将更改不频繁的代码进行单独编译。这将改善引用程序的编译速度,即使它增加了构建过程的复杂性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//新建 webpack.dll.conf.js
module.exports = {
entry: {
vendor: [react, ...]
},
output: {
filename: 'dll.[name].js'
},
plugins: [
new webpack.DllPlugin({
path: '[name]-manifest.json'
})
]
}

//webpack.base.conf.js
const manifest = require('./vendor-manifest.json')

plugins: [
new webpack.DllReferencePlugin({
manifest
})
]

最后需要手动将生成的dll.[name].js引入到html文件中去,当然这一步骤可以用assets-webpack-plugin插件优化。

图片等静态文件 dev prod

通常来说,我们会通过使用 file-loaderurl-loader 等 loader 来处理项目中的静态文件,如图片字体等文件。

1
2
3
4
5
6
7
8
9
//这样最终dist文件中就会生成font文件夹存放字体文件
{
test: /\.(woff|svg|eot|ttf)\??.*$/,
loader: "url-loader",
options: {
limit: 8192,
name: "font/[name].[hash:6].[ext]"
}
}

limit 属性是在文件大小超出 limit 的值才会单独打包,否则使用 base64 的方式引用,通常适用于小图片,这就是我们通常的文件处理方式。

使用base64引入图片的好处是减少http请求数,但相应的问题是base64占用的空间比普通的图片文件大一点。

source map dev

在开发环境中,我们比较关注调试的方便程度,而原始 webpack 打包后的 bundle 文件中可能包含来自多个文件的内容,对于程序的报错信息往往简单的指向这个 bundle 文件

而source map是为了帮助我们定位程序出现的错误对应的源代码的位置。使用sourceMap报错信息正确的指向了源码的错误位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1 使用devtool选项配置,有多个选项可选
module.exports = {
devtool: 'inline-source-map',
};

//2 使用plugins方式进行更细粒度的配置
module.exports = {
plugins: [
new webpack.SourceMapDevToolPlugin({
filename: '[name].js.map',
exclude: ['vendor.js']
})
]
};

//在使用uglifyjs-webpack-plugin时 需要开启sourceMap选项

UglifyJsPlugin 配合 tree shaking prod

Split CSS prod

将css放在js中引入势必会延缓css树的计算。 所以将css从js中分离,打包成单独的css文件,然后和js并行加载是我们项目的一个提升点,这样可以加快界面渲染速度,也可以单独做缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//使用插件 extract-text-webpack-plugin
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
//用于css未被提取(allChunks: false)
fallback: "style-loader",
use: 'css-loader'
})
}

new ExtractTextPlugin({
filename: 'common.[chunkhash].css'
allchunk: true
})

清理 /dist 文件夹 prod

webpack将打包的文件放在dist文件夹中,若是使用了hash文件名,则每次文件变动后重新打包生成的文件名都会不同,这会造成dist目录越来越混乱,好的做法是每次打包前先清理dist文件夹:

1
new CleanWebpackPlugin(pathsToClean, cleanOptions)

在内存中编译 dev

webpack-dev-middleware 提供了在内存中编译功能,它在文件更改后自动编译文件并保存在内存中,具体表现为,刷新浏览器即可看到我们的更改。

webpack-hot-middleware 提供了服务端推送功能,通常和 webpack-dev-middleware 配合使用,当文件更改并自动编译完成后,服务端通过 SSE(服务端发送事件)将更改信息推送到客户端,客户端会接收到一个 json 文件,其中包含了更改了的文件的一些信息,客户端会根据这些信息主动向服务端获取最新的文件。

相关文章