JS时间分片技术解决长任务导致的页面卡顿

目录
  • 起因
    • 处理办法
    • 事件循环
    • 浏览器渲染时机
    • 原始代码
      • 代码
      • 效果
    • 函数改造
      • 代码
      • 效果
    • 优化时间分片
      • 代码
      • 效果
    • 对比优化前后
      • 代码
      • 效果
  • 最后

起因

同事遇到一个动画展示的问题,就是下面要执行一个运算量很大的函数,他要加载一个 loading,但他发现把 loading 的元素 display: block; 页面中也不会立刻出现 loading 动画,出现动画的时候是运算函数执行完毕之后。

处理办法

有两种方法去处理这种耗时任务,第一种就是 webWorker,但是一些 dom 的操作做不了,于是就想到了通过 generator 函数来解决,下面先简单了解下事件循环。

事件循环

微任务:

1. Promise.then

2. Object.observe

3. MutaionObserver

宏任务:

1. script(整体代码)

2. setTimeout

3. setInterval

4. I/O

5. postMessage

6. MessageChannel

浏览器渲染时机

除去特殊情况,页面的渲染会在微任务队列清空后,宏任务执行前,所以我们可以让推入主执行栈的函数执行到一定时间就去休眠,然后在渲染之后的宏任务里面叫醒他,这样渲染或者用户交互都不会卡顿了!

原始代码

我们先模拟一个 js 长任务

代码

// style
@keyframes move {
    from {
        left: 0;
    }
    to {
        left: 100%;
    }
}
.move {
    position: absolute;
    animation: move 5s linear infinite;
}
// dom
<div class="move">123123123</div>
// script
function fnc () {
    let i = 0
    const start = performance.now()
    while (performance.now() - start <= 5000) {
        i++
    }
    return i
}
setTimeout(() => {
    fnc()
}, 1000)

效果

如下图,动画运行 1s 的时候,js 函数开始运行,动画会先停止渲染,然后等 js 主执行栈空闲之后动画才继续进行。

函数改造

我们把原来的函数改造为 generator 函数

代码

// generator 处理原来的函数
function * fnc_ () {
    let i = 0
    const start = performance.now()
    while (performance.now() - start <= 5000) {
        yield i++
    }
    return i
}
// 简易时间分片
function timeSlice (fnc, cb = setTimeout) {
    if(fnc.constructor.name !== 'GeneratorFunction') return fnc()
    return async function (...args) {
        const fnc_ = fnc(...args)
        let data
        do {
            data = fnc_.next(await data?.value)
            // 每执行一步就休眠,注册一个宏任务 setTimeout 来叫醒他
            await new Promise( resolve => cb(resolve))
        } while (!data.done)
        return data.value
    }
}
setTimeout(async () => {
    const fnc = timeSlice(fnc_)
    const start = performance.now()
    console.log('开始')
    const num = await fnc()
    console.log('结束', `${(performance.now() - start)/ 1000}s`)
    console.log(num)
}, 1000)

效果

动画根本不受影响,fps 一直很稳定,因为我们把耗时任务拆成很多个块来执行。

优化时间分片

上面的时间分片函数每执行一步,就会休眠,然后通过一个宏任务来唤醒他,但是这样的执行效率肯定是比较低的,我们再优化一下执行的效率,提升连续执行时间。

代码

// 精准时间分片
function timeSlice_ (fnc, time = 25, cb = setTimeout) {
    if(fnc.constructor.name !== 'GeneratorFunction') return fnc()
    return function (...args) {
        const fnc_ = fnc(...args)
        let data
        return new Promise(async function go (resolve, reject) {
            try {
                const start = performance.now()
                do {
                    data = fnc_.next(await data?.value)
                } while (!data.done && performance.now() - start < time)
                if (data.done) return resolve(data.value)
                cb(() => go(resolve, reject))
            } catch(e) {
                reject(e)
            }
        })
    }
}
setTimeout(async () => {
    const fnc1 = timeSlice_(fnc_)
    let start = performance.now()
    console.log('开始')
    const num = await fnc1()
    console.log('结束', `${(performance.now() - start)/ 1000}s`)
    console.log(num)
}, 1000);

效果

我们把函数分成了较大的块,这样函数执行的效率就会变高,fps 会稍微收到影响,但是在接受范围内。

对比优化前后

我们对比一下优化时间分片函数前后的效果

代码

setTimeout(async () => {
    const fnc = timeSlice(fnc_)
    const fnc1 = timeSlice_(fnc_)
    let start = performance.now()
    console.log('开始')
    const a = await fnc()
    console.log('结束', `${(performance.now() - start)/ 1000}s`)
    console.log('开始')
    start = performance.now()
    const b = await fnc1()
    console.log('结束', `${(performance.now() - start)/ 1000}s`)
    console.log(a, b)
}, 1000);

效果

对比优化后的时间分片函数,是之前效率的 4452 倍,我们做的只是提升了函数连续执行时间。

