后一個任務等待前一個任務結束再執行。程序執行順序與任務排列順序一致的,同步的。
參考:
http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
https://segmentfault.com/q/1010000000140970
在JavaScript中,回調函數具體的定義為:函數A作為參數(函數引用)傳遞到另一個函數B中,並且這個函數B執行函數A。我們就說函數A叫做回調函數。如果沒有名稱(函數表達式),就叫做匿名回調函數。
因此callback 不一定用於異步,一般同步(阻塞)的場景下也經常用到回調,比如要求執行某些操作后執行回調函數。
1、一個同步(阻塞)中使用回調的例子
var func1=function(callback){ //do something. (callback && typeof(callback) === "function") && callback(); } var func2=function(){} func1(func2);
2、改進為異步操作,不阻塞。異步編程的方法一:回調函數
function f1(callback){ setTimeout(function () { // f1的任務代碼 callback(); }, 1000); } f1(f2)
采用這種方式,我們把同步操作變成了異步操作,f1不會堵塞程序運行,相當於先執行程序的主要邏輯,將耗時的操作推遲執行。
回調函數的優點是簡單、容易理解和部署,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,而且每個任務只能指定一個回調函數。
3、回調總結
異步回調的例子:
$(document).ready(callback); $.ajax({ url: "test.html", context: document.body }).done(function() { $(this).addClass("done"); }).fail(function() { alert("error"); }).always(function() { alert("complete"); }); /** 注意的是,ajax請求確實是異步的,不過這請求是由瀏覽器新開一個線程請求,當請求的狀態變更時,如果先前已設置回調,這異步線程就產生狀態變更事件放到 JavaScript引擎的處理隊列中等待處理。見:http://www.phpv.net/html/1700.html */
回調什么時候執行
回調函數,一般在同步情境下是最后執行的,而在異步情境下有可能不執行,因為事件沒有被觸發或者條件不滿足。
回調函數的使用場合
- 資源加載:動態加載js文件后執行回調,加載iframe后執行回調,ajax操作回調,圖片加載完成執行回調,AJAX等等。
- DOM事件及Node.js事件基於回調機制(Node.js回調可能會出現多層回調嵌套的問題)。
- setTimeout的延遲時間為0,這個hack經常被用到,settimeout調用的函數其實就是一個callback的體現
- 鏈式調用:鏈式調用的時候,在賦值器(setter)方法中(或者本身沒有返回值的方法中)很容易實現鏈式調用,而取值器(getter)相對來說不好實現鏈式調用,因為你需要取值器返回你需要的數據而不是this指針,如果要實現鏈式方法,可以用回調函數來實現
- setTimeout、setInterval的函數調用得到其返回值。由於兩個函數都是異步的,即:他們的調用時序和程序的主流程是相對獨立的,所以沒有辦法在主體里面等待它們的返回值,它們被打開的時候程序也不會停下來等待,否則也就失去了setTimeout及setInterval的意義了,所以用return已經沒有意義,只能使用callback。callback的意義在於將timer執行的
- 結果通知給代理函數進行及時處理。
回調函數的傳遞
上面說了,要將函數引用或者函數表達式作為參數傳遞。
-
$.get('myhtmlpage.html', myCallBack);//這是對的 $.get('myhtmlpage.html', myCallBack('foo', 'bar'));//這是錯的,那么要帶參數呢? $.get('myhtmlpage.html', function(){//帶參數的使用函數表達式 myCallBack('foo', 'bar'); });另外,最好保證回調存在且必須是函數引用或者函數表達式:
(callback && typeof(callback) === "function") && callback();
4、異步編程的方法二:事件監聽(和發布訂閱模式原理一樣)
事件監聽:
var doc = $(document); function f2(){ console.log("done"); } function f1(){ setTimeout(function(){ doc.trigger("done") }, 1000); } doc.on("done", f2); f1();
發布訂閱:和事件監聽一模一樣啊。
觀察者模式所做的工作就是在解耦,讓耦合的雙方都依賴於抽象,而不是依賴於具體。從而使得各自的變化都不會影響到另一邊的變化。
var Observable = {//doc callbacks: [], add: function(fn) { this.callbacks.push(fn); }, fire: function() { this.callbacks.forEach(function(fn) { fn(); }) } } Observable.add(function() { alert(1) }) Observable.add(function() { alert(2) }) Observable.fire(); // 1, 2
4、異步編程的方法三:promise對象
Promises對象是CommonJS工作組提出的一種規范,目的是為異步編程提供統一接口。
簡單說,它的思想是,每一個異步任務返回一個Promise對象,該對象有一個then方法,允許指定回調函數。比如,f1的回調函數f2,可以寫成:
f1().then(f2);
f1要進行如下改寫(這里使用的是jQuery的實現):
function f1(){
var dfd = $.Deferred();
setTimeout(function () {
// f1的任務代碼
dfd.resolve();
}, 500);
return dfd.promise;
}
這樣寫的優點在於,回調函數變成了鏈式寫法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以實現許多強大的功能。
比如,指定多個回調函數:
f1().then(f2).then(f3);
再比如,指定發生錯誤時的回調函數:
f1().then(f2).fail(f3);
而且,它還有一個前面三種方法都沒有的好處:如果一個任務已經完成,再添加回調函數,該回調函數會立即執行。所以,你不用擔心是否錯過了某個事件或信號。這種方法的缺點就是編寫和理解,都相對比較難。
