本文介绍javascript的异步和单线程的关系,以及简单解释了单线程的js怎么异步处理。

异步计时函数setTimeout()

1
2
3
4
5
6
var begin = new Date();//代码开始
setTimeout(function(){
var end = new Date();
alert('你好,这个程序已经运行了' + (end - begin) + '毫秒');
},3000);//1秒后弹出对话框
while ((new Date() - begin) < 6000) {}//循环代码持续3秒,3秒后,整个js调用栈的程序才执行完毕(在控制台中可以看到3秒后才出现一个响应undefined)

上面的代码跟下面的对比一下:

1
2
3
4
5
6
var begin = new Date();//代码开始
setTimeout(function(){
var end = new Date();
alert('你好,这个程序已经运行了' + (end - begin) + '毫秒');
},6000);//6秒后弹出对话框
while ((new Date() - begin) < 3000) {}//循环代码持续3秒,3秒后,js调用栈执行完毕,控制台出现undefined,然后再过6-3=3秒后,出现弹框

解析:
当js解析器解析到setTimeout时,就调用了浏览器的api——异步函数setTimeout,此时js继续往下解析执行,而浏览器就另有一个线程来处理异步函数setTimeout,也就是1秒后把回调函数放入js执行队列中。而此时,js调用栈中继续执行剩下的代码,直至完成了后面的循环后(调用栈中所有代码已经执行完毕)才解析队列中的回调函数来执行。


异步IO函数

这里我们介绍下ajax,为了方便起见,直接使用jquery来说明

1
2
3
4
$.get('http://blog.zjien.com', function(resData){
alert(resData);
} );
while(true){}

js在解析并执行到$.get()时,发起http请求,我看打开F12可以看到请求是处于Pending状态,通过WireShark或Fiddler2可以查看到HTTP请求状态,可知请求是成功发送并且有响应,但是因为下面的while是死循环,因此$.get()里的回调函数一直在队列中排队,得不到js的解析和执行,呈现出假死的状态。
换一种形式来展示

1
2
3
4
$.get('http://blog.zjien.com',function(resData){
alert(resData);
});
alert('go on?');

js解析并执行到go on时,浏览器弹出窗口,暂时不要关闭窗口,通过F12可以看到,请求是Pending状态,但通过Wireshark等工具看到请求是完成并得到响应的,只是因为go on阻塞/阻止了时间循环,导致$.get的回调无法执行,只有按下确定关闭了go on框,回调才得以执行。这是在chrome的情况如此。
在Firefox下却不会出现此情况,go on并不能阻止事件循环,所以上面两个代码段的ajax($.get)的回调都可以执行,于是界面出现3个alert弹框。

注意,每个窗口有一个js线程(单线程),若一个窗口中有多个tag,那么这多个tag都是用同一个js线程。