原来你是这样的setTimeout

先上代码

1 console.log("start");
2 setTimeout(function(){
3     console.log("Hello");
4 },200);
5 setTimeout(function(){
6     console.log("World");
7 },100);
8 console.log("end");

start
end
undefined
World
Hello

如果看出了结果,那么我们修改一下代码

console.log("start");
setTimeout(function(){
    console.log("Hello");
},200);
setTimeout(function(){
    console.log("World");
},300);
for(var i = 0;i <= 1000; i++){
    console.log(i);
}
setTimeout(function(){
    console.log("I am here!");
},100);
console.log("end");

start

end

undefined
I am here!
Hello
World

是的,在我们看来这是正确的,没有错误的。

那我们再稍微修改一下我们的代码

console.log("start");
setTimeout(function(){
    console.log("Hello");
},200);
setTimeout(function(){
    console.log("World");
},300);
for(var i = 0;i <= 10000; i++){
    console.log(i);
}
setTimeout(function(){
    console.log("I am here!");
},100);
console.log("end");

start

end
undefined
Hello
World
I am here!

是不是很神奇。首先得从JS引擎说起。

JS引擎在内存中会分配堆区和栈区。

我们来看一段代码

1 function A(){
2     var a = 4;
3     b(a);
4 }
5 function B(num){
6     var newNum = num * num;
7     console.log(newNum);
8 }
9 A();

步骤:运行A()方法,将A()入栈。A上下文中存在变量a = 4

   调用B()方法,将B()入栈。B上下文中存在变量num = 4,newNum = 16

   调用B()方法中的console.log(),入栈,打印出16.

      继续运行,将console.log()、B()方法,出栈,A()方法运行完毕出栈,代码运行完毕,清空栈。

众所周知,JS引擎是单线程的,在某一个特定时间只能执行一个任务,并阻塞其他任务的执行,也就是说这些任务是串行的。用户不得不等待一个耗时的操作完成之后才能继续后面的操作,实际开发中我们可以使用异步代码来解决问题。

EventLoop事件循环

当异步方法比如这里的setTimeout(),或者Ajax请求、DOM事件执行的时候,会交由浏览器内核的其他模块去管理。当异步的方法满足触发条件之后,该模块就将方法推入到一个任务队列(task queue)中,当主线程代码执行完毕处于空闲状态的时候,就会检查任务队列,将队列中的第一个任务入栈执行,完毕后继续检查任务队列,如此循环。

前提条件:主线程处于空闲状态,这就是事件循环的模型。

我们来看一下第一个列子:

首先console.log()入栈,打印完毕之后出栈,紧接着执行到setTImeout()计时器,此时JS引擎会将定时器交给浏览器的另一个模块去管理,在这里我们称Timer()模块,紧接着第二个计时器也交给Timer模块。

然后执行到第二个console.log(),执行完毕后清空执行栈。

但是并没有结束,在主线程执行的同时,Timer()模块会检查其中的代码,一旦满足触发条件,就会将它添加到任务队列中,TImer2延迟100秒,所以在于Timer1被添加到队列开头。而主线程此时处于空闲状态,所以会检查任务队列是否有待执行的任务。

此时会将队列中的Timer2()执行,控制台打印,然后执行栈清空,继续检查任务队列,将Timer1()入栈并执行,控制台打印,清空执行栈。此时任务队列为空,清空执行栈。

然后我们来看第二段和第三段代码,就比较好玩了

和第一段不同的是,在最后一个定时器前加了一个for循环,我们模拟了一个1000和10000的for循环。

第三段比较奇怪。Timer3仅仅延迟了100毫秒,反而在两个Timer()之后执行了。

想想看,为什么?

原因很简单,因为在Timer1和Timer2加入到执行队列中后,主线程仍然执行着for循环中的代码,处于阻塞状态。队列中的Timer1和Timer2并不会得以执行。

当for循环结束,这时才将Timer3交由Timer模块去管理,继续执行后续代码打印"end",清空执行栈。虽然Timer3的延迟时间很短,但是加入任务队列后还是会排在Timer1和Timer2的后面,所以此时会按顺序执行任务队列中的代码。同时需要注意的是,这种情况下的三个定时器延迟执行的时间已经远远超过了指定的时间。

