【性能优化】超详细!带你从0到1领略前端性能优化的奥秘

502 阅读25分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

前言

作为一名优秀的前端工程师,用户体验肯定是要放在首位的,那么怎么做好用户体验,这其中可有不少奥妙。

试想一下,当你进入了一个网站,屏幕白屏了几秒,点一个东西又卡几秒,相信大部分人都会直接关掉这个网站(心里暗骂:这xx网站,真xx)。可见,一个网站的好坏,与性能有着巨大的关系。

看了网络上有不少性能优化的文章,但不少人应该和我有一样的感觉,对各种优化手段了如指掌,一到自己的项目,就懵了。

脑子:我懂了。手:你吹牛逼别带上我。

image.png

本篇文章的目的不光是要告诉你有哪些优化手段,而是要让你能够构建起自己的优化思路!!!

如果你有耐心看完,相信你会收获:

  1. 如何定位网站性能问题,对症下药。
  2. 我们对于一个优化点该从哪些地方下手

一、如何测试网站性能

1.性能指标

You can't manage what you can't measure. —— Peter Drucker

彼得·德鲁克 的一句话,“一件事如果你无法衡量它,你就无法管理它”,性能同样如此。如果没有一个准确的方案来对性能进行度量,那优化就无从谈起。

性能体现在各方各面,无论是加载速度还是用户体验,他都是性能的一类。

既然我们需要度量,那么哪些指标是可以用来对性能进行度量的呢?接下来我将带领大家一一探索。

首屏性能

首屏性能相信大家并不陌生,我们现在做的优化绝大部分都是围绕这一步去做的,那么大家可能会问,首屏性能我们能用些什么指标去衡量呢?

让我们看看Google提出的以用户体验为中心的性能指标。

image.png

可能有不少第一次接触性能优化同学对这些指标感到陌生,接下来我们一起去了解一下~

FP && FCP && FMP

FP(First Paint)翻译为首次绘制,表示浏览器第一次向屏幕传输像素的时间点,可以理解为浏览器首次开始绘制像素,页面首次在屏幕上发生了视觉变化的时间,但是这个指标意义不大

FCP(First Contentful Paint)翻译为首次内容绘制,代表浏览器第一次开始绘制屏幕内容的时间点,这个内容指的是什么呢?它代表的是来自DOM的内容(例如:文本,图片,SVG,canvas元素)。

image.png

在上方加载时间轴中,FCP发生在第二帧,可以看到第二帧是首批文本和图像元素在屏幕上完成渲染的时间点。

FMP(First Meaningful Paint)翻译为首次有效绘制,代表页面主要内容开始出现在屏幕的时间点。 FMP 本质上是一个主观认知指标,是通过一个算法来猜测某个时间点可能是 FMP,但是计算方式过于复杂而且不准确,后来 Google 也放弃了 FMP 的探测算法,转而采用更加明确的客观指标 - LCP

介绍完我们可能会问,那么什么范围内才是一个好的指标呢?我们可以根据Google给的指标图看一看:

image.png

可以看到,我们应该努力将首次内容绘制控制在1.8s以内,如果超过3.0s那么你的首屏性能已经非常的差了!

那么我们有什么方式可以改进FCP呢?下面我们看看Google给出的优化建议:

  1. 消除阻塞渲染的资源
  2. 缩小CSS
  3. 移除未使用的CSS
  4. 预连接到所需的来源
  5. 减少服务器响应时间
  6. 避免多个页面重定向
  7. 预加载关键请求
  8. 避免巨大的网络负载
  9. 使用高效的缓存策略服务静态资产
  10. 避免DOM过大
  11. 最小化关键请求深度
  12. 确保文本在网页字体加载期间保持可见
  13. 保持较低的请求数和较小的传输大小

LCP

LCP(Largest Contentful Paint)翻译为最大内容绘制,用于记录首屏中最大元素渲染的时间,和 FCP 不同的是,FCP 更关注浏览器什么时候开始绘制内容,比如一个 loading 页面或者骨架屏,并没有实际价值,所以 LCP 相较于 FCP 更适合作为首屏指标。

下面让我们看一组演示图:

image.png

我们可以看到它的最大绘制内容发生了改变,这说明最大绘制内容并不是不变的,而是会随着DOM的载入,和可视觉区的改变不断进行。(最大绘制内容包括图片、视频封面、文字等元素)

我们用WebPageTest(后面会介绍)这个网站测试Google可以很直观的看出当前LCP元素的信息:

image.png

image.png

让我们看一看LCP的指标图:

