中文字幕日韩一区二区_国产一区二区av_国产毛片av_久久久久国产一区_色婷婷电影_国产一区二区精品

JavaScript多線程編程簡(jiǎn)介

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

AJAX 開(kāi)發(fā)中的難題

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

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

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

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

現(xiàn)在已將讀入的文章緩存起來(lái),讓我們?cè)賮?lái)考慮一下函數(shù)backgroundLoad(),它應(yīng)用我們上面提到的緩存機(jī)制加載所有文章信息。其用途是,當(dāng)讀者在閱讀某篇文章時(shí),從后臺(tái)預(yù)加載它所有子文章。因?yàn)槲恼聰?shù)據(jù)是樹(shù)狀結(jié)構(gòu)的,所以很容易寫(xiě)一個(gè)遞歸的算法來(lái)遍歷樹(shù)并且加載所有的文章:

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

backgroundLoad()函數(shù)接收一個(gè)ID數(shù)組作為參數(shù),然后通過(guò)每個(gè)ID調(diào)用前面定義過(guò)的getArticldWithCache()方法,這樣就把每個(gè)ID對(duì)應(yīng)的文章緩存起來(lái)。之后再通過(guò)已加載文章的子文章ID數(shù)組遞歸調(diào)用backgroundLoad()方法,如此整個(gè)文章樹(shù)就被緩存起來(lái)。

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

如果在這個(gè)單線程環(huán)境里進(jìn)行同步通信會(huì)發(fā)生什么事情呢?同步通信會(huì)中斷瀏覽器的執(zhí)行直至獲得通信結(jié)果。在等待通信結(jié)果的過(guò)程中,由于服務(wù)器的調(diào)用還沒(méi)有完成,線程會(huì)停止響應(yīng)用戶(hù)并保持鎖定狀態(tài)直到調(diào)用返回。因?yàn)檫@個(gè)原因,當(dāng)瀏覽器在等待服務(wù)器響應(yīng)時(shí)它不能對(duì)用戶(hù)行為作出響應(yīng),所以看起來(lái)像是凍結(jié)了。當(dāng)執(zhí)行g(shù)etArticleWithCache()和backgroundLoad()會(huì)有同樣的問(wèn)題,因?yàn)樗鼈兌际腔趃etArticle()函數(shù)的。由于下載所有的文章可能會(huì)耗費(fèi)很可觀的一段時(shí)間,因此對(duì)于backgroundLoad()函數(shù)來(lái)說(shuō),瀏覽器在此段時(shí)間內(nèi)的凍結(jié)就是一個(gè)很?chē)?yán)重的問(wèn)題——既然瀏覽器都已經(jīng)凍結(jié),當(dāng)用戶(hù)正在閱讀文章時(shí)就不可能首先去執(zhí)行后臺(tái)預(yù)加載數(shù)據(jù),如果這樣做連當(dāng)前的文章都沒(méi)辦法讀。

如上所述,既然同步通信在使用中會(huì)造成如此嚴(yán)重的問(wèn)題,JavaScript就把異步通信作為一條基本原則。因此,我們可以基于異步通信改寫(xiě)上面的程序。JavaScript要求以一種事件驅(qū)動(dòng)的程序設(shè)計(jì)方式來(lái)寫(xiě)異步通信程序。在很多場(chǎng)合中,你都必須指定一個(gè)回調(diào)程序,一旦收到通信響應(yīng),這個(gè)函數(shù)就會(huì)被調(diào)用。例如,上面定義的getArticleWithCache()可以寫(xiě)成這樣:

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

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

單是上面這些改動(dòng)你可能已經(jīng)認(rèn)為相當(dāng)復(fù)雜了,但是對(duì)backgroundLoad()函數(shù)做得改動(dòng)將會(huì)更復(fù)雜,它也要被改寫(xiě)成可以處理回調(diào)函數(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();
}

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

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

這里還有另外一個(gè)問(wèn)題:如果你把一個(gè)沒(méi)有應(yīng)用異步通信的函數(shù)轉(zhuǎn)換為一個(gè)使用異步通信的函數(shù),那么重寫(xiě)的函數(shù)將需要一個(gè)回調(diào)函數(shù)作為新增參數(shù),這為已經(jīng)存在的APIs造成了很大問(wèn)題,因?yàn)閮?nèi)在的改變沒(méi)有把影響限于內(nèi)部,而是導(dǎo)致整體混亂的APIs以及API的其它使用者的改變。

造成這些問(wèn)題目的根本原因是什么呢?沒(méi)錯(cuò),正是JavaScript單線程機(jī)制導(dǎo)致了這些問(wèn)題。在單線程里執(zhí)行異步通信需要事件驅(qū)動(dòng)程序設(shè)計(jì)和復(fù)雜的語(yǔ)句。如果當(dāng)程序在等待服務(wù)器的響應(yīng)時(shí),有另外一個(gè)線程可以來(lái)處理用戶(hù)請(qǐng)求,那么上述復(fù)雜技術(shù)就不需要了。

試試多線程編程

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

