众所周知,JavaScript的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着。在事件队列中加一个延时,这样的问题便可以得到缓解。A:嘿,哥们儿,快点!B:我要三分钟,你先等着,完了叫你~A:好的,记得叫我啊~你(C)也等着吧,完了叫你~C:嗯!...告诉后面排队的人一个准确的时间,这样后面的人就可以利用这段时间去干点别的事情,而不是所有的人都排在队列后抱怨。我写了一段程序来解决这个问题:/***@authorBarretLee*@emailbarret.china@gmail.com*@description事件队列管理,含延时*/varQ={//保存队列信息a:[],//添加到队列queueq:function(d){//添加到队列如果不是函数或者数字则不处理if(!/function|number/.test(typeofd))return;Q.a.push(d);//返回对自身的引用returnQ;},//执行队列dequeued:function(){vars=Q.a.shift();//如果已经到了队列尽头则返回if(!s)return;//如果是函数,直接执行,然后继续dequeueif(typeofs===function){s(),Q.d();return;}//如果是数字,该数字作为延迟时间,延迟dequeuesetTimeout(function(){Q.d();},s);}};这段程序加了很多注释,相信有JS基础的童鞋都能够看懂,利用上面这段代码测试下://进程记录函数functionrecord(s){vardiv=document.createElement(div);div.innerHTML=s;console.log(s);document.body.appendChild(div);}Q.q(function(){record(0istyle='color:blue'3s之后搞定,0把1叫进来/i);}).q(3000)//延时3s.q(function(){record(1istyle='color:blue'2s之后搞定,1把2叫进来/i);}).q(2000)//延时2s.q(function(){record(2spanstyle='color:red'后面没人了,OK,厕所关门~/span);}).d();//执行队列可以戳戳这个DEMO。也可以直接运行这段程序:/***@authorBarretLee*@emailbarret.china@gmail.com*@description事件队列管理,含延时*/varQ={//保存队列信息a:[],//添加到队列queueq:function(d){//添加到队列如果不是函数或者数字则不处理if(!/function|number/.test(typeofd))return;(d);//返回对自身的引用returnQ;},//执行队列dequeued:function(){vars=Q.a.shift();//如果已经到了队列尽头则返回if(!s)return;//如果是函数,直接执行,然后继续dequeueif(typeofs===function){s(),Q.d();return;}//如果是数字,该数字作为延迟时间,延迟dequeuesetTimeout(function(){Q.d();},s);}};functionrecord(s){vardiv=document.createElement(div);div.innerHTML=s;console.log(s);document.body.appendChild(div);}Q.q(function(){record(0istyle='color:blue'3s之后搞定,0把1叫进来/i);}).q(3000).q(function(){record(1istyle='color:blue'2s之后搞定,1把2叫进来/i);}).q(2000).q(function(){record(2spanstyle='color:red'后面没人了,OK,厕所关门~/span);}).d();运行代码一、Javascript异步编程原理显然,上面这种方式和银行取号等待有些类似,只不过银行取号我们并不知道上一个人需要多久才会完成。这是一种非阻塞的方式处理问题。下面来探讨下JavaScript中的异步编程原理。1.setTimeout函数的弊端延时处理当然少不了setTimeout这个神器,很多人对setTimeout函数的理解就是:延时为n的话,函数会在n毫秒之后执行。事实上并非如此,这里存在三个问题,一个是setTimeout函数的及时性问题,可以测试下面这串代码:vard=newDate,count=0,f,timer;timer=setInterval(f=function(){if(newDate-d1000)clearInterval(timer),console.log(count);count++;},0);可以看出1s中运行的次数大概在200次左右,有人会说那是因为newDate和函数作用域的转换消耗了时间,其实不然,你可以再试试这段代码:vard=newDate,count=0;while(true){if(newDate-d1000){console.log(count);break;}count++;}我这里显示的是351813,也就是说count累加了35W+次,这说明了什么呢?setInterval和setTimeout函数运转的最短周期是5ms左右,这个数值在HTML规范中也是有提到的:5.Lettimeoutbethesecondmethodargument,orzeroiftheargumentwasomitted.如果timeout参数没有写,默认为07.Ifnestinglevelisgreaterthan5,andtimeoutislessthan4,thenincreasetimeoutto4.如果嵌套的层次大于5,并且timeout设置的数值小于4则直接取4.为了让函数可以更快速的相应,部分浏览器提供了更加高级的接口(当timeout为0的时候,可以使用下面的方式替代,速度更快):requestAnimationFrame它允许JavaScript以60+帧/s的速度处理动画,他的运行时间间隔比setTimeout是要短很多的。@司徒正美,他适合动画,使用他可以在tab失去焦点或者最小化的时候减缓运动,从而节省CPU资源,他的运行间隔确实比setTimeout要长。process.nextTick这个是NodeJS中的一个函数,利用他可以几乎达到上面看到的while循环的效率ajax或者插入节点的readState变化MutationObserver大约2-3mssetImmediatepostMessage这个相当快...这些东西下次有空再细谈。之前研究司徒正美的avalon源码的时候,看到了相关的内容,有兴趣的可以看看://视浏览器情况采用最快的异步回调varBrowserMutationObserver=window.MutationObserver||window.WebKitMutationObserverif(BrowserMutationObserver){//chrome18+,safari6+,firefox14+,ie11+,opera15avalon.nextTick=function(callback){//2-3msvarinput=DOC.createElement(input)varobserver=newBrowserMutationObserver(function(mutations){mutations.forEach(function(){callback()})})observer.observe(input,{attributes:true})input.setAttribute(value,Math.random())}}elseif(window.VBArray){//IE下这个通常只要1ms,而且没有副作用,不会发现请求,//setImmediate如果只执行一次,与setTimeout一样要140ms上下avalon.nextTick=function(callback){varnode=DOC.createElement(script)node.onreadystatechange=function(){callback()//在interactive阶段就触发node.onreadystatechange=nullroot.removeChild(node)node=null}root.appendChild(node)}}else{avalon.nextTick=function(callback){setTimeout(callback,0)}}上面说了一堆,目的是想说明,setTimeout是存在一定时间间隔的,并不是设定n毫秒执行,他就是n毫秒执行,可能会有一点时间的延迟(2ms左右)。然后说说他的第二个缺点,先看代码:vard=newDate;setTimeout(function(){console.log(showmeafter1s,butyoukonw:+(newDate-d));},1000);while(1)if(newDate-d2000)break;我们期望console在1s之后出结果,可事实上他却是在2075ms之后运行的,这就是JavaScript单线程给我们带来的烦恼,while循环阻塞了setTimeout函数的执行。接着是他的第三个毛病,try..catch捕捉不到他的错误:try{setTimeout(function(){thrownewError(我不希望这个错误出现!)},1000);}catch(e){console.log(e.message);}可以说setTimeout是异步编程不可缺少的角色,但是它本身就存在这么多的问题,这就要求我们用更加恰当的方式去规避!2.什么样的函数为异步的异步的概念和非阻塞是是息息相关的,我们通过ajax请求数据的时候,一般采用的是异步的方式:varxhr=newXMLHttpRequest();xhr.open('GET','/',true);xhr.send();xhr.onreadystatechange=function(){console.log(xhr.status);}在xhr.open中我们把第三个参数设置为true,也就是异步加载,当state发生改变的时候,xhr立即响应,触发相关的函数。有人想过用这样的方式来处理:while(1){if(xhr.status===complete){//dosomething();break;}}而事实上,这里的判断已经陷入了死循环,即便是xhr的status已经发生了改变,这个死循环也跳不出来,那么这里的异步是基于事件的。某个函数会导致将来再运行的另一个函数,后者取自于事件队列(若后面这个函数是作为参数传递给前者的,则称其为回调函数,简称为回调)。——摘自《AsyncJavascript》由于JavaScript的单线程特点,他没有提供一种机制以阻止函数在其异步操作结束之前返回,事实上,除非函数返回,否则不会触发任何异步事件。3.常见的异步模型1)最常见的一种方式是,高阶函数(泛函数)step1(function(res1){step2(function(res2){step3(f