一、通過定時器我們可以間隔設定時間重復調用某個函數,利用這個特性,我們可以做很多事,例如,12306上的每間隔5秒查詢自動查詢一次余票,簡單動畫的實現等等
二、定時器的格式:
定時器有兩種格式,分別是setInterval(func, time) 和 setTimeout(func, time),這兩個有一些區別
1、setInterval(func, tine);
(1)、 此定時器操作是這樣的,解釋到該語句時,是要間隔time時間后第一次執行func函數,間隔time時間后再次執行func函數,重復上述...
來個demo理解一下吧:
var count = 0; var print = function () { console.log(count++); } // 每間隔1秒調用一次print函數,所以帶引結果應該是從0,1,2...一直打印 setInterval(print, 5000);
P.S. 我們在setInterval()中傳遞函數時,我們一般用個匿名函數包裹一下,然后再匿名函數中執行我們要調用的函數,這樣我們可以在匿名函數中進行更多操作,更靈活一些,如下
var count = 0; var print = function () { console.log(count++); } // 每間隔1秒調用一次print函數,所以帶引結果應該是從0,1,2...一直打印 setInterval(function () { print(); }, 1000);
(2)、如何停止計時器?
setInterval有個返回值,我們獲取其返回值后,調用clearInterval(返回值), 即可停止計時器,如下demo
var count = 0; // 間隔1s打印一次cunt,當count為5時結束定時器,所以打印結果為 0, 1, 2, 3, 4 var timer = setInterval(function () { count === 5? clearInterval(timer) : console.log(count++); }, 1000);
2、setTimeout()
(1)、這個和上邊那個定時器用法一樣,都是傳函數和時間間隔,但運行時操作是不同,這個是間隔設定時間后,調用我們傳入的函數,然后就結束了,所以和上邊那個區別在於上邊那個是多次,而這個只有一次。
// 間隔1s后帶引setTimeout, 所以打印結果就是setTimeout setTimeout(function () { console.log('setTimeout'); }, 1000);
(2)、停止計時器,setTimeout()也有返回值,我們獲取返回值,然后執行clearTimeout(返回值),即可停止setTimeout()計時器.
(3)、利用setTimeout()來模擬setInterval()
原理很簡單,就是遞歸,在函數內不斷的停止計時器,再添加計時器,達到重復的目的,不多說,直接上demo:
var count = 0; var timer = null; function print() { // 每次添加定時器前,移除前一個定時器 clearTimeout(timer); //函數執行的語句 console.log(count++);
// 添加定時器
timer = setTimeout(function () {
print(); }, 1000);
// 循環結束條件 if(count == 10) { clearTimeout(timer); }
}
timer = setTimeout(function () { print(); }, 1000);
由代碼我們可以看出打印結果為0~9,
注意點:
i、我們調用的函數內首先第一件事就是移除上一個定時器,因為上一個定時器已經沒有作用了,所以要移除,然后再添加新的定時器
ii、在函數內最好在移除完定時器后緊跟着添加定時器,為什么呢?以為如果我們遇到這樣一種情況,定時器調用的函數需要執行很長一段時間,然后我們在函數最后添加定時器,最終就造成了這樣一種結果,那就是間隔時間變為 (函數執行時間 + 定時器間隔時間)極大的延遲了間隔時間,這肯定不是我們想要的。
那為什么添加到函數前面就沒這種情況了,因為在函數執行后首先就是移除上一個計時器,然后添加新的計時器,這時計時器就開始計時了,而函數正在主線程中被執行,經過計時器設定間隔時間后,將新添加的計時器函數添加到js執行隊列中,等主線程的函數執行完畢后,主線程空閑,則直接執行js執行隊列中等待的計時器函數。這就大大減少了函數執行時間的影響,因為定時器間隔計時和函數執行時間同時進行了,而不是函數執行完再進行計時器的間隔,這就使間隔時間盡量的接近計時器設定的間隔時間。
三、計時器執行原理和單線程:
1、我們都知道js語言是單線程的,那單線程是什么意思呢?那就是‘JavaScrip引擎是單線程運行的,瀏覽器無論什么時候都有且只有一個主線程在執行JavaScript程序’
2、js執行隊列:由於js是運行在單線程環境的,所以線程一直是執行者同一個任務,而我們是講各種欲執行的任務隨機添加到js執行上,當主線程空閑時就會立即執行當前隊首的任務。
也可以這樣理解,js執行隊列是很多有着各自事務要辦的人排成的長隊,而線程是一個可以處理各種事務的窗口,但是該窗口一次只能辦理一個人的事務,在窗口處理完當前人的事務后,窗口就處於空閑狀態,然后接着辦理長隊隊首人的事務,就這樣按照長隊順序,窗口一次處理一個人的事務。而我們所見的代碼執行僅僅是將該代碼事務隨機插入到js執行隊列中排隊等待而已,但因為瀏覽器js引擎處理事務非常快,所以給我們立即執行不用排隊等待的感覺。(注意:往js執行隊列插入任務時是隨機插入到隊列中的,並不一定是要插入到隊尾)
3、計時器工作方式:我們添加一個定時器后,在間隔我們設定時間后,將函數代碼隨機插入到js執行隊列中,等主線程空閑時且該函數代碼位於隊首,才會執行函數代碼。
4、總結來說,就是在JavaScript中沒有任何代碼是立即執行的,只能說一旦主線程空閑則盡快執行
5、setInteral帶來的問題:
(1)、假設使用重復定時器setInterval()定時器,然后添加的函數在線程需要執行很長時間,而重復定時器設定的間隔時間遠遠小於該函數執行時間,則在該函數執行期間,該定時器代碼會被多次添加到js執行隊列等待,這就導致第一次函數執行結束后之后,之前多次添加的函數代碼會連續執行而沒有間隔,並且第一個函數開始執行需要等主線程空閑才行,所以其第一次的間隔時間也不一定是我們設定的。(因為在上一個函數執行時多個函數代碼已經添加到js執行隊列中,所以函數執行結束后會立刻執行已經添加的函數代碼)
(2)、鑒於上述問題,js引擎設定了這樣一條規則:當使用setInterval()時,僅當js執行隊列中沒有該定時器的任何其他代碼實例時,才能將該定時器代碼添加到js執行隊列中。否則就是加入失敗,重新計時。
但此規則又帶來了其他問題;
i、某些間隔會被跳過
ii、多個定時器代碼執行之間的間隔可能會比預期的小,實際我們等待間隔大於等於我們設定的間隔時間。
(3)所以在使用setInterval()做動畫時要注意兩個問題:
i、不能使用固定步長作為做動畫,一定要使用百分比: 開始值 + (目標值 - 開始值) * (Date.now() - 開始時間)/ 時間區間
ii、如果主進程運行時間過長,會出現跳幀的現象(因為js引擎的這條規則而無法添加函數代碼)
(4)、解決:
使用鏈式setTimeout(),如上演示的用setTimeout()來模擬setInterval(),即每次在函數體執行結束的最后添加延時定時器,使函數執行之間的間隔最小為我們設定的間隔時間。
第三項參考來源:http://www.cnblogs.com/dojo-lzz/p/4606448.html
------------------------------------------------------------------------------------------------------end