image.png

可以看到我们最好应该将LCP控制在2.5s内。

那么我们有什么方式可以改进LCP呢?下面我们看看Google给出的优化建议:

  1. 优化关键渲染路径
  2. 优化你的CSS
  3. 优化你的图像
  4. 优化网页字体
  5. 优化你的JavaScript(针对csr(客户端渲染)的网站)
  6. 使用PRPL模式做到即使加载

TTI && FID

TTI(Time to Interactive)翻译为首次可交互时间,表示页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。

计算TTI需要下面几个步骤:

  1. 先进行FCP
  2. 超过5秒没有发现长任务(执行时间超过50ms的任务)和超过两个正在处理的GET请求
  3. 往5s前回溯找到最后一个长任务(没有找到则在FCP步骤停止执行)
  4. 步骤三找到的那个长任务结束时间即为TTI(没有长任务即和FCP值相同)

image.png

图中可清楚看到完整的四个步骤

长久以来,开发者为了追求更快的渲染速度而对页面进行了优化,但有时,这会以牺牲 TTI 为代价,例如SSR(服务端渲染)表面让页面看似可以进行交互(比如一些按钮的出现),但实际上是还不能进行交互的。主线程的阻塞导致这些交互的JavaScript代码还未完成加载。

官方推荐我们应该将TTI控制在5s以内

那么我们有什么方式可以改进TTI呢?下面我们看看Google给出的优化建议:

  1. 缩小JavaScript
  2. 预连接到所需资源
  3. 预加载关键请求
  4. 减少第三方代码影响
  5. 最小化关键请求
  6. 减少JavaScript执行时间
  7. 最小化主线程工作
  8. 保持较少的请求和较小的传输

FID(First Input Delay)翻译为首次输入延迟,表示用户首次和网站进行交互到浏览器响应该事件的实际延时时间。让我们想一下,如果你进入了一个页面,点击里面的跳转链接,没有任何反应,你会怎么办?

  1. 有耐心一点的,就会再等待一下,可能等待2~3s跳转过去了。(但这时的用户体验已经十分糟糕了)
  2. 没有耐心的,看没有反应,可能误认为网络卡了进行刷新,然后再次进入死循环。最终退出网站(这个时候我们就会成功损失一名用户)

你可能会问,FID判定的交互行为有什么呢?我们来看看:

  1. 点击、触摸、按键等(不包含滚动和缩放)
  2. 有事件绑定的行为,比如注册在某个 dom 上的 click 事件

按照预期,用户点击按钮的时候,回调函数会被直接触发,但是如果当前主线程渲染长任务占用,这个回调的执行就会被延后,就会导致 FID 时长增加。

但是 FID 作为一个“非客观值”,需要用户进行交互才能采集到,用户的交互时机,同样也会对指标的采集、统计造成影响。

让我们来看一看官方给的指标:

image.png

可以看到我们应该努力将其控制在100ms以内。

那么我们该如何去优化它呢?让我们看看官方给的建议:

  1. 减少第三方代码的影响
  2. 减少JavaScript执行时间
  3. 最小化主线程
  4. 保持较少的请求数和较小的传输

TBT

TBT(Total Blocking Time)翻译为总阻塞时间,表示FCP(首次内容绘制)TTI(可交互时间)的总时长。在这段时间里,主线程被阻塞,无法做出输入响应。

每当出现长任务(主线程上运行超过50ms的任务)时,主线程都被视作阻塞,在这个时期,用户无法和页面进行交互。

如果任务时间超过50ms时,也就是形成长任务,用户将会注意到延迟,认为页面很卡。

下面我们来看两张图片:

页面加载期间主线程图 -

image.png

长任务标注图 -

image.png

可以看到,我们有345ms的时间都成为了阻塞时间。

上文说到TTI会在5s内没有出现长任务才会被计算(忘记的同学请返回补课),所以TBTTTI两个指标是强相关的。

如果有了解到Google V8垃圾回收的同学,应该知道,V8垃圾回收中的增量就是将一次 GC 标记的过程,分成了很多小步,每执行完一小步就让应用逻辑执行一会儿,这样交替多次后完成一轮 GC 标记。这样做的目的就是为了减少阻塞时间。

那么我们该怎样去优化TBT呢?我看看看官方给出的建议:

  1. 减少第三方代码的影响
  2. 减少JavaScript执行时间
  3. 最小化主线程工作
  4. 保持较低的请求数和较小的传输大小

CLS

