一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

JavaScript多線程編程簡介

雖然有越來越多的網站在應用AJAX技術進行開發(fā),但是構建一個復雜的AJAX應用仍然是一個難題。造成這些困難的主要原因是什么呢?是與服務器的異步通信問題?還是GUI程序設計問題呢?通常這兩項工作都是由桌面程序來完成的,那究竟為何開發(fā)一個可以實現(xiàn)同樣功能的AJAX應用就這么困難呢?

AJAX 開發(fā)中的難題

讓我們通過一個簡單的例子來認識這個問題。假設你要建立一個樹形結構的公告欄系統(tǒng)(BBS),它可以根據用戶請求與服務器進行交互,動態(tài)加載每篇文章的信息,而不是一次性從服務器載入所有文章信息。每篇文章有四個相關屬性:系統(tǒng)中可以作為唯一標識的ID、發(fā)貼人姓名、文章內容以及包含其所有子文章ID的數(shù)組信息。首先假定有一個名為getArticle()的函數(shù)可以加載一篇文章信息。該函數(shù)接收的參數(shù)是要加載文章的ID,通過它可從服務器獲取文章信息。它返回的對象包含與文章相關的四條屬性:id,name,content和children。例程如下:

function ( id ) {
var a = getArticle(id);
document.writeln(a.name + "
" + a.content);
}

然而你也許會注意到,重復用同一個文章ID調用此函數(shù),需要與服務器之間進行反復且無益的通信。想要解決這個問題,可以考慮使用函數(shù)getArticleWithCache(),它相當于一個帶有緩存能力的getArticle()。在這個例子中,getArticle()返回的數(shù)據只是作為一個全局變量被保存下來:

var cache = {};
function getArticleWithCache ( id ) {
if ( !cache[id] ) {
cache[id] = getArticle(id);
}
return cache[id];
}

現(xiàn)在已將讀入的文章緩存起來,讓我們再來考慮一下函數(shù)backgroundLoad(),它應用我們上面提到的緩存機制加載所有文章信息。其用途是,當讀者在閱讀某篇文章時,從后臺預加載它所有子文章。因為文章數(shù)據是樹狀結構的,所以很容易寫一個遞歸的算法來遍歷樹并且加載所有的文章:

function backgroundLoad ( ids ) {
for ( var i=0; i < ids.length; i++ ) {
var a = getArticleWithCache(ids[i]);
backgroundLoad(a.children);
}
}

backgroundLoad()函數(shù)接收一個ID數(shù)組作為參數(shù),然后通過每個ID調用前面定義過的getArticldWithCache()方法,這樣就把每個ID對應的文章緩存起來。之后再通過已加載文章的子文章ID數(shù)組遞歸調用backgroundLoad()方法,如此整個文章樹就被緩存起來。

到目前為止,一切似乎看起來都很完美。然而,只要你有過開發(fā)AJAX應用的經驗,你就應該知曉這種幼稚的實現(xiàn)方法根本不會成功,這個例子成立的基礎是默認getArticle()用的是同步通信。可是,作為一條基本原則,JavaScript要求在與服務器進行交互時要用異步通信,因為它是單線程的。就簡單性而言,把每一件事情(包括GUI事件和渲染)都放在一個線程里來處理是一個很好的程序模型,因為這樣就無需再考慮線程同步這些復雜問題。另一方面,他也暴露了應用開發(fā)中的一個嚴重問題,單線程環(huán)境看起來對用戶請求響應迅速,但是當線程忙于處理其它事情時(比如說調用getArticle()),就不能對用戶的鼠標點擊和鍵盤操作做出響應。

如果在這個單線程環(huán)境里進行同步通信會發(fā)生什么事情呢?同步通信會中斷瀏覽器的執(zhí)行直至獲得通信結果。在等待通信結果的過程中,由于服務器的調用還沒有完成,線程會停止響應用戶并保持鎖定狀態(tài)直到調用返回。因為這個原因,當瀏覽器在等待服務器響應時它不能對用戶行為作出響應,所以看起來像是凍結了。當執(zhí)行getArticleWithCache()和backgroundLoad()會有同樣的問題,因為它們都是基于getArticle()函數(shù)的。由于下載所有的文章可能會耗費很可觀的一段時間,因此對于backgroundLoad()函數(shù)來說,瀏覽器在此段時間內的凍結就是一個很嚴重的問題——既然瀏覽器都已經凍結,當用戶正在閱讀文章時就不可能首先去執(zhí)行后臺預加載數(shù)據,如果這樣做連當前的文章都沒辦法讀。

如上所述,既然同步通信在使用中會造成如此嚴重的問題,JavaScript就把異步通信作為一條基本原則。因此,我們可以基于異步通信改寫上面的程序。JavaScript要求以一種事件驅動的程序設計方式來寫異步通信程序。在很多場合中,你都必須指定一個回調程序,一旦收到通信響應,這個函數(shù)就會被調用。例如,上面定義的getArticleWithCache()可以寫成這樣:

var cache = {};
function getArticleWithCache ( id, callback ) {
if ( !cache[id] ) {
callback(cache[id]);
} else {
getArticle(id, function( a ){
cache[id] = a;
callback(a);
});
}
}

這個程序也在內部調用了getArticle()函數(shù)。然而需要注意的是,為異步通信設計的這版getArticle()函數(shù)要接收一個函數(shù)作為第二個參數(shù)。當調用這個getArticle()函數(shù)時,與從前一樣要給服務器發(fā)送一個請求,不同的是,現(xiàn)在函數(shù)會迅速返回而非等待服務器的響應。這意味著,當執(zhí)行權交回給調用程序時,還沒有得到服務器的響應。如此一來,線程就可以去執(zhí)行其它任務直至獲得服務器響應,并在此時調用回調函數(shù)。一旦得到服務器響應,getArticle()的第二個參數(shù)作為預先定義的回調函數(shù)就要被調用,服務器的返回值即為其參數(shù)。同樣的,getArticleWithCache()也要做些改變,定義一個回調參數(shù)作為其第二個參數(shù)。這個回調函數(shù)將在被傳給getArticle()的回調函數(shù)中調用,因而它可以在服務器通信結束后被執(zhí)行。

單是上面這些改動你可能已經認為相當復雜了,但是對backgroundLoad()函數(shù)做得改動將會更復雜,它也要被改寫成可以處理回調函數(shù)的形式:

function backgroundLoad ( ids, callback ) {
var i = 0;
function l ( ) {
if ( i < ids.length ) {
getArticleWithCache(ids[i++], function( a ){
backgroundLoad(a.children, l);
});
} else {
callback();
}
}
l();
}

改動后的backgroundLoad()函數(shù)看上去和我們以前的那個函數(shù)已經相去甚遠,不過他們所實現(xiàn)的功能并無二致。這意味著這兩個函數(shù)都接受ID數(shù)組作為參數(shù),對于數(shù)組里的每個元素都要調用getArticleWithCache(),再應用已經獲得子文章ID遞歸調用backgroundLoad()。不過同樣是對數(shù)組的循環(huán)訪問,新函數(shù)中的就不太好辨認了,以前的程序中是用一個for循環(huán)語句完成的。為什么實現(xiàn)同樣功能的兩套函數(shù)是如此的大相徑庭呢?

這個差異源于一個事實:任何函數(shù)在遇到有需要同服務器進行通信情況后,都必須立刻返回,例如getArticleWithCache()。除非原來的函數(shù)不在執(zhí)行當中,否則應當接受服務器響應的回調函數(shù)都不能被調用。對于JavaScript,在循環(huán)過程中中斷程序并在稍后從這個斷點繼續(xù)開始執(zhí)行程序是不可能的,例如一個for語句。因此,本例利用遞歸傳遞回調函數(shù)實現(xiàn)循環(huán)結構而非一個傳統(tǒng)循環(huán)語句。對那些熟悉連續(xù)傳送風格(CPS)的人來說,這就是一個 CPS的手動實現(xiàn),因為不能使用循環(huán)語法,所以即便如前面提到的遍歷樹那么簡單的程序也得寫得很復雜。與事件驅動程序設計相關的問題是控制流問題:循環(huán)和其它控制流表達式可能比較難理解。

這里還有另外一個問題:如果你把一個沒有應用異步通信的函數(shù)轉換為一個使用異步通信的函數(shù),那么重寫的函數(shù)將需要一個回調函數(shù)作為新增參數(shù),這為已經存在的APIs造成了很大問題,因為內在的改變沒有把影響限于內部,而是導致整體混亂的APIs以及API的其它使用者的改變。

造成這些問題目的根本原因是什么呢?沒錯,正是JavaScript單線程機制導致了這些問題。在單線程里執(zhí)行異步通信需要事件驅動程序設計和復雜的語句。如果當程序在等待服務器的響應時,有另外一個線程可以來處理用戶請求,那么上述復雜技術就不需要了。

試試多線程編程

讓我來介紹一下Concurrent.Thread,它是一個允許JavaScript進行多線程編程的庫,應用它可以大大緩解上文提及的在AJAX開發(fā)中與異步通信相關的困難。這是一個用JavaScript寫成的免費的軟件庫,使用它的前提是遵守Mozilla PublicLicense和GNU General Public License這兩個協(xié)議。你可以從他們的網站 下載源代碼。

馬上來下載和使用源碼吧!假定你已經將下載的源碼保存到一個名為Concurrent.Thread.js的文件夾里,在進行任何操作之前,先運行如下程序,這是一個很簡單的功能實現(xiàn):

<script type="text/Javascript" src="Concurrent.Thread.js"></script>
<script type="text/Javascript">
Concurrent.Thread.create(function(){
var i = 0;
while ( 1 ) {
document.body.innerHTML += i++ + "<br>";
}
});
</script>

執(zhí)行這個程序將會順序顯示從0開始的數(shù)字,它們一個接一個出現(xiàn),你可以滾屏來看它。現(xiàn)在讓我們來仔細研究一下代碼,他應用while(1)條件制造了一個不會中止的循環(huán),通常情況下,象這樣不斷使用一個并且是唯一一個線程的JavaScript程序會導致瀏覽器看起來象凍結了一樣,自然也就不會允許你滾屏。那么為什么上面的這段程序允許你這么做呢?關鍵之處在于while(1)上面的那條Concurrent.Thread.create()語句,這是這個庫提供的一個方法,它可以創(chuàng)建一個新線程。被當做參數(shù)傳入的函數(shù)在這個新線程里執(zhí)行,讓我們對程序做如下微調:

<script type="text/Javascript" src="Concurrent.Thread.js"></script>
<script type="text/Javascript">
function f ( i ){
while ( 1 ) {
document.body.innerHTML += i++ + "<br>";
}
}
Concurrent.Thread.create(f, 0);
Concurrent.Thread.create(f, 100000);
</script>

在這個程序里有個新函數(shù)f()可以重復顯示數(shù)字,它是在程序段起始定義的,接著以f()為參數(shù)調用了兩次create()方法,傳給create()方法的第二個參數(shù)將會不加修改地傳給f()。執(zhí)行這個程序,先會看到一些從0開始的小數(shù),接著是一些從100,000開始的大數(shù),然后又是接著前面小數(shù)順序的數(shù)字。你可以觀察到程序在交替顯示小數(shù)和大數(shù),這說明兩個線程在同時運行。

讓我來展示Concurrent.Thread的另外一個用法。上面的例子調用create()方法來創(chuàng)建新線程。不調用庫里的任何APIs也有可能實現(xiàn)這個目的。例如,前面那個例子可以這樣寫:

<script type="text/Javascript" src="Concurrent.Thread.js"></script>
<script type="text/x-script.multithreaded-js">
var i = 1;
while ( 1 ) {
document.body.innerHTML += i++ + "<br>";
}
</script>

在script標簽內,很簡單地用JavaScript寫了一個無窮循環(huán)。你應該注意到標簽內的type屬性,那里是一個很陌生的值(text/x-script.multithreaded-js),如果這個屬性被放在script標簽內,那么Concurrent.Thread就會在一個新的線程內執(zhí)行標簽之間的程序。你應當記住一點,在本例一樣,必須將Concurrent.Thread庫包含進來。

有了Concurrent.Thread,就有可能自如的將執(zhí)行環(huán)境在線程之間進行切換,即使你的程序很長、連續(xù)性很強。我們可以簡要地討論下如何執(zhí)行這種操作。簡言之,需要進行代碼轉換。粗略地講,首先要把傳遞給create()的函數(shù)轉換成一個字符串,接著改寫直至它可以被分批分次執(zhí)行。然后這些程序可以依照調度程序逐步執(zhí)行。調度程序負責協(xié)調多線程,換句話說,它可以在適當?shù)臅r候做出調整以便每一個修改后的函數(shù)都會得到同等機會運行。Concurrent.Thread實際上并沒有創(chuàng)建新的線程,僅僅是在原本單線程的基礎上模擬了一個多線程環(huán)境。

