你可能知道Javascript語言的執行環境是單線程(single thread)
所謂單線程就是指一次只能完成一件任務如果有多個任務就必須排隊前面一個任務完成再執行後面一個任務以此類推
這種模式的好處是實現起來比較簡單執行環境相對單純壞處是只要有一個任務耗時很長後面的任務都必須排隊等著會拖延整個程序的執行常見的浏覽器無響應(假死)往往就是因為某一段Javascript代碼長時間運行(比如死循環)導致整個頁面卡在這個地方其他任務無法執行
為了解決這個問題Javascript語言將任務的執行模式分成兩種同步(Synchronous)和異步(Asynchronous)
同步模式就是上一段的模式後一個任務等待前一個任務結束然後再執行程序的執行順序與任務的排列順序是一致的同步的異步模式則完全不同每一個任務有一個或多個回調函數(callback)前一個任務結束後不是執行後一個任務而是執行回調函數後一個任務則是不等前一個任務結束就執行所以程序的執行順序與任務的排列順序是不一致的異步的
異步模式非常重要在浏覽器端耗時很長的操作都應該異步執行避免浏覽器失去響應最好的例子就是Ajax操作在服務器端異步模式甚至是唯一的模式因為執行環境是單線程的如果允許同步執行所有http請求服務器性能會急劇下降很快就會失去響應
本文總結了異步模式編程的種方法理解它們可以讓你寫出結構更合理性能更出色維護更方便的Javascript程序
一回調函數
這是異步編程最基本的方法
假定有兩個函數f和f後者等待前者的執行結果
f()
f()
如果f是一個很耗時的任務可以考慮改寫f把f寫成f的回調函數
function f(callback){
setTimeout(function () {
// f的任務代碼 callback()
} )
}
執行代碼就變成下面這樣
f(f)
采用這種方式我們把同步操作變成了異步操作f不會堵塞程序運行相當於先執行程序的主要邏輯將耗時的操作推遲執行
回調函數的優點是簡單容易理解和部署缺點是不利於代碼的閱讀和維護各個部分之間高度耦合(Coupling)流程會很混亂而且每個任務只能指定一個回調函數
二事件監聽
另一種思路是采用事件驅動模式任務的執行不取決於代碼的順序而取決於某個事件是否發生
還是以f和f為例首先為f綁定一個事件(這裡采用的jQuery的寫法)
fon(done f)
上面這行代碼的意思是當f發生done事件就執行f然後對f進行改寫
function f(){ setTimeout(function () { // f的任務代碼 ftrigger(done)
} )
}
ftrigger(done)表示執行完成後立即觸發done事件從而開始執行f
這種方法的優點是比較容易理解可以綁定多個事件每個事件可以指定多個回調函數而且可以去耦合(Decoupling)有利於實現模塊化缺點是整個程序都要變成事件驅動型運行流程會變得很不清晰
三發布/訂閱
上一節的事件完全可以理解成信號
我們假定存在一個信號中心某個任務執行完成就向信號中心發布(publish)一個信號其他任務可以向信號中心訂閱(subscribe)這個信號從而知道什麼時候自己可以開始執行這就叫做發布/訂閱模式(publishsubscribe pattern)又稱觀察者模式(observer pattern)
這個模式有多種實現下面采用的是Ben Alman的Tiny Pub/Sub這是jQuery的一個插件
首先f向信號中心jQuery訂閱done信號
jQuerysubscribe(done f)
然後f進行如下改寫
function f(){ setTimeout(function () { // f的任務代碼 jQuerypublish(done)
} )
}
jQuerypublish(done)的意思是f執行完成後向信號中心jQuery發布done信號從而引發f的執行
此外f完成執行後也可以取消訂閱(unsubscribe)
jQueryunsubscribe(done f)
這種方法的性質與事件監聽類似但是明顯優於後者因為我們可以通過查看消息中心了解存在多少信號每個信號有多少訂閱者從而監控程序的運行
四Promises對象
Promises對象是CommonJS工作組提出的一種規范目的是為異步編程提供統一接口
簡單說它的思想是每一個異步任務返回一個Promise對象該對象有一個then方法允許指定回調函數比如f的回調函數f可以寫成
f()then(f)
f要進行如下改寫(這裡使用的是jQuery的實現)
function f(){ var dfd = $Deferred()
setTimeout(function () { // f的任務代碼 dfdresolve()
} )
return dfdpromise;
}
這樣寫的優點在於回調函數變成了鏈式寫法程序的流程可以看得很清楚而且有一整套的配套方法可以實現許多強大的功能
比如指定多個回調函數
f()then(f)then(f)
再比如指定發生錯誤時的回調函數
f()then(f)fail(f)
而且它還有一個前面三種方法都沒有的好處如果一個任務已經完成再添加回調函數該回調函數會立即執行所以你不用擔心是否錯過了某個事件或信號這種方法的缺點就是編寫和理解都相對比較難
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19586.html