CLS(Cumulative Layout Shift)翻译为累积布局偏移,指的是页面产生的连续累计布局偏移分数,可能这样说大家会有点不知所然。那么大家想象一个场景,你正在阅读某篇文章的时候,突然文章位置被挤去了下面,如果有更坏的情况,你刚滑倒刚刚位置时候,又被挤去了右边。亦或者,当你准备点击登录的时候,突然登录被挤了下去,你点到了退出。相信此时你的内心一定是:

image.png

CLS的诞生就是为了来解决此类问题的,在日常开发中,我们经常会使用懒加载骨架屏来先加载页面框架,再进行动态渲染,填充框架。但是这类做法是很容易发生位置偏移的。

说了那么多,那么我们怎么来优化这项指标呢?

  1. 将图片都设置占位图,大家知道,一个页面中最大的元素通常都是图片,设置占位图可防止大量的偏移。
  2. dom元素给定固定的宽高, 大家可能都习惯用数据去撑开装取数据的dom,但在网速很慢的情况下,因为偏移产生的不良效果是非常明显的。
  3. 编写转换动画,例如:你用手机看小说时翻页会有一个类似翻书的动画。可以用此类动画来让位置偏移不那么突兀

页面流畅度

FPS

FPS(Frames Per Second)翻译为每秒传输帧数,这个指标相信爱打游戏的各位都不陌生,下面我们就来简单介绍一下。一般对于网页而言,最优的帧率在 60 FPS,如果越接近这个值,页面就越流畅,帧率如果远低于这个值,用户可能会明显感觉到卡顿,也就是俗称的掉帧

60 FPS 意味着页面每隔 16.5ms(1/60)就需要渲染一次,否则就会出现丢帧的现象,而浏览器中的 JavaScript 执行页面渲染都是会相互阻塞的,如果在代码中有非常复杂的逻辑占用了大量的执行时长,就会导致页面出现卡顿

Chromedevtools(按下F12打开) 中我们可以执行 control+shift+p 输入 show fps 来快速打开 fps 面板,如下图所示:

image.png

我们用掘金官网看一看fps

image.png

我们可以看到掘金官网的性能还是非常好的!(掘金牛逼!)

网络传输

大家应该都知道,我们一个完整的网站是拥有客户端服务端两端的,如果没有服务端,那么页面便没有交互,只是一个静态文件

那我们页面要展示数据,必须建立连接从服务端去获取,那么连接所耗费的时长必定会影响整个页面的性能,所以优化连接也是一个非常大的优化点!

TCT

