回調函數

首先,回調函數一點都不復雜,也一點都不難。

一、定義:

回調函數是從一個叫函數式編程的范式中衍生出來的。簡單來說,回調函數就是將函數作為變量。
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方法
  • 一般情況:精簡代碼
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容