JS异步阻塞的迷思

还是百度前端技术学院的“任务十九”可视化排序算法的题,在写出快速排序算法之后,要求用动画的形式把这个排序过程呈现出来。排序过程在CPU里不过是瞬间的事,但要转换成“缓慢的”动画效果给人类看,就不得不把这个过程速度降下来。

首先想到的是,Javascript有没有像C++、Java那样提供Sleep函数?

答案是:没有。因为Javascript是单线程语言,一旦Sleep,整个程序就阻塞住了,浏览器也将失去响应交互的能力,就像死了一样。因此,通过写个空循环来占用CPU时间以间接实现Sleep的方法,同样不可取。

此路不通,尝试别的思路。记得JS里有个常用的定时函数setTimeout,可以把指定的函数延时执行。于是我修改了排序函数代码,其中最后递归部分如下:

复制代码
function visualSort(array,low,high,barArray) {
    //排序
    ****
    // 递归对左右子序列排序
    var fn=arguments.callee;
    var that=this;
    setTimeout(function(){
        fn.call(that,array,low,i-1,barArray);
    },500);
    setTimeout(function(){
        fn.call(that,array,i+1,high,barArray);
    },500);
}
复制代码

这样屏幕上看到的竖条确实可以从低到高排好序,但还是有问题:动画的“帧数”也太少了吧?好多地方还没有被改成“正在排序”的颜色,就直接变成有序的了。

原因也不难理解,参考这篇文章:《关于setTimeout,理解JavaScript定时机制》 ,两个递归函数是被紧挨着放进JS引擎的任务队列的,前一个函数刚返回,就紧接着执行后一个函数,人的肉眼根本来不及看到GUI渲染的变化。

若是把两个递归函数的延时设成不同值,比如一个500一个800,倒是可以解决这个问题,但我们看到的排序执行顺序将会混乱:一会儿在执行左半边的递归,一会儿又跳到右半边执行一下。而且“每一帧”之间的间隔时间也不一定。这也不是我们想要的结果。

还有没有别的办法,可以精确地控制执行顺序和时间间隔?答案是:有!受这篇文章(《jQuery链式操作》)的启发,思路豁然开朗:只需用一个队列将待执行的函数一个个入队,定时出队并执行出队的函数就可以了。代码如下:

复制代码
function visualSort(array,low,high,barArray){
    //排序
    ****
    //递归对左右子序列排序
    var fn=arguments.callee;
    var that=this;
    if(i > low) actionList.push(function(){
        fn.call(that,array,low,i-1,barArray);
    });
    if(i < high) actionList.push(function(){
        fn.call(that,array,i+1,high,barArray);
    });
}
复制代码

然后在doAction函数里调用出队的函数、以及设置下次调用的时间间隔:

复制代码
function doAction(){
    if(actionList.length==0){
        isReady=false;
        //还原所有染色区域
        **
    }
    if(!isReady) return;
     (actionList.shift())(); // 取出第一个函数并执行  
    // 延时执行下一个函数
    setTimeout(arguments.callee,interval);
}
复制代码

开始时,将起始的visualSort函数入队,再调用一下doAction函数。嗯!这种方法通过回调函数的形式有序调用递归函数,效果是基本令人满意的。

按上述代码执行的快排递归过程,本质上是一个广度优先遍历二叉树的过程。要改成深度优先也很简单,加一个栈即可:

复制代码
// 排序函数的最后部分
function visualSort(array,low,high,barArray) {
    // 排序
    **
    // 对左子序列排序的递归函数入队,对右子序列排序的入栈
    var fn=arguments.callee;
    var that=this;
    if(i > low) leftList.push(function(){
        fn.call(that,array,low,i-1,barArray);
    });
    if(i < high) rightList.push(function(){
        fn.call(that,array,i+1,high,barArray);
    });
}

// 实现异步阻塞的函数
function doAction(){
    if(leftList.length + rightList.length==0){
        isReady=false;
        //还原所有染色区域
        **
    }
    if(!isReady) return;
    if(leftList.length>0){
        (leftList.shift())(); // 出队并执行    
    }
    else if(rightList.length>0){
        (rightList.pop())(); // 弹栈并执行
    }
    // 延时执行下一个函数
    setTimeout(arguments.callee,interval);
}
复制代码

 

这样就能看到竖条一根根从左到右排好啦。

 

posted on   大唐西域都护  阅读(1560)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示