TCT(Total Connection Time)翻译为整体连接耗时,表示着我们一个完整请求的消耗时间(包括建立连接传输断开连接

那么会影响连接消耗时常的原因有哪些呢?我们一起研究一下:

  1. 服务器距离用户端距离过远(距离越远,所耗费时间越长)
  2. 不断重复建立连接,不同域名的网络请求都需要重新建立连接。
  3. 客户端自身网络较慢。

可以见得除了用户自身问题,我们是有很大的优化空间的,那么我们可以做一些什么优化呢?

  1. 采用CDN,可以把一些较大资源放入CDN缩短距离,达到提升速度。(CDN通俗一点的解释就是:帮你找到资源离你当前网络地址最近的服务器,达到距离最短。感兴趣的同学可以自行查阅资料,这里不过多赘述)
  2. 使用预连接对域名进行预建连。例如:
<link rel="dns-prefetch" href="https://example.com">
  1. 将请求收拢在同一域名下(这里需要根据自己项目实际情况判断!不要为了减少建连次数而无脑收纳所有资源请求,每个域名都有请求并发数,不恰当的使用会引起反效果!)
  2. 由于拥有并发数量限制,若有资源请求被阻塞排队,可以根据具体需求,将资源进行合并至同一http请求中(根据项目实际情况判断!!不恰当使用会导致形成长任务起到反效果!)

网络优化很多时候更需要开发者对自己项目情况和用户群体的判断,一定不要一味的去使用!!

TTFB

TTFB(Time to First Byte)翻译为首个字节传输耗时,表示从发起请求到收到服务器响应第一个字节的时间,一般来说,如果首屏 html 请求的 TTFB 能达到100ms以内,就已经具备不错的体验了,如果超过了500ms,那么用户就能明显的感受到白屏大家可能对这个概念还有点模糊,让我们看看TTFB包含了哪些阶段呢?

  1. 重定向
  2. DNS查询
  3. TCP和SSL握手
  4. 发起HTTP请求后接到服务端的第一个响应报文时间

那我们该怎么去进行优化呢?让我们参考下下面这几种方式:

  1. 减少请求传输量
  2. 减少服务端处理时间(gzip压缩、增加缓存等)
  3. 进行懒加载

看到这同学们可能会存在一个疑问,TTFB越短越好吗?当然不是

这里我们仍然需要做好权衡,例如,我们开启gzip压缩的时候,一定会增加TTFB,但是资源体积的缩小对整个资源请求速度的提升是更为显著的。

就好比你玩赛车游戏:20m处有一个氮气包,但是却在最右边的位置,两辆车同时起泡,A车全力往前走,B车却花了一点额外时间去吃掉氮气包。那么在前20m,A车一定比B车快,但100m的话B车一定会比A车快。所以这就是需要你去权衡的地方。

2.测试工具

Sharp tools make good work.

工欲善其事,必先利其器。要想做好性能优化,我们必须有一个好的工具去测量各类指标。让我们一起看看有哪些比较好用性能测试工具吧!

Lighthouse

你可以通过在Chorme商城安装Lighthouse插件来获取如下指标(以掘金为例):

image.png

可以看到图中有我们对应的性能指标分数,能让我们有一个优化的方向。

image.png

Lighthouse也会给出一些优化的建议(细心的小伙伴会发现和上文中指标的建议都是一样的),大家可能会问到,那么怎么该根据建议去做相应的优化呢?别着急,下文中会详细介绍!

注意!!Lighthouse的各项指标,是通过你的电脑访问这一个单独页面而统计的,所以不具有权威性,它只能作为一个参考。(因为你电脑性能网速的不同,都会导致其中一些指标有偏差。例如网速快的FCP一定会比网速慢的更低!)

DevTools

DevTools是什么?你进入网页按下f12就知道了小傻瓜。

Network

image.png

我们通过Network来对性能分析提取有用的信息,例如:

  1. 选项区:Preserve log保留网络请求,比如我们需要刷新页面又担心数据也被刷新时,可以使用这项操作来保留数据请求。Disable cache禁用缓存,通常我们的资源都会被加上缓存机制,这样我们可能无法得到最新的数据或者真实的加载速度。No throttling可以模拟网络状态(例如3G等)
  2. 下面各项就是浏览器的请求列表
  3. size代表传输的文件大小,鼠标移入可以看到是否由gzip压缩,且压缩前后的大小。
  4. Waterfall代表资源加载每一步的顺序和耗时

Performance

image.png

我们可以从Performance里得到更加详细的信息,例如请求的对应的时间,以及各类指标对应的时间点。

Webpagetest

前文有提到Lighthouse只会根据自己电脑的性能及网速来测量各类指标,那么Webpagetest便可以让你在不同的网络环境对页面进行性能勘测(更具有真实性)。

webpagetest.org/ (附上网址)

image.png

我们可以选择不同的环境及配置去勘测性能,下面我们测试下Google的网站:

image.png

测试完成后可以看到各种指标及性能,感兴趣的小伙伴可以自己去体验一下,这里就不一一赘述了。

二、如何优化网站性能

上文介绍了性能的各类指标,也给出了各类指标的优化方向,但不少同学可能会很疑惑,感觉无从下手不要急,下面我将给大家展示几类优化思路,让你能够对症下药!

1.CSS优化

说到CSS优化,大家可能会觉得一个CSS能有什么优化的,其实不一,我们应该知道,在浏览器加载出来的过程中,是有一个步骤需要渲染cssom树的,尤其在网速较差的环境中,差异会及其明显!!(毕竟我们不能放弃网速不好的用户吧)

延迟加载非关键CSS

(以下适用建议大家可对照上面每条指标的优化手段哦!)

适用:缩小CSS 移除未使用的CSS

打开DevTools,ctrl+shift+p 搜索show coverage,对页面进行检测找出无用代码率

image.png

可以看到我们图中Unused Bytes这一栏,标注出了227570个字节,也就是73.6%的代码当前页面是没有立即用到的,图中标红的代码都是没有用到的,那么我们是不是有一个思路呢?

没错,聪明的你应该已经想到,我们可以先加载使用到的那部分CSS,再去网络空闲的时机,加载出剩余的CSS

思路我们已经有了,那么我们该怎么去做呢?

我们下载出对应json文件

image.png

我们打开json文件

image.png

startend代表有效代码的字节区间(可以编写一个js脚本提取),提取有效css后,在首屏中进行引入,取消掉全局css加载,将全局css防止合适的节点处进行加载(每个项目的情况不同,你需要判断项目中哪个时机最适合来进行这一步操作)

最终成果

image.png

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来做为示例。

在运行插件后(具体使用在此不多做讲解),我们会得到当前项目的分析图:

image.png

大家可以看一看,有没有发现其中有什么问题呢?

删除重复的JS包

image.png

有两个包完全重复了,为什么会发生这种情况呢?

例如当前有个项目A,他本身依赖D,但他引入了组件B组件C,并且BC都依赖了D,但是项目构建的时候并不知道,所以会将两个模块都引入进去,造成了重复依赖。

├── projectA 
│   └── node_modules 
│       │── packageD
│       │── pluginB 
│       │   └── node_modules 
│       │       └── packageD
│       │── pluginC 
│       │   └── nodule_modules 
│       │       └── packageD

那我们该怎么解决呢?

peerDependencies,想必大家对package.json这个文件并不陌生,他是管理我们依赖包的地方,每当我们npm install时,npm都会根据对应的模块版本安装依赖。那我们该怎么做呢?

我们只需要在pluginB/package.jsonpluginC/package.json中申明即可,例如:

pluginB/package.json

// peerDependencies和dependencies同级哦
{ 
  "peerDependencies": { 
    "packageD": "1.0.1"
  }
}

然后只在最外层的dependencies引入模块就行了。

这里对于npm包管理不多作介绍,感兴趣的朋友请自行查阅资料。

删除无用JavaScript

细心的朋友可以看到,我们的打包文件居然把mock.js也打包了进去,无论是处于什么原因,这都是没有用的,我们要干掉它!

image.png

我们应该找出打包后的无用依赖包,努力把我们的体积优化到最小

3.网络优化

CDN

前文有提到网络传输时间有很重要一部分是由距离来决定的。CDN的作用便是帮我们来解决这个问题。

如果公司有条件的话,我们的图片、依赖包等较大资源可以放在cdn上,如果没有也不用担心,下面给大家推荐一个免费的CDN网站 - jsdelivr

前文已经介绍了怎样分析构建后的依赖包,我们可以根据情况将较大依赖提取放入CDN,进行提效作用。

缓存机制

浏览器缓存虽然不能提高新用户的体验,但是对于老用户的体验是很有价值的。

此处不介绍该怎样给资源加入缓存,如果有兴趣可以查阅相关资料

我们可以将一些静态文件和长久稳定不变JS包放入缓存(此处需要自己根据项目情况判断)

例如:

  1. 若页面是弱交互的,例如官网,可以直接将整个htmlcss放入缓存。
  2. 页面上的图片字体等资源。
  3. VueReact等稳定版本的框架。

预加载

当你的网站空闲时,我们是否可以用来做一些事呢?

使用预加载,在网站空余时(需要自己判断),提前加载其他模块内容,将其加入缓存,这不光不会对当前页面造成影响,也将大幅提升其他模块的加载速度。

4.项目代码优化

懒加载

懒加载相信大家都不陌生了,但是我想问问大家为什么要使用懒加载呢?

由于连接的并发是有数量限制的,当请求太多达到限制,亦或者由于代码编写不当主线程被占用,我们的其他资源便无法加载。

懒加载的目的便是将页面上暂时不重要的元素推迟,直到它成为重要元素才进行加载

目前使用较多的有图片懒加载路由懒加载

减少回流重绘

回流:当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流

可能会导致回流的操作:

  1. 浏览器的窗口大小发生变化
  2. 添加或者删除可见的DOM元素
  3. 元素的字体大小发生变化
  4. 元素的尺寸或者位置发生变化
  5. 元素的内容发生变化

重绘:当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘

可能会导致重绘的操作:

  1. 字体颜色或背景发生变化
  2. border-radius、visibility、box-shadow等发生变化

注意:重绘不一定引起回流,回流一定引起重绘。

减少回流重绘的操作:

  1. 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
  2. 如果需要修改多个css属性,可以先将元素设置为display:none,然后操作完成之后再改变回来,这样就能大大降低回流次数。
  3. 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
  4. 避免频繁的操作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);
  };
};

总结

性能优化是一项很考验开发者功力的技术,每一个项目的优化方案可能都是不一样的,项目代码的编写不当也会引起阻塞从而造成性能问题,所以文章中不断强调需要自己去对情况进行判断。

性能优化内容远远不止如此,以上只代表个人的学习见解,若有错误,欢迎各位大佬指出。

还记得文章开头提到的吗?现在我希望大家都能做到那两点:

  1. 能定位项目性能问题
  2. 对于一个优化点该怎么入手