標籤:計算 eve 就是 作業 async and tun 訊息 UNC
javascript語言是一門“單線程”的語言,不像java語言,類繼承Thread再來個thread.start就可以開闢一個線程,所以,javascript就像一條流水線,僅僅是一條流水線而已,要麼加工,要麼封裝,不能同時進行多個任務和流程。
那麼這裡說的同步和非同步到底是什麼呢?如果你真的不懂,我希望你認真讀完這篇文章。其實我個人覺得js官方的文檔在使用兩個詞的時候並不準確,包括很多其他詞彙,都只是聽起來高深,但實際應用好像跟這些詞沒半毛錢關係。例如“事件委託”這個詞,不知道的人乍一看誰又能說出“事件委託”是什麼意思?委託什麼事件?怎麼個委託,我看不如乾脆就叫“事件在外層元素中的捕獲”,雖然長一點,一下就能看懂。
迴歸正軌,“同步”——一下就讓人想到“一起”這個詞;“非同步”呢,從字面來講,好像是在不同的(異)的ways上do something,那首先想到的詞可能是“一邊...一邊...”,比如‘小明一邊吃雪糕一邊寫作業’,這完全沒毛病,雪糕吃完了,作業也寫完了,這就是非同步?那就大錯特錯了!
其實同步和非同步,無論如何,做事情的時候都是只有一條流水線(單線程),同步和非同步差別就在於這條流水線上各個流程的執行順序不同。
最基礎的非同步是setTimeout和setInterval函數,很常見,但是很少人有人知道其實這就是非同步,因為它們可以控制js的執行順序。我們也可以簡單地理解為:可以改變程式正常執行順序的操作就可以看成是非同步作業。如下代碼:
<script type="text/javascript">
console.log( "1" );
setTimeout(function() {
console.log( "2" )
}, 0 );
setTimeout(function() {
console.log( "3" )
}, 0 );
setTimeout(function() {
console.log( "4" )
}, 0 );
console.log( "5" );
</script>
輸出結果如下:1 5 2 3 4
可見,儘管我們設定了setTimeout(function,time)中的等待時間為0,結果其中的function還是後執行。
Firefox瀏覽器的api文檔有這樣一句話:Because even though setTimeout was called with a delay of zero, it‘s placed on a queue and scheduled to run at the next opportunity, not immediately. Currently executing code must complete before functions on the queue are executed, the resulting execution order may not be as expected.
意思就是:儘管setTimeout的time延遲時間為0,其中的function也會被放入一個隊列中,等待下一個機會執行,當前的代碼(指不需要排入佇列中的程式)必須在該隊列的程式完成之前完成,因此結果可能不與預期結果相同。
這裡說到了一個“隊列”(即任務隊列),該隊列放的是什麼呢,放的就是setTimeout中的function,這些function依次加入該隊列,即該隊列中所有function中的程式將會在該隊列以外的所有代碼執行完畢之後再以此執行,這是為什麼呢?因為在執行程式的時候,瀏覽器會預設setTimeout以及ajax請求這一類的方法都是耗時程式(儘管可能不耗時),將其加入一個隊列中,該隊列是一個儲存耗時程式的隊列,在所有不耗時程式執行過後,再來依次執行該隊列中的程式。
又回到了最初的起點——javascript是單線程。單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。於是就有一個概念——任務隊列。如果排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閑著的,因為IO裝置(輸入輸出裝置)很慢(比如Ajax操作從網路讀取資料),不得不等著結果出來,再往下執行。於是JavaScript語言的設計者意識到,這時主線程完全可以不管IO裝置,掛起處於等待中的任務,先運行排在後面的任務。等到IO裝置返回了結果,再回過頭,把掛起的任務繼續執行下去。
於是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是非同步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;非同步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有等主線程任務執行完畢,"任務隊列"開始通知主線程,請求執行任務,該任務才會進入主線程執行。
具體來說,非同步運行機制如下:
(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
(2)主線程之外,還存在一個"任務隊列"(task queue)。只要非同步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。
只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重複。
"任務隊列"是一個事件的隊列(也可以理解成訊息的隊列),IO裝置完成一項任務,就在"任務隊列"中添加一個事件,表示相關的非同步任務可以進入"執行棧"了。主線程讀取"任務隊列",就是讀取裡面有哪些事件。
"任務隊列"中的事件,除了IO裝置的事件以外,還包括一些使用者產生的事件(比如滑鼠點擊、頁面滾動等等),比如$(selectot).click(function),這些都是相對耗時的操作。只要指定過這些事件的回呼函數,這些事件發生時就會進入"任務隊列",等待主線程讀取。
所謂"回呼函數"(callback),就是那些會被主線程掛起來的代碼,前面說的點擊事件$(selectot).click(function)中的function就是一個回呼函數。非同步任務必須指定回呼函數,當主線程開始執行非同步任務,就是執行對應的回呼函數。例如ajax的success,complete,error也都指定了各自的回呼函數,這些函數就會加入“任務隊列”中,等待執行。
如何理解javascript中的同步和非同步