在刷笔试题的时候,常常会碰到setTimeout的问题,只知道这个是设置定时器;可是考察的重点通常是在一个方法中包含了定时器,定时器中的打印和方法中打印的执行顺序问题,也许我说的有点儿难懂,下面就来看看setTimeout究竟是什么吧!javascript
setInterval()是按照指定的周期来调用定时器,方法会不断的调用定时器,直到使用clearInterval()中止或者窗口关闭html
setInterval(code,millisec,lang)java
经过setInterval实现时钟效果es6
<html> <body> <input type="text" id="clock" /> <script type="text/javascript"> //每隔1秒执行一次clock方法 var int=self.setInterval("clock()",1000); function clock() { var d=new Date(); var t=d.toLocaleTimeString(); document.getElementById("clock").value=t; } </script> <!-- 设置一个按钮,点击按钮即中止定时器 --> <button onclick="int=window.clearInterval(int)">中止</button> </body> </html>
效果图:面试
顾名思义,这个定时器只会执行一次,和setInterval()的区别就在这儿了,正是由于如此,setInterval()才须要使用clearInterval方法去取消定时器chrome
setTimeout(code,millisec,lang) ps:每一个参数的含义和setInterval()的均相同segmentfault
点击按钮3秒后弹出“Hello”promise
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>菜鸟教程(runoob.com)</title> </head> <body> <p>点击按钮,在等待 3 秒后弹出 "Hello"。</p> <button onclick="myFunction()">点我</button> <script> function myFunction() { setTimeout(function(){alert("Hello")},3000); } </script> </body> </html>
效果图:浏览器
使用计时器ID来取消计时器回调的发生,每一个计时器都会返回一个id,是为了取消定时器的方法能够获取到相应的计数器。多线程
//设置超时调用 var timeoutId = setTimeout(function (){ alert("hello World"); },1000); //取消掉用的代码 clearTimeout(timeoutId);
咱们都知道,js是单线程语言,全部的多线程都是假象,都是单线程模拟出来的。浏览器是多进程的,而浏览器的内核(渲染进程)是多线程的。不理解这句话的能够去看看这篇文章。
渲染进程中有一个js引擎线程,这个线程是用来处理javaScript脚本的(例如chrome的V8引擎),而咱们一直说的javaScript是单线程的就是由于这个。
那么问题来了,既然js是单线程的,那setTimeout的异步是怎么实现的呢?js在解析脚本的时候,会将任务分为两大类,同步任务和异步任务,它们在解析时会进入不一样的场所执行。
当主线程中的任务执行完毕后,也就是执行栈为空时,就会去任务队列中看有没有事件,若是有的话,就进入主线程执行,一直这样循环下去,这就是事件循环机制了,能够参照下面的图理解一下:
也许你对事件循环机制的过程仍是不太明白,那么我再解释清楚一点。例以下面这个例子:
console.log('start') setTimeout(function(){ console.log('setTimeout') },5000) console.log('end')
执行过程:
ps:
若是看懂了上面的例子,就知道其实setTimeout的第二个参数其实并不能准确的控制多少秒后执行里面的函数,而是控制多少秒后将这个函数放进任务队列中;这样也就一样能够解释,为何有时候明明设置的是2秒以后执行,却要等不止2秒(由于颇有可能定时线程将回调函数放进任务队列后,主线程还在执行执行栈中的任务,须要执行栈中的任务所有执行完后才会去任务队列中取任务)。
这样就会引起一个问题,咱们知道setInterval是隔必定的时间执行一次,如今理解了原理后,就知道实际上是隔必定的时间定时器线程将回调函数放进任务队列中。若是已经将回调函数放进任务队列,可是主线程正在执行一个很是耗时的任务,当这个任务执行完毕后,主线程去任务队列中取任务,这个时候,就会出现连续执行的状况,也就是说setInterval至关于失效了。
这一部分主要是针对在事件循环机制中setTimeout调顺序进行举例子,若是可以轻松的将例子看懂,就说明你是真的懂了事件循环机制的一部分,为何说是一部分呢,由于还有一个宏任务和微任务的知识点尚未涉及到,后面的进阶篇就会涉及到啦!
console.log('start') setTimeout(function(){ console.log('setTimeout') },0) console.log('end')
打印结果:(若是前面看懂了的同窗应该就会明白为何)
分析:其实和上面那个例子时同样的,只是这个0会给咱们一种会当即执行的假象,这个0是说明定时器线程会当即将回调函数放进任务队列而已,主线程仍是会将执行栈中的两个同步任务执行完成后再去任务队列中取任务,因此执行顺序和这里的秒数无关。并且即便执行栈为空,也不会0秒就执行,由于HTML的标准规定,setTimeout不超过4ms按照4ms来计算。
console.log('start') setTimeout(function(){ console.log('setTimeout') }(),0) console.log('end')
打印结果:(仔细对比与例1的区别)
分析:细心的同窗会发现,我将回调函数改为了当即执行函数,就改变了执行的顺序。首先咱们须要明确的是setTimeout的第一个参数是指函数的返回值,这里回调函数为当即执行函数时,返回值就是undefined了,因此会直接执行当即执行函数,也就是当即打印setTimeout,而真正的setTimeout函数就至关于没起做用。
setTimeout(() => { console.log('setTimeout') },3000) sleep(10000000)//伪代码,表示这个函数要执行好久好久
打印结果:
这个结果不说也知道,确定会打印出setTimeout的,可是重点却不在这儿~
重点在于,这个setTimeout是隔好久好久打印出来的,远远超过了3秒,这个例子也是很明确的体现了js的事件循环机制。
这一部分相对于基础篇,加上了做用域以及其余也是比较难以理解的东西,可能还须要补充一些其余知识才会明白,我会尽可能讲清楚,也会把我看的参考文章放在下面。
受到一篇文章的启发,咱们以按部就班的方式来阐述
问题:如下代码输出的是什么?
for(var i = 0;i < 5;i++){ console.log(i) }
答案:没错,你没有看错,就是一个简单的循环,就像你想的那样,连续输出0,1,2,3,4
问题:如下代码输出的是什么?若是把时间改成1000*i输出的又是什么?
for(var i = 0;i < 5;i++){ setTimeout(function(){ console.log(i) },1000) }
答案:
时间为1000时,1秒后会连续输出5个5;时间为1000*i时,会每隔一秒输出一个5,一共5个5
分析:
由上面的事件循环机制咱们知道,setTimeout是异步事件,会放在事件队列中等着主线程来执行,这个时候for循环中的i已经变成了5,因为定时器线程是在1秒后直接将5个setTimeout事件放进事件队列中,因此主线程在执行的时候就没有间隔了;当时间乘上一个i时,定时器会隔1秒将setTimeout事件放入队列,就会出现每隔一秒输出一个5的状况。
问题:若是想输出0,1,2,3,4应该怎么改?
分析:
出现上一题的状况主要是由于在setTimeout的回调函数中并无保存每次循环i的值,最后执行的时候,获得的i就是最后更新的i了(即为5),因此要解决这个问题,思路是要在回调函数中保存每次for循环中的i值。
解决方案1:使用es6中let代替var
分析:let是es6中新增的内容,做用和var同样,都是用来定义变量,可是最大的差异就是let会造成块级做用域,在本例中,就是每次循环,都会产生一个做用域,在该做用域中的变量是一个固定值,下次i变化时不会对这个i产生影响,也就是达到了咱们的目标。
for(let i = 0;i < 5;i++){ setTimeout(function(){ console.log(i) },1000*i) }
解决方案2:使用闭包
分析:就是直接在setTimeout函数的外面套一层当即执行函数,并将i值做为参数传到匿名函数中(这里的匿名函数也能够是命名函数),而后因为setTimeout中回调函数用到了匿名函数中的i,就会造成闭包。
for(var i = 0;i < 5;i++){ (function(i){ setTimeout(function(){ console.log(i) }, 1000 * i) }) (i) }
延伸:将代码变成下面这样会输出什么?(去掉匿名函数中的i)
分析:这里会输出5个5,也就是闭包没有起做用,根本缘由是i并无传进去,打印的仍是最后的i
for (var i = 0; i < 5; i++) { (function () { setTimeout(function () { console.log(i) }, i * 1000) })(i); }
解决方案3:将回调函数改为当即执行函数
分析:这个解决方案其实不是太好,若是要求是每隔1秒输出一个数字,这个方法就不适用了;这个方法会立马输出0,1,2,3,4,缘由结合基础篇应该就明白了
for (var i = 0; i < 5; i++) { setTimeout((function (i){ console.log(i); })(i), i * 1000) }
这一部分会涉及到promise,事件循环机制,宏任务和微任务的内容,算是比较难的部分了,若是以为比较难看懂,最好先去补一下基础知识,我这里就简单介绍一下。
我这里就不详细讲了,能够看这篇文章
问题:如下代码输出的是什么?
setTimeout(function () { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for (var i = 0; i < 10000; i++) { i == 9999 && resolve(); } console.log(3); }).then(function () { console.log(4); }); console.log(5);
答案:(是否是很懵,为何会是这样,下面看个人分析你就知道了)
分析:
宏任务微任务与同步事件异步事件的关系:
这些词都是用来描述事件的,只是从不一样的角度来描述,就像是胖子矮子与男生女生之间的联系。
关于setTimeout还有不少能够去研究的东西,我这里只是将我目前看到的相关内容进行总结,因为涉及的内容过多,若是没有相关内容的基础可能会比较难看懂,我也是为了这篇文章看了好多资料,这篇文章拖了大概一周才完工,有什么问题,能够留言告诉我呀!
若是你以为还不错,就请给个赞吧~