性能优化-基础知识

2020/11/7 性能优化
// 为什么要做性能优化
1. 性能是web网站和应用的支柱
----------------------------------------------------------------------------------------------
// 移动端挑战多
1. 设备硬件、网速、屏幕尺寸、交互方式
2. 用户更缺少耐心,大于3m加载导致53%的跳出率
3. 持续增长的移动用户和移动电商业务
----------------------------------------------------------------------------------------------
// RAIL测量模型
1. R:Response 响应 // 处理事件应在50ms以内完成
2. A:Animation 动画 // 每10ms产生一帧
3. I:Idle 空闲 // 尽可能增加空闲时间
4. L:Load 加载 // 在5s内完成内容加载并可以交互
----------------------------------------------------------------------------------------------
// 测试网站性能工具
1. webpagetest.org // 多测试地点,全面性能报告
2. chrome 中 DevTools 的 lighthouse // 网站整体质量评估,也可以通过 npm 安装,用命令行使用
    First Contentful Paint // 网页第一个出现的文字或图片的时间,从白屏到出现东西的时间
    Speed Index // 速度指数,小于4s就快,大于则需要优化
3. chrome 中的 DevTools 的 Network
    在 size 列中可以看到有两个值,上面是资源通过压缩后的大小,下面是资源本身的大小
    在打开 DevTools 的情况下可以按下 ESC 打开另一个菜单栏
4. chrome 中的 performance
    在 Main 栏中最下方可以看到调用的方法
----------------------------------------------------------------------------------------------
// 常用的性能测量API
1. performance 对象
    示例:
      window.addEventListener('load', (event) => {
        let timing = performance.getEntriesByType('navigation')[0];
        console.log(timing.domInteractive);
        console.log(timing.fetchStart);
        let diff = timing.domInteractive - timing.fetchStart;
        console.log("TTI: " + diff);
      })
    扩展:// performance.getEntriesByType('navigation')[0] 下的属性
      DNS 解析耗时: domainLookupEnd - domainLookupStart
      TCP 连接耗时: connectEnd - connectStart
      SSL 安全连接耗时: connectEnd - secureConnectionStart
      网络请求耗时 (TTFB): responseStart - requestStart
      数据传输耗时: responseEnd - responseStart
      DOM 解析耗时: domInteractive - responseEnd
      资源加载耗时: loadEventStart - domContentLoadedEventEnd
      First Byte时间: responseStart - domainLookupStart
      白屏时间: responseEnd - fetchStart
      首次可交互时间: domInteractive - fetchStart
      DOM Ready 时间: domContentLoadEventEnd - fetchStart
      页面完全加载时间: loadEventStart - fetchStart
      http 头部大小: transferSize - encodedBodySize
      重定向次数:performance.navigation.redirectCount
      重定向耗时: redirectEnd - redirectStart

2. PerformanceObserver
// 观察长任务
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry)
  }
})
observer.observe({entryTypes: ['longtask']})

3. visibilitychange // 事件,判断用户是否在当前这个页面
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
    // webkit prefix detected
    vEvent = 'webkitvisibilitychange';
}

function visibilityChanged() {
    if (document.hidden || document.webkitHidden) {
        console.log("Web page is hidden.")
    } else {
        console.log("Web page is visible.")
    }
}

document.addEventListener(vEvent, visibilityChanged, false);

4. navigator // 判断用户当前网络状态
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
var type = connection.effectiveType;

function updateConnectionStatus() {
  console.log("Connection type changed from " + type + " to " + connection.effectiveType);
  type = connection.effectiveType;
}

connection.addEventListener('change', updateConnectionStatus);
----------------------------------------------------------------------------------------------
// 从哪几方面入手优化
1. 渲染优化 // 浏览器
2. 代码优化
3. 资源优化
4. 构建优化
5. 传输加载优化
----------------------------------------------------------------------------------------------
// 渲染优化
// 浏览器渲染流程
1. 构建对象模型
    HTML ——> DOM // 构建 DOM 对象
    CSS ——> CSSOM // 构建 CSSOM 对象
2. 构建渲染树
    DOM + CSSOM = Render Tree
3. 重排/回流
    1. 重新计算尺寸和布局,可能会影响其他元素的位置
    2. 如元素高度增加,可能会使相邻元素位置下移
    3. 减少重排的方法
        1. 集中修改颜色,或直接切换 class、css
        2. 修改之前先设置 display: none,脱离文档流
        3. 使用 BFC 特性,不影响其他元素位置
            1. BFC 块级格式化上下文,内部的元素无论如何改动,都不会影响其他元素的位置
            2. 触发条件:
                1. 根节点 <html>
                2. float: left/right
                3. overflow: auto/scroll/hidden
                4. display: inline-block/table/table-row/table-cell
                5. display: flex/grid 的直接子元素
                6. position: absolute/fixed
        4. 频繁触发(resize、scroll)使用节流和防抖
        5. 使用 createDocumentFragment 批量操作 DOM
        6. 优化动画,使用 CSS3 和 requestAnimationFrame