总结:我们发现不论事件循环模型还是setTimeout机制,其实不是难点,但却是容易忽略的点。很多问题的产生是因为忽略了一些简单的原理导致的。

时间: 2024-08-13 21:04:17

原来你是这样的setTimeout的相关文章

setTimeout的异步传输机制

setTimeout是异步的,在设置完setTimeout后,指定代码会在设定的时间后加入到任务队列,但并不是立即执行,js是单线程语言,所有的代码按顺序执行,即同步执行,同步执行的代码放在执行队列中,而异步执行的setTimeout放在任务队列中,执行顺序是先执行完执行队列中的代码再去查看任务队列中是否有要执行的代码: 这段代码看上去好像4应该比5先打印出来,但实际上是先打印出5再打印出4: 就如上面所说的,1,3,5都放在执行队列中,而4,2放在任务队列中,所以4,2要等执行队列中的1,3,

setTimeout ,setInterval

1.setInterval是间隔执行,间隔多久执行一次,执行多次 setInvertal(function(){},500) 2.setTimeout是延迟执行,执行一次 setTimeout(function(){},500) 清除定时器 param=setInterval(function(){},300) clearInterval(param)关闭定时器

JS setTimeout clearTimeout

<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>计时器</title> </head> <script type="text/javascript"> var num=0; var i; fu

Javascript引擎单线程机制及setTimeout执行原理说明

setTimeout用法在实际项目中还是会时常遇到.比如浏览器会聪明的等到一个函数堆栈结束后才改变DOM,如果再这个函数堆栈中把页面背景先从白色设为红色,再设回白色,那么浏览器会认为DOM没有发生任何改变而忽略这两句话,因此我们可以通过setTimeout把“设回白色”函数加入下一个堆栈,那么就可以确保背景颜色发生过改变了(虽然速度很快可能无法被察觉). 总之,setTimeout增加了Javascript函数调用的灵活性,为函数执行顺序的调度提供极大便利. 然后,我们从基础的层面来看看:理解J

setTimeout与setInterval

setTimeout(表达式,时间)在执行时,是在载入后的延迟指定时间去执行一次表达式,计数一次. setInterval(表达式,时间)在载入后,每隔指定的时间就执行一次表达式. 总的来说,setTimeout单次调用,setInterval多次调用. 嵌套setTimeout方法:将setTimeout包含于被执行函数中,然后在函数外再次使用setTimeout来达到定时执行的目的,这样就能形成反复定时的效果. 使用setInterval需要手动停止触发,而使用嵌套setTimeout方法不

setTimeout可以传第三个甚至更多个参数

以前在使用setTimeout()方法的时候,都是传两个参数第一个参数是一个函数,第二个参数是毫秒数,表示异步处理过多少毫秒执行第一个函数参数.后来有看到有人给setTimeout()传第三个参数,不清楚传第三个参数是干嘛的,于是就学习了一下.第三个或者更多参数都是第一个函数的参数,详情请看https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout. 一个简单的例子如下: functi

js延时函数setTimeout

实现一个延时执行的效果,现记录如下: <html> <head> <script type="text/javascript" src="/jquery/jquery.js"></script> <script type="text/javascript"> function alertV(){ alert("000"); } setTimeout(alertV,10

循环/闭包/setTimeout/Promise 综合

控制台显示内容为? for (var i = 0; i < 5; i++) { console.log(i); } 控制台显示内容为? for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000 * i); } 控制台显示内容为? for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i

setTimeout()和setInterval()的用法

JS里设定延时: 使用SetInterval和设定延时函数setTimeout 很类似.setTimeout 运用在延迟一段时间,再进行某项操作. setTimeout("function",time) 设置一个超时对象 setInterval("function",time) 设置一个超时对象 SetInterval为自动重复,setTimeout不会重复. clearTimeout(对象) 清除已设置的setTimeout对象 clearInterval(对象)

setTimeout

setTimeout 只能保证在指定的时间后将任务(需要执行的函数)插入任务队列中等候,但是不保证这个任务在什么时候执行.一旦执行javascript的线程空闲出来,自行从队列中取出任务然后执行它. 1,setTimeout的好搭档"0": var start = new Date(); var end = 0; setTimeout(function() { console.log(new Date() - start); }, 0);//2,或3或4