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);
}

 

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

 

转载于:https://www.cnblogs.com/leegent/p/5326832.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值