1. 程式人生 > >JavaScript教程筆記(15)-非同步操作

JavaScript教程筆記(15)-非同步操作

1 單執行緒模型

單執行緒模型是指,JavaScript只在一個執行緒上執行,同時只能執行一個任務。

但是,這不是說JavaScript引擎只有一個執行緒,事實上,JavaScript引擎有多個執行緒,單個指令碼只能在一個執行緒上執行(稱為主執行緒),其它執行緒都是在後臺配合。

JavaScript之所以採用單執行緒,原因是不想讓瀏覽器變得太複雜,因為多執行緒需要共享資源,且有可能互相修改,對於一種指令碼語言來說,這太複雜了。所以,為了避免複雜性,JavaScript一開始就是單執行緒,這是JavaScript語言的核心特徵,將來也不會改變。

2 同步任務和非同步任務

任務分成兩類:同步任務(synchronous)和非同步任務(asynchronous)。

同步任務是在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務。

非同步任務是不進入主執行緒、而進入任務佇列的任務。只有引擎認為某個非同步任務可以執行了,該任務才會進入主執行緒執行。非同步任務不具有“堵塞”效應。

3 任務佇列和事件迴圈

JavaScript執行時,除了一個主執行緒,引擎還提供一個任務佇列(task queue),裡面是各種需要處理的非同步任務。

首先,主執行緒執行所有的同步任務。等到同步任務全部執行完,就去檢視任務佇列裡面的非同步任務。如果滿足條件,那麼非同步任務就會進入主執行緒開始執行,這時它就變成同步任務了。等到執行完,下一個非同步任務再進入主執行緒開始執行。一旦任務佇列清空,各方就結束執行。

非同步任務的寫法通常是回撥函式,一旦非同步任務重新進入主執行緒,就會執行對應的回撥函式。

那麼,JavaScript引擎怎麼知道非同步任務有沒有結果,能不能進入主執行緒呢?答案就是引擎不停地檢查,一遍又一遍,只要同步任務執行完了,引擎就會不斷地檢查任務佇列。這種檢查機制,就叫做“事件迴圈”。

4 非同步操作的模式

非同步操作有幾種模式。

4.1 回撥函式

回撥函式是非同步操作最基本的方法。

function f1(callback) {
    // ...
    callback();
}

function f2() {
    // ...
}

f1(f2);

上面程式碼中,f2是f1的回撥函式,必須是f1執行完了,才會去執行f2。

回撥函式的優點是簡單、容易理解和實現,缺點是不利於程式碼的閱讀和維護,各個部分之間高度耦合,使得程式結構混亂、流程難以追蹤,而且每個任務只能指定一個回撥函式。

4.2 事件監聽

事件驅動模式下,非同步任務的執行不取決於程式碼的順序,而取決於某個事件是否發生。

首先,為f1繫結一個事件(jQuery的寫法)

f1.on('done', f2);

上面程式碼的意思是,當f1發生done事件,就執行f2。然後改寫f1:

function f1() {
    setTimeout(function() {
        // ...
        f1.trigger('done');
    }, 1000);
}

上面程式碼中,f1.trigger(‘done’)表示,執行完成後,立即觸發’done’事件,從而開始執行f2。

這種方法的優點是容易理解,可以繫結多個事件,每個事件可以指定多個回撥函式,有利於模組化。缺點是整個程式都要變成事件驅動型,執行流程變得不清晰,閱讀程式碼的時候,很難看出主流程。

4.3 釋出/訂閱

事件可以理解成“訊號”,如果存在一個“訊號中心”,某個任務執行完成,就向訊號中心“釋出”(publish)一個訊號,其它任務可以向訊號中心“訂閱”(subscribe)這個訊號,從而知道自己什麼時候可以開始執行。這就叫做“釋出/訂閱模式”,以稱“觀察者模式”。

還是以jQuery舉例。首先,f2向訊號中心jQuery訂閱done訊號。

jQuery.subscribe('done', f2);

然後改寫f1:

function f1() {
    setTimeout(function() {
        // ...
        jQuery.publish('done');
    }, 1000);
}

上面程式碼中,jQuery.publish(‘done’)的意思是,向訊號中心jQuery釋出done訊號,從而引發f2的執行。

f2完成執行後,可以取消訂閱(unsubscribe)

jQuery.unsubscribe('done', f2);

這種方法的性質與“事件監聽”類似,但是明顯更優。因為可以通過檢視“訊息中心”,瞭解存在多少訊號、每個訊號有多少訂閱者,從而監控程式的執行。

注:本文適用於ES5規範,原始內容來自 JavaScript 教程,有修改。