您好,登錄后才能下訂單哦!
小編給大家分享一下JavaScript中setTimeout有什么用,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
定時器的介紹
js中有哪些定時器?
周期定時器:setInterval()
介紹
setInterval()是按照指定的周期來調用定時器,方法會不斷的調用定時器,直到使用clearInterval()停止或者窗口關閉。
語法
setInterval(code,millisec,lang)
code:要執行的方法體(必選)
millisec:每隔多少毫秒執行一次(單位是毫秒,如果設置為5000,即每5秒執行一次)(必選)
lang:指使用的語言(可選)
實例
通過setInterval實現時鐘效果
<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方法去取消定時器
??setTimeout(code,millisec,lang)????ps:每個參數的含義和setInterval()的均相同
??點擊按鈕3秒后彈出“Hello”
<!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,是為了取消定時器的方法可以獲取到相應的計數器。
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 < 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和promise都是宏任務,現在不理解沒關系,后面會通過例子幫助理解
微任務:是在宏任務執行完成之后執行的,也是有相應的微任務隊列存放微任務,比如promise中的then就是微任務
問題:以下代碼輸出的是什么?
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,將setTimeout放進事件隊列中;
碰到promise,執行console,打印2;
經過循環后,執行console,打印3;
到了then,由于then是微任務,會在宏任務執行完成后執行,放進微任務隊列;
遇到console,打印5;
至此,第一次的宏任務執行完成,接下來執行微任務隊列中的then,打印4;
現在執行棧中的任務都執行完了,現在就要去事件隊列中取事件,此時執行setTimeout這個宏任務,打印1;
宏任務微任務與同步事件異步事件的關系:
??這些詞都是用來描述事件的,只是從不同的角度來描述,就像是胖子矮子與男生女生之間的聯系
以上是“JavaScript中setTimeout有什么用”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。