4. 重绘
    1. 元素外观改变,如颜色、背景色
    2. 但元素的尺寸、定位不变,不会影响其他元素的位置
----------------------------------------------------------------------------------------------
// 渲染优化——浏览器关键渲染路径
JavaScript(触发视觉变化) ——> Style(重新计算样式) ——> Layout(布局即回流) ——> Paint(绘制即重绘) ——> Composite Layers(复合)
1. 渲染树只包含网页需要的节点
2. 布局是计算每个节点精确的位置和大小 // 盒模型
3. 绘制是像素化每个节点的过程
----------------------------------------------------------------------------------------------
// 渲染优化——影响回流的操作,可在 chrome 中的 performance 的 timings 查看 Layout
1. 添加或删除元素
2. 操作 style
3. display: none
4. offsetLeft、scrollTop、clienWidth // 每次获取都会计算最新值
5. 移动元素位置
6. 修改浏览器大小、字体大小
----------------------------------------------------------------------------------------------
// 渲染优化——减少重绘
1. 利用 performance 识别 paint 的瓶颈
2. 利用 will-change 创建新的图层 // 注意不可创建过多的图层
----------------------------------------------------------------------------------------------
// 渲染优化——避免 Layout thrashing
1. 避免回流
2. 读写分离 // 获取数据和设置数据
3. FastDom // 通过批量对 DOM 的读写操作来解决回流的库
----------------------------------------------------------------------------------------------
// 渲染优化——Composite thread 复合线程做什么,和 ps 中多个图层一样
1. 将页面拆分图层进行绘制再进行复合 // 浏览器自带的规则拆分复合
    可以给元素添加 willChange: 'transform' 来让浏览器提取到一个图层中
2. 利用 performance 中的 Frames 弹出的菜单栏的 layers
3. 哪些样式仅影响了复合不会影响重绘
    transform: translate(50px, 50px)
    transform: scale(0.5)
    transform: rotate(90deg)
    opacity: 0.5
----------------------------------------------------------------------------------------------
// 渲染优化——requestAnimationFrame 解决高频事件抖动
1. 会在 Layout 和 Paint 之前调用 // rAF
----------------------------------------------------------------------------------------------
// 代码优化
1. 一段 js 代码在浏览器中运行需要经过加载、解析编译、执行这几步骤
2. 相同大小的 js 和图片加载速度图片会更快
3. 通过 code splitting 进行代码拆分,按需加载
4. 通过 tree shaking 代码减重
5. 避免长任务
6. 避免超过 1kb 的行间脚本
7. 使用 rAF 和 rIC 进行时间调度
8. 先加载首屏需要的东西
----------------------------------------------------------------------------------------------
// 代码优化——抽象语法树
1. 源码 ——> 抽象语法树 ——> 字节码Bytecode ——> 机器码
2. 编译过程会进行优化
3. 运行时可能发生反优化
----------------------------------------------------------------------------------------------
// 代码优化——v8优化机制
1. 脚本流 // 大于30kb的脚本会单独开一个线程出来,边下载边解析
2. 字节码缓存 // 缓存
3. 懒解析 // 相当于对函数的懒加载
----------------------------------------------------------------------------------------------
// 代码优化——函数优化
1. 由于上面提到的机制,默认会对函数进行懒解析
2. 可以通过给函数体包裹一对小括号来告诉浏览器这个函数是需要被马上解析的
    const add = (a, b) => a + b
    const add = ((a, b) => a + b) // 优化
3. 如果压缩了代码,工具会把我们加的括号给去掉所以需要用到 optimize.js 来把去掉的括号加回来
   webpack 已经解决了这个问题,一些老项目可能会用到 // npm install optimize
