我們知道"同步"和"非同步"這個概念主要是對線程來說的,這裡怎麼能把JScirpt的函數弄成非同步方式來執行呢?! 這樣做又有什麼意義呢?
當我們使用JScript來操作DHMTL元素的時候,DHTML元素的可視更新是和指令碼的執行範圍有關的,也就是說指令碼的執行是必須"間斷"的,從而讓IE的從message pump裡拿到onpaint事件訊息,否則DHTML 屬性變化引起的的可視化更新並不會馬上發生。前面說的"間斷"不是真正意義上的程式中斷,而是指令碼執行的一個範圍的變化。
舉個例子來說,如果我們使用下面的JScript語句,我們是不可能類比出文字變大的動態效果的。
<script>
function foo(elmt)
{
var fontSize = 10;
for ( var i=0 ; i < 10 ; ++i )
{
fontSize += 10;
aDiv.style.fontSize = fontSize;
dtBegin = new Date();
do{;}while((new Date())-dtBegin < 500);
}
}
</script>
<button onclick="foo(this)">Click</button>
<div id="aDiv">font size</div>
問題就是指令碼執行在這個函數中始終沒有離開它的域,IE根本不會在fontSize改變的過程中重繪,而只能在執行10次迴圈後,顯示出fontSize為110的文字(需要注意這裡的矛盾並不是那個do while的延時迴圈,它只是耗點CPU而已)。
那麼這種效果怎麼來做呢?必須使用setInterval或者setTimeout來實現,這兩個函數將為指令碼執行開啟新的"線程",不過這些線程也是會block的,而不是真正的多線程的線程,否這就更亂套了,我們也不需要指令碼支援廣義上的非同步執行。下面我這個"非同步執行"的函數也就是基於setTimeout的特性來做的。
<html>
<body>
<button onclick="foo(this)">
Click</button>
<div id="aDiv">
font size</div>
<table>
<tbody>
<tr>
<td>
<button onclick="__doHeavyWork">
Heavy Work 1</button>
</td>
<td rowspan="2">
<span id="info"></span>
</td>
</tr>
<tr>
<td>
<button onclick="doHeavyWork">
Heavy Work 2</button>
</td>
</tr>
</tbody>
</table>
<script language="javascript">
function doHeavyWork(elmt)
{
__doHeavyWork.args = [];
__doHeavyWork.args.push(elmt);
info.innerText = 'begin';
window.setTimeout(__doHeavyWork, 1);
}
function __doHeavyWork(elmt)
{
if ( !elmt )
{
elmt = __doHeavyWork.args[0];
}
var dtBegin = new Date();
do
{
var a = 1+1;
}
while((new Date())-dtBegin < 2000 );
info.innerText = elmt.tagName + ': mession complete.'
}
</script>
</body>
</html>
點擊"Heavy Work 1"按鈕,我們是看不到瀏覽器裡面出現"Begin..."的提示的,而只能在最後看到一個"BUTTOM: mession complete."。而點擊"Heavy Work 2"按鈕,就能看到"Begin..."提示,當任務完成再顯示"... complete."。這裡使用setTimeout是很常見的了,不過有個小hack,我們可以把原函數的參數通過Function對象(就是大寫的Function)帶到__doHeavyWork函數中去。上例中我自己建了一個args數組對象來存放參數,因為setTimeout裡面只能寫函數名,沒有辦法帶參數的,到了__doHeavyWork裡,再把args裡的參數取出,這樣的好處是讓代碼變得內聚性很強,而不需要全域變數來cache doHeavyWork和__doHeavyWork之間的參數。
正是由於這裡的__doHeavyWork是通過setTimeout後的Timer來啟動執行的,已經和doHeavyWork不在同一個域中了,所以我們的info.innerText = 'begin...';在doHeavyWork完成後就會立即顯示執行效果,而不會因為函數的流程而block。同時當__doHeavyWork執行後,他又會在自己的域中block起來,所以我說這個"非同步執行"方式是引號擴起來的,而不是真正的多線程的非同步。
合理的使用這種JScript函數類比"非同步執行"的方式,可以讓複雜的網頁,特別是無刷性的網頁表現出更好的可視效果。