一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
前言
作为一名优秀的前端工程师,用户体验
肯定是要放在首位的,那么怎么做好用户体验,这其中可有不少奥妙。
试想一下,当你进入了一个网站,屏幕白屏了几秒,点一个东西又卡几秒,相信大部分人都会直接关掉这个网站(心里暗骂:这xx网站,真xx)。可见,一个网站的好坏,与性能有着巨大的关系。
看了网络上有不少性能优化的文章,但不少人应该和我有一样的感觉,对各种优化手段了如指掌,一到自己的项目,就懵了。
脑子:我懂了。手:你吹牛逼别带上我。
本篇文章的目的不光是要告诉你有哪些优化手段,而是要让你能够构建起自己的优化思路
!!!
如果你有耐心看完,相信你会收获:
如何定位网站性能问题,对症下药。
我们对于一个优化点该从哪些地方下手
一、如何测试网站性能
1.性能指标
You can't manage what you can't measure. —— Peter Drucker
彼得·德鲁克 的一句话,“一件事如果你无法衡量它,你就无法管理它”,性能同样如此。如果没有一个准确的方案来对性能进行度量,那优化就无从谈起。
性能体现在各方各面,无论是加载速度
还是用户体验
,他都是性能的一类。
既然我们需要度量,那么哪些指标是可以用来对性能进行度量的呢?接下来我将带领大家一一探索。
首屏性能
首屏性能相信大家并不陌生,我们现在做的优化绝大部分都是围绕这一步去做的,那么大家可能会问,首屏性能我们能用些什么指标去衡量呢?
让我们看看Google提出的以用户体验为中心的性能指标。
可能有不少第一次接触性能优化同学对这些指标感到陌生,接下来我们一起去了解一下~
FP && FCP && FMP
FP(First Paint)
翻译为首次绘制
,表示浏览器第一次向屏幕传输像素的时间点,可以理解为浏览器首次开始绘制像素,页面首次在屏幕上发生了视觉变化的时间,但是这个指标意义不大
。
FCP(First Contentful Paint)
翻译为首次内容绘制
,代表浏览器第一次开始绘制屏幕内容
的时间点,这个内容
指的是什么呢?它代表的是来自DOM
的内容(例如:文本,图片,SVG,canvas元素)。
在上方加载时间轴中,FCP发生在第二帧,可以看到第二帧是首批文本和图像元素在屏幕上完成渲染的时间点。
FMP(First Meaningful Paint)
翻译为首次有效绘制
,代表页面主要内容
开始出现在屏幕的时间点。
FMP 本质上是一个主观认知指标,是通过一个算法来猜测某个时间点可能是 FMP,但是计算方式过于复杂而且不准确,后来 Google 也放弃了 FMP 的探测算法,转而采用更加明确的客观指标 - LCP
。
介绍完我们可能会问,那么什么范围内才是一个好的指标呢?我们可以根据Google给的指标图看一看:
可以看到,我们应该努力将首次内容绘制控制在1.8s
以内,如果超过3.0s
那么你的首屏性能已经非常的差了!
那么我们有什么方式可以改进FCP
呢?下面我们看看Google
给出的优化建议:
- 消除阻塞渲染的资源
- 缩小CSS
- 移除未使用的CSS
- 预连接到所需的来源
- 减少服务器响应时间
- 避免多个页面重定向
- 预加载关键请求
- 避免巨大的网络负载
- 使用高效的缓存策略服务静态资产
- 避免DOM过大
- 最小化关键请求深度
- 确保文本在网页字体加载期间保持可见
- 保持较低的请求数和较小的传输大小
LCP
LCP(Largest Contentful Paint)
翻译为最大内容绘制
,用于记录首屏中最大元素渲染的时间,和 FCP
不同的是,FCP
更关注浏览器什么时候开始绘制内容,比如一个 loading
页面或者骨架屏
,并没有实际价值,所以 LCP
相较于 FCP
更适合作为首屏指标。
下面让我们看一组演示图:
我们可以看到它的
最大绘制内容
发生了改变
,这说明最大绘制内容
并不是不变的,而是会随着DOM
的载入,和可视觉区
的改变不断进行。(最大绘制内容包括图片、视频封面、文字等元素)
我们用WebPageTest(后面会介绍)这个网站测试Google可以很直观的看出当前LCP
元素的信息:
让我们看一看LCP
的指标图:
可以看到我们最好应该将LCP控制在2.5s
内。
那么我们有什么方式可以改进LCP
呢?下面我们看看Google
给出的优化建议:
- 优化关键渲染路径
- 优化你的CSS
- 优化你的图像
- 优化网页字体
- 优化你的JavaScript(针对
csr(客户端渲染)
的网站) - 使用PRPL模式做到即使加载
TTI && FID
TTI(Time to Interactive)
翻译为首次可交互时间
,表示页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。
计算TTI
需要下面几个步骤:
- 先进行
FCP
- 超过
5秒
没有发现长任务(执行时间超过50ms的任务)
和超过两个
正在处理的GET请求
往5s前回溯
找到最后一个长任务
(没有找到则在FCP
步骤停止执行)步骤三
找到的那个长任务结束时间
即为TTI(没有长任务即和FCP
值相同)
图中可清楚看到完整的四个步骤
长久以来,开发者为了追求更快的渲染速度而对页面进行了优化,但有时,这会以牺牲 TTI 为代价,例如SSR(服务端渲染)表面让页面看似可以进行交互(比如一些按钮的出现),但实际上是还不能进行交互的。主线程的阻塞导致这些交互的JavaScript代码还未完成加载。
官方推荐我们应该将TTI控制在5s以内
。
那么我们有什么方式可以改进TTI
呢?下面我们看看Google
给出的优化建议:
- 缩小JavaScript
- 预连接到所需资源
- 预加载关键请求
- 减少第三方代码影响
- 最小化关键请求
- 减少JavaScript执行时间
- 最小化主线程工作
- 保持较少的请求和较小的传输
FID(First Input Delay)
翻译为首次输入延迟
,表示用户首次
和网站进行交互
到浏览器响应
该事件的实际延时
时间。让我们想一下,如果你进入了一个页面,点击里面的跳转链接,没有任何反应,你会怎么办?
- 有耐心一点的,就会再等待一下,可能等待
2~3s
跳转过去了。(但这时的用户体验已经十分糟糕了) - 没有耐心的,看没有反应,可能误认为网络卡了进行刷新,然后再次进入死循环。最终退出网站(这个时候我们就会成功损失一名用户)
你可能会问,FID
判定的交互行为有什么呢?我们来看看:
- 点击、触摸、按键等(不包含滚动和缩放)
- 有事件绑定的行为,比如注册在某个 dom 上的 click 事件
按照预期,用户点击按钮的时候,回调函数会被直接触发,但是如果当前主线程
被渲染
、长任务
占用,这个回调的执行就会被延后
,就会导致 FID
时长增加。
但是 FID
作为一个“非客观值
”,需要用户进行交互才能采集到,用户的交互时机,同样也会对指标的采集、统计造成影响。
让我们来看一看官方给的指标:
可以看到我们应该努力将其控制在100ms
以内。
那么我们该如何去优化它呢?让我们看看官方给的建议:
- 减少第三方代码的影响
- 减少JavaScript执行时间
- 最小化主线程
- 保持较少的请求数和较小的传输
TBT
TBT(Total Blocking Time)
翻译为总阻塞时间
,表示FCP(首次内容绘制)
和TTI(可交互时间)
的总时长。在这段时间里,主线程
被阻塞,无法做出输入响应。
每当出现长任务(主线程上运行超过50ms的任务)
时,主线程都被视作阻塞
,在这个时期,用户无法和页面进行交互。
如果任务时间超过50ms
时,也就是形成长任务,用户将会注意到延迟,认为页面很卡。
下面我们来看两张图片:
页面加载期间主线程图 -
长任务标注图 -
可以看到,我们有345ms
的时间都成为了阻塞时间。
上文说到TTI
会在5s
内没有出现长任务才会被计算(忘记的同学请返回补课),所以TBT
和TTI
两个指标是强相关的。
如果有了解到Google V8垃圾回收
的同学,应该知道,V8垃圾回收中的增量
就是将一次 GC 标记的过程,分成了很多小步,每执行完一小步就让应用逻辑执行一会儿,这样交替多次后完成一轮 GC 标记。这样做的目的就是为了减少阻塞时间。
那么我们该怎样去优化TBT
呢?我看看看官方给出的建议:
- 减少第三方代码的影响
- 减少JavaScript执行时间
- 最小化主线程工作
- 保持较低的请求数和较小的传输大小
CLS
CLS(Cumulative Layout Shift)
翻译为累积布局偏移
,指的是页面产生的连续累计布局偏移分数
,可能这样说大家会有点不知所然。那么大家想象一个场景,你正在阅读某篇文章的时候,突然文章位置被挤去了下面,如果有更坏的情况,你刚滑倒刚刚位置时候,又被挤去了右边。亦或者,当你准备点击登录的时候,突然登录被挤了下去,你点到了退出。相信此时你的内心一定是:
CLS
的诞生就是为了来解决此类问题的,在日常开发中,我们经常会使用懒加载
和骨架屏
来先加载页面框架,再进行动态渲染,填充框架。但是这类做法是很容易发生位置偏移的。
说了那么多,那么我们怎么来优化这项指标呢?
- 将图片都设置
占位图
,大家知道,一个页面中最大的元素通常都是图片,设置占位图可防止大量的偏移。 - 将
dom元素
给定固定的宽高
, 大家可能都习惯用数据去撑开装取数据的dom,但在网速很慢的情况下,因为偏移产生的不良效果是非常明显的。 - 编写
转换动画
,例如:你用手机看小说时翻页会有一个类似翻书的动画。可以用此类动画来让位置偏移不那么突兀
。
页面流畅度
FPS
FPS(Frames Per Second)
翻译为每秒传输帧数
,这个指标相信爱打游戏的各位都不陌生,下面我们就来简单介绍一下。一般对于网页而言,最优的帧率在 60 FPS
,如果越接近这个值,页面就越流畅,帧率如果远低于这个值,用户可能会明显感觉到卡顿,也就是俗称的掉帧
。
60 FPS
意味着页面每隔 16.5ms(1/60)
就需要渲染一次,否则就会出现丢帧的现象,而浏览器中的 JavaScript 执行
和页面渲染
都是会相互阻塞的,如果在代码中有非常复杂的逻辑占用了大量的执行时长,就会导致页面出现卡顿
在 Chrome
的 devtools(按下F12打开)
中我们可以执行 control+shift+p
输入 show fps
来快速打开 fps
面板,如下图所示:
我们用掘金官网看一看fps
:
我们可以看到掘金官网的性能还是非常好的!(掘金牛逼!)
网络传输
大家应该都知道,我们一个完整的网站是拥有客户端
和服务端
两端的,如果没有服务端
,那么页面便没有交互
,只是一个静态文件
。
那我们页面要展示数据,必须建立连接从服务端
去获取,那么连接所耗费的时长必定会影响整个页面的性能,所以优化连接
也是一个非常大的优化点!
TCT
TCT(Total Connection Time)
翻译为整体连接耗时
,表示着我们一个完整请求的消耗时间(包括建立连接
、传输
、断开连接
)
那么会影响连接消耗时常的原因有哪些呢?我们一起研究一下:
- 服务器距离用户端距离过远(距离越远,所耗费时间越长)
- 不断重复建立连接,不同域名的网络请求都需要重新建立连接。
- 客户端自身网络较慢。
可以见得除了用户自身问题,我们是有很大的优化空间的,那么我们可以做一些什么优化呢?
- 采用
CDN
,可以把一些较大资源放入CDN
缩短距离,达到提升速度。(CDN通俗一点的解释就是:帮你找到资源离你当前网络地址最近的服务器,达到距离最短
。感兴趣的同学可以自行查阅资料,这里不过多赘述) - 使用
预连接
对域名进行预建连。例如:
<link rel="dns-prefetch" href="https://example.com">
- 将请求收拢在
同一域名
下(这里需要根据自己项目实际情况判断!不要为了减少建连次数
而无脑收纳所有资源请求,每个域名都有请求并发数
,不恰当的使用会引起反效果!) - 由于拥有
并发数量限制
,若有资源请求被阻塞排队,可以根据具体需求,将资源进行合并至同一http请求
中(根据项目实际情况判断!!不恰当使用会导致形成长任务
起到反效果!)
网络优化很多时候更需要开发者对自己项目情况和用户群体的判断,一定不要一味的去使用!!
TTFB
TTFB(Time to First Byte)
翻译为首个字节传输耗时
,表示从发起请求到收到服务器响应
的第一个字节
的时间,一般来说,如果首屏 html 请求的 TTFB 能达到100ms
以内,就已经具备不错的体验了,如果超过了500ms
,那么用户就能明显的感受到白屏大家可能对这个概念还有点模糊,让我们看看TTFB
包含了哪些阶段呢?
- 重定向
- DNS查询
- TCP和SSL握手
- 发起HTTP请求后接到服务端的第一个响应报文时间
那我们该怎么去进行优化呢?让我们参考下下面这几种方式:
- 减少请求传输量
- 减少服务端处理时间(gzip压缩、增加缓存等)
- 进行懒加载
看到这同学们可能会存在一个疑问,TTFB
越短越好吗?当然不是
!
这里我们仍然需要做好权衡,例如,我们开启gzip压缩
的时候,一定会增加TTFB
,但是资源体积
的缩小对整个资源请求速度的提升是更为显著的。
就好比你玩赛车游戏:20m处有一个氮气包,但是却在最右边的位置,两辆车同时起泡,A车全力往前走,B车却花了一点额外时间去吃掉氮气包。那么在前20m
,A车一定比B车快,但100m
的话B车一定会比A车快。所以这就是需要你去权衡的地方。
2.测试工具
Sharp tools make good work.
工欲善其事,必先利其器。要想做好性能优化,我们必须有一个好的工具去测量各类指标。让我们一起看看有哪些比较好用性能测试工具吧!
Lighthouse
你可以通过在Chorme
商城安装Lighthouse
插件来获取如下指标(以掘金为例):
可以看到图中有我们对应的性能指标分数,能让我们有一个优化的方向。
Lighthouse
也会给出一些优化的建议(细心的小伙伴会发现和上文中指标的建议都是一样的),大家可能会问到,那么怎么该根据建议去做相应的优化呢?别着急,下文中会详细介绍!
注意!!
Lighthouse
的各项指标,是通过你的电脑访问这一个单独页面而统计的,所以不具有权威性,它只能作为一个参考。(因为你电脑性能
和网速
的不同,都会导致其中一些指标有偏差。例如网速快的FCP
一定会比网速慢的更低!)
DevTools
DevTools
是什么?你进入网页按下f12
就知道了小傻瓜。
Network
我们通过Network
来对性能分析提取有用的信息,例如:
- 选项区:
Preserve log
保留网络请求,比如我们需要刷新页面又担心数据也被刷新时,可以使用这项操作来保留数据请求。Disable cache
禁用缓存,通常我们的资源都会被加上缓存机制,这样我们可能无法得到最新的数据或者真实的加载速度。No throttling
可以模拟网络状态(例如3G
等) - 下面各项就是浏览器的请求列表
size
代表传输的文件大小,鼠标移入可以看到是否由gzip
压缩,且压缩前后的大小。Waterfall
代表资源加载每一步的顺序和耗时
Performance
我们可以从Performance
里得到更加详细的信息,例如请求的对应的时间,以及各类指标对应的时间点。
Webpagetest
前文有提到Lighthouse
只会根据自己电脑的性能及网速来测量各类指标,那么Webpagetest
便可以让你在不同的网络环境
对页面进行性能勘测(更具有真实性)。
webpagetest.org/ (附上网址)
我们可以选择不同的环境及配置去勘测性能,下面我们测试下Google
的网站:
测试完成后可以看到各种指标及性能,感兴趣的小伙伴可以自己去体验一下,这里就不一一赘述了。
二、如何优化网站性能
上文介绍了性能的各类指标,也给出了各类指标的优化方向
,但不少同学可能会很疑惑,感觉无从下手不要急,下面我将给大家展示几类优化思路
,让你能够对症下药!
1.CSS优化
说到CSS优化
,大家可能会觉得一个CSS能有什么优化的,其实不一,我们应该知道,在浏览器加载出来的过程中,是有一个步骤需要渲染cssom树
的,尤其在网速较差的环境中,差异会及其明显!!(毕竟我们不能放弃网速不好的用户吧)
延迟加载非关键CSS
(以下适用建议大家可对照上面每条指标的优化手段哦!)
适用:缩小CSS
移除未使用的CSS
打开DevTools,ctrl+shift+p
搜索show coverage
,对页面进行检测找出无用代码率
可以看到我们图中Unused Bytes
这一栏,标注出了227570
个字节,也就是73.6%
的代码当前页面是没有立即
用到的,图中标红的代码都是没有用到的,那么我们是不是有一个思路呢?
没错,聪明的你应该已经想到,我们可以先加载使用到的那部分CSS
,再去网络空闲的时机,加载出剩余的CSS
。
思路我们已经有了,那么我们该怎么去做呢?
我们下载出对应json文件
我们打开json文件
start
和end
代表有效代码的字节区间(可以编写一个js脚本提取),提取有效css
后,在首屏中进行引入
,取消掉全局css
加载,将全局css
防止合适的节点处进行加载(每个项目的情况不同,你需要判断项目中哪个时机最适合来进行这一步操作)
最终成果
CSS的大小
(gzip压缩后)从41.3kb
降低到了1.6kb
,加载时间从219ms
降低80ms
。
可以看到我们的首屏加载速度便提高了近140ms
,在网络情况不佳的情况提升将更为显著!
缩小css体积
我们看一下下面两段代码:
body {
font-family: "Benton Sans", "Helvetica Neue", helvetica, arial, sans-serif;\
margin: 2em;
}
/* 所有标题需要相同的字体颜色 */
h1 {
font-style: italic;
color: #373fff;
background-color: #000000;
}
h2 {
font-style: italic;
color: #373fff;
background-color: #000000;
}
body{font-family:"Benton Sans","Helvetica Neue",helvetica,arial,sans-serif;margin:2em}h1,h2{font-style:italic;color:#373fff;background-color:#000}
我们可以看到两段代码的效果是一模一样的,但第一段的作用仅仅是让我们看得更加方便。
但是浏览器并不会受此影响,所以第一段代码就会因为一些无用字节
(注释、空格等)让我们的css体积
变大。
如果有了解过的同学此时应该很清楚怎么做了,没错,就是tree shaking
,也就是删除无用代码。
有同学会问了,那么我们怎么去做tree shaking
呢?难道自己人工一个个去删除吗?
当然不是!现在的手脚架都已经拥有能够tree shaking
的插件了,例如我们的Webpack
,在此不做过多的介绍了,如果有同学感兴趣的话,可以去下面这篇文章看一看Tree-Shaking 实现原理
2.JavaScript优化
这一小节中,我们将学会怎么去分析自己的项目!
还记得文章开始时提到的第一段话吗,“一件事如果你无法衡量它,你就无法管理它”,那我们该怎么了解到项目中的JavaScript
文件是否有用呢?
首先,你需要有一个能够分析构建产物
的工具,这里我采用了比较主流的webpack-bundle-analyzer
来做为示例。
在运行插件后(具体使用在此不多做讲解),我们会得到当前项目的分析图:
大家可以看一看,有没有发现其中有什么问题呢?
删除重复的JS包
有两个包完全重复
了,为什么会发生这种情况呢?
例如当前有个项目A
,他本身依赖D
,但他引入了组件B
和组件C
,并且B
和C
都依赖了D
,但是项目构建的时候并不知道,所以会将两个模块都引入进去,造成了重复依赖。
├── projectA
│ └── node_modules
│ │── packageD
│ │── pluginB
│ │ └── node_modules
│ │ └── packageD
│ │── pluginC
│ │ └── nodule_modules
│ │ └── packageD
那我们该怎么解决呢?
peerDependencies
,想必大家对package.json
这个文件并不陌生,他是管理我们依赖包的地方,每当我们npm install
时,npm都会根据对应的模块版本安装依赖。那我们该怎么做呢?
我们只需要在pluginB/package.json
和pluginC/package.json
中申明即可,例如:
pluginB/package.json
// peerDependencies和dependencies同级哦
{
"peerDependencies": {
"packageD": "1.0.1"
}
}
然后只在最外层的dependencies
引入模块就行了。
这里对于npm包管理不多作介绍,感兴趣的朋友请自行查阅资料。
删除无用JavaScript
细心的朋友可以看到,我们的打包文件居然把mock.js
也打包了进去,无论是处于什么原因,这都是没有用的,我们要干掉它!
我们应该找出打包后的无用依赖包,努力把我们的体积优化到最小
3.网络优化
CDN
前文有提到网络传输时间有很重要一部分是由距离
来决定的。CDN
的作用便是帮我们来解决这个问题。
如果公司有条件的话,我们的图片、依赖包
等较大资源可以放在cdn上,如果没有也不用担心,下面给大家推荐一个免费的CDN网站
- jsdelivr。
前文已经介绍了怎样分析构建后的依赖包,我们可以根据情况将较大依赖提取放入CDN,进行提效作用。
缓存机制
浏览器缓存虽然不能提高新用户
的体验,但是对于老用户
的体验是很有价值的。
此处不介绍该怎样给资源加入缓存,如果有兴趣可以查阅相关资料
我们可以将一些静态文件
和长久稳定不变
的JS包
放入缓存(此处需要自己根据项目情况判断)
例如:
- 若页面是弱交互的,例如
官网
,可以直接将整个html
和css
放入缓存。 - 页面上的
图片
、字体
等资源。 Vue
、React
等稳定版本的框架。
预加载
当你的网站空闲时,我们是否可以用来做一些事呢?
使用预加载,在网站空余
时(需要自己判断),提前加载其他模块内容,将其加入缓存,这不光不会对当前页面造成影响,也将大幅提升其他模块的加载速度。
4.项目代码优化
懒加载
懒加载相信大家都不陌生了,但是我想问问大家为什么要使用懒加载呢?
由于连接的并发是有数量限制
的,当请求太多达到限制,亦或者由于代码编写不当主线程
被占用,我们的其他资源便无法加载。
懒加载的目的便是将页面上暂时不重要
的元素推迟,直到它成为重要元素
才进行加载
目前使用较多的有图片懒加载
、路由懒加载
减少回流重绘
回流
:当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流
。
可能会导致回流的操作:
- 浏览器的窗口大小发生变化
- 添加或者删除可见的DOM元素
- 元素的字体大小发生变化
- 元素的尺寸或者位置发生变化
- 元素的内容发生变化
重绘
:当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘
。
可能会导致重绘的操作:
- 字体颜色或背景发生变化
- border-radius、visibility、box-shadow等发生变化
注意
:重绘不一定引起回流,回流一定引起重绘。
减少回流重绘的操作:
- 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
- 如果需要修改多个css属性,可以先将元素设置为display:none,然后操作完成之后再改变回来,这样就能大大降低回流次数。
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
- 避免频繁的操作dom
防抖节流
防抖
:通俗来说就是当一个事件一直处于频繁触发的情况下,禁止触发,直到频率降低到规定的程度,才允许触发。
效果
:短时间内多次触发,最终在停止触发后的某个指定时间执行一次函数-----只执行一次
代码:
const debounce = (fn, time) => {
let timeout = null
return function() {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, time);
}
}
节流
:通俗的来说就是一个事件不停的触发时,控制它在每个时间段只触发一次。
效果
:短时间内多次触发,即使触发仍在继续也可以根据指定时间触发一次函数----至少执行一次。
代码:
const throttle = (fn, time) => {
let flag = true;
return function () {
if (!flag) return;
flag = false;
fn.apply(this, arguments);
setTimeout(() => {
flag = true;
}, time);
};
};
总结
性能优化是一项很考验开发者功力的技术,每一个项目的优化方案可能都是不一样的,项目代码的编写不当也会引起阻塞从而造成性能问题,所以文章中不断强调需要自己去对情况进行判断。
性能优化内容远远不止如此,以上只代表个人的学习见解,若有错误,欢迎各位大佬指出。
还记得文章开头提到的吗?现在我希望大家都能做到那两点:
能定位项目性能问题
对于一个优化点该怎么入手