網頁制作Poluoluo文章簡介:了解Javascript函數如何工作啟發了我們:這些看似簡單的特征在其執行時相當復雜。但是,考慮到它的復雜性,我們會更加深入的研究它們。很明顯,計時器最終在復雜的應用程序中特別有用(計算昂貴的代碼、動畫、異步測試包)。但由於其易用性(特別是增加了閉包),它們往
1、昂貴計算的處理
在復雜Javascript應用程序開發中,最復雜的可能是用戶界面的單線程特性。而Javascript在處理用戶交互時最好的狀況是反應遲鈍,最糟糕的情況是無響應而導致浏覽器掛起(在Javascript執行時,頁面中所有的更新操作暫停)。源於這一事實,將所有復雜操作(任何多於100ms的計算)減小到可管理的程度就勢在必行。另外,如果運行了至少5秒鐘還沒有停止,一些浏覽器(如Firefox 、Opera)將產生一個提示框警告用戶腳本無相應。
這顯然是不可取的,產生一個無響應的界面並不好。但是,幾乎可以肯定的是,當你需要處理大量數據時就會出現這種情況(如處理數以千計的DOM元素會導致這種情況出現)。
此時,計時器就顯得尤為有用。由於計時器能有效的暫停Javascript代碼的執行,它也能阻止浏覽器將執行的代碼掛起(只要個別代碼還不足以使浏覽器掛起)。想到這一點,我們可以將正常的、密集的、循環計算納入到非阻塞的計算之中,讓我們看看下面這個例子,這種類型的計算是必需的。
一個長時運行的任務:
<table><tbody></tbody></table>
// Normal, intensive, operation
var table = document.getElementsByTagName("tbody");
for ( var i = 0; i < 2000; i++ ) {
var tr = document.createElement("tr");
for ( var t = 0; t < 6; t++ ){
var td = document.createElement("td");
td.appendChild( document.createTextNode("" + t));
tr.appendChild( td );
}
table.appendChild( tr );
}
}
在這個例子中,我們創建了總數為26000個DOM節點,並將數字填入表中,這太昂貴了,很有可能將浏覽器掛起以阻止用戶正常的交互。我們可以將計時器引入其中,從而得到與眾不同,也許更好的結果。
用計時器將耗時較長的任務拆分開來:
<table><tbody></tbody></table>
var table = document.getElementsByTagName("tbody");
var i = 0, max = 1999;
setTimeout(function(){
for ( var step = i + 500; i < step; i++ ) {
var tr = document.createElement("tr");
for ( var t = 0; t < 6; t++ ){
var td = document.createElement("td");
td.appendChild( document.createTextNode("" + t));
tr.appendChild( td );
}
}
table.appendChild( tr );
}
if ( i < max )
setTimeout( arguments.callee, 0 );
}, 0);
在我們修改的例子中,我們將密集的計算分成四部分,每個創建6500個節點。這些計算不太可能中斷浏覽器正常的流。最糟糕的情況也只是這些數字可能隨時調整(例如,使其在250-500之間變化,這樣我們的每一個單元將產生3500DOM個節點)。但是,給人印象最深的是我們如何改變我們的代碼以適應新的異步系統。我們需要做更多的工作以確保元素的數字正確生成(該循環不會永無休止)。這些代碼與我們初始的很相似。注意,我們使用閉包維持代碼片段間的迭代狀態,毫無疑問,不使用閉包,此代碼將更為復雜。
與原來的技術相比,使用該技術有一個明顯的變化。浏覽器的長時掛起被4個視覺化的頁面更新替代。雖然浏覽器嘗試著盡可能快得去執行這些代碼片段,它也在計時器的每一步操作之後渲染DOM變化(就好像大量的更新)。大多數情況下,用戶覺察不到此種類型的更新,但記住它們曾經發生過很重要。
有一種情況會使該技術能專門為我服務--我構建的計算大學生日程排列的應用程序。起初,該應用程序是典型的CGI(客戶與服務器交流,服務器計算出日程表之後將其返回)。但我對它作了改變,講所有的日程計算放到客戶端,這是日程計算的視圖:
這些計算的代價相當昂貴(需要遍歷數以千計的排列以找到正確的答案)。將日程計算分割成實實在在的單元使這一問題得到了解決(用已經完成的那部分更新用戶界面)。最後,用戶提交的是快速、反應靈敏、可用性較高的用戶界面。
如此有用的技術常常令人驚奇。你會發現它經常被用於長時運行的程序之中,就像測試箱(我們在這章末尾討論它)。更為重要的是,這種技術向我們顯示了解決浏覽器環境的限制是多麼的輕而易舉,同時也為用戶提供了豐富的經驗。
網頁制作Poluoluo文章簡介:了解Javascript函數如何工作啟發了我們:這些看似簡單的特征在其執行時相當復雜。但是,考慮到它的復雜性,我們會更加深入的研究它們。很明顯,計時器最終在復雜的應用程序中特別有用(計算昂貴的代碼、動畫、異步測試包)。但由於其易用性(特別是增加了閉包),它們往
2、中央計時器控件
在使用計時器時,出現了一個問題--在處理大量計時器時如何管理它們。在動畫處理中你嘗試去同步處理大量屬性時就尤其重要,你需要一種方法去管理它們。
你的計時器有一個核心的控制將賦予你很大的權力和靈活性,即:
你也必需認識到,增加同步計時器的數量有可能增加浏覽器垃圾回收出現的可能性。一般來說,浏覽器在搜尋並嘗試綁定任何零碎的東西(刪除未使用的變量、對象等)。因為它們通常在正常的Javascript引擎管理之外(通過其它線程),這時定時器的問題就尤為嚴重。一些浏覽器能處理這種情況而另外一些浏覽器會導致長時的垃圾回收循環出現。你也許會注意到這種問題--當你在一個浏覽器中看到的是漂亮、平滑的動畫,而在另一個浏覽器中動畫是走走停停完成的。減少計時器同步應用的數量對此大有裨益(這也是現代浏覽器引入類似中央計時器控件構件的原因)。
讓我們來看一個例子,我們有多個函數,這些函數分別對單個屬性產生動畫效果,但它們被一個單獨的計時器函數管理。
用計時器隊列控制多重動畫:
<div id="box" style="position:absolute;">Hello!</div>
var timers = {
timerID: 0,
timers: [],
start: function(){
if ( this.timerID )
return;
(function(){
for ( var i = 0; i < timers.timers.length; i++ )
if ( timers.timers[i]() === false ) {
timers.timers.splice(i, 1);
i--;
}
timers.timerID = setTimeout( arguments.callee, 0 );
})();
},
stop: function(){
clearTimeout( this.timerID );
this.timerID = 0;
},
add: function(fn){
this.timers.push( fn );
this.start();
}
};
var box = document.getElementsById("box"), left = 0, top = 20;
timers.add(function(){
box.style.left = left + "px";
if ( ++left > 50 )
return false;
});
timers.add(function(){
box.style.top = top + "px";
top += 2;
if ( top > 120 )
return false;
});
在這我們創建了一個中央控制結構。我們可以添加新的計時器回調函數,並可停止和開始它們的執行。此外,回調函數有能力在任何時候刪除自己,只需簡單的返回“false”即可(這比通常的clearTimeout模式更容易),讓我們逐一分析代碼看看它是如何工作的。
起初,所有回調函數連同當前計時器的ID(timers.ID)被存儲在一個中央數組中(timers.timers)。核心內容在start()函數內部,在這裡我們需要確認得是已沒有計時器在運行,如果是那樣,就開始我們的中央計時器。
計時器包含一個循環,它遍歷所有的回調函數,並按順序執行它們,它還檢查回調函數返回的值,如果是“false”,將從執行中移除。事實證明,這是計時器管理非常簡單的方式。
有一點非常重要,用這種方式組織計時器會確保回調函數總是按順序執行,那樣,正常的計時器總是得不到保證(浏覽器一個接一個的選擇執行)。
定時器的這種組織方式對於大型應用程序或任何形式的真正的Javascript動畫至關重要,當我們討論如何創建跨浏覽器動畫時,有一種解決方案肯定有助於將來任何形式的應用開發。
網頁制作Poluoluo文章簡介:了解Javascript函數如何工作啟發了我們:這些看似簡單的特征在其執行時相當復雜。但是,考慮到它的復雜性,我們會更加深入的研究它們。很明顯,計時器最終在復雜的應用程序中特別有用(計算昂貴的代碼、動畫、異步測試包)。但由於其易用性(特別是增加了閉包),它們往
3、異步測試
另外一種中央計時器控件能派上用場的情況是在你打算做異步測試的時候。這裡的問題是當我們需要對沒有立即完成的計算執行測試時(如計時器內的一些行為或XMLHttpRequest)。我們需要將測試包分解,這樣就會完全異步工作。
例如,在一個正常的測試包中,我們可以很容易的運行這些測試。但是,一旦在我們的測試中引入這種需求,我們需要分解它們並單獨處理。我們可以用下面的代碼達到我們期望的效果。
簡單的異步測試包:
(function(){
var queue = [], timer;
this.test = function(fn){
queue.push( fn );
resume();
};
this.pause = function(){
clearInterval( timer );
timer = 0;
};
this.resume = function(){
if ( !timer )
return;
timer = setInterval(function(){
if ( queue.length )
queue.shift()();
else
pause();
}, 1);
};
})();
test(function(){
pause();
setTimeout(function(){
assert( true, "First test completed" );
resume();
}, 100);
});
test(function(){
pause();
setTimeout(function(){
assert( true, "Second test completed" );
resume();
}, 200);
});
最重要的一個方面是,傳遞給test()的每一個函數至多包含一個異步測試。其異步性是通過使用pause()和resume()函數來定義的,這些函數在異步事件前後調用。實際上,上面的代碼只不過是保持異步行為的一種手段--其包含的函數以特定的順序執行(它不完全用於本測試包,但在這非常有用)。
讓我們看看使這種行為成為可能的代碼。函數的大部分功能包含在resume()函數中,它的行為與我們前面例子中的start()方法相似,但它主要用來處理隊列數據,其主要目的是取出一個函數並執行它。如果有一個在等待,它將完全停止運行。最重要的方面是,既然隊列處理代碼完全是異步的,他就能保證在我們調用pause()函數之後嘗試執行。
這一段簡短的代碼使我們的測試以完全異步的方式執行,但仍維持著test()函數執行的順序(如果結果具有毀壞性且影響其它測試,這就非常關鍵)。謝天謝地,我們可以看到,使用最有效的計時器,給一個存在的測試包增加可靠的異步測試並不需要所有的開銷。
網頁制作Poluoluo文章簡介:了解Javascript函數如何工作啟發了我們:這些看似簡單的特征在其執行時相當復雜。但是,考慮到它的復雜性,我們會更加深入的研究它們。很明顯,計時器最終在復雜的應用程序中特別有用(計算昂貴的代碼、動畫、異步測試包)。但由於其易用性(特別是增加了閉包),它們往
4、總結
了解Javascript函數如何工作啟發了我們:這些看似簡單的特征在其執行時相當復雜。但是,考慮到它的復雜性,我們會更加深入的研究它們。很明顯,計時器最終在復雜的應用程序中特別有用(計算昂貴的代碼、動畫、異步測試包)。但由於其易用性(特別是增加了閉包),它們往往易於掌握,即使是在最復雜的情況下。
附文章中提到的assert()函數:(該函數是在jQuery的基礎上創建的)
function assert(pass, msg){
var type = pass ? "PASS" : "FAIL";
jQuery("#results").append("<li class='" + type + "'><b>" + type + "</b> " + msg + "</li>");
}