一、简介
web 性能优化包括:无用代码移除 (Tree-shaking)、作用域提升(Scope hoisting)、代码分割(Code-spliting),交叉观察器(Intersection observer)、服务器推送(server push)、客户端提示(clients hints)、HTTP/2、service workers 等等这些手段。
性能优化不是在开发之后!根据性能的影响来做设计决策,性能必须被测量、监测和不断的完善。
以用户为中心的性能指标可以更好地洞察实际的用户体验。
1 古老的雅虎军规
尽量减少 HTTP 请求个数——须权衡
使用 CDN(内容分发网络)
为文件头指定 Expires 或 Cache-Control ,使内容具有缓存性。
~ 避免空的 src 和 href
使用 gzip 压缩内容
把 CSS 放到顶部
把 JS 放到底部
避免使用 CSS 表达式
将 CSS 和 JS 放到外部文件中
减少 DNS 查找次数
精简 CSS 和 JS
避免跳转
剔除重复的 JS 和 CSS
配置 ETags
使 AJAX 可缓存
尽早刷新输出缓冲
使用 GET 来完成 AJAX 请求
延迟加载
预加载
减少 DOM 元素个数
根据域名划分页面内容
尽量减少 iframe 的个数
避免 404
减少 Cookie 的大小
使用无 cookie 的域
减少 DOM 访问
开发智能事件处理程序
用 代替 @import
避免使用滤镜
优化图像
优化 CSS Spirite
不要在 HTML 中缩放图像——须权衡
favicon.ico要小而且可缓存
保持单个内容小于25K
打包组件成复合文本
2 Chrome DevTools的Network面板
2.1 查看 DOMContentLoaded 和 load 事件信息
我们要明白这两个时期先要了解DOM文档加载流程:
- 解析HTML结构。
- 加载外部脚本和样式表文件。
- 解析并执行脚本代码。
- 构造HTML DOM模型。// DOMContentLoaded 相当于jQuery中的ready
- 加载图片等外部文件。
- 页面加载完毕。// load
2.2 查看网络耗时
重定向→拉取缓存→DNS 查询→建立 TCP 链接→发起请求→接收响应→处理 HTML 元素→元素加载完成
2.3 查看资源发起者和依赖项
按住 Shift 并将鼠标悬停在资源上,可以查看其发起者和依赖项。 本部分将您悬停的资源称为目标。
二、网络传输性能优化
1. 浏览器缓存
2. 资源打包压缩
- 减少请求数
- 减小请求资源体积
- 提升网络传输速率
- JS 压缩 uglifyjs-webpack-plugin
- HTML 压缩 html-webpack-plugin
- 提取公共资源
entry: {
vendor: [
'react',
'react-dom',
'react-native-web',
'react-router',
'react-router-dom',
'mobx',
'mobx-react',
'axios',
'sa-sdk-javascript'
],
},
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'runtime'],
// filename: 'static/js/[name].js',
minChunks: Infinity
}),
- 提取 CSS 并压缩 mini-css-extract-plugin
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
]
- webpack 开发环境修改为生产环境
- 服务器上开启 Gzip 传输压缩(不要对图片文件进行 Gzip 压缩)
3. 这个就高级了
- 识别并删除未使用的 CSS / JavaScript
- 修剪 JavaScript 依赖项的大小
- 异步加载 JavaScript
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成
- 分包 优化首屏
webpack 配置
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'runtime'],
// filename: 'static/js/[name].js',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
children: true,
async: 'children-async'
}),
new ExtractTextPlugin({filename: 'static/css/[name].[chunkhash:5].css', allChunks: true}),
4. 图片资源优化
- 使用大小合适的图片
- 使用雪碧图
- 【强烈推荐】使用字体图标
- 逐步加载图像
- 使用webP (单独讲述)
5. 网络传输性能检测工具——Page Speed
6. 使用 CDN
7. HTTP / 2
- 迁移到HTTPS,然后打开HTTP / 2
- 是否采用 IPv6
三、页面渲染性能优化
1. 浏览器渲染过程(Webkit)
2. DOM 渲染层与 GPU 硬件加速
页面的真实样子就是这样,是由多个 DOM 元素渲染层(Layers)组成的,实际上一个页面在构建完 render tree 之后,是经历了这样的流程才最终呈现在我们面前的:
- 浏览器会先获取 DOM 树并依据样式将其分割成多个独立的渲染层
- CPU 将每个层绘制进绘图中
- 将位图作为纹理上传至 GPU(显卡)绘制
- GPU 将所有的渲染层缓存(如果下次上传的渲染层没有发生变化,GPU 就不需要对其进行重绘)并复合多个渲染层最终形成我们的图像
从上面的步骤我们可以知道,布局是由 CPU 处理的,而绘制则是由 GPU 完成的。
3. 重排与重绘
-
重排(reflow):渲染层内的元素布局发生修改,都会导致页面重新排列,比如窗口的尺寸发生变化、删除或添加 DOM 元素,修改了影响元素盒子大小的 CSS 属性(诸如:width、height、padding)
-
重绘(repaint):绘制,即渲染上色,所有对元素的视觉表现属性的修改,都会引发重绘
4. 优化策略
- CSS 属性读写分离:浏览器每次对元素样式进行读操作时,都必须进行一次重新渲染(重排 + 重绘),所以我们在使用 JS 对元素样式进行读写操作时,最好将两者分离开,先读后写,避免出现两者交叉使用的情况。最最最客观的解决方案,就是不用 JS 去操作元素样式,这也是我最推荐的
- 通过切换 class 或者 style.csstext 属性去批量操作元素样式
- DOM 元素离线更新:当对 DOM 进行相关操作时,例、appendChild 等都可以使用 Document Fragment 对象进行离线操作,带元素“组装”完成后再一次插入页面,或者使用 display:none 对元素隐藏,在元素“消失”后进行相关操作
- 将没用的元素设为不可见:visibility: hidden,这样可以减小重绘的压力,必要的时候再将元素显示
- 压缩 DOM 的深度,一个渲染层内不要有过深的子元素,少用 DOM 完成页面样式,多使用伪元素或者 box-shadow 取代
- 图片在渲染前指定大小:因为 img 元素是内联元素,所以在加载图片后会改变宽高,严重的情况会导致整个页面重排,所以最好在渲染前就指定其大小,或者让其脱离文档流
- 对页面中可能发生大量重排重绘的元素单独触发渲染层,使用 GPU 分担 CPU 压力。(这项策略需要慎用,得着重考量以牺牲 GPU 占用率能否换来可期的性能优化,毕竟页面中存在太多的渲染层对与 GPU 而言也是一种不必要的压力,通常情况下,我们会对动画元素采取硬件加速。)
四、JS 阻塞性能
参考资料
- ng 设置etag
- ng 图片 gzip