前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分享一次利用任务切片解决页面卡顿的性能优化~

分享一次利用任务切片解决页面卡顿的性能优化~

作者头像
winty
发布2023-11-12 13:19:01
2940
发布2023-11-12 13:19:01
举报
文章被收录于专栏:前端Q前端Q

一、代码背景

本次分享基于一次线上环境的卡顿事故,客户数据体量过大导致的页面卡顿甚至页面直接崩溃的问题。现在我们将会把此次事故抽象成为大家更好理解的案例,从而来进行分析和解决。

同时希望大家在阅读完之后可以了解到页面卡顿背后的底层原因,还有任务切片的解决原理!

二、正文

代码语言:javascript
复制
<body>
    <input type="text">
    <button id="my-button">执行任务</button>
    <!-- box容器 -->
    <div id="box" style="height:500px;width:400px;overflow: auto;margin-top:24px;">
</body>
代码语言:javascript
复制
// box容器溢出滚动
const box = document.getElementById('box')
for(let i=0;i<1000;i++){
    const myText = document.createElement('h2')
    myText.innerText = i
    box.appendChild(myText)
}

// 执行任务
const myButton = document.getElementById('my-button')
myButton.addEventListener('click',load)
function load() {
  const total = 300000;
  for (let i = 0; i < total; i++) {
      console.log(i)
  }
}

点击执行任务按钮会发现,我们不仅不能和输入框进行交互,连box容器区域的滚动也不再有响应,整个页面卡顿住了,直到load任务执行完成,页面才恢复响应,输入框才能正常使用,box容器区域也能正常响应滚动。

或许观察到这里有人已经能够想到解决方案了!

我知道了,长任务执行导致页面卡顿,使用任务切片的方式解决!

没错,这里确实是使用任务切片的方式能够解决!但是,我想问一下,任务切片解决卡顿问题的底层原理是什么样子的?或者说什么是卡顿问题,而任务切片又是如何解决这类问题的?

卡顿分析

保证页面的流畅性是前端的一个主要内容,页面卡顿会严重影响用户体验。这流畅性是需要一个指标来衡量的,那就是帧率(FPS),FPS 表示的是每秒钟画面更新次数,当今大多数设备的屏幕刷新率都是60次/秒。

不同帧率的体验

  • 帧率能够达到 50 ~ 60 FPS 的动画将会相当流畅,让人倍感舒适;
  • 帧率在 30 ~ 50 FPS 之间的动画,因人敏感程度不同,舒适度因人而异;
  • 帧率在 30 FPS 以下的动画,让人感觉到明显的卡顿和不适感;
  • 帧率波动很大的动画,亦会使人感觉到卡顿

也就是说想要保证页面流畅不卡顿,浏览器对每一帧画面的渲染工作需要在16ms(1000ms/60)之内完成!

想要保证页面流畅,需要做到每16ms渲染一次!

也就是说,前面在我们执行任务的时候,浏览器没有能够做到每16ms渲染一次,所以我们页面会卡顿不流畅。那么是什么导致了浏览器没有能够正常渲染呢?或许在探索真相之前,我们还需要先深入了解一下浏览器的事件循环机制

浏览器事件循环机制

浏览器事件循环机制是一种用于处理异步任务的机制。它的工作原理是不断地检查任务队列,执行队列中的任务,并等待新的任务加入。

执行顺序:

  1. 执行宏任务队列和微任务队列就不解释了。
  1. 进入Update the rendering阶段,这里有个rendering opportunity概念,浏览上下文渲染会根据屏幕刷新率、页面性能、页面是否在后台来确定是否需要渲染。而且渲染间隔通常是固定的。
  2. 如果不需要渲染,以下步骤(只列举常用的)也不会运行了:
  • run the resize steps,触发 resize 事件;
  • run the scroll steps,触发 scroll 事件;
  • update animations,触发animation相关事件;
  • run the fullscreen steps,执行 requestFullscreen 等 api;
  • run the animation frame callbacks,执行 requestAnimationFrame 回调;
  • run IntersectionObserver callbacks,图片懒加载经常使用;

4.重新渲染用户界面。

5. 判断宏任务队列或者微任务队列是否为空,如果为空则执行 Idle 空闲周期计算,判断是否需要执行 requestIdleCallback 的回调。

性能分析

通过我们上面对浏览器事件循环的深入了解,我们可以知道,浏览器没能每16ms渲染一次也能被解读为没能每16ms执行完一次事件循环。

结合我们页面的Performance可以看到,load函数的执行花费了6s多,而事件循环中的渲染需要等待前面任务执行完毕,才会判断执行。

也就是说,浏览器花费了6s多的时间才完成了一次事件循环,完成了一次渲染任务,而我们保持页面60FPS的最低要求是每16ms完成一次渲染,这就难怪页面会卡顿不流畅,这显然是不合理的!

任务切片(setTimeout、requestAnimationFrame)

这就类似我们去餐厅吃饭,我们几个人很饿,点了很多菜给到厨房,但是厨房却等所有菜都做完(长任务执行)才全部一次性端上来(渲染),这样的体验毫无疑问是十分差劲的。

正确的做法应该是我们点了很多菜(一个长任务),厨房做完一道菜(小任务执行),就端上来一道(渲染一次),这样分多次上菜(多个小任务多次渲染)才不会让顾客等待太久,也能提升用户体验。

回到我们的页面代码,我们也可以按照这个思路,将load函数代码拆分成多个小任务,保证16ms内能执行完一次事件循环,这样才能保持页面流畅不卡顿,而这个时候,就需要应用到我们的任务切片了!

终于回到任务切片了!

一般我们可以使用setTimeout或者requestAnimationFrame实现任务切片,这里我们使用setTimeout举例说明:

代码语言:javascript
复制
function load() {
    let total = 1000000;
    let length = 20;
    let page = total/length
    let index = 0;
    function loop(curTotal,curIndex){
        if(curTotal <= 0){
            return false;
        }
        let pageCount = Math.min(curTotal , length);
        setTimeout(()=>{
            for(let i = 0; i < pageCount; i++){
                console.log(i)
            }
            loop(curTotal - pageCount,curIndex + pageCount)
        },0)
    }
    loop(total,index);
  }

此时我们运行代码之后发现,点击执行任务按钮时,页面不再卡顿,输入框能够正常focus交互,box容器区域也能正常滚动,一整个流畅!

我们再根据页面Performance进行分析:

可以看到,load函数代码分成了无数小任务(output)进行执行,每一次小任务执行完,都判断是否需要渲染(这里可以看到由于事件循环之间的间隔时间太短,浏览器选择三次事件循环才执行一次渲染任务)。此时我们浏览器就做到了每16ms完成一次渲染任务的指标,自然页面也就保持流畅不会有卡顿了!

文章到这里就结束了,如果有需要补充或者错误的地方,欢迎在评论区指出。

作者:前端不死 链接:https://juejin.cn/post/7290753121702215691

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端Q 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、代码背景
  • 二、正文
  • 卡顿分析
  • 不同帧率的体验
  • 浏览器事件循环机制
  • 性能分析
  • 任务切片(setTimeout、requestAnimationFrame)
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档