雖然轉換后的函數(shù)看起來是運行在不同的線程內,但是實際上只有一個線程在做這所有的事情。在轉換后的函數(shù)內執(zhí)行同步通信仍然會造成瀏覽器凍結,你也許會認為以前的那些問題根本就沒有解決。不過你不必耽心,Concurrent.Thread提供了一個應用JavaScript的異步通信方式實現(xiàn)的定制通信庫,它被設計成當一個線程在等待服務器的響應時允許其它線程運行。這個通信庫存于Concurrent.Thread.Http下。它的用法如下所示:

<script type="text/Javascript" src="Concurrent.Thread.js"></script>
<script type="text/x-script.multithreaded-js">
var req = Concurrent.Thread.Http.get(url, ["Accept", "*"]);
if (req.status == 200) {
alert(req.responseText);
} else {
alert(req.statusText);
}
</script>

get()方法,就像它的名字暗示的那樣,可以通過HTTP的GET方法獲得指定URL的內容,它將目標URL作為第一個參數(shù),將一個代表HTTP請求頭的數(shù)組作為可選的第二個參數(shù)。get()方法與服務器交互,當?shù)玫?a href=/yuedu/fuwuqi/ target=_blank class=infotextkey>服務器的響應后就返回一個XMLHttpRequest對象作為返回值。當get()方法返回時,已經收到了服務器響應,所以就沒必要再用回調函數(shù)接收結果。自然,也不必再耽心當程序等待服務器的響應時瀏覽器凍結的情況了。另外,還有一個 post()方法可以用來發(fā)送數(shù)據到服務器

