xinglie / xinglie.github.io

blog
https://xinglie.github.io
153 stars 22 forks source link

magix5中的task与lowTask #107

Open xinglie opened 1 year ago

xinglie commented 1 year ago

任何代码的执行都是需要时间的

在浏览器环境下,DOM的操作尤其费时间,所以才会有各种各样的库和框架以求最小化的变更来操作DOM提升效率。

但是在条件一定的情况下,就是要操作许多费时的DOM我们该如何避免浏览器卡死或卡顿呢?

假如页面上有10000div,我们需要遍历它们并旋转,再把外接矩形的信息放在div内部,我们可能这样写

let divs = document.querySelectAll('div');
let deg = 0;
for(let div of divs){
    div.style.transform = `rotate(${deg++%360}deg)`;
    let bound = div.getBoundingClientRect();
    div.innerHTML = `x:${bound.x},y:${bound.y},width:${bound.width},height:${bound.height}`;
}

我们进行性能放大来看:假如每个div旋转并获取外接矩形所需要的时间是1ms,那么完成整个操作就需要10000ms,在这个时间内,浏览器是卡住的,这段时间内用户无法操作浏览器。

我们该如何即快速完成DOM操作又不卡浏览器呢?

其实在单线程的DOM操作上,我们必须去做转换才能解决问题,否则是无解的。

我们需要做的第一件事就是把操作进行方法封装,比如这样

let process = (div, deg) => {
    div.style.transform = `rotate(${deg%360}deg)`;
    let bound = div.getBoundingClientRect();
    div.innerHTML = `x:${bound.x},y:${bound.y},width:${bound.width},height:${bound.height}`;
};

let divs = document.querySelectAll('div');
let deg = 0;
for(let div of divs){
    process(div, deg++);
}

这样转换之后依然会有10000ms的卡住时间。那么接下来就需要使用magix5提供的task方法了,如下

import Magix from 'magix5';
let {task} = Magix;
let process = (div, deg) => {
    div.style.transform = `rotate(${deg%360}deg)`;
    let bound = div.getBoundingClientRect();
    div.innerHTML = `x:${bound.x},y:${bound.y},width:${bound.width},height:${bound.height}`;
};

let divs = document.querySelectAll('div');
let deg = 0;
for(let div of divs){
    task(process, div, deg++);
}

我们只需要把原来的process(div, deg++);换成task(process, div, deg++);magix5内部会自动根据浏览器性能,最大化的去异步执行process方法,不卡浏览器。

magix5中提供2种优先级的任务,正常优先级的task及低优先级的lowTask

import Magix from 'magix5';
let {task,lowTask} = Magix;
task(()=>{
   console.log('task 1 complete');
});
lowTask(()=>{
   console.log('low task complete');
});
task(()=>{
    console.log('task 2 complete');
});

以上代码会输出

// task 1 complete
// task 2 complete
// low task complete

当同时存在tasklowTask任务时,lowTask始终在最后被执行,您可以把一些低优先级且耗时的任务放在lowTask里。

那么如何知道tasklowTask执行完了呢?

我们可以直接使用taskFinalelowTaskFinale这2个方法,如下

import Magix from 'magix5';
let {task,taskFinale} = Magix;
let process = (div, deg) => {
    div.style.transform = `rotate(${deg%360}deg)`;
    let bound = div.getBoundingClientRect();
    div.innerHTML = `x:${bound.x},y:${bound.y},width:${bound.width},height:${bound.height}`;
};

let divs = document.querySelectAll('div');
let deg = 0;
for(let div of divs){
    task(process, div, deg++);
}
console.log('all div push to task');//这里仅仅是把任务安排上,尚未执行完所有的task
await taskFinale();
console.log('all task finished');//所有task执行完成

lowTaskFinaletaskFinale相同,只不过它是确保所有低优先级的任务完成。

如果要确保所有任务完成,可使用lowTaskFinale,如

import Magix from 'magix5';
let {task,lowTask,lowTaskFinale} = Magix;
task(()=>{
   console.log('task 1 complete');
});
lowTask(()=>{
   console.log('low task complete');
});
task(()=>{
    console.log('task 2 complete');
});
await lowTaskFinale();
console.log('all task finished');

扩展

除了上述的task, lowTask, taskFinale, lowTaskFinale4个核心的方法外,还有一个扩展方法taskIdle,在任务队列空闲至少多少毫秒后执行,如

import Magix from 'magix5';
let {task,lowTask,taskIdle} = Magix;
task(()=>{
   console.log('task 1 complete');
});
lowTask(()=>{
   console.log('low task complete');
});
task(()=>{
    console.log('task 2 complete');
});
await taskIdle(500);
console.log('browser idle');

在给定时间内,没有要执行的task才会执行后面的代码,否则只要有任务进来,taskIdle就会继续挂起。

需要注意的是这些方法是全局共用的,即一个view中调用task,也会影响其它viewtask调用的执行和回调时间,不过这些只是先后顺序而已,不会影响最终结果。