首先,回調函數一點都不復雜,也一點都不難。
一、定義:
回調函數是從一個叫函數式編程的范式中衍生出來的。簡單來說,回調函數就是將函數作為變量。
eg:
//常見的比如jquery的click事件
$('#btn').click(function(){ //這個一個匿名函數被當做參數傳到click函數了
cnosole.log('回調函數被回調了');
});
二、回調函數是如何運行的?
回調函數作為一個參數傳給其他函數;
我們傳遞的只是這個函數的定義,也就是不會執行;
回調函數是在接受的函數中的某個特定的“點”執行的。
三、回調函數本質上是閉包
回調函數作為參數傳進函數,在接受它的函數中的某個點執行(因為傳遞的是函數的定義,只有接受它的函數調用它,它才會執行)
這就相當于回調函數在接受它的函數中定義,那么其實回調函數其實就是一個閉包,它可以訪問接受它的函數里面的變量,也可以訪問全局變量。
四、回調函數基本原理
(1)使用命名或者匿名函數作為回調
//匿名函數的情況上面已經介紹了,這里就不重復
//命名函數回調
function sayHi(){
alert( 'hello!' );
}
function getName( name, callback ){
callback();
}
//調用
getName( 'jack', sayHi ); //hello!
(2)傳遞參數給回調
function sayHi( name ){
alert( 'hello!' + name + '!' );
}
function getName( name, callback ){
callback( name );
}
//調用
getName( 'jack', sayHi ); //hello,jack!
(3)在執行之前確保回調函數是一個函數
function sayHi( name ){
alert( 'hello!' + name + '!' );
}
function getName( name, callback ){
//確保callback是一個函數
if( typeof callback == 'function' ){
//已經確定就可以調用了
callback( name );
}
}
//調用
getName( 'jack', sayHi ); //hello,jack!
(4)一個坑:使用this對象的方法作為回調函數時
當回到函數是一個使用了this的對象的方法時,如下:
var user = {
name: "No set",
setUserName: function( name ){
//setUserName是thiss對象的一個方法,待會作為回調函數用
this.name = name + 'doctor';
}
}
我們必須改變執行回調函數的方法來保證this對象的上下文。否則如果回調函數被傳遞給一個全局函數,this對象要么指向window對象(瀏覽器中)。要么指向把包含方法的對象。
在下面的例子中,user.setUserName被執行時,this.name并沒設置user對象中的name。
相反,它將設置window對象中的name屬性,這是因為去全局函數中的this對象指向window對象。
function getName( name, callback ){
if( typeof callback === 'function' ){
callback( name );
}
}
getName( 'jack', user.setUserName );
console.log( user.name ); //Not set
console.log( window.name ); //jack
針對(4)點說到得這個坑,我們可以“使用call和apply函數來保存this”解決
我們知道,call和apply可以被用來設置函數內部的this對象以及給次函數傳遞變量。
call第一個參數被用來在函數內部當做this的對象,傳遞給函數的參數挨個傳遞
apply第一個參數也是被用來在函數內部當做this的對象,最后一個參數是傳遞給函數的值數組。
var user = {
name: "No set",
setUserName: function( name ){
//setUserName是thiss對象的一個方法,待會作為回調函數用
this.name = name + ' doctor';
}
}
function getName( name, callback, callbackObj ){
if( typeof callback === 'function' ){
//這里指定了callback函數里面的this的指向是callbackObj
callback.apply( callbackObj, [name] );
//callback.call( callbackObj, name ); 效果是一樣的
}
}
getName( 'jack', user.setUserName, user );
console.log( user.name ); //jack doctor
(5)允許多重調用回調函數
//jquery 的$.ajax中
function successCb(){}
function completeCb(){}
function errorCb(){}
$.ajax({
url: "http://testurl.com",
success: successCb,
complete: completeCb,
error: errorCb
})
(6)避免“回調地獄”
有時候業務需求會產生多層回調,就會出現下面的現象,也就是我們常說的“回調地獄”,導致代碼很不友好
var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
p_client.dropDatabase(function(err, done) {
p_client.createCollection('test_custom_key', function(err, collection) {
collection.insert({'a':1}, function(err, docs) {
collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
cursor.toArray(function(err, items) {
test.assertEquals(1, items.length);
// Let's close the db
p_client.close();
});
});
});
});
});
});
面對“回調地獄”,有兩個建議:
a)少用匿名回調函數,將回調函數先聲明好,在需要用到回調函數的時候,只傳遞函數名
b)將你的代碼封裝到一個模塊中,這樣你就可以利用這個模塊了,特別是大型項目,只需要導入改模塊就可以使用了。
五、總結
通過以上內容的介紹,你應該了解回調函數的相關情況了。我們可以看到,回調函數室友很多好處的,比如:
- 避免重復代碼--在擁有更多多功能函數的地方實現更好的抽象
- 讓代碼有更好地可維護性
- 是代碼更容易閱讀
- 編寫更多特定功能的函數
所以,在需要的時候大膽使用它吧,現在的web應用有些地方經常用到
- 異步調用(例如讀取文件,進行HTTP請求,等等)
- 時間監聽器/處理器
- setTimeout和setInterval方法
- 一般情況:精簡代碼