<script type="text/Javascript" src="Concurrent.Thread.js"></script>
<script type="text/x-script.multithreaded-js">
var req = Concurrent.Thread.Http.post(url, "key1=val1&key2=val2");
alert(req.statusText);
</script>

post()方法將目的URL作為第一個參數(shù),要發(fā)送的內容作為第二個參數(shù)。像get()方法那樣,你也可以將請求頭作為可選的第三個參數(shù)。

如果你用這個通信庫實現(xiàn)了第一個例子當中的getArticle()方法,那么你很快就能應用文章開頭示例的那種簡單的方法寫出getArticleWithCache(),backgroundLoad()以及其它調用了getArticle()方法的函數(shù)了。即使是那版backgroundLoad()正在讀文章數(shù)據,照例還有另外一個線程可以對用戶請求做出響應,瀏覽器因此也不會凍結。現(xiàn)在,你能理解在JavaScript中應用多線程有多實用了?

it知識庫JavaScript多線程編程簡介,轉載需保留來源!

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 国内精品视频区在线2021 | 婷婷春色 | 国产精品热久久毛片 | 国产成人精视频在线观看免费 | 久久不色 | 天天干夜夜怕 | 久久亚洲精品无码观看不卡 | 婷婷激情亚洲 | 丁香五六月婷婷 | 久草久操| 成人午夜在线观看国产 | 色视频网址 | 婷婷六月在线 | 国产美女免费国产 | 亚洲区一二三四区2021 | 91精品国产麻豆91久久久久久 | 97精品在线播放 | 国产91一区二这在线播放 | 四虎国产精品永久地址99新强 | 91免费永久在线地址 | 五月婷在线观看 | 国产91中文 | 伊人青草 | 五月天激情婷婷 | 中文字幕在线观看日韩 | 国产人伦激情在线观看 | 免费看黄网站在线 | 婷婷色影院 | 亚洲综合网在线 | 亚洲六月丁香六月婷婷蜜芽 | 欧美激情性色生活片在线观看 | 国内精品小视频在线 | 91年精品国产福利线观看久久 | 国产精品伦子一区二区三区 | 久久久久国产 | 在线观看永久免费 | 国产成人精品一区二区秒拍 | 丁香婷婷亚洲 | 福利片在线观看 | 日本三区| 国产精品每日在线观看男人的天堂 |