標籤:
掌握定時器工作原理必知:JavaScript引擎是單線程啟動並執行,瀏覽器無論在什麼時候都只且只有一個線程在運行JavaScript程式. 常言道:setTimeout和setInterval是偽線程。
Javascript是運行在單線程環境中的,在頁面的聲明周期中,不同時間可能有其他代碼在控制Javascript進程,比如:包含在<script>元素中的代碼、dom元素的事件處理常式、Ajax的回呼函數。定時器僅僅是在未來的某個時刻將代碼添加到代碼隊列中,執行時機是不能保證的。代碼隊列按照先進先出的原則在主進程空閑後將隊列中的代碼交給主線程運行。
在不同時間段內Javascript主進程處於不同狀態,開始時執行頁面中<script>元素內的代碼,初始載入完成後,主進程進入空閑狀態,這時候有dom元素產生click事件,事件處理代碼被添加到代碼隊列中,代碼隊列發現Javascript主進程處於空閑狀態,立即將隊列中的第一個元素交給主進程執行。便是這一個過程的時間軸。
在Javascript中沒有任何代碼是立刻執行的,帶一旦進程空閑則儘快執行。例如,當某個按鈕被按下時,事件處理函數會被添加到代碼隊列中。當接收到ajax響應時,回校函數的代碼被添加到隊列中。而定時器對隊列的工作方式是,當特定的事件過去後將代碼加入到隊列中。設定一個150ms後執行的定時器不代表代碼會在150ms之後執行,而是指代碼會在150ms後加入到代碼隊列中。等到主進程空閑時並且該元素位於隊列首位,其中的代碼便會立即執行,看上去好像是在精確的時間點上執行了。實際上隊列中的所有代碼都要等到主進程空閑之後才能執行,而不管他們是怎額添加到隊列中去的。
var ele = document.getElementById(‘btn‘);ele.onclick = function(){ setTimeout(function(){ document.getElementById(message).style.backgroundColor = "red"; }, 255); var start = Date.now(); while(Date.now() - start < 300) {};}
以上樣本中,定時器在255ms事被插入到代碼隊列中,但Javascript主線程有300ms處於運行狀態,那麼定時器代碼至少要在定時器設定之後的300ms後才會被執行。以下時間軸代表了上面代碼的執行過程。
重複定時器setInterval
為了確保定時器代碼插入到隊列總的最小間隔為指定時間。當使用setInterval()時,僅當沒有該定時器的任何其他代碼執行個體時,才能將定時器代碼添加到代碼隊列中。假設沒有這條原則,setInterval()建立的定時器確保了定時器代碼能夠規則的插入隊列中。那麼問題來了,假設Javascript主進程的已耗用時間非常長,那麼setInterval的代碼被多次添加到了代碼隊列中,等到主進程空閑時,定時器代碼便會連續執行多次而之間不會有任何停頓。
但是這條規則同樣也帶來了兩個問題:
- 某些間隔會被跳過
- 多個定時器的代碼執行之間的間隔可能會比預期的小
var ele = document.getElementById(‘btn‘);ele.onclick = function(){ setInterval(function(){
console.log(‘run interval‘); var start = Date.now(); while(Date.now() - start < 350) {}; }, 200); var start = Date.now(); while(Date.now() - start < 300) {};}
以上代碼中,click事件處理常式中通過setInterval設定了一個200ms的時間間隔的重複定時器。從以上代碼可以看出事件處理常式花了300ms多的時間完成,而定時器代碼也花了300ms的時間,這個時候就會出現跳過間隔且連續兩次運行定時器代碼的情況。請看:
中,第一個定時器在205ms出添加到隊列中的,但是直到過了300ms才能夠執行。當執行定時器代碼時,在405ms時有一個定時器代碼執行個體被添加到等待隊列中。在605ms處第一個定時器代碼仍然在運行,同時在代碼隊列中已經有了另一個定時器的代碼執行個體。所以在這個時間點上的定時器代碼不會被添加到隊列中。結果在205ms處添加的定時器代碼執行完畢後,405ms處添加的定時器代碼會立即執行。
所以在使用setInterval做動畫時要注意兩個問題:
- 不能使用固定步長作為做動畫,一定要使用百分比: 開始值 + (目標值 - 開始值) * (Date.now() - 開始時間)/ 時間區間
- 如果主進程已耗用時間過長,會出現跳幀的現象
為了避免setInterval的兩個缺點,可以使用鏈式setTimeout():
setTimeout(function(){ //其他處理 setTimeout(arguments.callee, interval);}, interval);
以上文章內容主要來自《Javascript進階程式設計》
Javascript定時器學習筆記