馬上來(lái)下載和使用源碼吧!假定你已經(jīng)將下載的源碼保存到一個(gè)名為Concurrent.Thread.js的文件夾里,在進(jìn)行任何操作之前,先運(yùn)行如下程序,這是一個(gè)很簡(jiǎn)單的功能實(shí)現(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í)行這個(gè)程序?qū)?huì)順序顯示從0開(kāi)始的數(shù)字,它們一個(gè)接一個(gè)出現(xiàn),你可以滾屏來(lái)看它。現(xiàn)在讓我們來(lái)仔細(xì)研究一下代碼,他應(yīng)用while(1)條件制造了一個(gè)不會(huì)中止的循環(huán),通常情況下,象這樣不斷使用一個(gè)并且是唯一一個(gè)線程的JavaScript程序會(huì)導(dǎo)致瀏覽器看起來(lái)象凍結(jié)了一樣,自然也就不會(huì)允許你滾屏。那么為什么上面的這段程序允許你這么做呢?關(guān)鍵之處在于while(1)上面的那條Concurrent.Thread.create()語(yǔ)句,這是這個(gè)庫(kù)提供的一個(gè)方法,它可以創(chuàng)建一個(gè)新線程。被當(dāng)做參數(shù)傳入的函數(shù)在這個(gè)新線程里執(zhí)行,讓我們對(duì)程序做如下微調(diào):

<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>

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

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

<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標(biāo)簽內(nèi),很簡(jiǎn)單地用JavaScript寫(xiě)了一個(gè)無(wú)窮循環(huán)。你應(yīng)該注意到標(biāo)簽內(nèi)的type屬性,那里是一個(gè)很陌生的值(text/x-script.multithreaded-js),如果這個(gè)屬性被放在script標(biāo)簽內(nèi),那么Concurrent.Thread就會(huì)在一個(gè)新的線程內(nèi)執(zhí)行標(biāo)簽之間的程序。你應(yīng)當(dāng)記住一點(diǎn),在本例一樣,必須將Concurrent.Thread庫(kù)包含進(jìn)來(lái)。

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

雖然轉(zhuǎn)換后的函數(shù)看起來(lái)是運(yùn)行在不同的線程內(nèi),但是實(shí)際上只有一個(gè)線程在做這所有的事情。在轉(zhuǎn)換后的函數(shù)內(nèi)執(zhí)行同步通信仍然會(huì)造成瀏覽器凍結(jié),你也許會(huì)認(rèn)為以前的那些問(wèn)題根本就沒(méi)有解決。不過(guò)你不必耽心,Concurrent.Thread提供了一個(gè)應(yīng)用JavaScript的異步通信方式實(shí)現(xiàn)的定制通信庫(kù),它被設(shè)計(jì)成當(dāng)一個(gè)線程在等待服務(wù)器的響應(yīng)時(shí)允許其它線程運(yùn)行。這個(gè)通信庫(kù)存于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()方法,就像它的名字暗示的那樣,可以通過(guò)HTTP的GET方法獲得指定URL的內(nèi)容,它將目標(biāo)URL作為第一個(gè)參數(shù),將一個(gè)代表HTTP請(qǐng)求頭的數(shù)組作為可選的第二個(gè)參數(shù)。get()方法與服務(wù)器交互,當(dāng)?shù)玫?a href=/yuedu/fuwuqi/ target=_blank class=infotextkey>服務(wù)器的響應(yīng)后就返回一個(gè)XMLHttpRequest對(duì)象作為返回值。當(dāng)get()方法返回時(shí),已經(jīng)收到了服務(wù)器響應(yīng),所以就沒(méi)必要再用回調(diào)函數(shù)接收結(jié)果。自然,也不必再耽心當(dāng)程序等待服務(wù)器的響應(yīng)時(shí)瀏覽器凍結(jié)的情況了。另外,還有一個(gè) post()方法可以用來(lái)發(fā)送數(shù)據(jù)到服務(wù)器

<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作為第一個(gè)參數(shù),要發(fā)送的內(nèi)容作為第二個(gè)參數(shù)。像get()方法那樣,你也可以將請(qǐng)求頭作為可選的第三個(gè)參數(shù)。

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

it知識(shí)庫(kù)JavaScript多線程編程簡(jiǎn)介,轉(zhuǎn)載需保留來(lái)源!

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

主站蜘蛛池模板: 97中文视频| 一区二区三区亚洲 | 草久网| 中文字幕成人在线 | 美国一级片在线观看 | 国产精品1区2区 | 国产黄色av网站 | 久草在线青青草 | 亚洲高清视频在线观看 | 欧美美女爱爱视频 | 欧美激情精品久久久久 | 国产精品夜夜夜一区二区三区尤 | 国产在线小视频 | 国产精品无码久久久久 | 久久99久久98精品免观看软件 | 99国产精品99久久久久久 | 97成人免费 | 亚洲码欧美码一区二区三区 | 一级毛片免费视频观看 | 婷婷免费视频 | av在线一区二区 | 黄色香蕉视频在线观看 | 欧美一级片在线播放 | 国产精品99久久久久久久久久久久 | 毛片免费看| av激情影院| 在线视频中文字幕 | 亚洲高清成人在线 | 亚洲91av | 国产精品久久久久久久久久久久 | 日韩激情一区 | 九九热视频这里只有精品 | 久久夜色精品国产 | 日本天堂一区二区 | 久久精品一区二区三区四区 | 黄色一级电影在线观看 | 在线一区 | 综合另类 | 免费午夜剧场 | 国产黄色av网站 | 国产成人99久久亚洲综合精品 |