hans7 发表于 2022-11-7 01:33

使用Chrome Performance进行性能分析(多个实战案例)

本帖最后由 hans7 于 2022-11-9 01:51 编辑

### 引言

以前一直以为用Chrome Performance面板进行性能分析很难。实际做过以后方能认识到这是多么简单!

海量图片预警!

**作者:(https://blog.csdn.net/hans774882968)以及(https://juejin.cn/user/1464964842528888)以及(https://www.52pojie.cn/home.php?mod=space&uid=1906177)**

本文52pojie:https://www.52pojie.cn/thread-1708906-1-1.html

### 重排分析

#### 例1:入门

这一节主要是在复现参考链接1的内容。我们来写一个简单的demo:点击`.div1`,该元素高度变大。

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>reflow</title>
<style>
    .div1 {
      width: 100px;
      height: 100px;
      background-color: deepskyblue;
    }
    .div2 {
      width: 200px;
      height: 200px;
      background-color: pink;
    }
</style>
</head>
<body>
<div class="div1"></div>
<div class="div2"></div>
<script>
    function main() {
      const div1 = document.querySelector('.div1')

      function div1Click() {
      div1.style.height = '200px'
      }

      div1.addEventListener('click', div1Click)
    }
    main()
</script>
</body>
</html>
```

我们打开Chrome Performance,点击箭头所示的按钮进行录制,并点击一下`.div1`,生成性能分析结果。



`Main`表示主线程,渲染和JS执行都是在主线程进行的。我们看到主线程有两个值得关注的Task,第一个Task主要**依次**由`Schedule Style Recalculation`、点击事件、`Recalculate Style`和`Layout`组成,第二个Task主要**依次**由`Paint`和`Composite Layers`组成。这里一个`Task`表示一个事件循环,`Layout`事件表示重排,`Paint`事件表示重绘,重排和重绘可能在同一个Task也可能不在同一个Task执行。

看第一个Task:



点击色块可以查看相关信息。

- 箭头所指为`Compile Code`,耗时37 μs。
- `div1Click`左侧有一个非常细的紫色块`Schedule Style Recalculation`。
- `div1Click`耗时0.11 ms。
- `click`结束后,右侧有3个紫色块:第一个是`Recalculate Style`,耗时0.12 ms。
- 第二个是`Layout`,耗时0.13 ms,`Layout root`是`#document`,`First Layout Invalidation`是这行代码`div1.style.height = '200px'`。如下图所示。
- 第三个是`Pre-Paint`,不关注。



第二个Task我们只看一下`Paint`:



可以看到`Layer Root`是`#document`,并且重绘耗时仅53μs。

#### 例2:重排和重绘各发生几次?

我们基于例1的代码,稍微改下`div1Click`:

```js
      function div1Click() {
      div1.style.height = '200px'
      div2.style.height = '100px'
      }
```

请问它引起了几次重排?我们看一下Performance面板:



可以看到只引发了一次重排(`Layout`)和一次重绘(鼠标所指的`Paint`),并且发生在不同的事件循环。这大概是因为浏览器的优化!

接下来试试参考链接1所说的“一次事件循环(即一个Task)当中触发多次重排”的情况。再改下代码:

```js
      function div1Click() {
      div1.style.height = '200px'
      console.log(div2.clientHeight)
      div2.style.height = '100px'
      console.log(div2.clientHeight)
      }
```

看下Performance面板:



第一个Task的两个箭头是两次`Layout`,它们左边相邻的紫色块分别都是`Schedule Style Recalculation`(非常细)和`Recalculate Style`。第二个Task鼠标指向的是`Paint`。所以`div1Click`在第一个Task触发了2次重排,但仅在第二个Task触发了1次重绘。

我们点击两个`Recalculate Style`,发现两者都有一个之前没看到的属性`Recalculation Forced`,相关的代码分别是两句`console.log(div2.clientHeight)`。为了探究这个属性是否真的导致了强制重排,我们把代码改成:

```js
      function div1Click() {
      div1.style.height = '200px'
      console.log(div2.clientHeight)
      div2.style.height = '100px'
      }
```

则发现重排仍发生2次,但第一次`Recalculate Style`有`Recalculation Forced div1Click@reflow.html:29`(即`console.log(div2.clientHeight)`),而第二次`Recalculate Style`没有。对比这些结果我们猜测,`Recalculation Forced`表示Performance面板分析出这次重排是被迫发生的,而发生的原因是我们读取了`div2.clientHeight`。

接下来我们把修改`div2`的代码包裹进宏任务。

```js
      function div1Click() {
      div1.style.height = '200px'
      console.log(div2.clientHeight)
      setTimeout(() => {
          div2.style.height = '100px'
          console.log(div2.clientHeight)
      })
      }
```

看下Performance面板,下面是刷新多次的结果:

图1



图2



图3



观察到的一些现象:

1. 重排肯定发生2次,但重绘可能发生1次也可能发生2次。如果重绘发生了2次,则`Timer Fired`的事件循环在重绘的事件循环之后。如果发生1次则`Timer Fired`的Task与点击的Task相邻。图1到图3重绘分别发生了1次、1次、2次。图1、图2的重绘Task和`Timer Fired`Task相隔长达几毫秒,所以截不进来。
2. `Schedule Style Recalculation`是非常细的紫色块,既可能在函数执行的下面也可能在函数执行的左侧。图1到图3`Schedule Style Recalculation`分别在匿名函数的左侧、左侧、下面。3个图`Schedule Style Recalculation`都在`div1Click`下面仅仅是巧合。
3. `Install Timer`是非常细的黄色块,既可能在`setTimeout`下面也可能在`setTimeout`左侧。图1到图3`Install Timer`分别在`setTimeout`下面、左侧、左侧。
4. 如果发生了重排,则`Schedule Style Recalculation`、`Recalculate Style`和`Layout`总是在同一个Task按时间顺序出现。

### 宏任务和微任务执行顺序分析

校招必考八股!来写一个简单的例子:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>task</title>
</head>
<body>
<script>
    function main() {
      console.log(1)
      setTimeout(() => {
      console.log(7)
      })
      console.log(2)

      function pro1() {
      function then1() {
          console.log(5)
      }

      new Promise((resolve) => {
          console.log(3)
          resolve()
      }).then(then1)
      }

      pro1()

      function pro2() {
      function then2() {
          console.log(6)
      }

      new Promise((resolve) => {
          setTimeout(() => console.log(8))
          resolve()
      }).then(then2)
      }

      pro2()

      function pro3() {
      new Promise((resolve) => {
          setTimeout(() => {
            console.log(9)
            resolve()
          })
      }).then(() => {
          console.log(10)
      })
      }

      pro3()
      setTimeout(() => {
      console.log(11)
      })
      console.log(4)
    }
    main()
</script>
</body>
</html>
```

我们把Promise放进函数里,并写个`then1`、`then2`,是期望在Performance里能方便地找到它们。如果不这么做,Performance里就不好找到它们(找不到的话就多刷新几次,看RP的)。

上面代码的输出为1~11。我们来简单分析一下:注意Promise传入的函数体也是同步任务,在执行`resolve()`的时候才会加入微任务队列。因此1~4执行后有2个微任务(5、6)和4个宏任务(7、8、9、11),先清空微任务再处理宏任务。执行9后将10加入微任务队列,因此10先于11执行。接下来看看Performance面板:

Task1:



点击查看的是`then2`,4个红色箭头指出的是`Install Timer`,绿色箭头指出的是`pro2`和`pro3`。

1. 看到执行顺序是`timer1 -> promise1 -> promise2 -> timer2 -> promise3 -> timer3 -> timer4`,于是我们验证了:`Promise`传入的函数体是同步任务。
2. 图右侧有一个`Run Microtasks`表示两个微任务`then1`、`then2`执行了。1~6是在同一个事件循环中执行的。

后续Tasks:



可以看到,即使定时器设置的延迟时间为0,第一个事件循环和这4个事件循环也并没有挨在一起,而是由一个有重绘`Paint`的事件循环隔开了。这里4个定时器分为4个事件循环,各有一个粉色的`(anonymous)`块。其中,第三个宏任务在跑完同步任务(输出9)以后执行了一个微任务(输出10)。

综上:

1. 不同的同步任务和不同的微任务可以在同一个事件循环中执行。
2. 当前事件循环执行同步任务的过程中会加入一些宏任务和微任务。所有的微任务都会在当前事件循环的末尾执行完毕,而宏任务都要在后续的事件循环才能执行。
3. 不同的宏任务在不同的事件循环中执行。
4. 可以通过Performance面板来分析各种宏任务和微任务的真实执行顺序。

### 一个简单的寻找性能瓶颈的例子:Janky Animation

这一节主要是在复现参考链接2。案例:https://googlechrome.github.io/devtools-samples/jank/

为了方便,我们把代码copy到本地,再加一个显示当前图片总数的feature。完整代码就不贴出来了,太长了。



我们看到:

1. 10个方块的时候,一个Task时间在3.5ms左右。
2. 一个事件循环恰好有10个`Schedule Style Recalculation`、`Recalculate Style`和`Layout`。
3. 和前面的案例不同,这里重排和重绘发生在同一个事件循环。

把方块个数增加到100:



我们看到一个Task时间增加到了23~25ms。再看方块个数增加到150和200的情况:



上图箭头指向的红条表示帧率过低。



上图绿色箭头指向的红色三角标签表示耗时超过50ms的long task。

对于左下角的饼图,`Idle`的时间占多数才是正常的,比如10个方块的饼图:



对于200个方块的情况,我们查看某个方块的`Layout`,可以看到Performance已经贴心地帮我们指出了“Forced reflow(强制重排)是可能的性能瓶颈”。



引起`Recalculation Forced`(在`Recalculate Style`事件)和`Layout Forced`(在`Layout`事件)的代码是同一行,点击查看:



于是我们找到了性能瓶颈:每个方块都要读取`offsetTop`,导致每个方块都引发了强制重排。

点击一下`Optimize`按钮,避免读取`offsetTop`:



可以看到无论有多少方块,一个Task都只引发了一次重排,所以时间降为7ms左右,饼图也优化了许多:



但是查看代码可以看到,读写`m.style`的代码成为了新的性能瓶颈。



这个需求仅仅是在展示运动的方块,因此我们可以用css3的`transforms`等属性,来达到类似的效果。另外,我们可以使用`will-change`属性,告知浏览器提前做好优化准备。

部分JS代码如下:

```js
app.update = function (timestamp) {
    for (var i = 0; i < app.count; i++) {
      var m = movers;
      if (app.optimize === 1) {
      var pos = m.classList.contains('down') ?
          m.offsetTop + distance : m.offsetTop - distance;
      if (pos < 0) pos = 0;
      if (pos > maxHeight) pos = maxHeight;
      m.style.top = pos + 'px';
      if (m.offsetTop === 0) {
          m.classList.remove('up');
          m.classList.add('down');
      }
      if (m.offsetTop === maxHeight) {
          m.classList.remove('down');
          m.classList.add('up');
      }
      } else if (app.optimize === 2) {
      var pos = parseInt(m.style.top.slice(0, m.style.top.indexOf('px')));
      m.classList.contains('down') ? pos += distance : pos -= distance;
      if (pos < 0) pos = 0;
      if (pos > maxHeight) pos = maxHeight;
      m.style.top = pos + 'px';
      if (pos === 0) {
          m.classList.remove('up');
          m.classList.add('down');
      }
      if (pos === maxHeight) {
          m.classList.remove('down');
          m.classList.add('up');
      }
      } else if (app.optimize === 3) {
      var pos = parseInt(
          m.style.transform.slice(
            m.style.transform.indexOf('(') + 1,
            m.style.transform.indexOf('px')
          )
      ) || 0;
      m.classList.contains('down') ? pos += distance : pos -= distance;
      if (pos < 0) pos = 0;
      if (pos > maxHeight) pos = maxHeight;
      m.style.transform = `translateY(${pos}px)`;
      if (pos === 0) {
          m.classList.remove('up');
          m.classList.add('down');
      }
      if (pos === maxHeight) {
          m.classList.remove('down');
          m.classList.add('up');
      }
      }
    }
    frame = window.requestAnimationFrame(app.update);
}
```

新增的CSS:

```css
.proto.mover {
will-change: transform;
}
```

这个方法有个美中不足之处:无法去除`m.style.top`(去除了优化就白做了),导致方块运动范围与前两种方案不一致。



此时我们看到只有`Schedule Style Recalculation`、`Recalculate Style`,没有`Layout`事件,说明没有引起重排。一个Task的时间进一步降低为5ms左右。

再看看优化2的饼图,也比优化1更好:



另外,使用`will-change`的元素会单独分出一个图层,我们可以用Layers面板查看。不使用`will-change`:



使用`will-change`:



### web worker案例

这一节主要是在复现参考链接4。

我们准备一段会阻塞渲染的代码:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>web worker</title>
</head>
<body>
<p id="p1"></p>
<p id="p2"></p>
<script>
function main() {
    let ans1 = 0n
    for (let i = 1n; i <= 10000000n; i++) {
      ans1 += i * i
    }
    const p1 = document.getElementById('p1'), p2 = document.getElementById('p2')
    p1.innerText = ans1.toString()
    let ans2 = 0n
    for (let i = 1n; i <= 10000000n; i++) {
      ans2 += i * i * i
    }
    p2.innerText = ans2.toString()
}
main()
</script>
</body>
</html>
```

Chrome打开无痕窗口,消除插件的影响。点击Performance面板的`Start profiling and reload page`按钮,得下图:



LCP大约3s,说明耗时计算的确阻塞了渲染。再放大看看`f1`和`f2`的分界点:



看来`f1`常数比`f2`小些,是符合直觉的。当然更好的方式是直接在`Sources`面板看哪些代码执行时间最长。



我们有可能优化这个页面的LCP嘛?如果计算任务可拆分,那么我们可以参考React Fiber的架构,把任务拆开,组织成链表。但是这两个简单任务不能再拆分。怎么办?在参考链接4了解到`Worker`可以开启额外线程来运行耗时任务,达到不阻塞渲染任务的目的。于是我们可以接着写代码:

`index.html`:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>web worker优化后</title>
</head>
<body>
<p id="p1">答案1</p>
<p id="p2">答案2</p>
<script>
function main() {
    function runWorker(url, message) {
      return new Promise((resolve, reject) => {
      const worker = new Worker(url)
      worker.postMessage(message)
      worker.addEventListener('message', (e) => {
          resolve(e.data)
      })
      worker.onerror = reject
      })
    }

    const p1 = document.getElementById('p1'), p2 = document.getElementById('p2')

    async function f1() {
      const ans = await runWorker('./web_worker1.js', 10000000n)
      p1.innerText = ans.toString()
    }

    async function f2() {
      const ans = await runWorker('./web_worker2.js', 10000000n)
      p2.innerText = ans.toString()
    }

    f1()
    f2()
}
main()
</script>
</body>
</html>
```

`web_worker1.js`:

```js
addEventListener('message', (e) => {
let ans = 0n;
let num = e.data;
for (let i = 1n; i <= num; i++) {
    ans += i * i
}
postMessage(ans);
});
```

`web_worker2.js`:

```js
addEventListener('message', (e) => {
let ans = 0n;
let num = e.data;
for (let i = 1n; i <= num; i++) {
    ans += i * i * i
}
postMessage(ans);
});
```

注意点:

1. `Worker`的第一个参数只能是`url`,这逼迫我们把耗时任务放到单个文件中。
2. 对于Chromium内核的浏览器,需要开启http服务器,再打开`index.html`才能运行。否则会报错`Failed to construct 'Worker': Script at '' cannot be accessed from origin 'null'.`。

Performance如下:



我们看到LCP变成了60ms,耗时任务额外开了两个线程去运行,不再影响页面交互。它们的事件可以展开上图的两个`Worker`来查看。

### 进度条组件性能瓶颈分析(React Hooks CDN)
这一节主要是在复现参考链接3。现在要求你用React写一个进度条:

- 支持播放、暂停、重播。
- 播放结束后,播放次数+1,并重新开始播放。
- 暂时不需要支持进度条拖拽等功能,纯展示。

实际上:不需要支持拖拽等功能时,用html5的`progress`标签最好;需要支持拖拽等功能时,只有做法1可行。但我们先忽略这些吧!

#### 用CDN运行React Hooks代码

先介绍一下怎么用CDN跑React Hooks。

1、我们需要导入`react`、`react-dom`和`Babel`:

```html
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
```

2、我们需要一个挂载点`div`,对应的挂载语句:`ReactDOM.render(React.createElement(App), document.querySelector('#app'))`。

3、我们在脚手架开发时使用的ES6 Module写法,需要改成`const {useState} = window.React`。

4、script标签需要声明为`<script type="text/babel">`。

#### 做法1:setTimeout

按照常人思维,我们大概率会这么写:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>progress组件</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
<style>
    .container {
      height: 10px;
      border-radius: 5px;
      border: 1px solid black;
    }

    .progress {
      height: 100%;
      width: 0;
      border-radius: 5px; /* 与.container一致 */
      background-color: red;
    }
</style>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
function main() {
    const {useState} = window.React
    let timer = null//递增进度的定时器
    let totalTime = 3000// 假设视频播放为3s

    function App() {
      const = useState(0)
      const = useState(0)// 进度
      const = useState(false)// 是否播放

      const handlerProgress = pre => {
      if (pre < 100) return pre + 1;
      else {
          // 使用setCount(count + 1)则无法及时更新
          setCount(count => count + 1)
          return 0   // 播放结束,重新开始播放
      }
      }

      // 开始播放 && 暂停播放
      const handleVideo = () => {
      setIsPlay(!isPlay)
      isPlay
          ? clearInterval(timer)
          : timer = setInterval(() => setProgress(handlerProgress), totalTime / 100)
      }

      // 重播
      const replay = () => {
      setIsPlay(true)
      if (timer) clearInterval(timer);
      setProgress(0)
      timer = setInterval(() => setProgress(handlerProgress), totalTime / 100)
      }

      return (
      <div id="root">
          <button onClick={handleVideo}>{isPlay ? '暂停' : '播放'}</button>
          <button onClick={replay}>重播</button>
          <span>{`播放次数为:${count}`}</span>
          <div className="container">
            <div className="progress" style={{width: `${progress}%`}}/>
          </div>
      </div>
      )
    }

    ReactDOM.render(React.createElement(App), document.querySelector('#app'));
}
main()
</script>
</body>
</html>
```

Performance如下:



可以看到,每次更新进度条都需要一个宏任务,并且要触发一次重排。

#### 做法2:构造`@keyframes`切换

接下来看一种比较精妙的做法(来自参考链接3)。这种做法自己想不到的,学一下:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>progress组件-v2</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
<style>
    .container {
      height: 10px;
      border-radius: 5px;
      border: 1px solid black;
    }

    .progress {
      height: 100%;
      width: 0;
      border-radius: 5px; /* 与.container一致 */
      background-color: red;
      animation-timing-function: linear;
    }

    .progress.play {   /* 使animation动画启动 */
      animation-play-state: running;
    }
    .progress.pause {    /* 使animation动画暂停 */
      animation-play-state: paused;
    }
    @keyframes animeSwitch0 {
      to {
      width: 100%;
      }
    }
    @keyframes animeSwitch1 {
      to {
      width: 100%;
      }
    }
</style>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
function main() {
    const {useState} = window.React
    let totalTime = 3000// 假设视频播放为3s

    function App() {
      const = useState(0)
      const = useState(false)
      const = useState(false)// 是否播放

      // 开始播放 && 暂停播放
      const handleVideo = () => {
      setIsPlay(!isPlay)
      }

      const playEnd = () => {
      setCount(count + 1)
      replay()
      }

      // 重播
      const replay = () => {
      setIsPlay(true)
      setAnimeSwitch(!animeSwitch)
      }

      return (
      <div id="root">
          <button onClick={handleVideo}>{isPlay ? '暂停' : '播放'}</button>
          <button onClick={replay}>重播</button>
          <span>{`播放次数为:${count}`}</span>
          <div className="container">
            <div
            className={`progress ${isPlay ? 'play' : 'pause'}`}
            style={{
                animationDuration: `${totalTime}ms`,
                animationName: `animeSwitch${animeSwitch + 0}`
            }}
            onAnimationEnd={playEnd}
            />
          </div>
      </div>
      )
    }

    ReactDOM.render(React.createElement(App), document.querySelector('#app'));
}
main()
</script>
</body>
</html>
```

因为这个进度条组件是纯展示组件,所以可以用`@keyframes`来完成所有的要求。关键的思路是:我们有两份完全一样的`@keyframes`,通过切换`animation-name`来实现重播功能。播放结束时重新播放的要求也可以视为一次重播来实现,具体实现是监听了`onAnimationEnd`事件。Performance如下:



这个做法依旧会频繁触发重排,因为`@keyframes`改变了元素的宽度。这种做法会导致Performance面板无法找到重排发生的原因。另外,性能的瓶颈比较突出:`playEnd()`。

#### 做法2的优化:使用`translateX + scaleX`来造成宽度改变的视觉效果

这个做法有一个更为精妙的优化,只需要修改做法2的CSS部分:

```css
    .container {
      height: 10px;
      border-radius: 5px;
      border: 1px solid black;
    }

    .progress {
      height: 100%;
      width: 100%;
      border-radius: 5px; /* 与.container一致 */
      background-color: red;
      animation-timing-function: linear;
      will-change: transform;
    }

    .progress.play {   /* 使animation动画启动 */
      animation-play-state: running;
    }
    .progress.pause {    /* 使animation动画暂停 */
      animation-play-state: paused;
    }
    @keyframes animeSwitch0 {
      0% {
      transform: translateX(-50%) scaleX(0);
      }

      to {
      transform: translateX(0) scaleX(1);
      }
    }
    @keyframes animeSwitch1 {
      0% {
      transform: translateX(-50%) scaleX(0);
      }

      to {
      transform: translateX(0) scaleX(1);
      }
    }
```

在做法2的基础上,因为只需要进行展示,所以我们用`translateX + scaleX`代替了宽度的变化(视觉效果一样即可)。设当前缩放属性为`scaleX(x)`,那么我们需要右移:`- (100% - x) / 2`,才能让起点始终左对齐。代入`0`和`100%`,分别得`-50%, 0`。这就确定了`@keyframes`的写法。显然这种写法不会引起重排。另外,因为这个方案只有视觉效果,所以我们不妨用`will-change: transform`把进度条提升到一个单独的图层。

做法3的Performance图懒得截了,可以看下文“性能评估”一节。做法2和做法3的图层对比:



做法3:



进度条的确提升到了单独的图层。

#### 性能评估

注:空闲时间的评估结论看上去不太对劲,仅供参考。

我们分两种情况:进度跨过了100%和进度没跨过100%,只录制2s左右。选择在第4次播放时开始录制。

进度没跨过100%:做法2的空闲时间 = `2004/2084`,做法1的空闲时间 = `2045/2144`,差为0.008,单位ms。差不多。

进度跨过100%:令我感到意外,做法2比做法1的性能差。不过做法3性能最好,还是符合直觉的。

做法1:



做法2:



做法3:



上图验证了我们的说法,做法3用`translateX + scaleX`代替做法2宽度的变化后,不会引起重排了。

### 总结

> Chrome Devtools 的 Performance 工具是网页性能分析的利器,它可以记录一段时间内的代码执行情况,比如 Main 线程的 Event Loop、每个 Event loop 的 Task,每个 Task 的调用栈,每个函数的耗时等,还可以定位到 Sources 中的源码位置。
>
> 性能优化的目标就是找到 Task 中的 long task,然后消除它。因为网页的渲染是一个宏任务,和 JS 的宏任务在同一个 Event Loop 中,是相互阻塞的。

### 参考资料

1. https://www.bilibili.com/video/BV1Pr4y1N7QZ
2. 你不知道的chrome performance调试技巧:https://juejin.cn/post/6977637532494200863
3. 我优化了进度条,页面性能竟提高了70%:https://juejin.cn/post/69768100169300050294. Chrome Performance + web worker:https://juejin.cn/post/7046805217668497445

hans7 发表于 2022-11-9 01:53

今天更新了一个web worker的简单案例和一个React Hooks CDN的有一定难度的案例~

hans7 发表于 2022-11-13 17:18

longbt 发表于 2022-11-12 23:37
这是开发测试必备技能啊

但是就这么一个必备技能,q内想找个通俗、详细+有实战案例的教程却还是难于登天{:1_936:}

yonghu99999 发表于 2022-11-7 07:03

好东西啊

hnwang 发表于 2022-11-7 08:20

收藏了学习一下,感谢分享

vethenc 发表于 2022-11-7 08:29

技术大佬,真的试图教会我{:1_926:}

SVIP008 发表于 2022-11-7 09:57

感觉看不懂,但要支持一下!

8970665 发表于 2022-11-7 13:09

是否看不懂的都是好东西!

wantwill 发表于 2022-11-7 16:11


学习了,虽然我也看不懂

wanghaowei666 发表于 2022-11-7 16:45

学习了,虽然我也看不懂

debug_cat 发表于 2022-11-7 17:26

后面可能对自己的网站进行优化,这个工具使用很棒

浔無涯 发表于 2022-11-8 09:05

非常有用, 之前也以为很难
页: [1] 2 3 4 5 6 7
查看完整版本: 使用Chrome Performance进行性能分析(多个实战案例)