最后

generator 函数中 yield 的位置非常关键,需要放到耗时的地方,优化后的时间分片函数也提供了 time 变量,你可以根据实际情况来改变你的 time 值。

以上就是JS时间分片技术解决长任务导致的页面卡顿的详细内容,更多关于js时间分片长任务分解的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解JS事件循环及宏任务微任务的原理

    目录 宏任务 微任务 事件循环 宏任务与微任务 微任务中创建宏任务 宏任务中创建微任务 宏任务中创建宏任务 微任务中创建微任务 总结 本质上来说,JavaScript是同步的.阻塞的.单线程语言,不管是在浏览器中还是nodejs环境下.浏览器在执行js代码和渲染DOM节点都是在同一个线程中,执行js代码就无法渲染DOM,渲染DOM的时候就无法执行js代码.如果按照这种同步方式执行,页面的渲染将会出现白屏甚至是报错,特别是遇到一些耗时比较长的网络请求或者js代码,因此在实际开发中一般是通过异步的方

  • 浅谈js中的宏任务和微任务

    目录 1.关于JavaScript 2.JavaScript事件循环 3.宏任务和微任务 4.拓展宏任务微任务 下面一道关于宏任务和微任务的题: setTimeout(function(){ console.log('1') }); new Promise(function(resolve){ console.log('2'); resolve(); }).then(function(){ console.log('3') }); console.log('4') 试问一下上面代码的执行顺序是啥

  • 在NodeJs中使用node-schedule增加定时器任务的方法

    java中直接使用定时器类就行了,但是在node中就没有这么简单了,只能使用setInterval或者setTimeout两个方法来实现,但是太繁琐了,搜索了之后发现node-schedule这个包,特意试用一下 版本 node版本12.16.2koa2版> 2.7.0 1. 安装 npm insatll node-schedule -S 2. 使用方法 2-1. 调用格式 // 任务名称可以用中文,也可以用英文,但必须唯一 schedule.scheduleJob(`任务名称`, `时间`,

  • JavaScript实现大文件分片上传处理

    很多时候我们在处理文件上传时,如视频文件,小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题: 1.文件过大,超出服务端的请求大小限制: 2.请求时间过长,请求超时: 3.传输中断,必须重新上传导致前功尽弃 这些问题很影响用户的体验感,所以下面介绍一种基于原生JavaScript进行文件分片处理上传的方案,具体实现过程如下: 1.通过dom获取文件对象,并且对文件进行MD5加密(文件内容+文件标题形式),采用SparkMD5进行文件加密: 2.进行分片设置,文件Fil

  • JS事件循环机制event loop宏任务微任务原理解析

    首先看一段代码 async function (){ await f2() console.log('f1') } async function f2(){ console.log('f2') } console.log('正常1') f1() setTimeout(()=>{ console.log('定时器') }) console.log('正常2') 正确的打印顺序应该是:正常1,f2 ,正常2,f1,定时器 为什么会出现这样打印顺序呢 首先javascript是一门单线程语言,在最新的

  • javascript之分片上传,断点续传的实际项目实现详解

    总所周知,上传大文件时由于各种原因突然中断,然后整个文件需要从头开始上传,这种情况非常令人抓狂??,那么怎样才可以避免这种尴尬的情况呢?分片上传或许可以搞定这个痛点 原理:文件上传就是把文件切割成一个个小小的块,然后块逐一上传 后台约定接口: 1.getRestChunkInfo 获取当前文件的上传情况 输入:{fileName, fileSize, md5, token} 输出:{chunk_list, chunk_size, chunk_count} chunk_list是一个数组,未上传块

  • JS时间分片技术解决长任务导致的页面卡顿

    目录 起因 处理办法 事件循环 浏览器渲染时机 原始代码 代码 效果 函数改造 代码 效果 优化时间分片 代码 效果 对比优化前后 代码 效果 最后 起因 同事遇到一个动画展示的问题,就是下面要执行一个运算量很大的函数,他要加载一个 loading,但他发现把 loading 的元素 display: block; 页面中也不会立刻出现 loading 动画,出现动画的时候是运算函数执行完毕之后. 处理办法 有两种方法去处理这种耗时任务,第一种就是 webWorker,但是一些 dom 的操作做

  • 使用ionic切换页面卡顿的解决方法

    使用ionic开发app的时候,会发现切换页面的动画会卡顿,并不流畅,为了保证用户体验,大部分人会使用禁用动画的方法$ionicConfigProvider.views.transition('no');,但并不是最好的解决思路,cordova提供了native transitions可以让页面切换近乎原型的体验.主要步骤如下: 1.npm install ionic-native-transitions --save 下载该文件,并放入www/lib文件夹下 2.在index.html中加入<

  • 解决angular 使用原生拖拽页面卡顿及表单控件输入延迟问题

    说明 之前有一个angular项目,页面上表单不算多,也就一百来个(这个不固定,有的地方多,有的地方少),但是再输入的时候会造成输入延迟,反应不灵敏,对用户体验极其不好.还有一个功能就是拖拽功能(原生,没有使用官方中的拖拽功能),从左边拖到右边区域,拖拽区域少的时候还挺流畅,但一旦有几百上千的时候反应极其的慢 原因(?) 上面两个问题其实都和angular的机制有关.一个双向绑定一个拖拽归根结底都是因为angular的变化检测 angular的双向绑定主要是脏数据检查,如果大量的检查,效率比较低

  • javascript引擎长时间独占线程造成卡顿的解决方案

    Javascript 引擎的单线程特性使得在处理一个较大的循环遍历时会长时间独占线程,导致其它事件(例如用户操作)无法及时响应,严重时造成卡顿甚至是假死现象.为解决上述问题,一种可行机制是将大的循环拆分成若干小的循环片段分片执行,使得Javascript 引擎有时机在各段之间插入执行其它事情,从而有效改善性能体验 Ansync.js 复制代码 代码如下: function Ansync (totalCount, segmentCount, workCallback, returnCallback

  • iOS中3DTouch预览导致TableView滑动卡顿问题解决的方法

    1.发现问题 今天一早来公司,一个同事举着他的6p对我们说:"你看看这是嘛啊...怎么划不动啊..."我一看,果然,滑两下TableView,大概加载2页多就卡飞了...顿时想以是他机子太老了,物理内存不够用balabala等等原因回怼时...人家后面又说了一句:"你看人家今日头条怎么滑都没事~". 好吧,我看看好吧. 虽然是在iPhone X上录的,但上下滑动卡顿依旧非常明显 2.排除问题 没错,我和你想的一样,十有八九应该是那几个老问题导致的: Cell高度计算

  • element table 数据量大页面卡顿的解决

    目录 element table数据量大页面卡顿 调用接口 table组件 data里使用到的数据 el-table大数据量渲染卡顿的一种思路 思路 滚动监听 slider element table数据量大页面卡顿 table显示医院列表,这里后台未做分页,总共数据大约8000条. 一次性全部赋值给table整个页面都会卡顿好几秒. 查看了请求接口到数据返回的时间为192ms,可以接受. 应该是页面渲染的问题. 这边就在前端做了分页处理. 调用接口 // 获取医院列表 getHospitalL

  • 解决vue 界面在苹果手机上滑动点击事件等卡顿问题

    用vue编写项目接近尾声,需要集成到移动端中,在webstorm上界面,运行效果都很完美,但是在苹果手机上各种问题都出现了,原生项目一向滑动流畅,事件响应迅速,可是苹果手机打开这个项目有两个问题, (1).滑动页面卡顿, (2).点击事件响应缓慢,百度才发现在苹果手机上有300ms的延迟. 一.滑动页面卡顿 //页面布局 <template> <div class='content'> 页面内容 </div> </template> 在对应的组件的最外层di

  • ubuntu端向日葵键盘输入卡顿问题及解决

    目录 1.问题现象 2.解决思路 3.操作过程 附录:ubuntu输入法框架 1.IBus 2.XIM 3.fcitx 总结 1.问题现象 最近,利用向日葵远程操控ubuntu电脑时,出现键盘输入延迟的情况. 具体表现为:鼠标操作正常.网页打开正常.网速正常,但是键盘输入会产生长时间延迟和丢数据的情况. 2.解决思路 网上多篇文章反应ibus输入法系统会产生卡顿. 将其切换成同一输入法系统(iBus)下的英文键盘(不是中文的shift切换英文输入),卡顿会消失. iBus的智能拼音输入法导致的输

  • VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法分析

    本文实例讲述了VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法.分享给大家供大家参考,具体如下: 业务场景 我们在使用vue 编写 代码时,我们有一个 多行文本框控件,希望在页面点击一个按钮 在 文本框焦点位置插入一个 {pk}的数据. 发现插入 这个数据后,这个数据并没有同步到 数据中,但是直接通过键盘输入,就可以改变数据. 原因分析 在通过 JS 修改控件的value 数据后,并没有触发到数据更新. 解决办法 Vue.component('rx-textarea', {

  • Node.js配合node-http-proxy解决本地开发ajax跨域问题

    情景: 前后端分离,本地前端开发调用接口会有跨域问题,一般有以下3种解决方法: 1. 后端接口打包到本地运行(缺点:每次后端更新都要去测试服下一个更新包,还要在本地搭建java运行环境,麻烦) 2. CORS跨域:后端接口在返回的时候,在header中加入'Access-Control-Allow-origin':* 之类的(有的时候后端不方便这样处理,前端就蛋疼了) 3. 用nodejs搭建本地http服务器,并且判断访问接口URL时进行转发,完美解决本地开发时候的跨域问题.  用到的技术:

随机推荐