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); }
这样就能看到竖条一根根从左到右排好啦。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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)