----------------------------------------------------------------------------------------------
// 代码优化——对象优化
1. 以相同顺序初始化对象成员,避免隐藏类的调整
2. 实例化后避免添加新属性
3. 尽量使用 Array 代替 array-like 对象 // array-like 即伪数组
4. 避免读取超过数组的长度
5. 避免元素类型转换 // [1, 2, 3].push(0.1) 就会造成元素类型转换
----------------------------------------------------------------------------------------------
// 代码优化——html优化
1. 减少 iframes 使用 // 可以先加载 iframes 标签,等加载完父容器再给 iframes 的 src 赋值加载
2. 压缩空白符
3. 避免节点深层级嵌套
4. 避免 table 布局
5. 删除注释
6. css 或 js 尽量外链
7. 删除元素默认属性
8. 可以借助 html-minifier 工具 // webpack 就是集成了这个工具进行html压缩
----------------------------------------------------------------------------------------------
// 代码优化——css优化
1. 降低 css 对渲染的阻塞
2. 利用 GPU 进行完成动画 // transform、opacity 不会影响重绘
3. 使用 contain 属性 // contain: layout 说明这个元素里面怎么变化都跟外面没关系,避免回流重绘
4. 使用 font-display 属性
----------------------------------------------------------------------------------------------
// 代码优化——js优化
1. css放在head,js放在body最下面
2. 尽早开始执行js,用DOMContentLoaded触发
3.DOM查询进行缓存
4. 频繁DOM操作合并到一起插入DOM结构
5. 节流throttle、防抖debounce
----------------------------------------------------------------------------------------------
// 代码优化——Vue
1. 合理使用v-show和v-if
2. 合理使用computed
3. v-for时加key,以及避免和v-if同时使用
4. 自定义事件、DOM事件及时销毁
5. 合理使用异步组件
6. 合理使用keep-alive
7. data层级不要太深
8. 使用SSR
----------------------------------------------------------------------------------------------
// 代码优化——React
1. 渲染列表时加 key
2. 自定义事件、DOM 事件及时销毁
3. 合理使用异步组件
4. 减少函数 bind this 的次数
5. 合理使用 SCU、PureComponent 和 memo
6. 合理使用 Immutable.js
7. 使用 SSR
----------------------------------------------------------------------------------------------
// 资源优化——为什么要压缩和合并?
1. 减少 http 请求数量
2. 减少请求资源的大小
----------------------------------------------------------------------------------------------
// 资源优化——html压缩
1. 使用在线工具进行压缩
2. 使用 html-minifier 等 npm 工具,也有集成了 clean-css
----------------------------------------------------------------------------------------------
// 资源优化——css压缩
1. 使用在线工具进行压缩
2. 使用 clean-css 等 npm 工具
----------------------------------------------------------------------------------------------
// 资源优化——压缩和混淆
1. 使用在线工具进行压缩
2. 使用 webpack 对 js 在构建时压缩
----------------------------------------------------------------------------------------------
// 资源优化——css、js 文件合并
1. 若干小文件 // maybe
2. 无冲突,服务相同的模块
3. 优化加载 // 不推荐,修改某一个会导致全部重新加载
----------------------------------------------------------------------------------------------
// 资源优化——图片
1. github.com/imagemin/imagemin // jpg 压缩
2. jpg 的优点:
    很高的压缩比,画质还可以被很好的被保持
    适用于展示大图、轮播图、视觉冲击
3. jpg 的缺点:
    有锯齿、模糊、边缘粗糙
4. github.com/imagemin/imagemin-pngquant // png 压缩
5. png 的优点:
    可以做透明的背景图片
    弥补了 jpg 的缺点
6. png 的缺点:
    体积大、适用于小图片
7. webp 的优点:
    和 png 一样的质量,比 png 小
8. webp 的缺点:
    不是标准,是 google 提出的,兼容性不好 // 80% 兼容
9. 图片懒加载
    原生:
        直接在 img 标签上加 loading="lazy" 属性即可实现懒加载 // 前提是浏览器支持
    插件:
        verlok/lazyload
        yall.js
        Blazy
10. 渐进式图片 // progressive JPEG
     基线式图片加载时是横向加载,渐进式图片加载时是全图模糊加载 // 默认是基线式图片
     相对于 基线式图片(Baseline JPEG) 对用户更友好
     解决方案:// 生成渐进式图片
        progressive-image
        ImageMagick
        libjpeg
        jpegtran
        jpeg-recompress
        imagemin
11. 响应式图片
     <img src="dog-200.jpg" sizes="100vw" srcset="dog-800.jpg 800w, dog-1200.jpg 1200w, dog-1800.jpg 1800w" />
     直接在 img 标签的属性上增加 sizes 和 srcset 属性即可实现在不同的设备上加载不同图片的大小
     也可以使用 <pictrue> 标签
----------------------------------------------------------------------------------------------
// 资源优化——字体
1. 什么是 FOITFOUT:
    Flash Of Invisible Text // 文字闪烁,从看不到到看到的过程
    Flash Of Unstyled Text // 文字开始看上去是一种样式,后来经过样式渲染又变成另一种样式
