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