30天學習計劃 js忍者祕籍 第8章 馴服執行緒和定時器
9.26-9.30
第8章 馴服執行緒和定時器
定時器可以在js中使用,但它不是js的一項功能,如果我們在非瀏覽器環境中使用js,很可能定時器就不存在了,需要自己實現自己的定時器版本。
定時器提供了一種讓一段程式碼在一定毫秒之後,再非同步執行的能力。由於js是單執行緒的特性(同一時間只能執行一處js程式碼),定時器提出了一種跳出這種限制的方法,以一種不太直觀的方式來執行程式碼。
8.1 定時器和執行緒是如何工作的
8.1.1 設定和清除定時器
js提供了兩種方式,用於建立定時器以及兩個相應的清除方法(刪除)。這些方法是window物件(全域性上下文)上的方法。
id = setTimeout(fn,delay) 啟動一個定時器,在一段時間(delay)之後執行傳入的callback,並返回該定時器的唯一標識
clearTimeout(id) 如果定時器還未觸發,傳入定時器標識即可取消(清除)該定時器
id = setInterval(fn,delay) 啟動一個定時器,在每隔一段時間之後都執行傳入的callback,並返回該定時器的唯一標識
clearInterval(id) 傳入間隔定時器標識,即可取消該間隔定時器
js定時器的延遲時間是不能保證的,原因和js執行緒的本質有很大關係。
8.1.2 執行執行緒中的定時器執行
在Web worker可用之前,瀏覽器中的所有js程式碼都是在單執行緒中執行的,是的,只有一個執行緒。
處理程式在執行時必須進行排隊執行,並且一個處理程式並不能中斷另外一個處理程式的執行。
8.1.3 timeout與interval之間的區別
示例8.1 兩種建立重複定時器的方式
setTimeout(function repeatMe(){ //定義一個timeout定時器,每10毫秒都重新呼叫自己
//code
setTimeout(repeatMe,10)
},10)
setInterval(function(){ //定義一個interval定時器,每10毫秒都觸發一次
//code
},10)
在setTimeout()程式碼中,要在前一個callback回撥執行結束並延遲10秒以後,才能再次執行setTimeout()。
而setInterval()則是每隔10毫秒就嘗試執行callback回撥,而不關注上一個callback是何時執行的。
.js引擎是單執行緒執行,非同步事件必須要排隊等待才能執行
.如果無法立即執行定時器,該定時器會被推遲到下一個可用的執行時間點上(可能更長,但不會比指定的延遲時間更少)。
.如果一直被延遲,到最後,interval間隔定時器可能會無延遲執行,並且同一個interval處理程式的多個例項不能同時進行排隊。
.setTimeout()和setInterval()在觸發同期的定義上是完全不同的。
8.2 定時器延遲的最小化及其可靠性
現代瀏覽器通常無法實現1毫秒粒度的可持續間隔,某些瀏覽器的實現可以非常接近。
當我們對setInterval()設定0毫秒的延遲時,ie瀏覽器定時器的callback回撥只會執行一次,和使用setTimeout效果一樣。
瀏覽器不保證我們指定的延遲間隔,雖然可以指定特定的延遲值,但其準確性卻並不總是能夠保證,尤其是在延遲值很小的時候。
8.3 處理昂貴的計算過程
js的單執行緒本質可能是js複雜應用程式開發中的最大“陷阱”。在js執行的時候,頁面渲染的所有更新操作都要暫停。
如果要保持介面有良好的響應能力,減少執行時間超過幾百毫秒的複雜操作,將其控制在可管理狀態是非常必要的。
如果一段指令碼的執行時間超過5秒,有些瀏覽器將彈出一個對話方塊警告使用者該指令碼“無法響應”。iPhone上的瀏覽器,將預設終止執行時間超過5秒鐘的指令碼。
作為定時器,它在一段時間之後,可以有效暫停一段js程式碼的執行,定時器還可以將程式碼的各個部分,分解成不會讓瀏覽器掛掉的碎片。
考慮到這一點,我們可以將強迴圈和操作轉化為非阻塞操作。
示例8.2 一個長時間執行的任務
var tbody = document.getElementsByTagName('tbody')[0];
for(var i=0; i<20000; i++){
var tr = document.createElement('tr');
for(var t=0; t<6; t++){
var td = document.createElement('td');
td.appendChild(document.createTextNode(i+','+t));
tr.appendChild(td);
}
tbody.appendChild(tr)
}
上例建立了240000個DOM節點,並使用大量的單元格來填充一個表格。這是非常昂貴的操作,明顯會增加瀏覽器的執行時間,從而阻止正常的使用者互動操作。
我們可以引入定時器,在程式碼執行的時候定期暫停休息
示例8.3 利用定時器分解長時間執行的任務
var tbody = document.getElementsByTagName('tbody')[0];
var rowCount = 20000;
var divideInto = 4;
var chunkSize = rowCount/divideInto;
var iteration = 0;
setTimeout(function generateRows(){
var base = (chunkSize)*iteration;
for(var i=0; i
var tr = document.createElement('tr');
for(var t=0; t<6; t++){
var td = document.createElement('td');
td.appendChild(document.createTextNode((i+base)+','+t+','+iteration));
tr.appendChild(td)
}
tbody.appendChild(tr);
}
iteration++;
if(iteration
setTimeout(generateRows,0)
}
},0);
上例將操作分成四步小操作,每個操作建立自己的DOM節點。這些較小的操作,則不太可能讓瀏覽器掛掉。
8.4 中央定時器控制
使用定時器可能出現的問題是對大批量定時器的管理。
同時建立大量的定時器,將會在瀏覽器中增加垃圾回收任務的可能性。垃圾回收就是瀏覽器遍歷其分配過的記憶體,並試圖刪除沒有任何應用的未使用物件的過程。定時器是一個特殊的問題,因為通常它們是在js單執行緒引擎之外的流程中進行管理。有些瀏覽器可以很好地處理這種情況,有些瀏覽器的垃圾回收週期則很長。一個動畫在某個瀏覽器中很漂亮、很流暢,但在另外一個瀏覽器中卻很卡頓。
在多個定時器中使用中央定時器控制,可以帶來很大的威力和靈活性。
.每個頁面在同一時間只需要執行一個定時器。
.可以根據需要暫停和恢復定時器。
.刪除回撥函式的過程變得很簡單。
示例8.4 管理多個處理程式的中央定時器控制
test suite
#box{position:absolute;width:60px;height:40px;border:1px solid #060; text-align:center;}
Hello!
var timers={
timerID:0,
timers:[],
add:function(fn){
this.timers.push(fn);
},
start:function runNext(){
if(this.timerID) return;
(function(){
if(timers.timers.length > 0){
for(var i=0; i
if(timers.timers[i]() === false){
timers.timers.splice(i,1);
i--
}
}
timers.timerID = setTimeout(runNext,0)
}
})()
},
stop:function(){
clearTimeout(this.timerID);
this.timerID=0;
}
}
var box = document.getElementById('box'),x=0,y=20;
timers.add(function(){
box.style.left = x + 'px';
if(++x>50) return false;
})
timers.add(function(){
box.style.top = y+'px';
y+=2;
if(y>120) return false;
})
timers.start();
一開始,所有的回撥函式都儲存於一個名為timers的陣列中,還包含當前定時器的一個ID,這些變數是定時器唯一需要維護的內容。
add()方法接受一個callback回撥,並簡單將其新增到timers陣列中。
start()方法首先確認沒有定時器在執行(通過檢查timerID是否有值),如果確認沒有定時器在執行,立即執行一個即時函式來開啟中央定時器。
在即時函式內,如果註冊了處理程式,就遍歷執行每個處理程式。如果有處理程式返回false,我們就從陣列中將其刪除,最後進行下一次排程。
以這種方式組織定時器,可以確保回撥函式總是按照新增的順序進行執行。而普通的定時器通常不會保證這種順序,有可能後面的一個處理程式在前面就執行了。
這種方式的定時器組織,對於大型應用程式或任何形式的js動畫來說都是至關重要的。
8.5 非同步測試
示例:簡單的非同步測試套件
(function(){
var queue = [],paused=false;
this.test = function(fn){
queue.push(fn);
runTest();
}
this.pause = function(){
paused = true;
}
this.resume = function(){
paused = false;
setTimeout(runTest,1)
}
function runTest(){
if(!paused && queue.length){
queue.shift();
if(!paused) resume();
}
}
})()
示例中,傳遞給test()方法的每個函式,最多隻包含一個非同步測試。它們的非同步性由pause()和resume()的使用所定義,這兩個方法分別在非同步事件之前或之後進行呼叫。這段程式碼是一種確保讓包含非同步行為的函式,以特定的順序進行執行的方式。
該佇列唯一的目的是在等待執行的時候,出列一個函式並進行執行。否則,就完全停止執行一個時間間隔。
這段程式碼,強制測試套件以純粹非同步方式進行執行,但同時又保證了測試執行的順序。
相關文章
- 馴服定時器和執行緒定時器執行緒
- JS忍者祕籍中的定時器機制詳解JS定時器
- 執行緒的馴服執行緒
- javascript忍者祕籍(第二版)翻譯學習 第2章 執行時的頁面構建過程JavaScript
- JS定時器和單執行緒非同步特性JS定時器執行緒非同步
- C#多執行緒學習(五) 多執行緒的自動管理(定時器)C#執行緒定時器
- C# 多執行緒學習(5) :多執行緒的自動管理(定時器)C#執行緒定時器
- 讓JS程式碼Level提升的忍者祕籍(實用)JS
- 多執行緒-定時器的概述和使用執行緒定時器
- ios 多執行緒定時器iOS執行緒定時器
- 我為什麼要推薦《JavaScript 忍者祕籍(第2版)》JavaScript
- Java中命名執行器服務執行緒和執行緒池Java執行緒
- 瀏覽器多執行緒和js單執行緒瀏覽器執行緒JS
- JS學習之Bom(window和定時器)JS定時器
- 【學習】你不知道的JavaScript + 忍者祕籍 -- 作用域 +閉包 小結JavaScript
- javascript忍者祕籍-第五章 閉包和作用域JavaScript
- ART執行時垃圾收集機制簡要介紹和學習計劃
- RT-Thread學習筆記3-執行緒間通訊 & 定時器thread筆記執行緒定時器
- javascript忍者祕籍-第三章 函式定義與引數JavaScript函式
- Node.js使用計時器定時執行函式詳解Node.js函式
- MongoDb學習之Explain執行計劃MongoDBAI
- 配置監聽器,建立執行緒定時執行業務邏輯執行緒行業
- linux設定crontab定時執行任務計劃Linux
- 學習 Tipask-第 1 天 規劃思路
- Java多執行緒19:定時器TimerJava執行緒定時器
- 第1講:程序和執行緒執行緒
- 執行緒池學習執行緒
- java 執行緒學習Java執行緒
- java執行緒學習Java執行緒
- Java多執行緒學習——執行緒通訊Java執行緒
- Java多執行緒學習(2)執行緒控制Java執行緒
- javascript忍者祕籍(第二版)翻譯學習 第一章 JavaScript無處不在JavaScript
- Python學習之程式和執行緒Python執行緒
- ubuntu上使用cron執行定時任務計劃Ubuntu
- oracle 執行計劃設定Oracle
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- 多執行緒學習一(多執行緒基礎)執行緒
- #大學#Java多執行緒學習02(執行緒同步)Java執行緒