在上一篇的文章中介绍了js异步原理,有的小伙伴可能还有些云里雾里,在本篇文章中将以新手很容易用错的setTimeout为例来解释js的异步原理。
首先我们看一个例子
<script>
console.log(1);
setTimeout(function(){
console.log(2);
},500);
console.log(3);
// 运行结果
// 1
// 3
// 2
</script>
这里通过setTimeout延迟500毫秒执行,所以结果是1,3,2。这看起来明明就是异步操作啊,为什么说是同步呢?我们接着看下一个例子。
<script>
var date = new Date();
console.log('first time: ' + date.getTime());
setTimeout(function(){
var date1 = new Date();
console.log('second time: ' + date1.getTime() );
console.log( date1.getTime() - date.getTime() );
},1000);
for(var i=0; i < 10000 ; i++){
console.log(1);
}
// 运行结果
// first time: 1524540272462
// (10000)1
// second time: 1524540274346
// 1884
</script>
神奇的事情出现了,假设js是异步,那么结果应该是这样的:
// first time: 1524540272462
//(x)1
// second time: 1524540273462
// 1000
//(10000-x)1
x为一秒内打印的1的数量。
但是实际结果settimeout并非是1000毫秒后执行的,而是1884毫秒。其原因是js是单线程,在打印完first time: 1524540272462后settimeout内的操作进入了“任务队列”,而1000毫秒到了以后,因为当前js正在执行打印1的操作,故js会在1打印完之后,才将settimeout内操作压进执行栈里。所以实际上我们看到的结果1884毫秒的原因就在于次。
这个例子说明js仍旧是单线程而非多线程,在同一时间内js只能做一件事情。
下面我们通过下面的简单例子也能够看出js是单线程的。
<script>
console.log('1');
setTimeout(function(){
console.log('2');
},10);
while(true){};
// 运行结果
// 1
</script>
浏览器打印出1后就卡死了,并没有打印出2来。因为执行栈必须把当前任务完成,任务队列里的操作才能进入执行栈。而执行栈内的操作是死循环,所以浏览器卡死,2也不会被打印出来。
下面来看一种特殊情况,当setTimeout的时间为0的时候是不是立即执行的。
console.log(1);
setTimeout(function(){
console.log(2);
},0);
console.log(3);
// 运行结果
// 1
// 3
// 2
结果证明即便延迟设为0,也不是立即执行的。因为 setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动。
setTimeout的最小时间间隔和浏览器及操作系统有关。在John Resig的《Javascript忍者的秘密》一书中提到--Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒),另外,MDC中关于setTimeout的介绍中也提到,Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒。