前言
前段時間剛看完《JS忍者祕籍》,雖說是15年出版的,有些東西是過時了,但像對原型鏈、閉包、正則、定時器之類的機制卻是不會過時的,裡面很多東西都講的很細,還是值得一讀的,本文將對這本書中對定時器機制的部分進行詳細的解析,如果喜歡的話可以點波贊/關注,支援一下,希望大家看完本文可以有所收穫。
個人部落格瞭解一下:obkoro1.com
準備
閱讀本文之前,推薦先閱讀Js 的事件迴圈(Event Loop)機制以及例項講解這篇文章來理解背後發生的事情,本文對事件迴圈機制不會很仔細的講解。
定時器解決的問題:
由於JS的單執行緒特性,定時器提供了一種跳出單執行緒限制的方法,即讓一段程式碼在一定毫秒之後,再非同步執行。
設定和清除定時器:
直接引用忍者祕籍中的圖片:
注意:
- 定時器的時間間隔設為0,也會有幾毫秒的延遲。
- 在使用
setTimeout
和setInterval
的時候最好將其賦值給一個變數,以便取消定時器。 - 在使用
Vue
的時候,setTimeout
和setInterval
的this指向的是window物件,訪問不到元件資料以及方法。 - 在使用
Vue
的時候,路由跳轉並不會銷燬setInterval
,但是元件已經銷燬了,這會導致問題。 - 在執行執行緒中
setTimeout
/setInterval
無法保證準時執行回撥函式的。 setInterval
呼叫有可能會被廢棄以及setInterval
的連續執行
第三點和第四點的解決方法可以參考我之前寫的Vue 實踐過程中的幾個問題。
接下來要講的是第五點和第六點,這兩點是最重要,也是本文要重點解析的內容。
執行執行緒中的定時器執行
下面來看忍者祕籍中的栗子:
讓我們看看這裡發生了什麼事情:
- 首先在0毫秒的時候有一個持續18毫秒的js程式碼塊要執行。
- 然後在0毫秒的時候設了兩個10毫秒延遲的定時器,
setTimeout
以及setInterval
,setTimeout
先設定。 - 在第6毫秒的時候有一個發生了滑鼠單擊事件。
事件排隊。
同時發生了這麼多事情,由於js的單執行緒特性,當執行緒正在執行狀態,有非同步事件觸發時,它就會排隊,並且線上程空閒時才進行執行。
這裡的非同步事件包括:滑鼠單擊,定時器觸發,ajax請求、promise等事件。
複製程式碼
讓我們回到栗子中:
栗子中首先有一個18毫秒的程式碼塊要執行,在這18毫秒中只能執行這段程式碼塊,其他事件觸發了之後只能排隊等待執行。
在程式碼塊還在執行期間,第6毫秒的時候,發生了一個滑鼠單擊事件,以及第10毫秒時的setTimeout
和setInterval
兩個處理程式,這三個事件不能立即執行,而是被新增到等待執行的佇列中。
先進先出(先排隊的先執行)
18毫秒的時候程式碼塊結束執行,有三個任務在排隊等待執行,根據先進先出的原則,此時會先執行click事件,setTimeout
和setInterval
將繼續排隊等待執行。
setInterval呼叫被廢棄
在click事件執行時,第20毫秒處,第二個setInterval
也到期了,因為此時已經click事件佔用了執行緒,所以setInterval
還是不能被執行,並且因為此時佇列中已經有一個setInterval
正在排隊等待執行,所以這一次的setInterval
的呼叫將被廢棄。
瀏覽器不會對同一個setInterval處理程式多次新增到待執行佇列。
setTimeout
/setInterval
無法保證準時執行回撥函式
click事件在第28毫秒處結束執行,有兩個任務(setTimeout
和setInterval
)正在等待執行,遵循先進先出的原則,setTimeout
早於setInterval
設定,所以先執行setTimeout
。
so:我們期望在第10毫秒處執行的setTimeout
處理程式,最終會在第28毫秒處才開始執行,這就是上文提到的setTimeout
/setInterval
無法保證準時執行回撥函式。
在30毫秒處,setInterval
又觸發了,因為佇列中已經有setInterval
在排隊,所以這次的觸發又作廢了。
setInterval的連續執行
setTimeout
執行結束,在第36毫秒處,佇列中的setInterval
處理程式才開始執行,setInterval
需要執行6毫秒。
在第40毫秒的時候setInterval
再次觸發,因為此時上一個setInterval
正在執行期間,佇列中並沒有setInterval
在排隊,這次觸發的setInterval
將進入佇列等候。
所以:setInterval
的處理時長不能比設定的間隔長,否則setInterval
將會沒有間隔的重複執行
第42毫秒的時候,第一個setInterval
結束,然後佇列中的setInterval
立即開始執行,在48毫秒的時候完成執行。然後50毫秒的時候再次觸發setInterval
,此時沒有任務在排隊,將會立即執行。
setTimeout按照一定的間隔週期性的觸發定時器。
上文說了,setInterval
的處理時長不能比設定的間隔長,否則setInterval
將會沒有間隔的重複執行。
但是對這個問題,很多情況下,我們並不能清晰的把控處理程式所消耗的時長,為了我們能按照一定的間隔週期性的觸發定時器,忍者祕籍中提供了下面這種使用方法:
// 實際上我不止在忍者祕籍中見過,在很多地方都見過這種技術。
setTimeout(function repeatMe(){
// do something
setTimeout(repeatMe,10);
// 執行完處理程式的內容後,在末尾再間隔10毫秒來呼叫該程式,這樣就能保證一定是10毫秒的週期呼叫
},10)
複製程式碼
忍者祕籍中關於定時器的其他知識:
- 定時器不能非常細粒化的控制執行的時間,書中建議在15ms以上。
- 可以使用定時器來分解長時間執行的任務,這裡可以自行谷歌。
任務佇列只有排隊這麼簡單嗎?
事實上,關於任務佇列並不是只有簡單的排隊而已,忍者祕籍中提到為了方便,使用了這個概念,如果希望能更清晰的瞭解背後的機制,再次推薦閱讀一下:Js 的事件迴圈(Event Loop)機制以及例項講解,
結語
這上面所有一切,都是由js單執行緒特性導致的,所以會有事件排隊、先進先出、setInterval呼叫被廢棄、定時器無法保證準時執行回撥函式以及出現setInterval的連續執行,時刻記住這一特性,很多關於事件執行順序的問題都能想的通,並且找出解決方法。
希望看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。
個人blog and 掘金個人主頁,如需轉載,請放上原文連結並署名。碼字不易,感謝支援!本人寫文章本著交流記錄的心態,寫的不好之處,不撕逼,但是歡迎指點。
如果喜歡本文的話,歡迎關注我的訂閱號,漫漫技術路,期待未來共同學習成長。
以上2018.6.17
參考資料:
JS忍者祕籍第8章:馴服執行緒和定時器