2. 字体未下载完成时,浏览器隐藏或自动降级,导致字体闪烁或导致样式变化 // 不可避免
3. font-display 样式属性:
    font-display: block // 一开始不让字体显示,等加载完再显示,前 3s 生效,如果 3s 后还没加载完则会使用浏览器的默认字体代替再等加载完再显示
    font-display: swap // 替换,一开始就用一个默认的字体显示,等字体加载完就替换回去
    font-display: fallback // 和 block 差不多,区别是等待时间变成 100ms
    font-display: optional // 移动端专用,会判断用户网络情况,如果 100ms 内没有把字体加载完则会用默认字体且不会再替换成自定义的字体
4. 在 @font-face 中可以使用 unicode-range 进行字符集拆分
5. 使用 base64 把字体进行转码嵌入到 css 或 js 中加载 // 可以解决兼容性问题,缺点是缓存不可控
----------------------------------------------------------------------------------------------
// 构建优化——webpack
1. 详见 webpack 章节
2. 可以通过 Stats 分析与可视化图
3. 可以通过 webpack-bundle-analyzer 进行体积分析
4. 可以通过 speed-measure-webpack-plugin 进行速度分析
5. 优化打包构建速度 // 开发体验和效率
    优化babel-loader // 开发和生产环境都可以
    IgnorePlugin // 避免引入无用模块,生产环境
    noPass // 不去打包哪些,生产环境
    happyPack // 多进程打包工具,生产环境
    ParallelUglifyPlugin // 多进程压缩工具,生产环境
    自动刷新 // 开发环境
    热更新 // 开发环境
    DllPlugin // 开发环境,针对于不变的模块单独打包到另一个文件夹中,下次打包时不用重新打包,而是直接拿来用
6. 优化产出代码 // 产品性能,体积更小,合理分包,不重复加载,速度更快,内存使用更少
    小图片base64编码
    bundle加hash
    懒加载
    提取公共代码 // 代码分割
    IgnorePlugin
    使用CDN加速
    使用production // Tree Shaking
    Scope Hosting
----------------------------------------------------------------------------------------------
// 传输加载优化——gzip
1. nginx 开启 gzip // 在 http 下配置
    gzip on; // 开启 gzip
    gzip_min_length 1k; // 最小 1k 才会压缩
    gzip_com_level 6; // 压缩等级, 1-9, 取6
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/xml text/javascript application/json; // 对哪些类型的文件进行压缩
    gzip_static on; // 对已经压缩的静态资源的有效利用
    gzip_vary on; // 在响应头部增加 gzip 告诉客户端启用了 gzip
    gzip_buffers 4 16k; // 优化压缩过程
    gzip_http_version 1.1 // http 版本
2. Brotli 是一个比 gzip 压缩比更高的东西,但只支持 https,大部分浏览器也支持
----------------------------------------------------------------------------------------------
// 传输加载优化——keep-alive
1. 一个持久的 TCP 连接,节省了连接创建时间
2. nginx 默认开启了 keep-alive ,配置参数看情况,不可随意配置,会消耗开销
    keepalive_timeout 65; // 大于 65s 没用就会关闭 TCP 连接,下次需要重新建立 TCP 连接
    keepalive_requests 100; // 大于 100 个请求就会关闭 TCP 连接,下次需要重新建立 TCP 连接
----------------------------------------------------------------------------------------------
// 传输加载优化——http 缓存
1. 详看 http 章节
2. 强制缓存 cache-control
3. 协商缓存 Last-Modified、Etag
----------------------------------------------------------------------------------------------
// 传输加载优化——Servive Workers
1. 加速重复访问
2. 离线支持
3. 原理是在客户端和服务端做了一层中间层,当离线时客户端直接向中间层拿数据
4. 延长了首屏时间,但页面总加载时间减少
5. 兼容性 // 93%的浏览器支持
6. 只能在 localhost 和 https 下使用
----------------------------------------------------------------------------------------------
// 传输加载优化——http2
1. 二进制传输 // 相比文本传输更安全
2. 请求响应多路复用
3. Service push // 服务端推送
4. 只能在 https 下使用
5. 适合较高的请求量
----------------------------------------------------------------------------------------------
// 传输加载优化——ssr
1. 加速首屏加载
2. 更好的 seo
3. next.js // react 的 ssr
4. nuxt.js // vue 的 ssr
5. 什么时候该使用 ssr ?
    大型的架构,动态页面,面向公众用户
    搜索引擎排名很重要
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
Last Updated: 2024/6/11 14:20:38
    飘向北方
    那吾克热-NW,尤长靖