這篇文章帶給大家的內容是關於面試:JavaScript中的setTimeout到底是什麼? ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
在刷筆試題的時候,常常會碰到setTimeout的問題,只知道這個是設定定時器;但是考察的重點一般是在一個方法中包含了定時器,計時器中的列印和方法中列印的執行順序問題,也許我說的有點難懂,下面就來看看setTimeout到底是什麼吧!
計時器的介紹
#js中有哪些定時器?
週期定時器:setInterval()
介紹
setInterval()是依照指定的週期來呼叫定時器,方法會不斷的呼叫定時器,直到使用clearInterval()停止或視窗關閉。
語法
setInterval(code,millisec,lang)
code:要執行的方法體(必選)
millisec:毫秒每隔多少次執行一次(單位是毫秒,如果設定為5000,即每5秒執行一次)(必選)
#lang:指使用的語言(可選)
實例
透過setInterval實作時脈效果
<input> <script> //每隔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>停止</button>
效果圖:
顧名思義,這個定時器只會執行一次,和setInterval()的區別就在這兒了,正是因為如此,setInterval()才需要使用clearInterval方法去取消定時器
setTimeout(code,millisec,lang) ps:每個參數的意義和setInterval()的皆相同
點擊按鈕3秒後彈出「Hello」
nbsp;html> <meta> <title>菜鸟教程(runoob.com)</title> <p>点击按钮,在等待 3 秒后弹出 "Hello"。</p> <button>点我</button> <script> function myFunction() { setTimeout(function(){alert("Hello")},3000); } </script>
效果圖:
## 使用計時器ID來取消計時器回呼的發生,每個計時器都會回傳一個id,是為了取消定時器的方法可以取得到對應的計數器。
clearInterval(id)
clearTimeout(id)
//设置超时调用 var timeoutId = setTimeout(function (){ alert("hello World"); },1000); //取消掉用的代码 clearTimeout(timeoutId);
我們都知道,js是單執行緒語言,所有的多執行緒都是假象,都是單執行緒模擬出來的。瀏覽器是多進程的,而瀏覽器的核心(渲染進程)是多執行緒的。
渲染進程中有一個js引擎線程,這個線程是用來處理javaScript腳本的(例如chrome的V8引擎),而我們一直說的javaScript是單線程的就是因為這個。
那麼問題來了,既然js是單線程的,那setTimeout的非同步是怎麼實現的呢? js在解析腳本的時候,會將任務分成兩大類,同步任務和非同步任務,它們在解析時會進入不同的場所執行。
同步任務:會進入主執行緒的執行棧,也就是js引擎執行緒管理的地方,依照順序執行
非同步任務:進入Event Table中,並註冊函數,當回呼函數的條件滿足時,就會將回呼函數放進Event Queue中,也就是任務佇列中。
當主執行緒中的任務執行完畢後,也就是執行端為空時,就會去任務佇列中看有沒有事件,如果有的話,就進入主執行緒執行,一直這樣循環下去,這就是事件循環機制了,可以參考下面的圖來理解一下:
# 也許你對事件循環機制的過程還是不太明白,那我再解釋清楚一點。例如下面這個例子:
console.log('start') setTimeout(function(){ console.log('setTimeout') },5000) console.log('end')
執行過程:
#開始解析,遇到console.log,是同步任務,進入主線程,直接執行,列印start;
往下走,遇到setTimeout,是非同步任務,進入Event Table,並註冊回呼函數;
再往下走,遇到console.log,直接執行,印end;
5s后,将回调函数放进Event Queue,此时执行栈刚好为空,主线程会去任务队列中取出这个回调函数,执行,打印setTimeout
ps:
第1,3步都是js引擎线程干的事情,主线程执行任务;
第2步是渲染进程中的事件触发线程(专门管理任务队列的)管理;
第4步是定时器线程控制的(也就是setTiemout和setInterval所在的进程),定时器线程专门用来控制什么时候将回调函数放进任务队列。
如果看懂了上面的例子,就知道其实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 <p><strong>答案:</strong>没错,你没有看错,就是一个简单的循环,就像你想的那样,连续输出0,1,2,3,4</p><h3>难度:OO</h3><p><strong>问题:</strong>以下代码输出的是什么?如果把时间改为1000*i输出的又是什么?</p><pre class="brush:php;toolbar:false">for(var i = 0;i <p><strong>答案:</strong><br> 时间为1000时,1秒后会连续输出5个5;时间为1000*i时,会每隔一秒输出一个5,一共5个5<br><strong>分析:</strong><br> 由上面的事件循环机制我们知道,setTimeout是异步事件,会放在事件队列中等着主线程来执行,这个时候for循环中的i已经变成了5,由于定时器线程是在1秒后直接将5个setTimeout事件放进事件队列中,所以主线程在执行的时候就没有间隔了;当时间乘上一个i时,定时器会隔1秒将setTimeout事件放入队列,就会出现每隔一秒输出一个5的情况。</p><h3>难度:OOO</h3><p><strong>问题:</strong>如果想输出0,1,2,3,4应该怎么改?<br><strong>分析:</strong><br> 出现上一题的情况主要是因为在setTimeout的回调函数中并没有保存每次循环i的值,最后执行的时候,得到的i就是最后更新的i了(即为5),所以要解决这个问题,思路是要在回调函数中保存每次for循环中的i值。</p><p><strong>解决方案1:</strong>使用es6中let代替var<br><strong>分析:</strong>let是es6中新增的内容,作用和var一样,都是用来定义变量,但是最大的差别就是let会形成块级作用域,在本例中,就是每次循环,都会产生一个作用域,在该作用域中的变量是一个固定值,下次i变化时不会对这个i产生影响,也就是达到了我们的目标。</p><pre class="brush:php;toolbar:false">for(let i = 0;i <p><strong>解决方案2:</strong>使用闭包<br><strong>分析:</strong>就是直接在setTimeout函数的外面套一层立即执行函数,并将i值作为参数传到匿名函数中(这里的匿名函数也可以是命名函数),然后由于setTimeout中回调函数用到了匿名函数中的i,就会形成闭包。</p><pre class="brush:php;toolbar:false">for(var i = 0;i <p><strong>延伸:</strong>将代码变成下面这样会输出什么?(去掉匿名函数中的i)<br><strong>分析:</strong>这里会输出5个5,也就是闭包没有起作用,根本原因是i并没有传进去,打印的还是最后的i</p><pre class="brush:php;toolbar:false">for (var i = 0; i <p><strong>解决方案3:</strong>将回调函数改成立即执行函数<br><strong>分析:</strong>这个解决方案其实不是太好,如果要求是每隔1秒输出一个数字,这个方法就不适用了;这个方法会立马输出0,1,2,3,4,原因结合基础篇应该就明白了</p><pre class="brush:php;toolbar:false">for (var i = 0; i <h3>难度:OOOO</h3><p> 这一部分会涉及到promise,事件循环机制,宏任务和微任务的内容,算是比较难的部分了,如果觉得比较难看懂,最好先去补一下基础知识,我这里就简单介绍一下。</p><h4>promise对象</h4><p>我这里就不详细讲了。</p><h4>宏任务和微任务</h4>
宏任务:可以理解成将代码块走一遍的过程,setTimeout和promise都是宏任务,现在不理解没关系,后面会通过例子帮助理解
微任务:是在宏任务执行完成之后执行的,也是有相应的微任务队列存放微任务,比如promise中的then就是微任务
问题:以下代码输出的是什么?
setTimeout(function () { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for (var i = 0; i <p><strong>答案:</strong>(是不是很懵,为什么会是这样,下面看我的分析你就知道了)</p><p style="text-align: center;"><span class="img-wrap"><img src="https://img.php.cn//upload/image/617/114/318/1543217488584753.png" title="1543217488584753.png" alt="面試:JavaScript中的setTimeout到底是什麼?"></span></p><p><strong>分析:</strong></p><ol class=" list-paddingleft-2"> <li><p>进入宏任务(从第一行到最后一行执行一遍的过程),碰到setTimeout,将setTimeout放进事件队列中;</p></li> <li><p>碰到promise,执行console,<strong>打印2</strong>;</p></li> <li><p>经过循环后,执行console,<strong>打印3</strong>;</p></li> <li><p>到了then,由于then是微任务,会在宏任务执行完成后执行,放进微任务队列;</p></li> <li><p>遇到console,<strong>打印5</strong>;</p></li> <li><p>至此,第一次的宏任务执行完成,接下来执行微任务队列中的then,<strong>打印4</strong>;</p></li> <li><p>现在执行栈中的任务都执行完了,现在就要去事件队列中取事件,此时执行setTimeout这个宏任务,<strong>打印1</strong>;</p></li> </ol><p><strong>宏任务微任务与同步事件异步事件的关系:</strong><br> 这些词都是用来描述事件的,只是从不同的角度来描述,就像是胖子矮子与男生女生之间的联系</p><p class="comments-box-content"><br></p><p> </p>
以上是面試:JavaScript中的setTimeout到底是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!