页面生命周期
地址栏输入url 发生了什么?
加载优化
- 判断输入的为搜索关键字还是请求的url,构建成完整的url请求
2. 查找本地缓存,没找到资源继续(http缓存,PWA)
3. DNS解析,获取IP地址(DNS缓存)
4. https需要建立TLS连接
5. 三次握手建立TCP连接(连接复用 http1.1 connection:keep-alive;http2)
6. 浏览器构建请求头、请求行等,把域名相关的cookie带到请求头中(减小cookie体积)
7. 服务器接收到信息后,返回响应信息,如果是301,302等重定向,会重新发送请求(减少重定向)
8. 浏览器接收到文件,比如html,解析html,边接收边解析,不用等html加载完成
9. 碰到js、css(外联的)等,请求js、css资源,js会阻塞加载,css不直接阻塞(css放头部,js放尾部;cdn)
减少请求数量(缓存,小图片转成base64,字体图标,雪碧图等)
减少请求体积(压缩,删除注释,tree shaking,拆包,gzip等)
渲染优化
- 构建DOM树 DOM Tree
11. 样式计算 Recaculate Style
12. 布局计算 Layout Tree
13. 分层树 Layer Tree
14. 图层绘制 Paint
15. 栅格化 Raster
16. 合成和显示
减少重排,重绘
合理使用合成
一个大的原则就是让单个帧的生成速度变快
- 渲染进程将 HTML 内容转换为能够读懂的DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令DrawQuad给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
重排(更新了元素的几何属性)
重绘(更新元素的绘制属性)
直接合成阶段
不同阶段的优化策略
打包优化
vite
用于开发阶段
webpack
- noParse
noParse表示不需要解析的文件,有的文件可能是来自第三方的文件,被 providePlugin引入作为windows上的变量来使用,这样的文件相对比较大,并且已经是被打包过的,所以把这种文件排除在外是很有必要的,配置如下
module: {
noParse: [/proj4\.js/]
}
- exclude
某些loader会有这样一个属性,目的是指定loader作用的范围,exclude表示排除某些文件不需要babel-loader处理,loader的作用范围小了,打包速度自然就快了,用babel-loader举一个简单例子
{
test: /\.js$/,
loader: "babel-loader",
exclude: path.resolve(__dirname, 'node_modules')
}
- devtool
这个配置是一个调试项,不同的配置展示效果不一样,打包大小和打包速度也不一样,比如开发环境下cheap-source-map肯定比source-map快,至于为什么,强烈推荐自己之前写的这一篇讲解devtool的文章:webpack devtools篇讲的非常详细。
{
devtool: 'cheap-source-map'
}
- .eslintignore
让不必要的文件禁止eslint,只对需要的文件eslint
- cache-loader
这个loader就是在第一次打包的时候会缓存打包的结果,在第二次打包的时候就会直接读取缓存的内容,从而提高打包效率。但是也需要合理利用,我们要记住一点你加的每一个loader,plugins都会带来额外的打包时间。这个额外时间比他带来的减少时间多,那么一味的增加这个loader就没意义,所以cache-loader最好用在耗时比较大的loader上,配置如下
{
rules: [
{
test: /\.vue$/,
use: [
'cache-loader',
'vue-loader'
],
include: path.resolve(__dirname, './src')
}
]
}
terser-webpack-plugin
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
cache: true
})
],
}
DllPlugin,webpack.DllReferencePlugin
先把第三方依赖先打包一次生成一个js文件,然后真正打包项目代码时候,会根据映射文件直接从打包出来的js文件获取所需要的对象,而不用再去打包第三方文件。只不过这种情况打包配置稍微麻烦点,需要写一个webpack.dll.js。大致如下
webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
library: ["vue", "moment"]
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'json-dll'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
path: './json-dll/library.json',
name: '[name].json'
})
]
}
webpack.dev.js
new AddAssetHtmlWebpack({
filepath: path.resolve(__dirname, './json-dll/library.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: require("./json-dll/library.json")
})
- webpack-cdn-plugin
- webpack-bundle-analyzer
分析打包后的文件,进行合适的优化
- speed-measure-webpack-plugin
这个插件可以告诉我们打包时候每一个loader或者plugin花费了多少时间,从而对耗时比较长的plugin和loader做优化
代码优化
单帧速度变快
减少js脚本执行的时间
有时js执行时间过长,会影响主线程执行渲染任务的时间,表现就是页面反应很慢。针对这种情况,一般有两种策略:
- 将一次执行的函数分解为多个任务,使得每次的执行时间不要过久(react time slicing)
- 利用Web Workers,主线程外的线程,不会影响主线程 (CPU 密集型任务)
减少重排重绘
- js 尽量减少对样式的操作,能用 css 完成的就用 css
- 对 dom 操作尽量少,能用 createDocumentFragment 的地方尽量用
- 如果必须要用 js 操作样式,能合并尽量合并不要分多次操作
- resize 事件 最好加上防抖,能尽量少触发就少触发
- 加载图片的时候,提前写好宽高
合理利用 CSS 合成动画
如果能提前知道对某个元素执行动画操作,那就最好将其标记为 will-change,这是告诉渲染引擎需要将该元素单独生成一个图层
避免频繁的垃圾回收
我们知道 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。
所以要尽量避免产生那些临时垃圾数据。那该怎么做呢?可以尽可能优化储存结构,尽可能避免小颗粒对象的产生。
其他
- 删除不使用的代码和功能
- 数据存在性判断,避免报错
- 尽可能缓存 (computed,Vue组件复用,react memo等)
- 避免内存泄漏
- 框架使用中的优化
- ……
体验优化
- 首屏优化
- 大量数据渲染优化
- 使用节流 throttle 和防抖 debounce
- 骨架图
- 分块加载,加载失败fallback
- 交互体验优化
- ……