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 教程,有修改。