今天在看表严肃老师的视频的时候,讲到了setTimeout的操作,由此引发了对js的异步操作的思考。
javascript一直是一个单线程的语言,无法同一时间执行多段代码。其能实现异步操作完全得益于浏览器的多线程。
当一段代码执行的时候,其余的剩余的任务就会形成一个队列,排队等待。
一旦当前任务执行完毕后,就会从队列中获取下一个任务,因此这种执行方式也被叫做”阻塞式“。
例如,一次鼠标点击事件,ajax执行完毕的回调函数,setTimeout的回调函数。这些事件处理程序或者回调函数并不会马上执行,而是马上排队到执行队列的尾部。一旦线程(单个)有空余的时候,就会马上去执行。
假如:当前队列中正在执行一个非常耗时的操作,那么此时当用户点击鼠标事件的时候,这个事件并不会马上被执行,而是会马上被放到队列的尾部,当当前的耗时操作执行完毕后,执行到它的时候才会被执行到。
再假如:当前代码中有一个setTimeout,那么浏览器会在其设定的计时后将它插入到事件执行队列的尾部,并不能保证它能被执行到,所以,setTimeout的计时参数只是告诉浏览器什么时候把它的回调函数放在队列的尾部,而不是告诉浏览器什么时候去执行这个回调函数。
这样就比较清楚了,就能很好的理解setTimeout函数为什么不是异步的了。看似异步的执行,归根结底还是需要等到当前队列中的同步代码执行完毕,执行到它的回调函数的时候,才正常。
来看这一段代码:
var start = new Date();
setTimeout(function(){
var end = new Date();
console.log("Time elapsed: ", end - start, "ms");
}, 300);
while (new Date - start <= 1000)
{
}
此时输出语句会在1000ms后执行,不是300ms后,也不是1300ms后,而是1000ms后,原理如下:
当执行到这块代码的时候,浏览器看到了setTimeout,并且看到了300,就会理解到,我会在300ms后把你插入到队列的尾部。这时候就运行到了while循环,在这里会停留1000ms,当停留300ms的时候,这时候浏览器就把setTimeout的回调函数插入到了执行队列的尾部,这时候还需要在这里等待700ms,当700ms停留完之后,此时在这里总共停留了1000ms,这时候队列后续就执行到了setTimeout的回调函数了,这个时候就在1000ms左右执行到了setTimeout中的回调函数。
再来看一段代码:
function a()
{
setTimeout(function(){console.log(1);},0);
console.log(2);
}
a();
代码的输出结果是2,1。
执行过程大致如下:
浏览器执行到这段代码的时候,执行了一个函数a,在执行函数的时候,先执行到setTimeout,观察到计时0,浏览器会认为,我需要马上把你放到当前的执行队列的最后面,因为当前执行队列里面已经存在了console.log(2);了,所以,会先输出2,然后立马执行回调函数,并输出1。
看到这里,应该能明白setTimeout的执行过程了。
来看下面两种情况吧:
<input type="text" onkeydown="show(this.value)">
<div></div>
<script type="text/javascript">
function show(val) {
document.getElementsByTagName('div')[0].innerHTML = val;
}
</script>
上面这段代码执行的时候,并不会如我们所愿,会把当前输入框的最新的值回显到div中,而是回显的旧的值。
<input type="text" onkeydown="var self=this; setTimeout(function() {show(self.value)}, 0)">
<div></div>
<script type="text/javascript">
function show(val) {
document.getElementsByTagName('div')[0].innerHTML = val;
}
</script>
上面这段代码执行的时候,会达到我们的预期效果,将我们最新输入的值回显到div中。
原因如下:
上面的keydown的事件执行的时候涉及到了两个任务:
1:将键盘输入的值显示到输入框中 (系统的)
2:将输入框中的值回显到div中(我们自定义的)
非常显然。必需要先让浏览器将字符回写到文本框。然后我们才干获取其内容写到div中。改变顺序,这这正是setTimeout(0)的作用。
网友评论